@axhub/genie 0.2.7 → 0.2.8

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 (117) hide show
  1. package/dist/api-docs.html +2 -2
  2. package/dist/assets/App-CTKZtqB1.js +460 -0
  3. package/dist/assets/App-qxJ8_QYu.css +32 -0
  4. package/dist/assets/ReviewApp-DM6BNAzR.js +1 -0
  5. package/dist/assets/{_basePickBy-C19AekOu.js → _basePickBy-CqJbRZ9y.js} +1 -1
  6. package/dist/assets/{_baseUniq-JsnevLw_.js → _baseUniq-BS8YH8jO.js} +1 -1
  7. package/dist/assets/{arc-BLpcuBlf.js → arc-BBmKEN-S.js} +1 -1
  8. package/dist/assets/architectureDiagram-2XIMDMQ5-N5lcb82R.js +36 -0
  9. package/dist/assets/{blockDiagram-WCTKOSBZ-DQBLwsUS.js → blockDiagram-WCTKOSBZ-DTMwHuLn.js} +3 -3
  10. package/dist/assets/c4Diagram-IC4MRINW-BTKlkXI9.js +10 -0
  11. package/dist/assets/channel-1oJBvF-0.js +1 -0
  12. package/dist/assets/{chunk-4BX2VUAB-De63kbgc.js → chunk-4BX2VUAB-DUdoTxAc.js} +1 -1
  13. package/dist/assets/{chunk-55IACEB6-DtTDDdM9.js → chunk-55IACEB6-Bm_92xe4.js} +1 -1
  14. package/dist/assets/{chunk-FMBD7UC4-DHuwd8tw.js → chunk-FMBD7UC4-CGW0g62g.js} +1 -1
  15. package/dist/assets/{chunk-JSJVCQXG-BgytFtmO.js → chunk-JSJVCQXG-DYkTH3w1.js} +1 -1
  16. package/dist/assets/{chunk-KX2RTZJC-nZdp86aN.js → chunk-KX2RTZJC-C9oTlISU.js} +1 -1
  17. package/dist/assets/chunk-NQ4KR5QH-CM50ygWP.js +220 -0
  18. package/dist/assets/{chunk-QZHKN3VN-DvUQ3mnO.js → chunk-QZHKN3VN-7dzpYeNJ.js} +1 -1
  19. package/dist/assets/chunk-WL4C6EOR-Cm9nQrsr.js +189 -0
  20. package/dist/assets/classDiagram-VBA2DB6C-d5TeKFM4.js +1 -0
  21. package/dist/assets/classDiagram-v2-RAHNMMFH-d5TeKFM4.js +1 -0
  22. package/dist/assets/clone-CinxIlEu.js +1 -0
  23. package/dist/assets/cose-bilkent-S5V4N54A-Ccp_p0JZ.js +1 -0
  24. package/dist/assets/cytoscape.esm-2ZfV8NB5.js +331 -0
  25. package/dist/assets/{dagre-KLK3FWXG-CHYIvW47.js → dagre-KLK3FWXG-fBwTLUp9.js} +1 -1
  26. package/dist/assets/diagram-E7M64L7V-CeNVmFUp.js +24 -0
  27. package/dist/assets/{diagram-IFDJBPK2-Dzsiln_C.js → diagram-IFDJBPK2-CtavyLGa.js} +1 -1
  28. package/dist/assets/{diagram-P4PSJMXO-DKnGbUpE.js → diagram-P4PSJMXO-CpQTjQwc.js} +1 -1
  29. package/dist/assets/erDiagram-INFDFZHY-B8R5vwhd.js +70 -0
  30. package/dist/assets/{flowDiagram-PKNHOUZH-BAZ2-jKp.js → flowDiagram-PKNHOUZH-BvkVVwIQ.js} +4 -4
  31. package/dist/assets/ganttDiagram-A5KZAMGK-DOu3hSNa.js +292 -0
  32. package/dist/assets/{gitGraphDiagram-K3NZZRJ6-BflpyjGy.js → gitGraphDiagram-K3NZZRJ6-C7zT67YE.js} +1 -1
  33. package/dist/assets/{graph-suelaXFh.js → graph-D11wiwHo.js} +1 -1
  34. package/dist/assets/highlighted-body-TPN3WLV5-Babpthg-.js +1 -0
  35. package/dist/assets/index-DFxzgWoO.js +2 -0
  36. package/dist/assets/index-YCFGDVKw.css +1 -0
  37. package/dist/assets/{infoDiagram-LFFYTUFH-pfD1FA3p.js → infoDiagram-LFFYTUFH-BmA7IpQG.js} +1 -1
  38. package/dist/assets/ishikawaDiagram-PHBUUO56-BEquZd3E.js +70 -0
  39. package/dist/assets/journeyDiagram-4ABVD52K-BfemGz7f.js +139 -0
  40. package/dist/assets/{kanban-definition-K7BYSVSG-FWinmur1.js → kanban-definition-K7BYSVSG-CWja3mln.js} +5 -5
  41. package/dist/assets/{layout-vcz43XvZ.js → layout-BLUNf-PJ.js} +1 -1
  42. package/dist/assets/{linear-le4gc0vx.js → linear-DukIV_Xv.js} +1 -1
  43. package/dist/assets/mermaid-O7DHMXV3-SgtM28qI.js +1038 -0
  44. package/dist/assets/mindmap-definition-YRQLILUH-4UjqXITU.js +68 -0
  45. package/dist/assets/{pieDiagram-SKSYHLDU-C7PKDh3b.js → pieDiagram-SKSYHLDU-8AxqJd0M.js} +2 -2
  46. package/dist/assets/quadrantDiagram-337W2JSQ-D60m8V8r.js +7 -0
  47. package/dist/assets/requirementDiagram-Z7DCOOCP-zqh9jBVf.js +73 -0
  48. package/dist/assets/{sankeyDiagram-WA2Y5GQK-4gulcOP4.js → sankeyDiagram-WA2Y5GQK-CDZILTLI.js} +3 -3
  49. package/dist/assets/sequenceDiagram-2WXFIKYE-7BReFd0L.js +145 -0
  50. package/dist/assets/{stateDiagram-RAJIS63D-CB4Vl7qM.js → stateDiagram-RAJIS63D-HPTVdIG4.js} +1 -1
  51. package/dist/assets/stateDiagram-v2-FVOUBMTO-DTUf5_gC.js +1 -0
  52. package/dist/assets/timeline-definition-YZTLITO2-CTVllFgr.js +61 -0
  53. package/dist/assets/{treemap-KZPCXAKY-DZSEE6Hz.js → treemap-KZPCXAKY-BtyxboJZ.js} +58 -58
  54. package/dist/assets/vendor-codemirror-Dz7_EqNA.js +39 -0
  55. package/dist/assets/vendor-react-Cpt6D04s.js +59 -0
  56. package/dist/assets/vendor-xterm-DfaPXD3y.js +66 -0
  57. package/dist/assets/{vennDiagram-LZ73GAT5-8E_G06fI.js → vennDiagram-LZ73GAT5-D96ZI6Mg.js} +4 -4
  58. package/dist/assets/xychartDiagram-JWTSCODW-eRk-39YO.js +7 -0
  59. package/dist/index.html +4 -4
  60. package/package.json +34 -33
  61. package/server/acp-runtime/client.js +82 -15
  62. package/server/acp-runtime/index.js +5 -16
  63. package/server/channels/runtime/AgentRuntimeAdapter.js +1 -10
  64. package/server/claude-sdk.js +1 -3
  65. package/server/cli.js +136 -0
  66. package/server/gemini-cli.js +1 -3
  67. package/server/index.js +86 -14
  68. package/server/openai-codex.js +1 -3
  69. package/server/opencode-cli.js +1 -3
  70. package/server/projects.js +128 -85
  71. package/server/routes/cc-connect.js +1131 -0
  72. package/server/routes/cli-auth.js +1 -73
  73. package/server/routes/commands.js +4 -9
  74. package/server/routes/projects.js +45 -24
  75. package/server/session-core/providerDiscovery.js +8 -3
  76. package/server/utils/ccConnectManager.js +390 -0
  77. package/server/utils/ccConnectState.js +575 -0
  78. package/server/utils/resolveCommandPath.js +71 -0
  79. package/server/utils/workspaceRoots.js +154 -0
  80. package/dist/assets/App-BWSqiXAT.js +0 -220
  81. package/dist/assets/App-DrlLKa8f.css +0 -1
  82. package/dist/assets/ReviewApp-nz3mbArg.js +0 -1
  83. package/dist/assets/architectureDiagram-2XIMDMQ5-CarjBOOv.js +0 -36
  84. package/dist/assets/c4Diagram-IC4MRINW-CGobwBIj.js +0 -10
  85. package/dist/assets/channel-DkFNxV_H.js +0 -1
  86. package/dist/assets/chunk-NQ4KR5QH-CMH6EDP2.js +0 -220
  87. package/dist/assets/chunk-WL4C6EOR-Dn7db_6t.js +0 -189
  88. package/dist/assets/classDiagram-VBA2DB6C-DtwCEe8S.js +0 -1
  89. package/dist/assets/classDiagram-v2-RAHNMMFH-DtwCEe8S.js +0 -1
  90. package/dist/assets/clone-C0lCEIEO.js +0 -1
  91. package/dist/assets/cose-bilkent-S5V4N54A-DD_nzqsz.js +0 -1
  92. package/dist/assets/cytoscape.esm-5J0xJHOV.js +0 -321
  93. package/dist/assets/diagram-E7M64L7V-TVdvHtGc.js +0 -24
  94. package/dist/assets/erDiagram-INFDFZHY-5Kw0bByo.js +0 -70
  95. package/dist/assets/ganttDiagram-A5KZAMGK-CsADFkcq.js +0 -292
  96. package/dist/assets/highlighted-body-OFNGDK62-CZrBMazC.js +0 -1
  97. package/dist/assets/index-B01NxbUv.css +0 -1
  98. package/dist/assets/index-DW5pGgQ_.js +0 -2
  99. package/dist/assets/ishikawaDiagram-PHBUUO56-ndm9snwO.js +0 -70
  100. package/dist/assets/journeyDiagram-4ABVD52K-HgF2t7z5.js +0 -139
  101. package/dist/assets/mermaid-GHXKKRXX-CK8m3lad.js +0 -870
  102. package/dist/assets/mindmap-definition-YRQLILUH-CNq9SKj4.js +0 -68
  103. package/dist/assets/quadrantDiagram-337W2JSQ-B7FnztNO.js +0 -7
  104. package/dist/assets/requirementDiagram-Z7DCOOCP-Bl_BM2Th.js +0 -73
  105. package/dist/assets/sequenceDiagram-2WXFIKYE-VEuJDwyJ.js +0 -145
  106. package/dist/assets/stateDiagram-v2-FVOUBMTO-C85ucl39.js +0 -1
  107. package/dist/assets/timeline-definition-YZTLITO2-BPGKhi7f.js +0 -61
  108. package/dist/assets/vendor-codemirror-CyOKkaQZ.js +0 -31
  109. package/dist/assets/vendor-react-CP4yFTs7.js +0 -8
  110. package/dist/assets/vendor-xterm-DfcmCpbH.js +0 -66
  111. package/dist/assets/xychartDiagram-JWTSCODW-CbBk50-O.js +0 -7
  112. package/server/acp-runtime/client.test.js +0 -688
  113. package/server/acp-runtime/session-store.test.js +0 -89
  114. package/server/cli.test.js +0 -76
  115. package/server/external-agent/service.test.js +0 -53
  116. package/server/external-agent/ws.test.js +0 -289
  117. package/shared/conversationEvents.test.js +0 -403
