@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.
Files changed (112) hide show
  1. package/README.md +5 -1
  2. package/dist/agent/AcpCliAdapter.d.ts +75 -0
  3. package/dist/agent/AcpCliAdapter.d.ts.map +1 -0
  4. package/dist/agent/AcpCliAdapter.js +292 -0
  5. package/dist/agent/AcpCliAdapter.js.map +1 -0
  6. package/dist/agent/AcpCliAdapter.test.d.ts +2 -0
  7. package/dist/agent/AcpCliAdapter.test.d.ts.map +1 -0
  8. package/dist/agent/AcpCliAdapter.test.js +235 -0
  9. package/dist/agent/AcpCliAdapter.test.js.map +1 -0
  10. package/dist/agent/ClaudeCliAdapter.d.ts +40 -5
  11. package/dist/agent/ClaudeCliAdapter.d.ts.map +1 -1
  12. package/dist/agent/ClaudeCliAdapter.js +212 -15
  13. package/dist/agent/ClaudeCliAdapter.js.map +1 -1
  14. package/dist/agent/ClaudeCliAdapter.test.d.ts +2 -0
  15. package/dist/agent/ClaudeCliAdapter.test.d.ts.map +1 -0
  16. package/dist/agent/ClaudeCliAdapter.test.js +320 -0
  17. package/dist/agent/ClaudeCliAdapter.test.js.map +1 -0
  18. package/dist/agent/CliAdapterBase.d.ts +29 -12
  19. package/dist/agent/CliAdapterBase.d.ts.map +1 -1
  20. package/dist/agent/CliAdapterBase.js +76 -36
  21. package/dist/agent/CliAdapterBase.js.map +1 -1
  22. package/dist/agent/CodexCliAdapter.d.ts +51 -0
  23. package/dist/agent/CodexCliAdapter.d.ts.map +1 -0
  24. package/dist/agent/CodexCliAdapter.js +226 -0
  25. package/dist/agent/CodexCliAdapter.js.map +1 -0
  26. package/dist/agent/CodexCliAdapter.test.d.ts +2 -0
  27. package/dist/agent/CodexCliAdapter.test.d.ts.map +1 -0
  28. package/dist/agent/CodexCliAdapter.test.js +316 -0
  29. package/dist/agent/CodexCliAdapter.test.js.map +1 -0
  30. package/dist/agent/CopilotAcpAdapter.d.ts +7 -0
  31. package/dist/agent/CopilotAcpAdapter.d.ts.map +1 -0
  32. package/dist/agent/CopilotAcpAdapter.js +29 -0
  33. package/dist/agent/CopilotAcpAdapter.js.map +1 -0
  34. package/dist/agent/ProviderRegistry.d.ts +39 -3
  35. package/dist/agent/ProviderRegistry.d.ts.map +1 -1
  36. package/dist/agent/ProviderRegistry.js +43 -8
  37. package/dist/agent/ProviderRegistry.js.map +1 -1
  38. package/dist/agent/ProviderRegistry.test.js +52 -0
  39. package/dist/agent/ProviderRegistry.test.js.map +1 -1
  40. package/dist/agent/adapters.d.ts +7 -5
  41. package/dist/agent/adapters.d.ts.map +1 -1
  42. package/dist/agent/adapters.js +92 -17
  43. package/dist/agent/adapters.js.map +1 -1
  44. package/dist/agent/adapters.test.js +22 -0
  45. package/dist/agent/adapters.test.js.map +1 -1
  46. package/dist/agent/cliAvailability.d.ts +51 -13
  47. package/dist/agent/cliAvailability.d.ts.map +1 -1
  48. package/dist/agent/cliAvailability.js +163 -44
  49. package/dist/agent/cliAvailability.js.map +1 -1
  50. package/dist/agent/cliAvailability.test.js +140 -53
  51. package/dist/agent/cliAvailability.test.js.map +1 -1
  52. package/dist/agent/session.d.ts +2 -1
  53. package/dist/agent/session.d.ts.map +1 -1
  54. package/dist/agent/session.js +15 -4
  55. package/dist/agent/session.js.map +1 -1
  56. package/dist/agent/session.test.js +29 -8
  57. package/dist/agent/session.test.js.map +1 -1
  58. package/dist/core/workspace.d.ts +6 -0
  59. package/dist/core/workspace.d.ts.map +1 -1
  60. package/dist/core/workspace.js +17 -7
  61. package/dist/core/workspace.js.map +1 -1
  62. package/dist/core/workspace.test.js +12 -1
  63. package/dist/core/workspace.test.js.map +1 -1
  64. package/dist/gui/assets/DashboardPage-CUspfCap.js +1 -0
  65. package/dist/gui/assets/GettingStartedPage-BqtgARZW.js +1 -0
  66. package/dist/gui/assets/ProjectsPage-UBoQxI2G.js +1 -0
  67. package/dist/gui/assets/{ScaffoldRepoInline-D-2ZeuDK.js → ScaffoldRepoInline-DqoourJe.js} +1 -1
  68. package/dist/gui/assets/SettingsPage-BZl24tqD.js +1 -0
  69. package/dist/gui/assets/StartWorkPage-BGNFdBfj.js +1 -0
  70. package/dist/gui/assets/{StrategiesPage-Cizm0GWk.js → StrategiesPage-D0Y6A4Tw.js} +2 -2
  71. package/dist/gui/assets/WorkspaceLauncher-Cxv0O_wt.js +1 -0
  72. package/dist/gui/assets/WorkspacesPage-Cvb1rOeh.js +6 -0
  73. package/dist/gui/assets/index-CllPiiFL.js +52 -0
  74. package/dist/gui/assets/index-DGCBhMAn.css +2 -0
  75. package/dist/gui/index.html +2 -2
  76. package/dist/server.d.ts +13 -0
  77. package/dist/server.d.ts.map +1 -1
  78. package/dist/server.js +267 -39
  79. package/dist/server.js.map +1 -1
  80. package/dist/server.test.js +324 -18
  81. package/dist/server.test.js.map +1 -1
  82. package/dist/types.d.ts +13 -0
  83. package/dist/types.d.ts.map +1 -1
  84. package/dist/utils/detect-editors.d.ts.map +1 -1
  85. package/dist/utils/detect-editors.js +6 -0
  86. package/dist/utils/detect-editors.js.map +1 -1
  87. package/dist/utils/detect-editors.test.js +12 -0
  88. package/dist/utils/detect-editors.test.js.map +1 -1
  89. package/dist/utils/session-finder.d.ts +14 -0
  90. package/dist/utils/session-finder.d.ts.map +1 -1
  91. package/dist/utils/session-finder.js +127 -10
  92. package/dist/utils/session-finder.js.map +1 -1
  93. package/dist/utils/session-finder.test.js +104 -2
  94. package/dist/utils/session-finder.test.js.map +1 -1
  95. package/dist/utils/workspace-launch.d.ts +37 -0
  96. package/dist/utils/workspace-launch.d.ts.map +1 -0
  97. package/dist/utils/workspace-launch.js +200 -0
  98. package/dist/utils/workspace-launch.js.map +1 -0
  99. package/dist/utils/workspace-launch.test.d.ts +2 -0
  100. package/dist/utils/workspace-launch.test.d.ts.map +1 -0
  101. package/dist/utils/workspace-launch.test.js +94 -0
  102. package/dist/utils/workspace-launch.test.js.map +1 -0
  103. package/package.json +5 -3
  104. package/dist/gui/assets/DashboardPage-HNC3-OP0.js +0 -1
  105. package/dist/gui/assets/GettingStartedPage-Dguoya_Z.js +0 -1
  106. package/dist/gui/assets/ProjectsPage-DVR4j4sw.js +0 -1
  107. package/dist/gui/assets/SettingsPage-Bauo77q3.js +0 -1
  108. package/dist/gui/assets/StartWorkPage-DvuFEa5Y.js +0 -1
  109. package/dist/gui/assets/WorkspacesPage-DYOVfZqy.js +0 -6
  110. package/dist/gui/assets/external-link-BtYhMxno.js +0 -1
  111. package/dist/gui/assets/index-BqeoAIB1.js +0 -52
  112. 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
