@axhub/genie 0.2.7 → 0.2.9

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 (131) hide show
  1. package/LICENSE +21 -675
  2. package/dist/api-docs.html +2 -2
  3. package/dist/assets/App-GBcTeeUS.js +460 -0
  4. package/dist/assets/App-qxJ8_QYu.css +32 -0
  5. package/dist/assets/ReviewApp-C9K--AQE.js +1 -0
  6. package/dist/assets/{_basePickBy-C19AekOu.js → _basePickBy-DR_8uFCo.js} +1 -1
  7. package/dist/assets/{_baseUniq-JsnevLw_.js → _baseUniq-D0njlQ_7.js} +1 -1
  8. package/dist/assets/{arc-BLpcuBlf.js → arc-CKlr_Rec.js} +1 -1
  9. package/dist/assets/architectureDiagram-2XIMDMQ5-BmO_uLUH.js +36 -0
  10. package/dist/assets/{blockDiagram-WCTKOSBZ-DQBLwsUS.js → blockDiagram-WCTKOSBZ-DhAeO-56.js} +3 -3
  11. package/dist/assets/c4Diagram-IC4MRINW-C67kFoXx.js +10 -0
  12. package/dist/assets/channel-V3MBjKys.js +1 -0
  13. package/dist/assets/{chunk-4BX2VUAB-De63kbgc.js → chunk-4BX2VUAB-mLLagvJi.js} +1 -1
  14. package/dist/assets/{chunk-55IACEB6-DtTDDdM9.js → chunk-55IACEB6-Lx-hOjlM.js} +1 -1
  15. package/dist/assets/{chunk-FMBD7UC4-DHuwd8tw.js → chunk-FMBD7UC4-Bt-XmVUV.js} +1 -1
  16. package/dist/assets/{chunk-JSJVCQXG-BgytFtmO.js → chunk-JSJVCQXG-Cya6gaDV.js} +1 -1
  17. package/dist/assets/{chunk-KX2RTZJC-nZdp86aN.js → chunk-KX2RTZJC-Bd7Ig6tF.js} +1 -1
  18. package/dist/assets/chunk-NQ4KR5QH-5UAE0Vg-.js +220 -0
  19. package/dist/assets/{chunk-QZHKN3VN-DvUQ3mnO.js → chunk-QZHKN3VN-BAxZ8m7w.js} +1 -1
  20. package/dist/assets/chunk-WL4C6EOR-DjDPvUUP.js +189 -0
  21. package/dist/assets/classDiagram-VBA2DB6C-C790yYiY.js +1 -0
  22. package/dist/assets/classDiagram-v2-RAHNMMFH-C790yYiY.js +1 -0
  23. package/dist/assets/clone-BbMGfZwt.js +1 -0
  24. package/dist/assets/cose-bilkent-S5V4N54A-D-60XrkJ.js +1 -0
  25. package/dist/assets/cytoscape.esm-2ZfV8NB5.js +331 -0
  26. package/dist/assets/{dagre-KLK3FWXG-CHYIvW47.js → dagre-KLK3FWXG-bqu3ZS4K.js} +1 -1
  27. package/dist/assets/diagram-E7M64L7V-BueeqoYm.js +24 -0
  28. package/dist/assets/{diagram-IFDJBPK2-Dzsiln_C.js → diagram-IFDJBPK2-D4fDv2E7.js} +1 -1
  29. package/dist/assets/{diagram-P4PSJMXO-DKnGbUpE.js → diagram-P4PSJMXO-WqipY3fN.js} +1 -1
  30. package/dist/assets/erDiagram-INFDFZHY-D0oVnO-x.js +70 -0
  31. package/dist/assets/{flowDiagram-PKNHOUZH-BAZ2-jKp.js → flowDiagram-PKNHOUZH-DzbGyxrr.js} +4 -4
  32. package/dist/assets/ganttDiagram-A5KZAMGK-BwhbbgCP.js +292 -0
  33. package/dist/assets/{gitGraphDiagram-K3NZZRJ6-BflpyjGy.js → gitGraphDiagram-K3NZZRJ6-DZgAh_KM.js} +1 -1
  34. package/dist/assets/{graph-suelaXFh.js → graph-DzKos-N0.js} +1 -1
  35. package/dist/assets/highlighted-body-TPN3WLV5-CKDMgz3X.js +1 -0
  36. package/dist/assets/index-DiQlHzGj.js +2 -0
  37. package/dist/assets/index-Drat2nB9.css +1 -0
  38. package/dist/assets/{infoDiagram-LFFYTUFH-pfD1FA3p.js → infoDiagram-LFFYTUFH-BFicZbTf.js} +1 -1
  39. package/dist/assets/ishikawaDiagram-PHBUUO56-CtihxDxl.js +70 -0
  40. package/dist/assets/journeyDiagram-4ABVD52K-Du00J8_d.js +139 -0
  41. package/dist/assets/{kanban-definition-K7BYSVSG-FWinmur1.js → kanban-definition-K7BYSVSG-BJi9S0iQ.js} +5 -5
  42. package/dist/assets/{layout-vcz43XvZ.js → layout-B80Sityu.js} +1 -1
  43. package/dist/assets/{linear-le4gc0vx.js → linear-sRQLOf5H.js} +1 -1
  44. package/dist/assets/mermaid-O7DHMXV3-CBuVs4eJ.js +1038 -0
  45. package/dist/assets/mindmap-definition-YRQLILUH-C5IL_xi-.js +68 -0
  46. package/dist/assets/{pieDiagram-SKSYHLDU-C7PKDh3b.js → pieDiagram-SKSYHLDU-CeTwlJ8z.js} +2 -2
  47. package/dist/assets/quadrantDiagram-337W2JSQ-COfUcLWt.js +7 -0
  48. package/dist/assets/requirementDiagram-Z7DCOOCP-DSb-CJ5B.js +73 -0
  49. package/dist/assets/{sankeyDiagram-WA2Y5GQK-4gulcOP4.js → sankeyDiagram-WA2Y5GQK-8jtuVb45.js} +3 -3
  50. package/dist/assets/sequenceDiagram-2WXFIKYE-C2VpkMwA.js +145 -0
  51. package/dist/assets/{stateDiagram-RAJIS63D-CB4Vl7qM.js → stateDiagram-RAJIS63D-fmwMqxxc.js} +1 -1
  52. package/dist/assets/stateDiagram-v2-FVOUBMTO-9GGXVWrR.js +1 -0
  53. package/dist/assets/timeline-definition-YZTLITO2-Dx1hP5lg.js +61 -0
  54. package/dist/assets/{treemap-KZPCXAKY-DZSEE6Hz.js → treemap-KZPCXAKY-CkLOdYCZ.js} +58 -58
  55. package/dist/assets/vendor-codemirror-BxPY6emf.js +39 -0
  56. package/dist/assets/vendor-react-xmA_f8ig.js +59 -0
  57. package/dist/assets/vendor-xterm-DfaPXD3y.js +66 -0
  58. package/dist/assets/{vennDiagram-LZ73GAT5-8E_G06fI.js → vennDiagram-LZ73GAT5-D6KWcnln.js} +4 -4
  59. package/dist/assets/xychartDiagram-JWTSCODW-6fh6qmzN.js +7 -0
  60. package/dist/index.html +5 -5
  61. package/package.json +36 -35
  62. package/server/acp-runtime/client.js +91 -17
  63. package/server/acp-runtime/index.js +5 -16
  64. package/server/acp-runtime/session-store.js +4 -4
  65. package/server/channels/runtime/AgentRuntimeAdapter.js +1 -10
  66. package/server/claude-sdk.js +1 -3
  67. package/server/cli.js +159 -2
  68. package/server/external-agent/service.js +24 -6
  69. package/server/external-agent/ws.js +63 -3
  70. package/server/gemini-cli.js +1 -3
  71. package/server/index.js +120 -19
  72. package/server/openai-codex.js +1 -3
  73. package/server/opencode-cli.js +1 -3
  74. package/server/projects.js +654 -236
  75. package/server/routes/cc-connect.js +1131 -0
  76. package/server/routes/cli-auth.js +1 -73
  77. package/server/routes/commands.js +4 -9
  78. package/server/routes/projects.js +45 -24
  79. package/server/routes/session-core.js +149 -86
  80. package/server/session-core/eventStore.js +45 -18
  81. package/server/session-core/providerAdapters.js +50 -13
  82. package/server/session-core/providerDiscovery.js +8 -3
  83. package/server/session-core/runtimeState.js +8 -0
  84. package/server/utils/ccConnectManager.js +390 -0
  85. package/server/utils/ccConnectState.js +575 -0
  86. package/server/utils/resolveCommandPath.js +71 -0
  87. package/server/utils/workspaceRoots.js +154 -0
  88. package/shared/conversationEvents.js +78 -14
  89. package/dist/assets/App-BWSqiXAT.js +0 -220
  90. package/dist/assets/App-DrlLKa8f.css +0 -1
  91. package/dist/assets/ReviewApp-nz3mbArg.js +0 -1
  92. package/dist/assets/architectureDiagram-2XIMDMQ5-CarjBOOv.js +0 -36
  93. package/dist/assets/c4Diagram-IC4MRINW-CGobwBIj.js +0 -10
  94. package/dist/assets/channel-DkFNxV_H.js +0 -1
  95. package/dist/assets/chunk-NQ4KR5QH-CMH6EDP2.js +0 -220
  96. package/dist/assets/chunk-WL4C6EOR-Dn7db_6t.js +0 -189
  97. package/dist/assets/classDiagram-VBA2DB6C-DtwCEe8S.js +0 -1
  98. package/dist/assets/classDiagram-v2-RAHNMMFH-DtwCEe8S.js +0 -1
  99. package/dist/assets/clone-C0lCEIEO.js +0 -1
  100. package/dist/assets/cose-bilkent-S5V4N54A-DD_nzqsz.js +0 -1
  101. package/dist/assets/cytoscape.esm-5J0xJHOV.js +0 -321
  102. package/dist/assets/diagram-E7M64L7V-TVdvHtGc.js +0 -24
  103. package/dist/assets/erDiagram-INFDFZHY-5Kw0bByo.js +0 -70
  104. package/dist/assets/ganttDiagram-A5KZAMGK-CsADFkcq.js +0 -292
  105. package/dist/assets/highlighted-body-OFNGDK62-CZrBMazC.js +0 -1
  106. package/dist/assets/index-B01NxbUv.css +0 -1
  107. package/dist/assets/index-DW5pGgQ_.js +0 -2
  108. package/dist/assets/ishikawaDiagram-PHBUUO56-ndm9snwO.js +0 -70
  109. package/dist/assets/journeyDiagram-4ABVD52K-HgF2t7z5.js +0 -139
  110. package/dist/assets/mermaid-GHXKKRXX-CK8m3lad.js +0 -870
  111. package/dist/assets/mindmap-definition-YRQLILUH-CNq9SKj4.js +0 -68
  112. package/dist/assets/quadrantDiagram-337W2JSQ-B7FnztNO.js +0 -7
  113. package/dist/assets/requirementDiagram-Z7DCOOCP-Bl_BM2Th.js +0 -73
  114. package/dist/assets/sequenceDiagram-2WXFIKYE-VEuJDwyJ.js +0 -145
  115. package/dist/assets/stateDiagram-v2-FVOUBMTO-C85ucl39.js +0 -1
  116. package/dist/assets/timeline-definition-YZTLITO2-BPGKhi7f.js +0 -61
  117. package/dist/assets/vendor-codemirror-CyOKkaQZ.js +0 -31
  118. package/dist/assets/vendor-react-CP4yFTs7.js +0 -8
  119. package/dist/assets/vendor-xterm-DfcmCpbH.js +0 -66
  120. package/dist/assets/xychartDiagram-JWTSCODW-CbBk50-O.js +0 -7
  121. package/server/_legacy-providers/README.md +0 -30
  122. package/server/_legacy-providers/claude-sdk.js +0 -956
  123. package/server/_legacy-providers/gemini-cli.js +0 -368
  124. package/server/_legacy-providers/openai-codex.js +0 -705
  125. package/server/_legacy-providers/opencode-cli.js +0 -674
  126. package/server/acp-runtime/client.test.js +0 -688
  127. package/server/acp-runtime/session-store.test.js +0 -89
  128. package/server/cli.test.js +0 -76
  129. package/server/external-agent/service.test.js +0 -53
  130. package/server/external-agent/ws.test.js +0 -289
  131. package/shared/conversationEvents.test.js +0 -403
