@mrpatronz/nexusflow 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/agent/AcpCliAdapter.d.ts +75 -0
- package/dist/agent/AcpCliAdapter.d.ts.map +1 -0
- package/dist/agent/AcpCliAdapter.js +292 -0
- package/dist/agent/AcpCliAdapter.js.map +1 -0
- package/dist/agent/AcpCliAdapter.test.d.ts +2 -0
- package/dist/agent/AcpCliAdapter.test.d.ts.map +1 -0
- package/dist/agent/AcpCliAdapter.test.js +235 -0
- package/dist/agent/AcpCliAdapter.test.js.map +1 -0
- package/dist/agent/ClaudeCliAdapter.d.ts +40 -5
- package/dist/agent/ClaudeCliAdapter.d.ts.map +1 -1
- package/dist/agent/ClaudeCliAdapter.js +212 -15
- package/dist/agent/ClaudeCliAdapter.js.map +1 -1
- package/dist/agent/ClaudeCliAdapter.test.d.ts +2 -0
- package/dist/agent/ClaudeCliAdapter.test.d.ts.map +1 -0
- package/dist/agent/ClaudeCliAdapter.test.js +320 -0
- package/dist/agent/ClaudeCliAdapter.test.js.map +1 -0
- package/dist/agent/CliAdapterBase.d.ts +29 -12
- package/dist/agent/CliAdapterBase.d.ts.map +1 -1
- package/dist/agent/CliAdapterBase.js +76 -36
- package/dist/agent/CliAdapterBase.js.map +1 -1
- package/dist/agent/CodexCliAdapter.d.ts +51 -0
- package/dist/agent/CodexCliAdapter.d.ts.map +1 -0
- package/dist/agent/CodexCliAdapter.js +226 -0
- package/dist/agent/CodexCliAdapter.js.map +1 -0
- package/dist/agent/CodexCliAdapter.test.d.ts +2 -0
- package/dist/agent/CodexCliAdapter.test.d.ts.map +1 -0
- package/dist/agent/CodexCliAdapter.test.js +316 -0
- package/dist/agent/CodexCliAdapter.test.js.map +1 -0
- package/dist/agent/CopilotAcpAdapter.d.ts +7 -0
- package/dist/agent/CopilotAcpAdapter.d.ts.map +1 -0
- package/dist/agent/CopilotAcpAdapter.js +29 -0
- package/dist/agent/CopilotAcpAdapter.js.map +1 -0
- package/dist/agent/ProviderRegistry.d.ts +39 -3
- package/dist/agent/ProviderRegistry.d.ts.map +1 -1
- package/dist/agent/ProviderRegistry.js +43 -8
- package/dist/agent/ProviderRegistry.js.map +1 -1
- package/dist/agent/ProviderRegistry.test.js +52 -0
- package/dist/agent/ProviderRegistry.test.js.map +1 -1
- package/dist/agent/adapters.d.ts +7 -5
- package/dist/agent/adapters.d.ts.map +1 -1
- package/dist/agent/adapters.js +92 -17
- package/dist/agent/adapters.js.map +1 -1
- package/dist/agent/adapters.test.js +22 -0
- package/dist/agent/adapters.test.js.map +1 -1
- package/dist/agent/cliAvailability.d.ts +51 -13
- package/dist/agent/cliAvailability.d.ts.map +1 -1
- package/dist/agent/cliAvailability.js +163 -44
- package/dist/agent/cliAvailability.js.map +1 -1
- package/dist/agent/cliAvailability.test.js +140 -53
- package/dist/agent/cliAvailability.test.js.map +1 -1
- package/dist/agent/session.d.ts +2 -1
- package/dist/agent/session.d.ts.map +1 -1
- package/dist/agent/session.js +15 -4
- package/dist/agent/session.js.map +1 -1
- package/dist/agent/session.test.js +29 -8
- package/dist/agent/session.test.js.map +1 -1
- package/dist/core/workspace.d.ts +6 -0
- package/dist/core/workspace.d.ts.map +1 -1
- package/dist/core/workspace.js +17 -7
- package/dist/core/workspace.js.map +1 -1
- package/dist/core/workspace.test.js +12 -1
- package/dist/core/workspace.test.js.map +1 -1
- package/dist/gui/assets/DashboardPage-CUspfCap.js +1 -0
- package/dist/gui/assets/GettingStartedPage-BqtgARZW.js +1 -0
- package/dist/gui/assets/ProjectsPage-UBoQxI2G.js +1 -0
- package/dist/gui/assets/{ScaffoldRepoInline-D-2ZeuDK.js → ScaffoldRepoInline-DqoourJe.js} +1 -1
- package/dist/gui/assets/SettingsPage-BZl24tqD.js +1 -0
- package/dist/gui/assets/StartWorkPage-BGNFdBfj.js +1 -0
- package/dist/gui/assets/{StrategiesPage-Cizm0GWk.js → StrategiesPage-D0Y6A4Tw.js} +2 -2
- package/dist/gui/assets/WorkspaceLauncher-Cxv0O_wt.js +1 -0
- package/dist/gui/assets/WorkspacesPage-Cvb1rOeh.js +6 -0
- package/dist/gui/assets/index-CllPiiFL.js +52 -0
- package/dist/gui/assets/index-DGCBhMAn.css +2 -0
- package/dist/gui/index.html +2 -2
- package/dist/server.d.ts +13 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +267 -39
- package/dist/server.js.map +1 -1
- package/dist/server.test.js +324 -18
- package/dist/server.test.js.map +1 -1
- package/dist/types.d.ts +13 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/detect-editors.d.ts.map +1 -1
- package/dist/utils/detect-editors.js +6 -0
- package/dist/utils/detect-editors.js.map +1 -1
- package/dist/utils/detect-editors.test.js +12 -0
- package/dist/utils/detect-editors.test.js.map +1 -1
- package/dist/utils/session-finder.d.ts +14 -0
- package/dist/utils/session-finder.d.ts.map +1 -1
- package/dist/utils/session-finder.js +127 -10
- package/dist/utils/session-finder.js.map +1 -1
- package/dist/utils/session-finder.test.js +104 -2
- package/dist/utils/session-finder.test.js.map +1 -1
- package/dist/utils/workspace-launch.d.ts +37 -0
- package/dist/utils/workspace-launch.d.ts.map +1 -0
- package/dist/utils/workspace-launch.js +200 -0
- package/dist/utils/workspace-launch.js.map +1 -0
- package/dist/utils/workspace-launch.test.d.ts +2 -0
- package/dist/utils/workspace-launch.test.d.ts.map +1 -0
- package/dist/utils/workspace-launch.test.js +94 -0
- package/dist/utils/workspace-launch.test.js.map +1 -0
- package/package.json +5 -3
- package/dist/gui/assets/DashboardPage-HNC3-OP0.js +0 -1
- package/dist/gui/assets/GettingStartedPage-Dguoya_Z.js +0 -1
- package/dist/gui/assets/ProjectsPage-DVR4j4sw.js +0 -1
- package/dist/gui/assets/SettingsPage-Bauo77q3.js +0 -1
- package/dist/gui/assets/StartWorkPage-DvuFEa5Y.js +0 -1
- package/dist/gui/assets/WorkspacesPage-DYOVfZqy.js +0 -6
- package/dist/gui/assets/external-link-BtYhMxno.js +0 -1
- package/dist/gui/assets/index-BqeoAIB1.js +0 -52
- package/dist/gui/assets/index-CsA2SueF.css +0 -2
|
@@ -7,29 +7,52 @@
|
|
|
7
7
|
* when it cannot run, so a chat opens, the first turn fails, and the message the
|
|
8
8
|
* user sees is whatever the CLI happened to print.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* desktop app then works while an independently spawned `claude -p` cannot
|
|
14
|
-
* authenticate at all — the tokens simply are not on disk for it to find.
|
|
10
|
+
* Provider-owned status commands are the source of truth. In particular,
|
|
11
|
+
* NexusFlow must not infer Claude login from private credential-file shapes:
|
|
12
|
+
* newer installations may use a keychain or host-managed subscription login.
|
|
15
13
|
*
|
|
16
|
-
* Checks are synchronous because `ProviderAdapter.isConfigured()` is
|
|
17
|
-
*
|
|
18
|
-
*
|
|
14
|
+
* Checks are synchronous because `ProviderAdapter.isConfigured()` is. Probe output
|
|
15
|
+
* is reduced to a closed setup state so account and credential details never cross
|
|
16
|
+
* the server boundary.
|
|
19
17
|
*/
|
|
20
18
|
export interface CliStatus {
|
|
21
19
|
/** Whether a turn stands a chance of succeeding. */
|
|
22
20
|
usable: boolean;
|
|
23
21
|
/** User-facing explanation. Present whenever `usable` is false. */
|
|
24
22
|
message?: string;
|
|
23
|
+
setupIssue?: 'missing-cli' | 'signed-out' | 'probe-failed';
|
|
24
|
+
recoveryCommand?: string;
|
|
25
|
+
recoveryLabel?: string;
|
|
25
26
|
}
|
|
26
27
|
export interface DetectOptions {
|
|
27
28
|
/** Overrides for tests. */
|
|
28
29
|
env?: NodeJS.ProcessEnv;
|
|
29
|
-
homeDir?: string;
|
|
30
30
|
/** Skips the PATH scan when availability is already known. */
|
|
31
31
|
hasBinary?: boolean;
|
|
32
32
|
}
|
|
33
|
+
export interface CodexDetectOptions extends DetectOptions {
|
|
34
|
+
/** Injected command outcome for deterministic tests. */
|
|
35
|
+
loginStatus?: {
|
|
36
|
+
exitCode: number | null;
|
|
37
|
+
error?: string;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export interface ClaudeDetectOptions extends DetectOptions {
|
|
41
|
+
/** Injected command outcome for deterministic tests. */
|
|
42
|
+
authStatus?: {
|
|
43
|
+
exitCode: number | null;
|
|
44
|
+
stdout?: string;
|
|
45
|
+
error?: string;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export interface CopilotDetectOptions extends DetectOptions {
|
|
49
|
+
/** Injected command outcome for deterministic tests. */
|
|
50
|
+
helpStatus?: {
|
|
51
|
+
exitCode: number | null;
|
|
52
|
+
output?: string;
|
|
53
|
+
error?: string;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
33
56
|
/**
|
|
34
57
|
* Resolves an executable on PATH the way a shell would, honouring PATHEXT on
|
|
35
58
|
* Windows so `claude.cmd` and `claude.exe` both count.
|
|
@@ -38,11 +61,26 @@ export declare function findExecutable(name: string, env?: NodeJS.ProcessEnv): s
|
|
|
38
61
|
/**
|
|
39
62
|
* Whether `claude` can be driven in print mode.
|
|
40
63
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
64
|
+
* `claude auth status --json` is the provider-owned authority. NexusFlow only
|
|
65
|
+
* inspects the boolean `loggedIn` field and never reads or returns credential,
|
|
66
|
+
* account, auth-method, or provider values.
|
|
44
67
|
*/
|
|
45
|
-
export declare function detectClaudeCliStatus(options?:
|
|
68
|
+
export declare function detectClaudeCliStatus(options?: ClaudeDetectOptions): CliStatus;
|
|
46
69
|
/** Whether the Antigravity CLI is present. It manages its own auth. */
|
|
47
70
|
export declare function detectAntigravityCliStatus(options?: DetectOptions): CliStatus;
|
|
71
|
+
/**
|
|
72
|
+
* Whether Codex can run non-interactively with the user's existing CLI login.
|
|
73
|
+
* `codex login status` is the supported auth probe and does not expose or copy
|
|
74
|
+
* the credential store. A ChatGPT subscription login is sufficient; no API key
|
|
75
|
+
* is required.
|
|
76
|
+
*/
|
|
77
|
+
export declare function detectCodexCliStatus(options?: CodexDetectOptions): CliStatus;
|
|
78
|
+
/**
|
|
79
|
+
* Whether the installed Copilot CLI exposes its supported ACP server.
|
|
80
|
+
*
|
|
81
|
+
* Copilot has no documented non-interactive login-status command. Session
|
|
82
|
+
* creation is therefore the authentication authority; this probe only avoids
|
|
83
|
+
* advertising older installations that cannot speak ACP at all.
|
|
84
|
+
*/
|
|
85
|
+
export declare function detectCopilotCliStatus(options?: CopilotDetectOptions): CliStatus;
|
|
48
86
|
//# sourceMappingURL=cliAvailability.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cliAvailability.d.ts","sourceRoot":"","sources":["../../src/agent/cliAvailability.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"cliAvailability.d.ts","sourceRoot":"","sources":["../../src/agent/cliAvailability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,MAAM,WAAW,SAAS;IACxB,oDAAoD;IACpD,MAAM,EAAE,OAAO,CAAC;IAChB,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,aAAa,GAAG,YAAY,GAAG,cAAc,CAAC;IAC3D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,2BAA2B;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,kBAAmB,SAAQ,aAAa;IACvD,wDAAwD;IACxD,WAAW,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3D;AAED,MAAM,WAAW,mBAAoB,SAAQ,aAAa;IACxD,wDAAwD;IACxD,UAAU,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3E;AAED,MAAM,WAAW,oBAAqB,SAAQ,aAAa;IACzD,wDAAwD;IACxD,UAAU,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3E;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,GAAG,IAAI,CAsBhG;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,mBAAwB,GAAG,SAAS,CAgElF;AAED,uEAAuE;AACvE,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,aAAkB,GAAG,SAAS,CAUjF;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,kBAAuB,GAAG,SAAS,CAiDhF;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,oBAAyB,GAAG,SAAS,CAgDpF"}
|
|
@@ -7,19 +7,17 @@
|
|
|
7
7
|
* when it cannot run, so a chat opens, the first turn fails, and the message the
|
|
8
8
|
* user sees is whatever the CLI happened to print.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* desktop app then works while an independently spawned `claude -p` cannot
|
|
14
|
-
* authenticate at all — the tokens simply are not on disk for it to find.
|
|
10
|
+
* Provider-owned status commands are the source of truth. In particular,
|
|
11
|
+
* NexusFlow must not infer Claude login from private credential-file shapes:
|
|
12
|
+
* newer installations may use a keychain or host-managed subscription login.
|
|
15
13
|
*
|
|
16
|
-
* Checks are synchronous because `ProviderAdapter.isConfigured()` is
|
|
17
|
-
*
|
|
18
|
-
*
|
|
14
|
+
* Checks are synchronous because `ProviderAdapter.isConfigured()` is. Probe output
|
|
15
|
+
* is reduced to a closed setup state so account and credential details never cross
|
|
16
|
+
* the server boundary.
|
|
19
17
|
*/
|
|
20
18
|
import * as fsSync from 'node:fs';
|
|
21
|
-
import * as os from 'node:os';
|
|
22
19
|
import * as path from 'node:path';
|
|
20
|
+
import { spawnSync } from 'node:child_process';
|
|
23
21
|
/**
|
|
24
22
|
* Resolves an executable on PATH the way a shell would, honouring PATHEXT on
|
|
25
23
|
* Windows so `claude.cmd` and `claude.exe` both count.
|
|
@@ -47,55 +45,73 @@ export function findExecutable(name, env = process.env) {
|
|
|
47
45
|
}
|
|
48
46
|
return null;
|
|
49
47
|
}
|
|
50
|
-
/** Reads the on-disk Claude credential store. Null when absent or unreadable. */
|
|
51
|
-
function readStoredOauth(homeDir) {
|
|
52
|
-
const file = path.join(homeDir, '.claude', '.credentials.json');
|
|
53
|
-
try {
|
|
54
|
-
const parsed = JSON.parse(fsSync.readFileSync(file, 'utf-8'));
|
|
55
|
-
return parsed.claudeAiOauth ?? null;
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
// Missing, malformed, or stored somewhere else entirely — all "unknown".
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
48
|
/**
|
|
63
49
|
* Whether `claude` can be driven in print mode.
|
|
64
50
|
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
51
|
+
* `claude auth status --json` is the provider-owned authority. NexusFlow only
|
|
52
|
+
* inspects the boolean `loggedIn` field and never reads or returns credential,
|
|
53
|
+
* account, auth-method, or provider values.
|
|
68
54
|
*/
|
|
69
55
|
export function detectClaudeCliStatus(options = {}) {
|
|
70
56
|
const env = options.env ?? process.env;
|
|
71
|
-
const
|
|
72
|
-
const hasBinary = options.hasBinary ??
|
|
57
|
+
const executable = options.hasBinary === false ? null : findExecutable('claude', env);
|
|
58
|
+
const hasBinary = options.hasBinary ?? executable !== null;
|
|
73
59
|
if (!hasBinary) {
|
|
74
60
|
return {
|
|
75
61
|
usable: false,
|
|
76
|
-
message: 'The
|
|
62
|
+
message: 'The Claude Code CLI was not found on PATH.',
|
|
63
|
+
setupIssue: 'missing-cli',
|
|
64
|
+
recoveryCommand: 'npm install -g @anthropic-ai/claude-code',
|
|
65
|
+
recoveryLabel: 'Copy install command',
|
|
77
66
|
};
|
|
78
67
|
}
|
|
79
|
-
if (env.ANTHROPIC_API_KEY
|
|
68
|
+
if (env.ANTHROPIC_API_KEY
|
|
69
|
+
|| env.ANTHROPIC_AUTH_TOKEN
|
|
70
|
+
|| env.CLAUDE_CODE_USE_BEDROCK === '1'
|
|
71
|
+
|| env.CLAUDE_CODE_USE_VERTEX === '1'
|
|
72
|
+
|| env.CLAUDE_CODE_USE_FOUNDRY === '1')
|
|
80
73
|
return { usable: true };
|
|
81
|
-
|
|
82
|
-
if (!
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
74
|
+
let authStatus = options.authStatus;
|
|
75
|
+
if (!authStatus) {
|
|
76
|
+
const result = spawnSync(executable ?? 'claude', ['auth', 'status', '--json'], {
|
|
77
|
+
env,
|
|
78
|
+
encoding: 'utf-8',
|
|
79
|
+
timeout: 5_000,
|
|
80
|
+
windowsHide: true,
|
|
81
|
+
shell: process.platform === 'win32' && /\.(cmd|bat)$/i.test(executable ?? ''),
|
|
82
|
+
});
|
|
83
|
+
authStatus = {
|
|
84
|
+
exitCode: result.status,
|
|
85
|
+
stdout: result.stdout ?? '',
|
|
86
|
+
error: result.error?.message,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (!authStatus.error && typeof authStatus.stdout === 'string') {
|
|
90
|
+
try {
|
|
91
|
+
const parsed = JSON.parse(authStatus.stdout);
|
|
92
|
+
if (parsed.loggedIn === true)
|
|
93
|
+
return { usable: true };
|
|
94
|
+
if (parsed.loggedIn === false) {
|
|
95
|
+
return {
|
|
96
|
+
usable: false,
|
|
97
|
+
message: 'Claude Code is installed but not signed in. Use your Claude subscription; no API key is required.',
|
|
98
|
+
setupIssue: 'signed-out',
|
|
99
|
+
recoveryCommand: 'claude auth login',
|
|
100
|
+
recoveryLabel: 'Copy sign-in command',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// A changed or malformed provider response is a compatibility failure,
|
|
106
|
+
// not evidence that the user is signed out.
|
|
107
|
+
}
|
|
108
|
+
}
|
|
91
109
|
return {
|
|
92
110
|
usable: false,
|
|
93
|
-
message:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
: 'The claude CLI is installed but not signed in — its stored credentials are empty. '
|
|
98
|
-
+ 'Run `claude` in a terminal and sign in, or set ANTHROPIC_API_KEY and use the Claude (API) provider.',
|
|
111
|
+
message: 'NexusFlow could not verify Claude Code login. Check it in a terminal, then recheck.',
|
|
112
|
+
setupIssue: 'probe-failed',
|
|
113
|
+
recoveryCommand: 'claude auth status --json',
|
|
114
|
+
recoveryLabel: 'Copy status command',
|
|
99
115
|
};
|
|
100
116
|
}
|
|
101
117
|
/** Whether the Antigravity CLI is present. It manages its own auth. */
|
|
@@ -109,4 +125,107 @@ export function detectAntigravityCliStatus(options = {}) {
|
|
|
109
125
|
message: 'The Antigravity CLI (`agy`) was not found on PATH.',
|
|
110
126
|
};
|
|
111
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Whether Codex can run non-interactively with the user's existing CLI login.
|
|
130
|
+
* `codex login status` is the supported auth probe and does not expose or copy
|
|
131
|
+
* the credential store. A ChatGPT subscription login is sufficient; no API key
|
|
132
|
+
* is required.
|
|
133
|
+
*/
|
|
134
|
+
export function detectCodexCliStatus(options = {}) {
|
|
135
|
+
const env = options.env ?? process.env;
|
|
136
|
+
const executable = options.hasBinary === false ? null : findExecutable('codex', env);
|
|
137
|
+
const hasBinary = options.hasBinary ?? executable !== null;
|
|
138
|
+
if (!hasBinary) {
|
|
139
|
+
return {
|
|
140
|
+
usable: false,
|
|
141
|
+
message: 'The Codex CLI was not found on PATH.',
|
|
142
|
+
setupIssue: 'missing-cli',
|
|
143
|
+
recoveryCommand: 'npm install -g @openai/codex',
|
|
144
|
+
recoveryLabel: 'Copy install command',
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
let loginStatus = options.loginStatus;
|
|
148
|
+
if (!loginStatus) {
|
|
149
|
+
const result = spawnSync(executable ?? 'codex', ['login', 'status'], {
|
|
150
|
+
env,
|
|
151
|
+
encoding: 'utf-8',
|
|
152
|
+
timeout: 5_000,
|
|
153
|
+
windowsHide: true,
|
|
154
|
+
shell: process.platform === 'win32' && /\.(cmd|bat)$/i.test(executable ?? ''),
|
|
155
|
+
});
|
|
156
|
+
loginStatus = {
|
|
157
|
+
exitCode: result.status,
|
|
158
|
+
error: result.error?.message,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
if (!loginStatus.error && loginStatus.exitCode === 0)
|
|
162
|
+
return { usable: true };
|
|
163
|
+
if (!loginStatus.error && loginStatus.exitCode === 1) {
|
|
164
|
+
return {
|
|
165
|
+
usable: false,
|
|
166
|
+
message: 'Codex is installed but not signed in. Use your ChatGPT account; no API key is required.',
|
|
167
|
+
setupIssue: 'signed-out',
|
|
168
|
+
recoveryCommand: 'codex login',
|
|
169
|
+
recoveryLabel: 'Copy sign-in command',
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
usable: false,
|
|
174
|
+
message: 'NexusFlow could not verify Codex login. Check it in a terminal, then recheck.',
|
|
175
|
+
setupIssue: 'probe-failed',
|
|
176
|
+
recoveryCommand: 'codex login status',
|
|
177
|
+
recoveryLabel: 'Copy status command',
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Whether the installed Copilot CLI exposes its supported ACP server.
|
|
182
|
+
*
|
|
183
|
+
* Copilot has no documented non-interactive login-status command. Session
|
|
184
|
+
* creation is therefore the authentication authority; this probe only avoids
|
|
185
|
+
* advertising older installations that cannot speak ACP at all.
|
|
186
|
+
*/
|
|
187
|
+
export function detectCopilotCliStatus(options = {}) {
|
|
188
|
+
const env = options.env ?? process.env;
|
|
189
|
+
const executable = options.hasBinary === false ? null : findExecutable('copilot', env);
|
|
190
|
+
const hasBinary = options.hasBinary ?? executable !== null;
|
|
191
|
+
if (!hasBinary) {
|
|
192
|
+
return {
|
|
193
|
+
usable: false,
|
|
194
|
+
message: 'The GitHub Copilot CLI was not found on PATH. Install it, then run `copilot login`.',
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
// A classic PAT takes precedence over saved OAuth but is unsupported by the
|
|
198
|
+
// Copilot CLI, so this is one credential failure we can identify safely.
|
|
199
|
+
const token = env.COPILOT_GITHUB_TOKEN ?? env.GH_TOKEN ?? env.GITHUB_TOKEN;
|
|
200
|
+
if (token?.startsWith('ghp_')) {
|
|
201
|
+
return {
|
|
202
|
+
usable: false,
|
|
203
|
+
message: 'GitHub Copilot CLI does not support classic `ghp_` tokens. Remove the overriding token and run `copilot login`.',
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
let helpStatus = options.helpStatus;
|
|
207
|
+
if (!helpStatus) {
|
|
208
|
+
const result = spawnSync(executable ?? 'copilot', ['help'], {
|
|
209
|
+
env,
|
|
210
|
+
encoding: 'utf-8',
|
|
211
|
+
timeout: 5_000,
|
|
212
|
+
windowsHide: true,
|
|
213
|
+
shell: process.platform === 'win32' && /\.(cmd|bat)$/i.test(executable ?? ''),
|
|
214
|
+
});
|
|
215
|
+
helpStatus = {
|
|
216
|
+
exitCode: result.status,
|
|
217
|
+
output: `${result.stdout ?? ''}\n${result.stderr ?? ''}`,
|
|
218
|
+
error: result.error?.message,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
if (helpStatus.exitCode === 0 && /--acp\b/.test(helpStatus.output ?? '')) {
|
|
222
|
+
return { usable: true };
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
usable: false,
|
|
226
|
+
message: helpStatus.error
|
|
227
|
+
? `NexusFlow could not inspect GitHub Copilot CLI ACP support (${helpStatus.error}). Update Copilot CLI and try again.`
|
|
228
|
+
: 'The installed GitHub Copilot CLI does not expose ACP. Update it, then run `copilot login`.',
|
|
229
|
+
};
|
|
230
|
+
}
|
|
112
231
|
//# sourceMappingURL=cliAvailability.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cliAvailability.js","sourceRoot":"","sources":["../../src/agent/cliAvailability.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"cliAvailability.js","sourceRoot":"","sources":["../../src/agent/cliAvailability.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import * as fs from 'node:fs/promises';
|
|
|
2
2
|
import * as os from 'node:os';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
5
|
-
import { detectAntigravityCliStatus, detectClaudeCliStatus, findExecutable, } from './cliAvailability.js';
|
|
5
|
+
import { detectAntigravityCliStatus, detectClaudeCliStatus, detectCodexCliStatus, detectCopilotCliStatus, findExecutable, } from './cliAvailability.js';
|
|
6
6
|
let home = '';
|
|
7
7
|
beforeEach(async () => {
|
|
8
8
|
home = await fs.mkdtemp(path.join(os.tmpdir(), 'nexusflow-cliavail-'));
|
|
@@ -10,19 +10,6 @@ beforeEach(async () => {
|
|
|
10
10
|
afterEach(async () => {
|
|
11
11
|
await fs.rm(home, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
|
|
12
12
|
});
|
|
13
|
-
/** Writes a credential store with the given oauth block. */
|
|
14
|
-
async function writeCredentials(oauth) {
|
|
15
|
-
await fs.mkdir(path.join(home, '.claude'), { recursive: true });
|
|
16
|
-
await fs.writeFile(path.join(home, '.claude', '.credentials.json'), JSON.stringify({ claudeAiOauth: oauth }), 'utf-8');
|
|
17
|
-
}
|
|
18
|
-
/** The observed real-world shape: scopes present, every token empty. */
|
|
19
|
-
const HOLLOW = {
|
|
20
|
-
accessToken: '',
|
|
21
|
-
refreshToken: '',
|
|
22
|
-
expiresAt: 0,
|
|
23
|
-
scopes: ['user:inference'],
|
|
24
|
-
subscriptionType: 'team',
|
|
25
|
-
};
|
|
26
13
|
describe('findExecutable', () => {
|
|
27
14
|
it('finds a file on PATH', async () => {
|
|
28
15
|
const name = process.platform === 'win32' ? 'thing.cmd' : 'thing';
|
|
@@ -38,58 +25,72 @@ describe('findExecutable', () => {
|
|
|
38
25
|
});
|
|
39
26
|
describe('detectClaudeCliStatus', () => {
|
|
40
27
|
it('is unusable when the binary is missing, and says so', () => {
|
|
41
|
-
const status = detectClaudeCliStatus({ hasBinary: false, env: {}
|
|
28
|
+
const status = detectClaudeCliStatus({ hasBinary: false, env: {} });
|
|
42
29
|
expect(status.usable).toBe(false);
|
|
43
30
|
expect(status.message).toMatch(/not found on PATH/);
|
|
31
|
+
expect(status).toMatchObject({
|
|
32
|
+
setupIssue: 'missing-cli',
|
|
33
|
+
recoveryCommand: 'npm install -g @anthropic-ai/claude-code',
|
|
34
|
+
});
|
|
44
35
|
});
|
|
45
|
-
it('is usable when an API key is set
|
|
46
|
-
await writeCredentials(HOLLOW);
|
|
36
|
+
it('is usable when an API key is set without inspecting auth status', () => {
|
|
47
37
|
const status = detectClaudeCliStatus({
|
|
48
38
|
hasBinary: true,
|
|
49
39
|
env: { ANTHROPIC_API_KEY: 'sk-test' },
|
|
50
|
-
|
|
40
|
+
authStatus: { exitCode: 1, stdout: '{not json', error: 'must not leak' },
|
|
51
41
|
});
|
|
52
|
-
expect(status
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
expect(
|
|
61
|
-
|
|
42
|
+
expect(status).toEqual({ usable: true });
|
|
43
|
+
});
|
|
44
|
+
it.each([
|
|
45
|
+
{ ANTHROPIC_AUTH_TOKEN: 'gateway-token' },
|
|
46
|
+
{ CLAUDE_CODE_USE_BEDROCK: '1' },
|
|
47
|
+
{ CLAUDE_CODE_USE_VERTEX: '1' },
|
|
48
|
+
{ CLAUDE_CODE_USE_FOUNDRY: '1' },
|
|
49
|
+
])('accepts a declared provider-owned external auth mode', (externalEnv) => {
|
|
50
|
+
expect(detectClaudeCliStatus({
|
|
51
|
+
hasBinary: true,
|
|
52
|
+
env: externalEnv,
|
|
53
|
+
authStatus: { exitCode: 1, stdout: JSON.stringify({ loggedIn: false }) },
|
|
54
|
+
})).toEqual({ usable: true });
|
|
62
55
|
});
|
|
63
|
-
it('
|
|
64
|
-
await writeCredentials(HOLLOW);
|
|
56
|
+
it('accepts provider-owned subscription login without returning account metadata', () => {
|
|
65
57
|
const status = detectClaudeCliStatus({
|
|
66
58
|
hasBinary: true,
|
|
67
|
-
env: {
|
|
68
|
-
|
|
59
|
+
env: {},
|
|
60
|
+
authStatus: {
|
|
61
|
+
exitCode: 0,
|
|
62
|
+
stdout: JSON.stringify({ loggedIn: true, authMethod: 'claude.ai', apiProvider: 'firstParty', email: 'private@example.test' }),
|
|
63
|
+
},
|
|
69
64
|
});
|
|
70
|
-
expect(status
|
|
71
|
-
expect(status.message).toMatch(/holds your tokens in memory/i);
|
|
72
|
-
// The message must be actionable, not just descriptive.
|
|
73
|
-
expect(status.message).toMatch(/ANTHROPIC_API_KEY/);
|
|
74
|
-
});
|
|
75
|
-
it('is usable with an access token present', async () => {
|
|
76
|
-
await writeCredentials({ ...HOLLOW, accessToken: 'tok' });
|
|
77
|
-
expect(detectClaudeCliStatus({ hasBinary: true, env: {}, homeDir: home }).usable).toBe(true);
|
|
65
|
+
expect(status).toEqual({ usable: true });
|
|
78
66
|
});
|
|
79
|
-
it('
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
67
|
+
it('maps a parsed signed-out response to keyless Claude login recovery', () => {
|
|
68
|
+
const status = detectClaudeCliStatus({
|
|
69
|
+
hasBinary: true,
|
|
70
|
+
env: {},
|
|
71
|
+
authStatus: { exitCode: 1, stdout: JSON.stringify({ loggedIn: false }) },
|
|
72
|
+
});
|
|
73
|
+
expect(status).toMatchObject({
|
|
74
|
+
usable: false,
|
|
75
|
+
setupIssue: 'signed-out',
|
|
76
|
+
recoveryCommand: 'claude auth login',
|
|
77
|
+
recoveryLabel: 'Copy sign-in command',
|
|
78
|
+
});
|
|
79
|
+
expect(status.message).toMatch(/no API key is required/i);
|
|
88
80
|
});
|
|
89
|
-
it(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
81
|
+
it.each([
|
|
82
|
+
{ name: 'malformed output', authStatus: { exitCode: 0, stdout: '{ not json' } },
|
|
83
|
+
{ name: 'unknown schema', authStatus: { exitCode: 0, stdout: '{"authenticated":true}' } },
|
|
84
|
+
{ name: 'timeout', authStatus: { exitCode: null, stdout: '', error: 'timed out at C:\\private\\path' } },
|
|
85
|
+
])('sanitizes a $name auth probe failure', ({ authStatus }) => {
|
|
86
|
+
const status = detectClaudeCliStatus({ hasBinary: true, env: {}, authStatus });
|
|
87
|
+
expect(status).toMatchObject({
|
|
88
|
+
usable: false,
|
|
89
|
+
setupIssue: 'probe-failed',
|
|
90
|
+
recoveryCommand: 'claude auth status --json',
|
|
91
|
+
});
|
|
92
|
+
expect(status.message).not.toContain('private');
|
|
93
|
+
expect(status).not.toHaveProperty('stdout');
|
|
93
94
|
});
|
|
94
95
|
});
|
|
95
96
|
describe('detectAntigravityCliStatus', () => {
|
|
@@ -102,4 +103,90 @@ describe('detectAntigravityCliStatus', () => {
|
|
|
102
103
|
expect(status.message).toMatch(/agy/);
|
|
103
104
|
});
|
|
104
105
|
});
|
|
106
|
+
describe('detectCodexCliStatus', () => {
|
|
107
|
+
it('is unusable when the binary is missing', () => {
|
|
108
|
+
const status = detectCodexCliStatus({ hasBinary: false, env: {} });
|
|
109
|
+
expect(status.usable).toBe(false);
|
|
110
|
+
expect(status.message).toMatch(/not found on PATH/i);
|
|
111
|
+
expect(status).toMatchObject({
|
|
112
|
+
setupIssue: 'missing-cli',
|
|
113
|
+
recoveryCommand: 'npm install -g @openai/codex',
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
it('accepts a saved ChatGPT or API login reported by the CLI', () => {
|
|
117
|
+
const status = detectCodexCliStatus({
|
|
118
|
+
hasBinary: true,
|
|
119
|
+
env: {},
|
|
120
|
+
loginStatus: { exitCode: 0 },
|
|
121
|
+
});
|
|
122
|
+
expect(status).toEqual({ usable: true });
|
|
123
|
+
});
|
|
124
|
+
it('directs signed-out users to ChatGPT login without demanding an API key', () => {
|
|
125
|
+
const status = detectCodexCliStatus({
|
|
126
|
+
hasBinary: true,
|
|
127
|
+
env: {},
|
|
128
|
+
loginStatus: { exitCode: 1 },
|
|
129
|
+
});
|
|
130
|
+
expect(status.usable).toBe(false);
|
|
131
|
+
expect(status.message).toMatch(/no API key is required/i);
|
|
132
|
+
expect(status).toMatchObject({
|
|
133
|
+
setupIssue: 'signed-out',
|
|
134
|
+
recoveryCommand: 'codex login',
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
it('reports a timed-out or failed auth probe actionably', () => {
|
|
138
|
+
const status = detectCodexCliStatus({
|
|
139
|
+
hasBinary: true,
|
|
140
|
+
env: {},
|
|
141
|
+
loginStatus: { exitCode: null, error: 'timed out' },
|
|
142
|
+
});
|
|
143
|
+
expect(status.usable).toBe(false);
|
|
144
|
+
expect(status).toMatchObject({
|
|
145
|
+
setupIssue: 'probe-failed',
|
|
146
|
+
recoveryCommand: 'codex login status',
|
|
147
|
+
});
|
|
148
|
+
expect(status.message).not.toMatch(/timed out/);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
describe('detectCopilotCliStatus', () => {
|
|
152
|
+
it('is unusable when the binary is missing', () => {
|
|
153
|
+
const status = detectCopilotCliStatus({ hasBinary: false, env: {} });
|
|
154
|
+
expect(status.usable).toBe(false);
|
|
155
|
+
expect(status.message).toMatch(/not found on PATH/i);
|
|
156
|
+
});
|
|
157
|
+
it('accepts an installation whose help advertises ACP', () => {
|
|
158
|
+
expect(detectCopilotCliStatus({
|
|
159
|
+
hasBinary: true,
|
|
160
|
+
env: {},
|
|
161
|
+
helpStatus: { exitCode: 0, output: ' --acp Start Agent Client Protocol server' },
|
|
162
|
+
})).toEqual({ usable: true });
|
|
163
|
+
});
|
|
164
|
+
it('asks users with an older CLI to update it', () => {
|
|
165
|
+
const status = detectCopilotCliStatus({
|
|
166
|
+
hasBinary: true,
|
|
167
|
+
env: {},
|
|
168
|
+
helpStatus: { exitCode: 0, output: 'Usage: copilot' },
|
|
169
|
+
});
|
|
170
|
+
expect(status.usable).toBe(false);
|
|
171
|
+
expect(status.message).toMatch(/does not expose ACP/i);
|
|
172
|
+
});
|
|
173
|
+
it('reports a failed or timed-out capability probe', () => {
|
|
174
|
+
const status = detectCopilotCliStatus({
|
|
175
|
+
hasBinary: true,
|
|
176
|
+
env: {},
|
|
177
|
+
helpStatus: { exitCode: null, error: 'timed out' },
|
|
178
|
+
});
|
|
179
|
+
expect(status.usable).toBe(false);
|
|
180
|
+
expect(status.message).toMatch(/timed out/);
|
|
181
|
+
});
|
|
182
|
+
it('rejects an overriding classic PAT that Copilot cannot use', () => {
|
|
183
|
+
const status = detectCopilotCliStatus({
|
|
184
|
+
hasBinary: true,
|
|
185
|
+
env: { COPILOT_GITHUB_TOKEN: 'ghp_classic' },
|
|
186
|
+
helpStatus: { exitCode: 0, output: '--acp' },
|
|
187
|
+
});
|
|
188
|
+
expect(status.usable).toBe(false);
|
|
189
|
+
expect(status.message).toMatch(/classic/i);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
105
192
|
//# sourceMappingURL=cliAvailability.test.js.map
|