- * The case worth naming: the Claude Code desktop app keeps OAuth tokens in memory
11
- * and injects them into the sessions it spawns itself, so `~/.claude/.credentials.json`
12
- * can exist with scopes but **empty tokens**. An interactive `claude` under the
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, and they are
17
- * deliberately conservative: a provider is only reported unusable when there is
18
- * positive evidence it cannot work, never merely because something is unfamiliar.
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
- * Order matters: an API key makes the credential store irrelevant, and an absent
42
- * store is treated as unknown rather than broken so an installation that keeps
43
- * tokens elsewhere is never wrongly disabled.
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?: DetectOptions): CliStatus;
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;;;;;;;;;;;;;;;;;;GAkBG;AAMH,MAAM,WAAW,SAAS;IACxB,oDAAoD;IACpD,MAAM,EAAE,OAAO,CAAC;IAChB,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,2BAA2B;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,GAAG,IAAI,CAsBhG;AAoBD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,aAAkB,GAAG,SAAS,CAkC5E;AAED,uEAAuE;AACvE,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,aAAkB,GAAG,SAAS,CAUjF"}
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
- * The case worth naming: the Claude Code desktop app keeps OAuth tokens in memory
11
- * and injects them into the sessions it spawns itself, so `~/.claude/.credentials.json`
12
- * can exist with scopes but **empty tokens**. An interactive `claude` under the
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, and they are
17
- * deliberately conservative: a provider is only reported unusable when there is
18
- * positive evidence it cannot work, never merely because something is unfamiliar.
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
- * Order matters: an API key makes the credential store irrelevant, and an absent
66
- * store is treated as unknown rather than broken so an installation that keeps
67
- * tokens elsewhere is never wrongly disabled.
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 homeDir = options.homeDir ?? os.homedir();
72
- const hasBinary = options.hasBinary ?? findExecutable('claude', env) !== null;
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 claude CLI was not found on PATH. Install Claude Code, or pick a provider that uses an API key.',
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
- const oauth = readStoredOauth(homeDir);
82
- if (!oauth)
83
- return { usable: true };
84
- const hasToken = Boolean(oauth.accessToken) || Boolean(oauth.refreshToken);
85
- if (hasToken)
86
- return { usable: true };
87
- // Tokens are absent, so nothing spawned from here can authenticate or renew.
88
- const hostManaged = Boolean(env.CLAUDE_CODE_ENTRYPOINT)
89
- || env.CLAUDE_CODE_SDK_HAS_HOST_AUTH_REFRESH === '1'
90
- || env.CLAUDECODE === '1';
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: hostManaged
94
- ? 'The claude CLI is installed but its stored credentials are empty, so a headless turn cannot authenticate. '
95
- + 'This happens when the Claude Code app holds your tokens in memory for its own sessions instead of writing them to disk. '
96
- + 'Run `claude` in a normal terminal and sign in, or set ANTHROPIC_API_KEY and use the Claude (API) provider.'
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;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,MAAM,MAAM,SAAS,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAiBlC;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,MAAyB,OAAO,CAAC,GAAG;IAC/E,MAAM,SAAS,GAAG,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;IAC7C,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAE5B,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IAC3D,MAAM,UAAU,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO;QAC7C,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,IAAI,qBAAqB,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;QACnE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAET,KAAK,MAAM,GAAG,IAAI,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;QACxD,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,IAAI,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QAC5F,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACnC,IAAI,CAAC;gBACH,IAAI,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE;oBAAE,OAAO,SAAS,CAAC;YAC5D,CAAC;YAAC,MAAM,CAAC;gBACP,0BAA0B;YAC5B,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAQD,iFAAiF;AACjF,SAAS,eAAe,CAAC,OAAe;IACtC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,mBAAmB,CAAC,CAAC;IAChE,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAoC,CAAC;QACjG,OAAO,MAAM,CAAC,aAAa,IAAI,IAAI,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,yEAAyE;QACzE,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,UAAyB,EAAE;IAC/D,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACvC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC;IAEhD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,cAAc,CAAC,QAAQ,EAAE,GAAG,CAAC,KAAK,IAAI,CAAC;IAC9E,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO;YACL,MAAM,EAAE,KAAK;YACb,OAAO,EAAE,qGAAqG;SAC/G,CAAC;IACJ,CAAC;IAED,IAAI,GAAG,CAAC,iBAAiB;QAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAEnD,MAAM,KAAK,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IACvC,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAEpC,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC3E,IAAI,QAAQ;QAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAEtC,6EAA6E;IAC7E,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC;WAClD,GAAG,CAAC,qCAAqC,KAAK,GAAG;WACjD,GAAG,CAAC,UAAU,KAAK,GAAG,CAAC;IAE5B,OAAO;QACL,MAAM,EAAE,KAAK;QACb,OAAO,EAAE,WAAW;YAClB,CAAC,CAAC,4GAA4G;kBAC1G,0HAA0H;kBAC1H,4GAA4G;YAChH,CAAC,CAAC,oFAAoF;kBAClF,qGAAqG;KAC5G,CAAC;AACJ,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,0BAA0B,CAAC,UAAyB,EAAE;IACpE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACvC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,cAAc,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,CAAC;IAE3E,OAAO,SAAS;QACd,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE;QAClB,CAAC,CAAC;YACA,MAAM,EAAE,KAAK;YACb,OAAO,EAAE,oDAAoD;SAC9D,CAAC;AACN,CAAC"}
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: {}, homeDir: home });
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, regardless of the credential store', async () => {
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
- homeDir: home,
40
+ authStatus: { exitCode: 1, stdout: '{not json', error: 'must not leak' },
51
41
  });