@@ -1,9 +1,9 @@
1
1
  import express from 'express';
2
- import { constants as fsConstants } from 'fs';
3
2
  import fs from 'fs/promises';
4
3
  import path from 'path';
5
4
  import os from 'os';
6
5
  import { listOpencodeModels } from '../opencode-cli.js';
6
+ import { resolveCommandPath } from '../utils/resolveCommandPath.js';
7
7
 
8
8
  const router = express.Router();
9
9
  const INSTALLATION_CACHE_TTL_MS = 12 * 60 * 60 * 1000;
@@ -22,78 +22,6 @@ const PROVIDER_INSTALL_HINTS = {
22
22
  const installationStatusCache = new Map(); // provider -> status
23
23
  const installationStatusInFlight = new Map(); // provider -> Promise
24
24
 
25
- function getLocatorCommand() {
26
- return process.platform === 'win32' ? 'where' : 'which';
27
- }
28
-
29
- async function isRunnableCommand(candidatePath) {
30
- try {
31
- if (process.platform === 'win32') {
32
- await fs.access(candidatePath, fsConstants.F_OK);
33
- return true;
34
- }
35
-
36
- await fs.access(candidatePath, fsConstants.X_OK);
37
- return true;
38
- } catch {
39
- return false;
40
- }
41
- }
42
-
43
- async function resolveCommandPath(command) {
44
- const normalizedCommand = String(command || '').trim();
45
- if (!normalizedCommand) {
46
- return {
47
- found: false,
48
- resolvedPath: null,
49
- reason: 'Command name is empty'
50
- };
51
- }
52
-
53
- const isDirectPath = normalizedCommand.includes(path.sep) || (process.platform === 'win32' && normalizedCommand.includes('/'));
54
- const pathEntries = isDirectPath
55
- ? ['']
56
- : String(process.env.PATH || '')
57
- .split(path.delimiter)
58
- .map((entry) => entry.trim())
59
- .filter(Boolean);
60
-
61
- const windowsExtensions = process.platform === 'win32'
62
- ? String(process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM')
63
- .split(';')
64
- .map((ext) => ext.trim())
65
- .filter(Boolean)
66
- : [''];
67
-
68
- const candidatePaths = [];
69
- for (const baseDir of pathEntries) {
70
- const baseCandidate = isDirectPath ? normalizedCommand : path.join(baseDir, normalizedCommand);
71
- candidatePaths.push(baseCandidate);
72
-
73
- if (process.platform === 'win32' && !path.extname(baseCandidate)) {
74
- for (const ext of windowsExtensions) {
75
- candidatePaths.push(`${baseCandidate}${ext}`);
76
- }
77
- }
78
- }
79
-
80
- for (const candidatePath of candidatePaths) {
81
- if (await isRunnableCommand(candidatePath)) {
82
- return {
83
- found: true,
84
- resolvedPath: candidatePath,
85
- reason: null
86
- };
87
- }
88
- }
89
-
90
- return {
91
- found: false,
92
- resolvedPath: null,
93
- reason: `${normalizedCommand} not found in PATH`
94
- };
95
- }
96
-
97
25
  function buildInstallationStatus(provider, command, installed, resolvedPath, reason, cacheHit = false) {
98
26
  return {
99
27
  success: true,
@@ -199,13 +199,8 @@ Custom commands can be created in:
199
199
  }, {});
200
200
 
201
201
  const currentProvider = context?.provider || 'claude';
202
- const providerDefaults = {
203
- claude: CLAUDE_MODELS.DEFAULT,
204
- codex: CODEX_MODELS.DEFAULT,
205
- gemini: GEMINI_MODELS.DEFAULT,
206
- opencode: OPENCODE_MODELS.DEFAULT
207
- };
208
- const currentModel = context?.model || providerDefaults[currentProvider] || CLAUDE_MODELS.DEFAULT;
202
+ const discoveredCurrentModel = providerDiscovery.find((item) => item.provider === currentProvider)?.currentModel || null;
203
+ const currentModel = context?.model || discoveredCurrentModel || null;
209
204
  const opencodeDiscovery = providerDiscovery.find((item) => item.provider === 'opencode') || null;
210
205
 
211
206
  return {
@@ -220,7 +215,7 @@ Custom commands can be created in:
220
215
  opencodeDiscovery,
221
216
  message: args.length > 0
222
217
  ? `Switching to model: ${args[0]}`
223
- : `Current model: ${currentModel}`
218
+ : (currentModel ? `Current model: ${currentModel}` : 'Current model is managed by the active provider configuration.')
224
219
  }
225
220
  };
226
221
  },