@@ -37,6 +37,18 @@ async function normalizeLegacyLoadResult(result, provider, sessionId) {
37
37
  };
38
38
  }
39
39
 
40
+ function createEventLoadResult(events = [], source = 'acp') {
41
+ const normalizedEvents = Array.isArray(events) ? events : [];
42
+ return {
43
+ events: normalizedEvents,
44
+ total: normalizedEvents.length,
45
+ hasMore: false,
46
+ offset: 0,
47
+ limit: null,
48
+ source
49
+ };
50
+ }
51
+
40
52
  function mergeSessionLists(legacySessions = [], acpSessions = []) {
41
53
  const merged = new Map();
42
54
 
@@ -59,21 +71,30 @@ function mergeSessionLists(legacySessions = [], acpSessions = []) {
59
71
  });
60
72
  }
61
73
 
62
- async function loadAcpEvents(provider, sessionId) {
74
+ async function loadAcpEvents(provider, sessionId, nativeHistoryLoader = null) {
63
75
  const record = await findAcpSessionRecord(sessionId, provider);
64
76
  if (!record) {
65
77
  return null;
66
78
  }
67
79
 
80
+ if (typeof nativeHistoryLoader === 'function') {
81
+ try {
82
+ const nativeResult = await nativeHistoryLoader();
83
+ const normalizedResult = await normalizeLegacyLoadResult(nativeResult, provider, sessionId);
84
+ if (Array.isArray(normalizedResult)) {
85
+ return createEventLoadResult(normalizedResult, 'acp');
86
+ }
87
+ return {
88
+ ...normalizedResult,
89
+ source: 'acp'
90
+ };
91
+ } catch {
92
+ // Fall back to the mirrored event store when native history is unavailable.
93
+ }
94
+ }
95
+
68
96
  const events = await readMirroredConversationEvents(provider, sessionId);
69
- return {
70
- events,
71
- total: events.length,
72
- hasMore: false,
73
- offset: 0,
74
- limit: null,
75
- source: 'acp'
76
- };
97
+ return createEventLoadResult(events, 'acp');
77
98
  }
78
99
 
79
100
  const PROVIDER_ADAPTERS = {
@@ -89,7 +110,11 @@ const PROVIDER_ADAPTERS = {
89
110
  );
90
111
  },
91
112
  async loadEvents({ projectName, sessionId, limit = null, offset = 0 }) {
92
- const acpResult = await loadAcpEvents('claude', sessionId);
113
+ const acpResult = await loadAcpEvents(
114
+ 'claude',
115
+ sessionId,
116
+ () => getSessionMessages(projectName, sessionId, limit, offset),
117
+ );
93
118
  if (acpResult) {
94
119
  return acpResult;
95
120
  }
@@ -110,7 +135,11 @@ const PROVIDER_ADAPTERS = {
110
135
  );
111
136
  },
112
137
  async loadEvents({ sessionId, limit = null, offset = 0 }) {
113
- const acpResult = await loadAcpEvents('codex', sessionId);
138
+ const acpResult = await loadAcpEvents(
139
+ 'codex',
140
+ sessionId,
141
+ () => getCodexSessionMessages(sessionId, limit, offset),
142
+ );
114
143
  if (acpResult) {
115
144
  return acpResult;
116
145
  }
@@ -131,7 +160,11 @@ const PROVIDER_ADAPTERS = {
131
160
  );
132
161
  },
133
162
  async loadEvents({ sessionId, limit = null, offset = 0 }) {
134
- const acpResult = await loadAcpEvents('gemini', sessionId);
163
+ const acpResult = await loadAcpEvents(
164
+ 'gemini',
165
+ sessionId,
166
+ () => getGeminiSessionMessages(sessionId, limit, offset),
167
+ );
135
168
  if (acpResult) {
136
169
  return acpResult;
137
170
  }
@@ -152,7 +185,11 @@ const PROVIDER_ADAPTERS = {
152
185
  );
153
186
  },
154
187
  async loadEvents({ sessionId, limit = null, offset = 0 }) {
155
- const acpResult = await loadAcpEvents('opencode', sessionId);
188
+ const acpResult = await loadAcpEvents(
189
+ 'opencode',
190
+ sessionId,
191
+ () => getOpencodeSessionMessages(sessionId, limit, offset),
192
+ );
156
193
  if (acpResult) {
157
194
  return acpResult;
158
195
  }
@@ -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 = {
@@ -266,6 +266,14 @@ function createRuntimeSignature(snapshot) {
266
266
  });
267
267
  }
268
268
 
269
+ export function seedSessionRuntimeSubscriptionSnapshot(subscriptionKey, snapshot) {
270
+ if (!subscriptionKey || !snapshot) {
271
+ return;
272
+ }
273
+
274
+ lastBroadcastSignatures.set(subscriptionKey, createRuntimeSignature(snapshot));
275
+ }
276
+
269
277
  export function createSessionRuntimeSubscriptionKey(provider, sessionId) {
270
278
  const normalizedProvider = normalizeProvider(provider);
271
279
  const normalizedSessionId = normalizeNonEmptyString(sessionId);
@@ -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
+ }