52
- expect(status.usable).toBe(true);
53
- expect(status.message).toBeUndefined();
54
- });
55
- it('reports unusable when the credential store has no tokens at all', async () => {
56
- // The real failure: an interactive claude works because its host injects
57
- // tokens, but a spawned `claude -p` finds nothing on disk to use.
58
- await writeCredentials(HOLLOW);
59
- const status = detectClaudeCliStatus({ hasBinary: true, env: {}, homeDir: home });
60
- expect(status.usable).toBe(false);
61
- expect(status.message).toMatch(/not signed in|credentials are empty/i);
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('names the host-managed case when the app is providing auth', async () => {
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: { CLAUDE_CODE_ENTRYPOINT: 'claude-desktop' },
68
- homeDir: home,
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.usable).toBe(false);
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('is usable with only a refresh token, which can mint a new one', async () => {
80
- await writeCredentials({ ...HOLLOW, refreshToken: 'refresh' });
81
- expect(detectClaudeCliStatus({ hasBinary: true, env: {}, homeDir: home }).usable).toBe(true);
82
- });
83
- it('assumes usable when there is no credential file to judge', () => {
84
- // Conservative on purpose: an install that stores tokens elsewhere must not
85
- // be disabled just because this particular file is absent.
86
- const status = detectClaudeCliStatus({ hasBinary: true, env: {}, homeDir: home });
87
- expect(status.usable).toBe(true);
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('assumes usable when the credential file is malformed', async () => {
90
- await fs.mkdir(path.join(home, '.claude'), { recursive: true });
91
- await fs.writeFile(path.join(home, '.claude', '.credentials.json'), '{ not json', 'utf-8');
92
- expect(detectClaudeCliStatus({ hasBinary: true, env: {}, homeDir: home }).usable).toBe(true);
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