@@ -254,7 +249,7 @@ Custom commands can be created in:
254
249
  packageName,
255
250
  uptime: uptimeFormatted,
256
251
  uptimeSeconds: Math.floor(uptime),
257
- model: context?.model || 'claude-sonnet-4.5',
252
+ model: context?.model || null,
258
253
  provider: context?.provider || 'claude',
259
254
  nodeVersion: process.version,
260
255
  platform: process.platform
@@ -1,13 +1,23 @@
1
1
  import express from 'express';
2
2
  import { promises as fs } from 'fs';
3
3
  import path from 'path';
4
- import os from 'os';
5
4
  import { addProjectManually } from '../projects.js';
5
+ import {
6
+ DEFAULT_WORKSPACES_ROOT,
7
+ HAS_WORKSPACES_ROOT_RESTRICTION,
8
+ WORKSPACES_ROOTS,
9
+ formatAllowedWorkspaceRoots,
10
+ isPathWithinAllowedRoots,
11
+ resolveAllowedWorkspaceRoots
12
+ } from '../utils/workspaceRoots.js';
6
13
 
7
- const router = express.Router();
14
+ export {
15
+ DEFAULT_WORKSPACES_ROOT,
16
+ HAS_WORKSPACES_ROOT_RESTRICTION,
17
+ WORKSPACES_ROOTS
18
+ } from '../utils/workspaceRoots.js';
8
19
 
