Open OAuth login URLs during setup
This commit is contained in:
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@companion-ai/feynman",
|
"name": "@companion-ai/feynman",
|
||||||
"version": "0.2.13",
|
"version": "0.2.14",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@companion-ai/feynman",
|
"name": "@companion-ai/feynman",
|
||||||
"version": "0.2.13",
|
"version": "0.2.14",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@companion-ai/alpha-hub": "^0.1.2",
|
"@companion-ai/alpha-hub": "^0.1.2",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@companion-ai/feynman",
|
"name": "@companion-ai/feynman",
|
||||||
"version": "0.2.13",
|
"version": "0.2.14",
|
||||||
"description": "Research-first CLI agent built on Pi and alphaXiv",
|
"description": "Research-first CLI agent built on Pi and alphaXiv",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { writeFileSync } from "node:fs";
|
|||||||
|
|
||||||
import { readJson } from "../pi/settings.js";
|
import { readJson } from "../pi/settings.js";
|
||||||
import { promptChoice, promptText } from "../setup/prompts.js";
|
import { promptChoice, promptText } from "../setup/prompts.js";
|
||||||
|
import { openUrl } from "../system/open-url.js";
|
||||||
import { printInfo, printSection, printSuccess, printWarning } from "../ui/terminal.js";
|
import { printInfo, printSection, printSuccess, printWarning } from "../ui/terminal.js";
|
||||||
import {
|
import {
|
||||||
buildModelStatusSnapshotFromRecords,
|
buildModelStatusSnapshotFromRecords,
|
||||||
@@ -126,7 +127,13 @@ export async function loginModelProvider(authPath: string, providerId?: string,
|
|||||||
await authStorage.login(provider.id, {
|
await authStorage.login(provider.id, {
|
||||||
onAuth: (info: { url: string; instructions?: string }) => {
|
onAuth: (info: { url: string; instructions?: string }) => {
|
||||||
printSection(`Login: ${provider.name ?? provider.id}`);
|
printSection(`Login: ${provider.name ?? provider.id}`);
|
||||||
printInfo(`Open this URL: ${info.url}`);
|
const opened = openUrl(info.url);
|
||||||
|
if (opened) {
|
||||||
|
printInfo("Opened the login URL in your browser.");
|
||||||
|
} else {
|
||||||
|
printWarning("Couldn't open your browser automatically.");
|
||||||
|
}
|
||||||
|
printInfo(`Auth URL: ${info.url}`);
|
||||||
if (info.instructions) {
|
if (info.instructions) {
|
||||||
printInfo(info.instructions);
|
printInfo(info.instructions);
|
||||||
}
|
}
|
||||||
|
|||||||
51
src/system/open-url.ts
Normal file
51
src/system/open-url.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { spawn } from "node:child_process";
|
||||||
|
|
||||||
|
import { resolveExecutable } from "./executables.js";
|
||||||
|
|
||||||
|
type ResolveExecutableFn = (name: string, fallbackPaths?: string[]) => string | undefined;
|
||||||
|
|
||||||
|
type OpenUrlCommand = {
|
||||||
|
command: string;
|
||||||
|
args: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getOpenUrlCommand(
|
||||||
|
url: string,
|
||||||
|
platform = process.platform,
|
||||||
|
resolveCommand: ResolveExecutableFn = resolveExecutable,
|
||||||
|
): OpenUrlCommand | undefined {
|
||||||
|
if (platform === "win32") {
|
||||||
|
return {
|
||||||
|
command: "cmd",
|
||||||
|
args: ["/c", "start", "", url],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (platform === "darwin") {
|
||||||
|
const command = resolveCommand("open");
|
||||||
|
return command ? { command, args: [url] } : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const command = resolveCommand("xdg-open");
|
||||||
|
return command ? { command, args: [url] } : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openUrl(url: string): boolean {
|
||||||
|
const command = getOpenUrlCommand(url);
|
||||||
|
if (!command) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const child = spawn(command.command, command.args, {
|
||||||
|
detached: true,
|
||||||
|
stdio: "ignore",
|
||||||
|
windowsHide: true,
|
||||||
|
});
|
||||||
|
child.on("error", () => {});
|
||||||
|
child.unref();
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
tests/open-url.test.ts
Normal file
45
tests/open-url.test.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import test from "node:test";
|
||||||
|
import assert from "node:assert/strict";
|
||||||
|
|
||||||
|
import { getOpenUrlCommand } from "../src/system/open-url.js";
|
||||||
|
|
||||||
|
test("getOpenUrlCommand uses open on macOS when available", () => {
|
||||||
|
const command = getOpenUrlCommand(
|
||||||
|
"https://example.com",
|
||||||
|
"darwin",
|
||||||
|
(name) => (name === "open" ? "/usr/bin/open" : undefined),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(command, {
|
||||||
|
command: "/usr/bin/open",
|
||||||
|
args: ["https://example.com"],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getOpenUrlCommand uses xdg-open on Linux when available", () => {
|
||||||
|
const command = getOpenUrlCommand(
|
||||||
|
"https://example.com",
|
||||||
|
"linux",
|
||||||
|
(name) => (name === "xdg-open" ? "/usr/bin/xdg-open" : undefined),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(command, {
|
||||||
|
command: "/usr/bin/xdg-open",
|
||||||
|
args: ["https://example.com"],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getOpenUrlCommand uses cmd start on Windows", () => {
|
||||||
|
const command = getOpenUrlCommand("https://example.com", "win32");
|
||||||
|
|
||||||
|
assert.deepEqual(command, {
|
||||||
|
command: "cmd",
|
||||||
|
args: ["/c", "start", "", "https://example.com"],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getOpenUrlCommand returns undefined when no opener is available", () => {
|
||||||
|
const command = getOpenUrlCommand("https://example.com", "linux", () => undefined);
|
||||||
|
|
||||||
|
assert.equal(command, undefined);
|
||||||
|
});
|
||||||
@@ -41,7 +41,7 @@ On Windows:
|
|||||||
& ([scriptblock]::Create((irm https://feynman.is/install.ps1))) -Version stable
|
& ([scriptblock]::Create((irm https://feynman.is/install.ps1))) -Version stable
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also pin an exact version by replacing `stable` with a version such as `0.2.13`.
|
You can also pin an exact version by replacing `stable` with a version such as `0.2.14`.
|
||||||
|
|
||||||
## pnpm
|
## pnpm
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user