9
- // Configure allowed workspace root (defaults to user's home directory)
10
- export const WORKSPACES_ROOT = process.env.WORKSPACES_ROOT || os.homedir();
20
+ const router = express.Router();
11
21
 
12
22
  // System-critical paths that should never be used as workspace directories
13
23
  export const FORBIDDEN_PATHS = [
@@ -42,8 +52,14 @@ export const FORBIDDEN_PATHS = [
42
52
  * @param {string} requestedPath - The path to validate
43
53
  * @returns {Promise<{valid: boolean, resolvedPath?: string, error?: string}>}
44
54
  */
45
- export async function validateWorkspacePath(requestedPath) {
55
+ export async function validateWorkspacePath(requestedPath, options = {}) {
46
56
  try {
57
+ const {
58
+ fsImpl = fs,
59
+ hasWorkspaceRootRestriction = HAS_WORKSPACES_ROOT_RESTRICTION,
60
+ allowedWorkspaceRoots = WORKSPACES_ROOTS
61
+ } = options;
62
+
47
63
  // Resolve to absolute path
48
64
  let absolutePath = path.resolve(requestedPath);
49
65
 
@@ -79,14 +95,14 @@ export async function validateWorkspacePath(requestedPath) {
79
95
  let realPath;
80
96
  try {
81
97
  // Check if path exists to resolve real path
82
- await fs.access(absolutePath);
83
- realPath = await fs.realpath(absolutePath);
98
+ await fsImpl.access(absolutePath);
99
+ realPath = await fsImpl.realpath(absolutePath);
84
100
  } catch (error) {
85
101
  if (error.code === 'ENOENT') {
86
102
  // Path doesn't exist yet - check parent directory
87
103
  let parentPath = path.dirname(absolutePath);
88
104
  try {
89
- const parentRealPath = await fs.realpath(parentPath);
105
+ const parentRealPath = await fsImpl.realpath(parentPath);
90
106
 
91
107
  // Reconstruct the full path with real parent
92
108
  realPath = path.join(parentRealPath, path.basename(absolutePath));
@@ -104,31 +120,36 @@ export async function validateWorkspacePath(requestedPath) {
104
120
  }
105
121
  }
106
122
 
107
- // Resolve the workspace root to its real path
108
- const resolvedWorkspaceRoot = await fs.realpath(WORKSPACES_ROOT);
123
+ if (hasWorkspaceRootRestriction && allowedWorkspaceRoots.length > 0) {
124
+ const resolvedWorkspaceRoots = await resolveAllowedWorkspaceRoots({
125
+ fsImpl,
126
+ allowedWorkspaceRoots
127
+ });
109
128
 
110
- // Ensure the resolved path is contained within the allowed workspace root
111
- if (!realPath.startsWith(resolvedWorkspaceRoot + path.sep) &&
112
- realPath !== resolvedWorkspaceRoot) {
113
- return {
114
- valid: false,
115
- error: `Workspace path must be within the allowed workspace root: ${WORKSPACES_ROOT}`
116
- };
129
+ if (!isPathWithinAllowedRoots(realPath, resolvedWorkspaceRoots)) {
130
+ return {
131
+ valid: false,
132
+ error: `Workspace path must be within one of the allowed workspace roots: ${formatAllowedWorkspaceRoots(allowedWorkspaceRoots)}`
133
+ };
134
+ }
117
135
  }
118
136
 
119
137
  // Additional symlink check for existing paths
120
138
  try {
121
- await fs.access(absolutePath);
122
- const stats = await fs.lstat(absolutePath);
139
+ await fsImpl.access(absolutePath);
140
+ const stats = await fsImpl.lstat(absolutePath);
123
141
 
124
- if (stats.isSymbolicLink()) {
142
+ if (stats.isSymbolicLink() && hasWorkspaceRootRestriction && allowedWorkspaceRoots.length > 0) {
143
+ const resolvedWorkspaceRoots = await resolveAllowedWorkspaceRoots({
144
+ fsImpl,
145
+ allowedWorkspaceRoots
146
+ });
125
147
  // Verify symlink target is also within allowed root
126
- const linkTarget = await fs.readlink(absolutePath);
148
+ const linkTarget = await fsImpl.readlink(absolutePath);
127
149
  const resolvedTarget = path.resolve(path.dirname(absolutePath), linkTarget);
128
- const realTarget = await fs.realpath(resolvedTarget);
150
+ const realTarget = await fsImpl.realpath(resolvedTarget);
129
151
 
130
- if (!realTarget.startsWith(resolvedWorkspaceRoot + path.sep) &&
131
- realTarget !== resolvedWorkspaceRoot) {
152
+ if (!isPathWithinAllowedRoots(realTarget, resolvedWorkspaceRoots)) {
132
153
  return {
133
154
  valid: false,
134
155
  error: 'Symlink target is outside the allowed workspace root'
@@ -151,7 +151,12 @@ async function discoverOpenCodeModels(projectPath) {
151
151
  }
152
152
 
153
153
  async function discoverClaudeModels() {
154
- return { models: mapModelOptions(CLAUDE_MODELS.OPTIONS, 'runtime-fallback'), source: 'runtime-fallback', error: null, currentModel: process.env.CLAUDE_MODEL || CLAUDE_MODELS.DEFAULT };
154
+ return {
155
+ models: mapModelOptions(CLAUDE_MODELS.OPTIONS, 'runtime-fallback'),
156
+ source: 'runtime-fallback',
157
+ error: null,
158
+ currentModel: null
159
+ };
155
160
  }
156
161
 
157
162
  async function discoverCodexModels() {
@@ -163,11 +168,11 @@ async function discoverCodexModels() {
163
168
  if (match) currentModel = match[1];
164
169
  } catch {
165
170
  }
166
- return { models: mapModelOptions(CODEX_MODELS.OPTIONS, 'runtime-fallback'), source: 'runtime-fallback', error: null, currentModel: currentModel || CODEX_MODELS.DEFAULT };
171
+ return { models: mapModelOptions(CODEX_MODELS.OPTIONS, 'runtime-fallback'), source: 'runtime-fallback', error: null, currentModel: currentModel || null };
167
172
  }
168
173
 
169
174
  async function discoverGeminiModels() {
170
- return { models: mapModelOptions(GEMINI_MODELS.OPTIONS, 'runtime-fallback'), source: 'runtime-fallback', error: null, currentModel: GEMINI_MODELS.DEFAULT };
175
+ return { models: mapModelOptions(GEMINI_MODELS.OPTIONS, 'runtime-fallback'), source: 'runtime-fallback', error: null, currentModel: null };
171
176
  }
172
177
 
173
178
  const AUTH_CHECKERS = {
@@ -0,0 +1,390 @@
1
+ import fs from 'fs';
2
+ import { spawnSync } from 'child_process';
3
+ import crossSpawn from 'cross-spawn';
4
+ import { detectProviderInstallationStatus } from '../routes/cli-auth.js';
5
+ import { resolveCommandPath } from './resolveCommandPath.js';
6
+ import { spawnCommand } from './spawnCommand.js';
7
+ import {
8
+ CC_CONNECT_PLATFORMS,
9
+ CC_CONNECT_PROVIDERS,
10
+ getPlatformConnection,
11
+ listCcConnectPlatformSummaries,
12
+ readCcConnectState,
13
+ upsertPlatformConnection,
14
+ writeCcConnectConfig,
15
+ writeCcConnectState
16
+ } from './ccConnectState.js';
17
+
18
+ const CC_CONNECT_COMMAND = 'cc-connect';
19
+ const COMMAND_TIMEOUT_MS = 5000;
20
+ const DAEMON_COMMAND_TIMEOUT_MS = 20000;
21
+
22
+ const PROVIDER_ORDER = CC_CONNECT_PROVIDERS.map((provider) => provider.id);
23
+ const PLATFORM_MAP = Object.fromEntries(CC_CONNECT_PLATFORMS.map((platform) => [platform.id, platform]));
24
+
25
+ function createCcConnectError(code, message) {
26
+ const error = new Error(message);
27
+ error.code = code;
28
+ return error;
29
+ }
30
+
31
+ function nowIso() {
32
+ return new Date().toISOString();
33
+ }
34
+
35
+ function runCommandSync({ command, args = [], timeoutMs = COMMAND_TIMEOUT_MS, cwd = process.cwd(), env = process.env }) {
36
+ const runner = process.platform === 'win32' ? crossSpawn.sync : spawnSync;
37
+ const result = runner(command, args, {
38
+ cwd,
39
+ env,
40
+ encoding: 'utf8',
41
+ timeout: timeoutMs
42
+ });
43
+
44
+ return {
45
+ status: result.status,
46
+ stdout: result.stdout || '',
47
+ stderr: result.stderr || '',
48
+ error: result.error || null
49
+ };
50
+ }
51
+
52
+ function buildCommandEnv() {
53
+ const env = { ...process.env };
54
+ delete env.CLAUDECODE;
55
+ return env;
56
+ }
57
+
58
+ function buildShortErrorMessage(result) {
59
+ const stderr = String(result?.stderr || '').trim();
60
+ if (stderr) {
61
+ return stderr.replace(/\s+/g, ' ').slice(0, 260);
62
+ }
63
+
64
+ const stdout = String(result?.stdout || '').trim();
65
+ if (stdout) {
66
+ return stdout.replace(/\s+/g, ' ').slice(0, 260);
67
+ }
68
+
69
+ if (result?.error?.message) {
70
+ return result.error.message;
71
+ }
72
+
73
+ return 'Unknown error';
74
+ }
75
+
76
+ function runCcConnectDaemonCommand(args, timeoutMs = DAEMON_COMMAND_TIMEOUT_MS) {
77
+ return runCommandSync({
78
+ command: CC_CONNECT_COMMAND,
79
+ args,
80
+ timeoutMs,
81
+ env: buildCommandEnv()
82
+ });
83
+ }
84
+
85
+ function startCcConnectViaDaemon(configPath) {
86
+ const statusResult = runCcConnectDaemonCommand(['daemon', 'status']);
87
+ const isRunning = statusResult.status === 0 && /status:\s+running/i.test(statusResult.stdout || '');
88
+
89
+ if (isRunning) {
90
+ const restartResult = runCcConnectDaemonCommand(['daemon', 'restart']);
91
+ if (restartResult.status === 0) {
92
+ return { ok: true, detail: 'cc-connect daemon restarted with the latest configuration.' };
93
+ }
94
+
95
+ return {
96
+ ok: false,
97
+ detail: `Configuration written, but daemon restart failed: ${buildShortErrorMessage(restartResult)}`
98
+ };
99
+ }
100
+
101
+ const startResult = runCcConnectDaemonCommand(['daemon', 'start']);
102
+ if (startResult.status === 0) {
103
+ return { ok: true, detail: 'cc-connect daemon started with the latest configuration.' };
104
+ }
105
+
106
+ const installResult = runCcConnectDaemonCommand(['daemon', 'install', '--config', configPath], 60000);
107
+ if (installResult.status === 0) {
108
+ return { ok: true, detail: 'cc-connect daemon installed and started.' };
109
+ }
110
+
111
+ return {
112
+ ok: false,
113
+ detail: `Configuration written, but daemon start failed: ${buildShortErrorMessage(installResult)}`
114
+ };
115
+ }
116
+
117
+ function startCcConnectDetached(configPath) {
118
+ try {
119
+ const child = spawnCommand(CC_CONNECT_COMMAND, ['--config', configPath], {
120
+ detached: true,
121
+ stdio: 'ignore',
122
+ env: buildCommandEnv(),
123
+ windowsHide: true
124
+ });
125
+ child.unref();
126
+ return { ok: true, detail: 'cc-connect started in the background.' };
127
+ } catch (error) {
128
+ return {
129
+ ok: false,
130
+ detail: `Configuration written, but background start failed: ${error.message}`
131
+ };
132
+ }
133
+ }
134
+
135
+ export function ensureCcConnectDaemonRunning(configPath) {
136
+ if (process.platform === 'win32') {
137
+ return startCcConnectDetached(configPath);
138
+ }
139
+
140
+ return startCcConnectViaDaemon(configPath);
141
+ }
142
+
143
+ export function normalizeCcConnectPlatformId(value) {
144
+ const platformId = String(value || '').trim().toLowerCase();
145
+ return PLATFORM_MAP[platformId] ? platformId : null;
146
+ }
147
+
148
+ export function normalizeCcConnectProviderId(value) {
149
+ const providerId = String(value || '').trim().toLowerCase();
150
+ return PROVIDER_ORDER.includes(providerId) ? providerId : null;
151
+ }
152
+
153
+ export function mergeAvailableCcConnectProviders(configuredProviders = [], installedProviderIds = [], activeProvider = null) {
154
+ const available = new Set();
155
+
156
+ for (const providerId of configuredProviders) {
157
+ if (PROVIDER_ORDER.includes(providerId)) {
158
+ available.add(providerId);
159
+ }
160
+ }
161
+
162
+ for (const providerId of installedProviderIds) {
163
+ if (PROVIDER_ORDER.includes(providerId)) {
164
+ available.add(providerId);
165
+ }
166
+ }
167
+
168
+ if (activeProvider && PROVIDER_ORDER.includes(activeProvider)) {
169
+ available.add(activeProvider);
170
+ }
171
+
172
+ return PROVIDER_ORDER.filter((providerId) => available.has(providerId));
173
+ }
174
+
175
+ function buildPlatformSummaries(state, installedProviderIds) {
176
+ return listCcConnectPlatformSummaries(state).map((platform) => {
177
+ if (!platform.connected) {
178
+ return platform;
179
+ }
180
+
181
+ const connection = getPlatformConnection(state, platform.id);
182
+ const configuredProviders = mergeAvailableCcConnectProviders(
183
+ connection?.configuredProviders,
184
+ installedProviderIds,
185
+ connection?.activeProvider || null
186
+ );
187
+
188
+ return {
189
+ ...platform,
190
+ configuredProviders
191
+ };
192
+ });
193
+ }
194
+
195
+ function resolveInstalledProviderIdsFromStatuses(statuses = []) {
196
+ return statuses.filter((provider) => provider.installed).map((provider) => provider.id);
197
+ }
198
+
199
+ export async function getCcConnectStatus() {
200
+ const resolved = await resolveCommandPath(CC_CONNECT_COMMAND);
201
+ if (!resolved.found) {
202
+ return {
203
+ installed: false,
204
+ version: null,
205
+ isBeta: false,
206
+ resolvedPath: null
207
+ };
208
+ }
209
+
210
+ const result = runCommandSync({
211
+ command: resolved.resolvedPath || CC_CONNECT_COMMAND,
212
+ args: ['--version'],
213
+ timeoutMs: COMMAND_TIMEOUT_MS,
214
+ env: buildCommandEnv()
215
+ });
216
+ const version = String(result.stdout || '').trim() || null;
217
+
218
+ return {
219
+ installed: true,
220
+ version,
221
+ isBeta: Boolean(version && /beta|alpha|rc|pre/i.test(version)),
222
+ resolvedPath: resolved.resolvedPath || null
223
+ };
224
+ }
225
+
226
+ export async function getCcConnectProviderStatuses() {
227
+ return Promise.all(
228
+ PROVIDER_ORDER.map(async (providerId) => {
229
+ const label = CC_CONNECT_PROVIDERS.find((provider) => provider.id === providerId)?.label || providerId;
230
+ try {
231
+ const status = await detectProviderInstallationStatus(providerId);
232
+ return {
233
+ id: providerId,
234
+ label,
235
+ ...status
236
+ };
237
+ } catch (error) {
238
+ return {
239
+ id: providerId,
240
+ label,
241
+ success: false,
242
+ installed: false,
243
+ error: error.message
244
+ };
245
+ }
246
+ })
247
+ );
248
+ }
249
+
250
+ export async function getCcConnectInstalledProviderIds() {
251
+ const statuses = await getCcConnectProviderStatuses();
252
+ return resolveInstalledProviderIdsFromStatuses(statuses);
253
+ }
254
+
255
+ export async function getCcConnectConnectionSummary(platformId = null) {
256
+ const normalizedPlatformId = platformId == null ? null : normalizeCcConnectPlatformId(platformId);
257
+ if (platformId != null && !normalizedPlatformId) {
258
+ throw createCcConnectError('UNSUPPORTED_PLATFORM', 'Unsupported platform.');
259
+ }
260
+
261
+ const ccConnect = await getCcConnectStatus();
262
+ const providers = await getCcConnectProviderStatuses();
263
+ const installedProviderIds = resolveInstalledProviderIdsFromStatuses(providers);
264
+ const state = readCcConnectState();
265
+ const platforms = buildPlatformSummaries(state, installedProviderIds);
266
+
267
+ return {
268
+ ccConnect,
269
+ providers,
270
+ platforms,
271
+ platform: normalizedPlatformId
272
+ ? platforms.find((entry) => entry.id === normalizedPlatformId) || null
273
+ : null
274
+ };
275
+ }
276
+
277
+ function normalizeProjectPath(value) {
278
+ return typeof value === 'string' ? value.trim() : '';
279
+ }
280
+
281
+ function validateProjectPath(projectPath) {
282
+ const normalizedProjectPath = normalizeProjectPath(projectPath);
283
+ if (!normalizedProjectPath) {
284
+ throw createCcConnectError('PROJECT_PATH_REQUIRED', 'projectPath is required.');
285
+ }
286
+
287
+ if (!fs.existsSync(normalizedProjectPath)) {
288
+ throw createCcConnectError('PROJECT_PATH_NOT_FOUND', 'Selected project path does not exist.');
289
+ }
290
+
291
+ return normalizedProjectPath;
292
+ }
293
+
294
+ export async function updateCcConnectPlatformBinding({ platformId, provider, projectPath }) {
295
+ const normalizedPlatformId = normalizeCcConnectPlatformId(platformId);
296
+ if (!normalizedPlatformId) {
297
+ throw createCcConnectError('UNSUPPORTED_PLATFORM', 'Unsupported platform.');
298
+ }
299
+
300
+ const hasProviderUpdate = provider !== undefined;
301
+ const hasProjectUpdate = projectPath !== undefined;
302
+ if (!hasProviderUpdate && !hasProjectUpdate) {
303
+ throw createCcConnectError('NO_MUTATION', 'At least one of provider or projectPath is required.');
304
+ }
305
+
306
+ const ccConnect = await getCcConnectStatus();
307
+ if (!ccConnect.installed) {
308
+ throw createCcConnectError('CC_CONNECT_NOT_INSTALLED', 'cc-connect is not installed.');
309
+ }
310
+
311
+ const currentState = readCcConnectState();
312
+ const connection = getPlatformConnection(currentState, normalizedPlatformId);
313
+ if (!connection) {
314
+ throw createCcConnectError('PLATFORM_NOT_CONNECTED', `${PLATFORM_MAP[normalizedPlatformId].label} is not connected.`);
315
+ }
316
+
317
+ const installedProviderIds = await getCcConnectInstalledProviderIds();
318
+ const availableProviders = mergeAvailableCcConnectProviders(
319
+ connection.configuredProviders,
320
+ installedProviderIds,
321
+ connection.activeProvider
322
+ );
323
+
324
+ let nextProvider = connection.activeProvider;
325
+ let nextProjectPath = connection.projectPath;
326
+ const changedFields = [];
327
+
328
+ if (hasProviderUpdate) {
329
+ const normalizedProviderId = normalizeCcConnectProviderId(provider);
330
+ if (!normalizedProviderId) {
331
+ throw createCcConnectError('UNSUPPORTED_PROVIDER', `Unsupported provider: ${provider}`);
332
+ }
333
+
334
+ if (!availableProviders.includes(normalizedProviderId)) {
335
+ throw createCcConnectError('PROVIDER_NOT_AVAILABLE', `Unsupported provider: ${normalizedProviderId}`);
336
+ }
337
+
338
+ if (normalizedProviderId !== nextProvider) {
339
+ changedFields.push('provider');
340
+ nextProvider = normalizedProviderId;
341
+ }
342
+ }
343
+
344
+ if (hasProjectUpdate) {
345
+ const normalizedProjectPath = validateProjectPath(projectPath);
346
+ if (normalizedProjectPath !== nextProjectPath) {
347
+ changedFields.push('projectPath');
348
+ nextProjectPath = normalizedProjectPath;
349
+ }
350
+ }
351
+
352
+ if (changedFields.length === 0) {
353
+ return {
354
+ platform: normalizedPlatformId,
355
+ label: PLATFORM_MAP[normalizedPlatformId].label,
356
+ provider: nextProvider,
357
+ projectPath: nextProjectPath,
358
+ configuredProviders: availableProviders,
359
+ changed: false,
360
+ changedFields,
361
+ message: 'No changes applied.'
362
+ };
363
+ }
364
+
365
+ const nextState = upsertPlatformConnection(currentState, normalizedPlatformId, {
366
+ ...connection,
367
+ configuredProviders: availableProviders,
368
+ activeProvider: nextProvider,
369
+ projectPath: nextProjectPath,
370
+ updatedAt: nowIso()
371
+ });
372
+
373
+ writeCcConnectState(nextState);
374
+ const { configPath } = writeCcConnectConfig(nextState);
375
+ const daemonResult = ensureCcConnectDaemonRunning(configPath);
376
+ if (!daemonResult.ok) {
377
+ throw createCcConnectError('DAEMON_UPDATE_FAILED', daemonResult.detail);
378
+ }
379
+
380
+ return {
381
+ platform: normalizedPlatformId,
382
+ label: PLATFORM_MAP[normalizedPlatformId].label,
383
+ provider: nextProvider,
384
+ projectPath: nextProjectPath,
385
+ configuredProviders: availableProviders,
386
+ changed: true,
387
+ changedFields,
388
+ message: daemonResult.detail
389
+ };
390
+ }