@ken-jo/agent-connector 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -2
- package/dist/{chunk-AKJGNTU3.js → chunk-3RBLIHPZ.js} +21 -21
- package/dist/chunk-3RBLIHPZ.js.map +1 -0
- package/dist/chunk-G5CQHVQG.js +511 -0
- package/dist/chunk-G5CQHVQG.js.map +1 -0
- package/dist/chunk-KMAKDD3Z.js +3010 -0
- package/dist/chunk-KMAKDD3Z.js.map +1 -0
- package/dist/{chunk-KHO5WNTP.js → chunk-KVAAFYRR.js} +18 -2
- package/dist/chunk-KVAAFYRR.js.map +1 -0
- package/dist/{chunk-O2SBA5KR.js → chunk-NJU275US.js} +9 -9
- package/dist/{chunk-WCETQWWK.js → chunk-OP4KBXUO.js} +1 -17
- package/dist/chunk-OP4KBXUO.js.map +1 -0
- package/dist/{package-O63K4S7T.js → chunk-WNREHWKL.js} +20 -114
- package/dist/chunk-WNREHWKL.js.map +1 -0
- package/dist/{chunk-4ML7SZ7R.js → chunk-ZAM4M7LI.js} +7 -7
- package/dist/cli/sdk.js +1 -1
- package/dist/cli.js +1 -1
- package/dist/{detect-JK7T46N6.js → detect-U3E7NVNW.js} +3 -3
- package/dist/{doctor-62BHIXOR.js → doctor-DZTWED4O.js} +35 -5
- package/dist/doctor-DZTWED4O.js.map +1 -0
- package/dist/{hook-AC7APC66.js → hook-EVY7LOUQ.js} +6 -6
- package/dist/{install-DMW2C7SV.js → install-OWAS5UIE.js} +31 -7
- package/dist/install-OWAS5UIE.js.map +1 -0
- package/dist/kilo-PKAJCWPX.js +852 -0
- package/dist/kilo-PKAJCWPX.js.map +1 -0
- package/dist/{kilo-cli-2RNODHWY.js → kilo-cli-QWUVXG2O.js} +157 -6
- package/dist/kilo-cli-QWUVXG2O.js.map +1 -0
- package/dist/{kimi-Q5RG7CYT.js → kimi-EVD7YKXS.js} +79 -4
- package/dist/kimi-EVD7YKXS.js.map +1 -0
- package/dist/{kiro-EKTRW424.js → kiro-UQRY6EIO.js} +68 -3
- package/dist/kiro-UQRY6EIO.js.map +1 -0
- package/dist/{leaderboard-R5HRJHNU.js → leaderboard-CSWHI4UY.js} +2 -2
- package/dist/package-WFOS4QVU.js +130 -0
- package/dist/package-WFOS4QVU.js.map +1 -0
- package/dist/{pi-SJPIL4VE.js → pi-WMTUX6ES.js} +102 -23
- package/dist/pi-WMTUX6ES.js.map +1 -0
- package/dist/{qwen-code-IFCM6BJY.js → qwen-code-MRRIUQ3P.js} +78 -5
- package/dist/qwen-code-MRRIUQ3P.js.map +1 -0
- package/dist/runtime/index.js +4 -4
- package/dist/{serve-LFOUSZAD.js → serve-3C2VFPAY.js} +6 -6
- package/dist/{status-M7JLZVLC.js → status-MKQYX5MW.js} +16 -7
- package/dist/status-MKQYX5MW.js.map +1 -0
- package/dist/{telemetry-3NVEXPYX.js → telemetry-3DN6XGYB.js} +2 -2
- package/dist/uninstall-WG6PFWF3.js +162 -0
- package/dist/uninstall-WG6PFWF3.js.map +1 -0
- package/dist/{upgrade-76BQZBMA.js → upgrade-32NC2RKI.js} +26 -8
- package/dist/upgrade-32NC2RKI.js.map +1 -0
- package/dist/{usage-G3GSCX23.js → usage-CJOM2WQM.js} +2 -2
- package/dist/{usage-event-QUTMNBS5.js → usage-event-472X3DOY.js} +5 -5
- package/dist/{warp-LLOSXR7A.js → warp-X6MB67NT.js} +81 -3
- package/dist/warp-X6MB67NT.js.map +1 -0
- package/dist/{zed-HYOITK7F.js → zed-NTCYW4V2.js} +68 -3
- package/dist/zed-NTCYW4V2.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-AKJGNTU3.js.map +0 -1
- package/dist/chunk-KHO5WNTP.js.map +0 -1
- package/dist/chunk-WCETQWWK.js.map +0 -1
- package/dist/chunk-Z2BVYKZL.js +0 -369
- package/dist/chunk-Z2BVYKZL.js.map +0 -1
- package/dist/doctor-62BHIXOR.js.map +0 -1
- package/dist/install-DMW2C7SV.js.map +0 -1
- package/dist/kilo-ISR5FBBJ.js +0 -408
- package/dist/kilo-ISR5FBBJ.js.map +0 -1
- package/dist/kilo-cli-2RNODHWY.js.map +0 -1
- package/dist/kimi-Q5RG7CYT.js.map +0 -1
- package/dist/kiro-EKTRW424.js.map +0 -1
- package/dist/package-O63K4S7T.js.map +0 -1
- package/dist/pi-SJPIL4VE.js.map +0 -1
- package/dist/qwen-code-IFCM6BJY.js.map +0 -1
- package/dist/status-M7JLZVLC.js.map +0 -1
- package/dist/uninstall-H6FGMIHN.js +0 -73
- package/dist/uninstall-H6FGMIHN.js.map +0 -1
- package/dist/upgrade-76BQZBMA.js.map +0 -1
- package/dist/warp-LLOSXR7A.js.map +0 -1
- package/dist/zed-HYOITK7F.js.map +0 -1
- /package/dist/{chunk-O2SBA5KR.js.map → chunk-NJU275US.js.map} +0 -0
- /package/dist/{chunk-4ML7SZ7R.js.map → chunk-ZAM4M7LI.js.map} +0 -0
- /package/dist/{detect-JK7T46N6.js.map → detect-U3E7NVNW.js.map} +0 -0
- /package/dist/{hook-AC7APC66.js.map → hook-EVY7LOUQ.js.map} +0 -0
- /package/dist/{leaderboard-R5HRJHNU.js.map → leaderboard-CSWHI4UY.js.map} +0 -0
- /package/dist/{serve-LFOUSZAD.js.map → serve-3C2VFPAY.js.map} +0 -0
- /package/dist/{telemetry-3NVEXPYX.js.map → telemetry-3DN6XGYB.js.map} +0 -0
- /package/dist/{usage-G3GSCX23.js.map → usage-CJOM2WQM.js.map} +0 -0
- /package/dist/{usage-event-QUTMNBS5.js.map → usage-event-472X3DOY.js.map} +0 -0
|
@@ -0,0 +1,3010 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
installInstructions,
|
|
4
|
+
packageConnector
|
|
5
|
+
} from "./chunk-WNREHWKL.js";
|
|
6
|
+
import {
|
|
7
|
+
MARKETPLACE_NAME,
|
|
8
|
+
agyPluginInstalled,
|
|
9
|
+
agyStagingRoot,
|
|
10
|
+
anyClaudeAgentConnectorPlugins,
|
|
11
|
+
anyCodexAgentConnectorPlugins,
|
|
12
|
+
anyDroidAgentConnectorPlugins,
|
|
13
|
+
anyMarketplaceRecordsRemain,
|
|
14
|
+
claudeKnownMarketplacePath,
|
|
15
|
+
claudePluginInstalled,
|
|
16
|
+
claudePluginKey,
|
|
17
|
+
claudeStagingRoot,
|
|
18
|
+
codexMarketplaceSource,
|
|
19
|
+
codexPluginInstalled,
|
|
20
|
+
codexPluginKey,
|
|
21
|
+
codexStagingRoot,
|
|
22
|
+
connectorHasMarketplaceRecords,
|
|
23
|
+
droidMarketplaceSource,
|
|
24
|
+
droidPluginInstalled,
|
|
25
|
+
droidPluginKey,
|
|
26
|
+
droidStagingRoot,
|
|
27
|
+
geminiExtensionInstalled,
|
|
28
|
+
geminiStagingRoot,
|
|
29
|
+
hashDirectory,
|
|
30
|
+
marketplaceEvidence,
|
|
31
|
+
marketplaceRoot,
|
|
32
|
+
npmConfigFilePath,
|
|
33
|
+
npmPluginArrayEntry,
|
|
34
|
+
npmPluginInstalled,
|
|
35
|
+
npmStagingRoot,
|
|
36
|
+
qwenExtensionInstalled,
|
|
37
|
+
qwenStagingRoot,
|
|
38
|
+
readMarketplaceInstalls,
|
|
39
|
+
readNpmConfig,
|
|
40
|
+
writeMarketplaceInstalls
|
|
41
|
+
} from "./chunk-G5CQHVQG.js";
|
|
42
|
+
import {
|
|
43
|
+
log,
|
|
44
|
+
spawnChild
|
|
45
|
+
} from "./chunk-KVAAFYRR.js";
|
|
46
|
+
import {
|
|
47
|
+
REGISTERED_PLATFORM_IDS,
|
|
48
|
+
detectInstalledPlatforms,
|
|
49
|
+
loadAdapter
|
|
50
|
+
} from "./chunk-NJU275US.js";
|
|
51
|
+
import {
|
|
52
|
+
deregisterConnector,
|
|
53
|
+
loadRegisteredConnector,
|
|
54
|
+
registerConnector
|
|
55
|
+
} from "./chunk-3JCRUROU.js";
|
|
56
|
+
import {
|
|
57
|
+
configPatchManualEdit
|
|
58
|
+
} from "./chunk-GSGDG2BV.js";
|
|
59
|
+
import {
|
|
60
|
+
hasMemoryLedger
|
|
61
|
+
} from "./chunk-N6HH7K6P.js";
|
|
62
|
+
import {
|
|
63
|
+
connectorDir,
|
|
64
|
+
connectorsDir,
|
|
65
|
+
dataRoot,
|
|
66
|
+
ensureDir,
|
|
67
|
+
ensureHomeBin,
|
|
68
|
+
homeBinPath
|
|
69
|
+
} from "./chunk-TRQKHP7D.js";
|
|
70
|
+
|
|
71
|
+
// src/core/installer.ts
|
|
72
|
+
import { existsSync, readdirSync, rmSync } from "fs";
|
|
73
|
+
import { dirname, join } from "path";
|
|
74
|
+
import { fileURLToPath } from "url";
|
|
75
|
+
async function installConnector(opts) {
|
|
76
|
+
const { connector, modulePath, scope, projectDir, dryRun, force } = opts;
|
|
77
|
+
if (!dryRun) {
|
|
78
|
+
try {
|
|
79
|
+
ensureHomeBin(resolveCliEntry());
|
|
80
|
+
} catch (err) {
|
|
81
|
+
log.warn(`ensureHomeBin failed: ${errMessage(err)}`);
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
registerConnector(connector, modulePath);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
log.warn(`registerConnector failed: ${errMessage(err)}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const targets = await resolveTargets(opts.targets, connector.targets, projectDir);
|
|
90
|
+
const result = newResult(connector.id, dryRun);
|
|
91
|
+
if (targets.length === 0) {
|
|
92
|
+
result.warnings.push(
|
|
93
|
+
"no target platforms resolved (none installed / detected, or all filtered out)"
|
|
94
|
+
);
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
for (const id of targets) {
|
|
98
|
+
const evidence = marketplaceEvidence(connector.id, id);
|
|
99
|
+
if (evidence) {
|
|
100
|
+
const detail = `"${connector.id}" is already installed via MARKETPLACE on ${id} (${evidence}) \u2014 refusing the direct install. Run \`agent-connector uninstall --method marketplace --targets ${id}\` first, or keep the marketplace install.`;
|
|
101
|
+
result.changes.push({ platform: id, action: "warn", detail });
|
|
102
|
+
result.warnings.push(detail);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const adapter = await tryLoadAdapter(id, result);
|
|
106
|
+
if (!adapter) continue;
|
|
107
|
+
const ctx = buildContext(connector, id, scope, projectDir, dryRun, force);
|
|
108
|
+
if (!dryRun) {
|
|
109
|
+
runStep(id, "backupSettings", result, () => {
|
|
110
|
+
const backup = adapter.backupSettings(ctx);
|
|
111
|
+
if (backup) {
|
|
112
|
+
result.changes.push({
|
|
113
|
+
platform: id,
|
|
114
|
+
action: "create",
|
|
115
|
+
path: backup,
|
|
116
|
+
detail: "backed up settings before install"
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
runStep(id, "installServer", result, () => {
|
|
122
|
+
pushAll(result.changes, adapter.installServer(ctx));
|
|
123
|
+
});
|
|
124
|
+
runStep(id, "installHooks", result, () => {
|
|
125
|
+
pushAll(result.changes, adapter.installHooks(ctx));
|
|
126
|
+
});
|
|
127
|
+
const nativeHooks = connector.platforms[id]?.nativeHooks;
|
|
128
|
+
const nativeCount = nativeHooks ? Object.keys(nativeHooks).length : 0;
|
|
129
|
+
if (nativeCount > 0 && !(adapter.capabilities.supportsNativeHooks ?? false)) {
|
|
130
|
+
result.changes.push({
|
|
131
|
+
platform: id,
|
|
132
|
+
action: "warn",
|
|
133
|
+
detail: `nativeHooks not supported on ${id}; ${nativeCount} skipped`
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (connector.commands.length) {
|
|
137
|
+
runStep(id, "installCommands", result, () => {
|
|
138
|
+
pushAll(result.changes, adapter.installCommands(ctx));
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
if (connector.skills.length) {
|
|
142
|
+
runStep(id, "installSkills", result, () => {
|
|
143
|
+
pushAll(result.changes, adapter.installSkills(ctx));
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
if (connector.subagents.length) {
|
|
147
|
+
runStep(id, "installSubagents", result, () => {
|
|
148
|
+
pushAll(result.changes, adapter.installSubagents(ctx));
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
if ((connector.memory ?? []).length) {
|
|
152
|
+
runStep(id, "installMemory", result, () => {
|
|
153
|
+
pushAll(result.changes, adapter.installMemory(ctx));
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
const configPatches = connector.platforms[id]?.configPatch ?? [];
|
|
157
|
+
if (configPatches.length > 0) {
|
|
158
|
+
if (adapter.capabilities.supportsConfigPatch ?? false) {
|
|
159
|
+
runStep(id, "installConfigPatches", result, () => {
|
|
160
|
+
pushAll(result.changes, adapter.installConfigPatches(ctx));
|
|
161
|
+
});
|
|
162
|
+
} else {
|
|
163
|
+
result.changes.push({
|
|
164
|
+
platform: id,
|
|
165
|
+
action: "warn",
|
|
166
|
+
detail: `configPatch not supported on ${id}; ${configPatches.length} skipped`
|
|
167
|
+
});
|
|
168
|
+
for (const patch of configPatches) {
|
|
169
|
+
result.changes.push({
|
|
170
|
+
platform: id,
|
|
171
|
+
action: "warn",
|
|
172
|
+
detail: `configPatch ${patch.key} skipped on ${id} \u2014 ${configPatchManualEdit(patch)}`
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
async function syncConnector(opts) {
|
|
181
|
+
return installConnector(opts);
|
|
182
|
+
}
|
|
183
|
+
async function uninstallConnector(opts) {
|
|
184
|
+
const { connectorId, scope, projectDir, dryRun, purge } = opts;
|
|
185
|
+
const connector = await loadConnectorForUninstall(connectorId);
|
|
186
|
+
const result = newResult(connectorId, dryRun);
|
|
187
|
+
const targets = await resolveTargets(opts.targets, connector.targets, projectDir);
|
|
188
|
+
if (targets.length === 0) {
|
|
189
|
+
result.warnings.push(
|
|
190
|
+
"no target platforms resolved for uninstall (none installed / detected, or all filtered out)"
|
|
191
|
+
);
|
|
192
|
+
if (purge) purgeFrameworkState(connectorId, dryRun, result);
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
for (const id of targets) {
|
|
196
|
+
const adapter = await tryLoadAdapter(id, result);
|
|
197
|
+
if (!adapter) continue;
|
|
198
|
+
const ctx = buildContext(connector, id, scope, projectDir, dryRun);
|
|
199
|
+
if (adapter.capabilities.supportsConfigPatch ?? false) {
|
|
200
|
+
runStep(id, "uninstallConfigPatches", result, () => {
|
|
201
|
+
pushAll(result.changes, adapter.uninstallConfigPatches(ctx));
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
if ((connector.memory ?? []).length > 0 || hasMemoryLedger(connector.id)) {
|
|
205
|
+
runStep(id, "uninstallMemory", result, () => {
|
|
206
|
+
pushAll(result.changes, adapter.uninstallMemory(ctx));
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
if (connector.subagents.length) {
|
|
210
|
+
runStep(id, "uninstallSubagents", result, () => {
|
|
211
|
+
pushAll(result.changes, adapter.uninstallSubagents(ctx));
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
if (connector.commands.length) {
|
|
215
|
+
runStep(id, "uninstallCommands", result, () => {
|
|
216
|
+
pushAll(result.changes, adapter.uninstallCommands(ctx));
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
if (connector.skills.length) {
|
|
220
|
+
runStep(id, "uninstallSkills", result, () => {
|
|
221
|
+
pushAll(result.changes, adapter.uninstallSkills(ctx));
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
runStep(id, "uninstallHooks", result, () => {
|
|
225
|
+
pushAll(result.changes, adapter.uninstallHooks(ctx));
|
|
226
|
+
});
|
|
227
|
+
runStep(id, "uninstallServer", result, () => {
|
|
228
|
+
pushAll(result.changes, adapter.uninstallServer(ctx));
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
if (purge) purgeFrameworkState(connectorId, dryRun, result);
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
function purgeFrameworkState(connectorId, dryRun, result) {
|
|
235
|
+
const platform = connectorId;
|
|
236
|
+
if (connectorHasMarketplaceRecords(connectorId)) {
|
|
237
|
+
const detail = `--purge skipped: "${connectorId}" still records marketplace installs \u2014 run \`agent-connector uninstall --method marketplace\` first`;
|
|
238
|
+
result.changes.push({ platform, action: "warn", detail });
|
|
239
|
+
result.warnings.push(detail);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const recordDir = connectorDir(connectorId);
|
|
243
|
+
const recordExisted = existsSync(recordDir);
|
|
244
|
+
if (recordExisted) {
|
|
245
|
+
if (!dryRun) {
|
|
246
|
+
try {
|
|
247
|
+
deregisterConnector(connectorId);
|
|
248
|
+
} catch (err) {
|
|
249
|
+
log.warn(`deregisterConnector failed: ${errMessage(err)}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
result.changes.push({
|
|
253
|
+
platform,
|
|
254
|
+
action: "remove",
|
|
255
|
+
path: recordDir,
|
|
256
|
+
detail: "deregistered connector record"
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
if (anyMarketplaceRecordsRemain(connectorId)) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (noConnectorsRemain(connectorId, dryRun, recordExisted)) {
|
|
263
|
+
const binPath = homeBinPath();
|
|
264
|
+
if (existsSync(binPath)) {
|
|
265
|
+
if (!dryRun) {
|
|
266
|
+
try {
|
|
267
|
+
rmSync(binPath, { force: true });
|
|
268
|
+
} catch (err) {
|
|
269
|
+
log.warn(`home-bin removal failed: ${errMessage(err)}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
result.changes.push({
|
|
273
|
+
platform,
|
|
274
|
+
action: "remove",
|
|
275
|
+
path: binPath,
|
|
276
|
+
detail: "removed home-bin (no connectors remain)"
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function noConnectorsRemain(connectorId, dryRun, recordExisted) {
|
|
282
|
+
const dir = connectorsDir();
|
|
283
|
+
if (!existsSync(dir)) return true;
|
|
284
|
+
let entries;
|
|
285
|
+
try {
|
|
286
|
+
entries = readdirSync(dir);
|
|
287
|
+
} catch {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
const remaining = entries.filter(
|
|
291
|
+
(e) => !(dryRun && recordExisted && e === connectorId)
|
|
292
|
+
);
|
|
293
|
+
return remaining.length === 0;
|
|
294
|
+
}
|
|
295
|
+
function resolveCliEntry() {
|
|
296
|
+
try {
|
|
297
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
298
|
+
for (let i = 0; i < 8; i++) {
|
|
299
|
+
if (existsSync(join(dir, "package.json"))) {
|
|
300
|
+
const cli = join(dir, "dist", "cli.js");
|
|
301
|
+
if (existsSync(cli)) return cli;
|
|
302
|
+
}
|
|
303
|
+
const parent = dirname(dir);
|
|
304
|
+
if (parent === dir) break;
|
|
305
|
+
dir = parent;
|
|
306
|
+
}
|
|
307
|
+
const sameDir = fileURLToPath(new URL("./cli.js", import.meta.url));
|
|
308
|
+
if (existsSync(sameDir)) return sameDir;
|
|
309
|
+
} catch {
|
|
310
|
+
}
|
|
311
|
+
const argv1 = process.argv[1];
|
|
312
|
+
if (argv1 && argv1.length > 0) return argv1;
|
|
313
|
+
return "cli.js";
|
|
314
|
+
}
|
|
315
|
+
async function resolveTargets(flagTargets, connectorTargets, projectDir) {
|
|
316
|
+
let requested;
|
|
317
|
+
if (flagTargets && flagTargets.length > 0) {
|
|
318
|
+
requested = flagTargets;
|
|
319
|
+
} else if (Array.isArray(connectorTargets) && connectorTargets.length > 0) {
|
|
320
|
+
requested = connectorTargets;
|
|
321
|
+
} else {
|
|
322
|
+
requested = await autoDetectIds(projectDir);
|
|
323
|
+
}
|
|
324
|
+
const seen = /* @__PURE__ */ new Set();
|
|
325
|
+
const out = [];
|
|
326
|
+
for (const id of requested) {
|
|
327
|
+
if (!REGISTERED_PLATFORM_IDS.has(id)) continue;
|
|
328
|
+
if (seen.has(id)) continue;
|
|
329
|
+
seen.add(id);
|
|
330
|
+
out.push(id);
|
|
331
|
+
}
|
|
332
|
+
return out;
|
|
333
|
+
}
|
|
334
|
+
async function autoDetectIds(projectDir) {
|
|
335
|
+
try {
|
|
336
|
+
const detected = await detectInstalledPlatforms(projectDir);
|
|
337
|
+
return detected.filter((p) => p.installed).map((p) => p.id);
|
|
338
|
+
} catch (err) {
|
|
339
|
+
log.warn(`platform detection failed: ${errMessage(err)}`);
|
|
340
|
+
return [];
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function buildContext(connector, id, defaultScope, projectDir, dryRun, force = false) {
|
|
344
|
+
return {
|
|
345
|
+
connector,
|
|
346
|
+
scope: connector.platforms[id]?.scope ?? defaultScope,
|
|
347
|
+
projectDir,
|
|
348
|
+
homeBinPath: homeBinPath(),
|
|
349
|
+
dataRoot: dataRoot(),
|
|
350
|
+
dryRun,
|
|
351
|
+
force
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
async function tryLoadAdapter(id, result) {
|
|
355
|
+
try {
|
|
356
|
+
const adapter = await loadAdapter(id);
|
|
357
|
+
if (adapter) return adapter;
|
|
358
|
+
const detail = `no adapter registered for ${id}`;
|
|
359
|
+
result.changes.push({ platform: id, action: "warn", detail });
|
|
360
|
+
result.warnings.push(detail);
|
|
361
|
+
return null;
|
|
362
|
+
} catch (err) {
|
|
363
|
+
const detail = `failed to load adapter for ${id}: ${errMessage(err)}`;
|
|
364
|
+
result.changes.push({ platform: id, action: "warn", detail });
|
|
365
|
+
result.warnings.push(detail);
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
function runStep(id, step, result, fn) {
|
|
370
|
+
try {
|
|
371
|
+
fn();
|
|
372
|
+
} catch (err) {
|
|
373
|
+
const detail = `${step} failed on ${id}: ${errMessage(err)}`;
|
|
374
|
+
result.changes.push({ platform: id, action: "warn", detail });
|
|
375
|
+
result.warnings.push(detail);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
async function resolveUninstallTargets(connectorId, flagTargets, projectDir) {
|
|
379
|
+
const connector = await loadConnectorForUninstall(connectorId);
|
|
380
|
+
return resolveTargets(flagTargets, connector.targets, projectDir);
|
|
381
|
+
}
|
|
382
|
+
async function loadConnectorForUninstall(connectorId) {
|
|
383
|
+
try {
|
|
384
|
+
return await loadRegisteredConnector(connectorId);
|
|
385
|
+
} catch (err) {
|
|
386
|
+
log.warn(
|
|
387
|
+
`could not load registered connector "${connectorId}" for uninstall (${errMessage(err)}); proceeding with a minimal id-only context`
|
|
388
|
+
);
|
|
389
|
+
return syntheticConnector(connectorId);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function syntheticConnector(id) {
|
|
393
|
+
return {
|
|
394
|
+
id,
|
|
395
|
+
displayName: id,
|
|
396
|
+
version: "0.0.0",
|
|
397
|
+
hooks: {},
|
|
398
|
+
hookEvents: [],
|
|
399
|
+
telemetry: {
|
|
400
|
+
enabled: true,
|
|
401
|
+
modelFamilyHint: "auto",
|
|
402
|
+
measureToolDefs: true,
|
|
403
|
+
hostNativeUsage: false,
|
|
404
|
+
store: "ndjson",
|
|
405
|
+
calibration: { anthropicCountTokens: false }
|
|
406
|
+
},
|
|
407
|
+
commands: [],
|
|
408
|
+
skills: [],
|
|
409
|
+
subagents: [],
|
|
410
|
+
memory: [],
|
|
411
|
+
platforms: {},
|
|
412
|
+
targets: "auto"
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
function newResult(connectorId, dryRun) {
|
|
416
|
+
return { connectorId, dryRun, changes: [], warnings: [] };
|
|
417
|
+
}
|
|
418
|
+
function pushAll(target, records) {
|
|
419
|
+
for (const r of records) target.push(r);
|
|
420
|
+
}
|
|
421
|
+
function errMessage(err) {
|
|
422
|
+
return err instanceof Error ? err.message : String(err);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// src/core/marketplace.ts
|
|
426
|
+
import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync4, rmSync as rmSync9 } from "fs";
|
|
427
|
+
import { join as join10 } from "path";
|
|
428
|
+
|
|
429
|
+
// src/core/marketplace-drivers/claude.ts
|
|
430
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync, rmSync as rmSync2, writeFileSync } from "fs";
|
|
431
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
432
|
+
|
|
433
|
+
// src/core/marketplace-drivers/shared.ts
|
|
434
|
+
import { existsSync as existsSync2, realpathSync, statSync } from "fs";
|
|
435
|
+
import { delimiter, join as join2, resolve } from "path";
|
|
436
|
+
var HOST_COMMAND_TIMEOUT_MS = 3e4;
|
|
437
|
+
function findOnPath(command) {
|
|
438
|
+
const pathVar = process.env.PATH ?? "";
|
|
439
|
+
const exts = process.platform === "win32" ? (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").concat("") : [""];
|
|
440
|
+
for (const dir of pathVar.split(delimiter)) {
|
|
441
|
+
if (dir === "") continue;
|
|
442
|
+
for (const ext of exts) {
|
|
443
|
+
const candidate = join2(dir, command + ext.toLowerCase());
|
|
444
|
+
try {
|
|
445
|
+
if (existsSync2(candidate) && statSync(candidate).isFile()) {
|
|
446
|
+
return candidate;
|
|
447
|
+
}
|
|
448
|
+
} catch {
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
function runHostCommand(command, args, optionsOrTimeout = HOST_COMMAND_TIMEOUT_MS) {
|
|
455
|
+
const opts = typeof optionsOrTimeout === "number" ? { timeoutMs: optionsOrTimeout } : optionsOrTimeout;
|
|
456
|
+
const timeoutMs = opts.timeoutMs ?? HOST_COMMAND_TIMEOUT_MS;
|
|
457
|
+
const cwd = opts.cwd;
|
|
458
|
+
return new Promise((resolvePromise) => {
|
|
459
|
+
let stdout = "";
|
|
460
|
+
let stderr = "";
|
|
461
|
+
let timedOut = false;
|
|
462
|
+
let settled = false;
|
|
463
|
+
const settle = (result) => {
|
|
464
|
+
if (settled) return;
|
|
465
|
+
settled = true;
|
|
466
|
+
clearTimeout(timer);
|
|
467
|
+
resolvePromise(result);
|
|
468
|
+
};
|
|
469
|
+
let child;
|
|
470
|
+
try {
|
|
471
|
+
child = spawnChild(command, args, {
|
|
472
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
473
|
+
env: process.env,
|
|
474
|
+
// Only set cwd when a caller asked for it — omitting the key inherits
|
|
475
|
+
// the parent's cwd, keeping claude/codex/agy spawns byte-identical.
|
|
476
|
+
...cwd != null ? { cwd } : {}
|
|
477
|
+
});
|
|
478
|
+
} catch (err) {
|
|
479
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
480
|
+
resolvePromise({
|
|
481
|
+
ok: false,
|
|
482
|
+
code: null,
|
|
483
|
+
stdout: "",
|
|
484
|
+
stderr: "",
|
|
485
|
+
timedOut: false,
|
|
486
|
+
error: message
|
|
487
|
+
});
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
const timer = setTimeout(() => {
|
|
491
|
+
timedOut = true;
|
|
492
|
+
try {
|
|
493
|
+
child.kill("SIGKILL");
|
|
494
|
+
} catch {
|
|
495
|
+
}
|
|
496
|
+
}, timeoutMs);
|
|
497
|
+
child.stdout?.on("data", (chunk) => {
|
|
498
|
+
stdout += chunk.toString("utf8");
|
|
499
|
+
});
|
|
500
|
+
child.stderr?.on("data", (chunk) => {
|
|
501
|
+
stderr += chunk.toString("utf8");
|
|
502
|
+
});
|
|
503
|
+
child.on("error", (err) => {
|
|
504
|
+
settle({
|
|
505
|
+
ok: false,
|
|
506
|
+
code: null,
|
|
507
|
+
stdout,
|
|
508
|
+
stderr,
|
|
509
|
+
timedOut,
|
|
510
|
+
error: err.message
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
child.on("close", (code) => {
|
|
514
|
+
settle({
|
|
515
|
+
ok: code === 0 && !timedOut,
|
|
516
|
+
code,
|
|
517
|
+
stdout,
|
|
518
|
+
stderr,
|
|
519
|
+
timedOut
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
function firstLine(text) {
|
|
525
|
+
const line = text.split(/\r?\n/).find((l) => l.trim() !== "");
|
|
526
|
+
return line ? line.trim() : "";
|
|
527
|
+
}
|
|
528
|
+
function samePath(a, b) {
|
|
529
|
+
if (a == null || b == null) return false;
|
|
530
|
+
const norm = (p) => {
|
|
531
|
+
let s = p;
|
|
532
|
+
if (process.platform === "win32") {
|
|
533
|
+
s = s.replace(/^\\\\\?\\UNC\\/, "\\\\").replace(/^\\\\\?\\/, "");
|
|
534
|
+
}
|
|
535
|
+
try {
|
|
536
|
+
s = realpathSync.native(s);
|
|
537
|
+
} catch {
|
|
538
|
+
s = resolve(s);
|
|
539
|
+
}
|
|
540
|
+
return process.platform === "win32" ? s.toLowerCase() : s;
|
|
541
|
+
};
|
|
542
|
+
return norm(a) === norm(b);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// src/core/marketplace-drivers/claude.ts
|
|
546
|
+
var PLATFORM = "claude-code";
|
|
547
|
+
function claudeBinary() {
|
|
548
|
+
return findOnPath("claude");
|
|
549
|
+
}
|
|
550
|
+
function claudeManualInstallCommands(connectorId, stagingRoot) {
|
|
551
|
+
return `claude plugin marketplace add ${stagingRoot} && claude plugin install ${claudePluginKey(connectorId)}`;
|
|
552
|
+
}
|
|
553
|
+
function warn(detail, path) {
|
|
554
|
+
return { platform: PLATFORM, action: "warn", detail, ...path ? { path } : {} };
|
|
555
|
+
}
|
|
556
|
+
function failDetail(step, r) {
|
|
557
|
+
if (r.timedOut) return `${step} timed out`;
|
|
558
|
+
if (r.error) return `${step} failed to spawn: ${r.error}`;
|
|
559
|
+
const line = firstLine(r.stderr) || firstLine(r.stdout);
|
|
560
|
+
return `${step} exited ${r.code}${line ? `: ${line}` : ""}`;
|
|
561
|
+
}
|
|
562
|
+
async function claudeDriveInstall(connectorId, stagingRoot, pluginDir) {
|
|
563
|
+
const changes = [];
|
|
564
|
+
const pluginKey = claudePluginKey(connectorId);
|
|
565
|
+
const bin = claudeBinary();
|
|
566
|
+
if (!bin) {
|
|
567
|
+
changes.push(
|
|
568
|
+
warn(
|
|
569
|
+
`claude CLI not found on PATH \u2014 bundle staged but not installed. Install manually: ${claudeManualInstallCommands(connectorId, stagingRoot)}`,
|
|
570
|
+
stagingRoot
|
|
571
|
+
)
|
|
572
|
+
);
|
|
573
|
+
return { changes, installed: false };
|
|
574
|
+
}
|
|
575
|
+
const validation = await runHostCommand(bin, ["plugin", "validate", pluginDir]);
|
|
576
|
+
if (!validation.ok && !validation.error) {
|
|
577
|
+
changes.push(
|
|
578
|
+
warn(
|
|
579
|
+
`bundle failed \`claude plugin validate\` \u2014 not installing. ` + failDetail("validate", validation),
|
|
580
|
+
pluginDir
|
|
581
|
+
)
|
|
582
|
+
);
|
|
583
|
+
return { changes, installed: false };
|
|
584
|
+
}
|
|
585
|
+
const registeredAt = claudeKnownMarketplacePath(MARKETPLACE_NAME);
|
|
586
|
+
if (registeredAt === stagingRoot) {
|
|
587
|
+
changes.push({
|
|
588
|
+
platform: PLATFORM,
|
|
589
|
+
action: "skip",
|
|
590
|
+
path: stagingRoot,
|
|
591
|
+
detail: `marketplace "${MARKETPLACE_NAME}" already registered`
|
|
592
|
+
});
|
|
593
|
+
} else if (registeredAt != null) {
|
|
594
|
+
changes.push(
|
|
595
|
+
warn(
|
|
596
|
+
`a marketplace named "${MARKETPLACE_NAME}" is already registered at ${registeredAt} (not ours) \u2014 refusing to touch it. Remove it first (claude plugin marketplace remove ${MARKETPLACE_NAME}) or install manually: ` + claudeManualInstallCommands(connectorId, stagingRoot)
|
|
597
|
+
)
|
|
598
|
+
);
|
|
599
|
+
return { changes, installed: false };
|
|
600
|
+
} else {
|
|
601
|
+
const add = await runHostCommand(bin, [
|
|
602
|
+
"plugin",
|
|
603
|
+
"marketplace",
|
|
604
|
+
"add",
|
|
605
|
+
stagingRoot
|
|
606
|
+
]);
|
|
607
|
+
if (!add.ok) {
|
|
608
|
+
changes.push(
|
|
609
|
+
warn(
|
|
610
|
+
`could not register the local marketplace \u2014 ` + failDetail("claude plugin marketplace add", add) + `. Install manually: ${claudeManualInstallCommands(connectorId, stagingRoot)}`,
|
|
611
|
+
stagingRoot
|
|
612
|
+
)
|
|
613
|
+
);
|
|
614
|
+
return { changes, installed: false };
|
|
615
|
+
}
|
|
616
|
+
changes.push({
|
|
617
|
+
platform: PLATFORM,
|
|
618
|
+
action: "create",
|
|
619
|
+
path: stagingRoot,
|
|
620
|
+
detail: `registered local marketplace "${MARKETPLACE_NAME}" (claude plugin marketplace add)`
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
if (claudePluginInstalled(connectorId)) {
|
|
624
|
+
changes.push({
|
|
625
|
+
platform: PLATFORM,
|
|
626
|
+
action: "skip",
|
|
627
|
+
detail: `plugin ${pluginKey} already installed`
|
|
628
|
+
});
|
|
629
|
+
return { changes, installed: true };
|
|
630
|
+
}
|
|
631
|
+
const install = await runHostCommand(bin, ["plugin", "install", pluginKey]);
|
|
632
|
+
if (!install.ok || !claudePluginInstalled(connectorId)) {
|
|
633
|
+
changes.push(
|
|
634
|
+
warn(
|
|
635
|
+
`plugin install did not complete \u2014 ` + failDetail(`claude plugin install ${pluginKey}`, install)
|
|
636
|
+
)
|
|
637
|
+
);
|
|
638
|
+
return { changes, installed: false };
|
|
639
|
+
}
|
|
640
|
+
changes.push({
|
|
641
|
+
platform: PLATFORM,
|
|
642
|
+
action: "create",
|
|
643
|
+
detail: `installed plugin ${pluginKey} (scope user)`
|
|
644
|
+
});
|
|
645
|
+
return { changes, installed: true };
|
|
646
|
+
}
|
|
647
|
+
async function claudeDriveUninstall(connectorId) {
|
|
648
|
+
const changes = [];
|
|
649
|
+
const pluginKey = claudePluginKey(connectorId);
|
|
650
|
+
if (!claudePluginInstalled(connectorId)) {
|
|
651
|
+
changes.push({
|
|
652
|
+
platform: PLATFORM,
|
|
653
|
+
action: "skip",
|
|
654
|
+
detail: `plugin ${pluginKey} not installed on claude-code`
|
|
655
|
+
});
|
|
656
|
+
return { changes, removed: true };
|
|
657
|
+
}
|
|
658
|
+
const bin = claudeBinary();
|
|
659
|
+
if (!bin) {
|
|
660
|
+
changes.push(
|
|
661
|
+
warn(
|
|
662
|
+
`claude CLI not found on PATH \u2014 cannot drive the uninstall. Run manually: claude plugin uninstall ${pluginKey}`
|
|
663
|
+
)
|
|
664
|
+
);
|
|
665
|
+
return { changes, removed: false };
|
|
666
|
+
}
|
|
667
|
+
const uninstall = await runHostCommand(bin, ["plugin", "uninstall", pluginKey]);
|
|
668
|
+
if (!uninstall.ok || claudePluginInstalled(connectorId)) {
|
|
669
|
+
changes.push(
|
|
670
|
+
warn(`plugin uninstall did not complete \u2014 ` + failDetail(`claude plugin uninstall ${pluginKey}`, uninstall))
|
|
671
|
+
);
|
|
672
|
+
return { changes, removed: false };
|
|
673
|
+
}
|
|
674
|
+
changes.push({
|
|
675
|
+
platform: PLATFORM,
|
|
676
|
+
action: "remove",
|
|
677
|
+
detail: `uninstalled plugin ${pluginKey} (claude plugin uninstall)`
|
|
678
|
+
});
|
|
679
|
+
return { changes, removed: true };
|
|
680
|
+
}
|
|
681
|
+
async function claudeDriveMarketplaceRemove(stagingRoot) {
|
|
682
|
+
const bin = claudeBinary();
|
|
683
|
+
if (!bin) {
|
|
684
|
+
return [
|
|
685
|
+
warn(
|
|
686
|
+
`claude CLI not found on PATH \u2014 marketplace registration "${MARKETPLACE_NAME}" left behind. Run manually: claude plugin marketplace remove ${MARKETPLACE_NAME}`,
|
|
687
|
+
stagingRoot
|
|
688
|
+
)
|
|
689
|
+
];
|
|
690
|
+
}
|
|
691
|
+
const remove = await runHostCommand(bin, [
|
|
692
|
+
"plugin",
|
|
693
|
+
"marketplace",
|
|
694
|
+
"remove",
|
|
695
|
+
MARKETPLACE_NAME
|
|
696
|
+
]);
|
|
697
|
+
if (!remove.ok) {
|
|
698
|
+
return [
|
|
699
|
+
warn(
|
|
700
|
+
`could not remove the marketplace registration \u2014 ` + failDetail(`claude plugin marketplace remove ${MARKETPLACE_NAME}`, remove),
|
|
701
|
+
stagingRoot
|
|
702
|
+
)
|
|
703
|
+
];
|
|
704
|
+
}
|
|
705
|
+
return [
|
|
706
|
+
{
|
|
707
|
+
platform: PLATFORM,
|
|
708
|
+
action: "remove",
|
|
709
|
+
path: stagingRoot,
|
|
710
|
+
detail: `removed marketplace registration "${MARKETPLACE_NAME}" (no plugins remain)`
|
|
711
|
+
}
|
|
712
|
+
];
|
|
713
|
+
}
|
|
714
|
+
async function claudeDriveUpdate(connectorId, stagingRoot, pluginDir) {
|
|
715
|
+
const pluginKey = claudePluginKey(connectorId);
|
|
716
|
+
const bin = claudeBinary();
|
|
717
|
+
if (!bin) {
|
|
718
|
+
return {
|
|
719
|
+
changes: [
|
|
720
|
+
warn(
|
|
721
|
+
`claude CLI not found on PATH \u2014 bundle re-staged but the installed copy was not updated. Run manually: claude plugin update ${pluginKey}`,
|
|
722
|
+
stagingRoot
|
|
723
|
+
)
|
|
724
|
+
],
|
|
725
|
+
installed: false
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
if (!claudePluginInstalled(connectorId)) {
|
|
729
|
+
return claudeDriveInstall(connectorId, stagingRoot, pluginDir);
|
|
730
|
+
}
|
|
731
|
+
const update = await runHostCommand(bin, ["plugin", "update", pluginKey]);
|
|
732
|
+
if (update.ok) {
|
|
733
|
+
return {
|
|
734
|
+
changes: [
|
|
735
|
+
{
|
|
736
|
+
platform: PLATFORM,
|
|
737
|
+
action: "update",
|
|
738
|
+
detail: `updated plugin ${pluginKey} (claude plugin update)`
|
|
739
|
+
}
|
|
740
|
+
],
|
|
741
|
+
installed: true
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
const changes = [
|
|
745
|
+
warn(
|
|
746
|
+
`\`claude plugin update\` did not complete (` + failDetail("update", update) + `) \u2014 falling back to uninstall + install`
|
|
747
|
+
)
|
|
748
|
+
];
|
|
749
|
+
const un = await claudeDriveUninstall(connectorId);
|
|
750
|
+
changes.push(...un.changes);
|
|
751
|
+
if (!un.removed) return { changes, installed: claudePluginInstalled(connectorId) };
|
|
752
|
+
const re = await claudeDriveInstall(connectorId, stagingRoot, pluginDir);
|
|
753
|
+
changes.push(...re.changes);
|
|
754
|
+
return { changes, installed: re.installed };
|
|
755
|
+
}
|
|
756
|
+
function errMessage2(err) {
|
|
757
|
+
return err instanceof Error ? err.message : String(err);
|
|
758
|
+
}
|
|
759
|
+
function stagedClaudePlugins(stagingRoot) {
|
|
760
|
+
if (!existsSync3(stagingRoot)) return [];
|
|
761
|
+
try {
|
|
762
|
+
return readdirSync2(stagingRoot).filter(
|
|
763
|
+
(name) => existsSync3(join3(stagingRoot, name, ".claude-plugin", "plugin.json"))
|
|
764
|
+
).sort();
|
|
765
|
+
} catch {
|
|
766
|
+
return [];
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
function regenerateClaudeCatalog(stagingRoot, changes) {
|
|
770
|
+
const catalogPath = join3(stagingRoot, ".claude-plugin", "marketplace.json");
|
|
771
|
+
const plugins = stagedClaudePlugins(stagingRoot).map((name) => {
|
|
772
|
+
let description = `${name} \u2014 connector emitted by agent-connector`;
|
|
773
|
+
try {
|
|
774
|
+
const manifest = JSON.parse(
|
|
775
|
+
readFileSync(join3(stagingRoot, name, ".claude-plugin", "plugin.json"), "utf8")
|
|
776
|
+
);
|
|
777
|
+
if (typeof manifest.description === "string") description = manifest.description;
|
|
778
|
+
} catch {
|
|
779
|
+
}
|
|
780
|
+
return { name, source: `./${name}`, description };
|
|
781
|
+
});
|
|
782
|
+
const catalog = {
|
|
783
|
+
name: MARKETPLACE_NAME,
|
|
784
|
+
owner: { name: MARKETPLACE_NAME },
|
|
785
|
+
plugins
|
|
786
|
+
};
|
|
787
|
+
const serialized = `${JSON.stringify(catalog, null, 2)}
|
|
788
|
+
`;
|
|
789
|
+
let existing = null;
|
|
790
|
+
try {
|
|
791
|
+
existing = readFileSync(catalogPath, "utf8");
|
|
792
|
+
} catch {
|
|
793
|
+
}
|
|
794
|
+
if (existing === serialized) return;
|
|
795
|
+
ensureDir(dirname2(catalogPath));
|
|
796
|
+
writeFileSync(catalogPath, serialized, "utf8");
|
|
797
|
+
changes.push({
|
|
798
|
+
platform: PLATFORM,
|
|
799
|
+
action: existing == null ? "create" : "update",
|
|
800
|
+
path: catalogPath,
|
|
801
|
+
detail: `regenerated shared marketplace catalog (${plugins.length} plugin(s))`
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
function stageClaudeBundle(connector, changes) {
|
|
805
|
+
const stagingRoot = claudeStagingRoot();
|
|
806
|
+
const pluginDir = join3(stagingRoot, connector.id);
|
|
807
|
+
const existed = existsSync3(pluginDir);
|
|
808
|
+
const result = packageConnector(connector, {
|
|
809
|
+
outDir: stagingRoot,
|
|
810
|
+
format: "claude-plugin"
|
|
811
|
+
});
|
|
812
|
+
changes.push({
|
|
813
|
+
platform: PLATFORM,
|
|
814
|
+
action: existed ? "update" : "create",
|
|
815
|
+
path: pluginDir,
|
|
816
|
+
detail: `staged marketplace bundle (${result.files.length} files, claude-plugin)`
|
|
817
|
+
});
|
|
818
|
+
regenerateClaudeCatalog(stagingRoot, changes);
|
|
819
|
+
return { pluginDir, contentHash: hashDirectory(pluginDir) };
|
|
820
|
+
}
|
|
821
|
+
var claudeDriver = {
|
|
822
|
+
platform: PLATFORM,
|
|
823
|
+
format: "claude-plugin",
|
|
824
|
+
binary: claudeBinary,
|
|
825
|
+
stagingRoot: claudeStagingRoot,
|
|
826
|
+
pluginDir(id) {
|
|
827
|
+
return join3(claudeStagingRoot(), id);
|
|
828
|
+
},
|
|
829
|
+
installed: claudePluginInstalled,
|
|
830
|
+
stage(connector, changes) {
|
|
831
|
+
return stageClaudeBundle(connector, changes).contentHash;
|
|
832
|
+
},
|
|
833
|
+
planInstall(connector, changes) {
|
|
834
|
+
const stagingRoot = claudeStagingRoot();
|
|
835
|
+
const registeredAt = claudeKnownMarketplacePath(MARKETPLACE_NAME);
|
|
836
|
+
changes.push({
|
|
837
|
+
platform: PLATFORM,
|
|
838
|
+
action: registeredAt === stagingRoot ? "skip" : "create",
|
|
839
|
+
path: stagingRoot,
|
|
840
|
+
detail: registeredAt === stagingRoot ? `marketplace "${MARKETPLACE_NAME}" already registered` : `run: claude plugin marketplace add ${stagingRoot}`
|
|
841
|
+
});
|
|
842
|
+
changes.push({
|
|
843
|
+
platform: PLATFORM,
|
|
844
|
+
action: claudePluginInstalled(connector.id) ? "skip" : "create",
|
|
845
|
+
detail: claudePluginInstalled(connector.id) ? `plugin ${claudePluginKey(connector.id)} already installed` : `run: claude plugin install ${claudePluginKey(connector.id)}`
|
|
846
|
+
});
|
|
847
|
+
},
|
|
848
|
+
planUninstall(id, changes) {
|
|
849
|
+
const stagingRoot = claudeStagingRoot();
|
|
850
|
+
const pluginDir = join3(stagingRoot, id);
|
|
851
|
+
const pluginKey = claudePluginKey(id);
|
|
852
|
+
changes.push({
|
|
853
|
+
platform: PLATFORM,
|
|
854
|
+
action: claudePluginInstalled(id) ? "remove" : "skip",
|
|
855
|
+
detail: claudePluginInstalled(id) ? `run: claude plugin uninstall ${pluginKey}` : `plugin ${pluginKey} not installed on claude-code`
|
|
856
|
+
});
|
|
857
|
+
if (existsSync3(pluginDir)) {
|
|
858
|
+
changes.push({
|
|
859
|
+
platform: PLATFORM,
|
|
860
|
+
action: "remove",
|
|
861
|
+
path: pluginDir,
|
|
862
|
+
detail: "remove staged marketplace bundle"
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
const othersStaged = stagedClaudePlugins(stagingRoot).some((n) => n !== id);
|
|
866
|
+
if (!othersStaged && claudeKnownMarketplacePath(MARKETPLACE_NAME) === stagingRoot) {
|
|
867
|
+
changes.push({
|
|
868
|
+
platform: PLATFORM,
|
|
869
|
+
action: "remove",
|
|
870
|
+
path: stagingRoot,
|
|
871
|
+
detail: `run: claude plugin marketplace remove ${MARKETPLACE_NAME} (when no plugins remain)`
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
},
|
|
875
|
+
async driveInstall(id) {
|
|
876
|
+
const stagingRoot = claudeStagingRoot();
|
|
877
|
+
const out = await claudeDriveInstall(id, stagingRoot, join3(stagingRoot, id));
|
|
878
|
+
return { changes: out.changes, ok: out.installed };
|
|
879
|
+
},
|
|
880
|
+
async driveUninstall(id) {
|
|
881
|
+
const out = await claudeDriveUninstall(id);
|
|
882
|
+
return { changes: out.changes, ok: out.removed };
|
|
883
|
+
},
|
|
884
|
+
async driveUpdate(id) {
|
|
885
|
+
const stagingRoot = claudeStagingRoot();
|
|
886
|
+
const out = await claudeDriveUpdate(id, stagingRoot, join3(stagingRoot, id));
|
|
887
|
+
return { changes: out.changes, ok: out.installed };
|
|
888
|
+
},
|
|
889
|
+
async finishUninstall(id, changes) {
|
|
890
|
+
const stagingRoot = claudeStagingRoot();
|
|
891
|
+
const pluginDir = join3(stagingRoot, id);
|
|
892
|
+
if (existsSync3(pluginDir)) {
|
|
893
|
+
try {
|
|
894
|
+
rmSync2(pluginDir, { recursive: true, force: true });
|
|
895
|
+
changes.push({
|
|
896
|
+
platform: PLATFORM,
|
|
897
|
+
action: "remove",
|
|
898
|
+
path: pluginDir,
|
|
899
|
+
detail: "removed staged marketplace bundle"
|
|
900
|
+
});
|
|
901
|
+
} catch (err) {
|
|
902
|
+
changes.push({
|
|
903
|
+
platform: PLATFORM,
|
|
904
|
+
action: "warn",
|
|
905
|
+
path: pluginDir,
|
|
906
|
+
detail: `could not remove staged bundle: ${errMessage2(err)}`
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
if (existsSync3(stagingRoot)) regenerateClaudeCatalog(stagingRoot, changes);
|
|
911
|
+
const nothingStaged = stagedClaudePlugins(stagingRoot).length === 0;
|
|
912
|
+
if (nothingStaged && !anyClaudeAgentConnectorPlugins() && claudeKnownMarketplacePath(MARKETPLACE_NAME) === stagingRoot) {
|
|
913
|
+
changes.push(...await claudeDriveMarketplaceRemove(stagingRoot));
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
|
|
918
|
+
// src/core/marketplace-drivers/codex.ts
|
|
919
|
+
import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync2, rmSync as rmSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
920
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
921
|
+
var PLATFORM2 = "codex";
|
|
922
|
+
function codexBinary() {
|
|
923
|
+
return findOnPath("codex");
|
|
924
|
+
}
|
|
925
|
+
function codexManualInstallCommands(connectorId, stagingRoot) {
|
|
926
|
+
return `codex plugin marketplace add ${stagingRoot} && codex plugin add ${codexPluginKey(connectorId)}`;
|
|
927
|
+
}
|
|
928
|
+
function errMessage3(err) {
|
|
929
|
+
return err instanceof Error ? err.message : String(err);
|
|
930
|
+
}
|
|
931
|
+
function warn2(detail, path) {
|
|
932
|
+
return { platform: PLATFORM2, action: "warn", detail, ...path ? { path } : {} };
|
|
933
|
+
}
|
|
934
|
+
function failDetail2(step, r) {
|
|
935
|
+
if (r.timedOut) return `${step} timed out`;
|
|
936
|
+
if (r.error) return `${step} failed to spawn: ${r.error}`;
|
|
937
|
+
const line = firstLine(r.stderr) || firstLine(r.stdout);
|
|
938
|
+
return `${step} exited ${r.code}${line ? `: ${line}` : ""}`;
|
|
939
|
+
}
|
|
940
|
+
function stagedCodexPlugins(stagingRoot) {
|
|
941
|
+
if (!existsSync4(stagingRoot)) return [];
|
|
942
|
+
try {
|
|
943
|
+
return readdirSync3(stagingRoot).filter(
|
|
944
|
+
(name) => existsSync4(join4(stagingRoot, name, ".codex-plugin", "plugin.json"))
|
|
945
|
+
).sort();
|
|
946
|
+
} catch {
|
|
947
|
+
return [];
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
function codexCatalogPath(stagingRoot) {
|
|
951
|
+
return join4(stagingRoot, ".agents", "plugins", "marketplace.json");
|
|
952
|
+
}
|
|
953
|
+
function regenerateCodexCatalog(stagingRoot, changes) {
|
|
954
|
+
const catalogPath = codexCatalogPath(stagingRoot);
|
|
955
|
+
const plugins = stagedCodexPlugins(stagingRoot).map((name) => {
|
|
956
|
+
let description = `${name} \u2014 connector emitted by agent-connector`;
|
|
957
|
+
try {
|
|
958
|
+
const manifest = JSON.parse(
|
|
959
|
+
readFileSync2(join4(stagingRoot, name, ".codex-plugin", "plugin.json"), "utf8")
|
|
960
|
+
);
|
|
961
|
+
if (typeof manifest.description === "string") description = manifest.description;
|
|
962
|
+
} catch {
|
|
963
|
+
}
|
|
964
|
+
return { name, source: `./${name}`, description };
|
|
965
|
+
});
|
|
966
|
+
const catalog = { name: MARKETPLACE_NAME, owner: { name: MARKETPLACE_NAME }, plugins };
|
|
967
|
+
const serialized = `${JSON.stringify(catalog, null, 2)}
|
|
968
|
+
`;
|
|
969
|
+
let existing = null;
|
|
970
|
+
try {
|
|
971
|
+
existing = readFileSync2(catalogPath, "utf8");
|
|
972
|
+
} catch {
|
|
973
|
+
}
|
|
974
|
+
if (existing === serialized) return;
|
|
975
|
+
ensureDir(dirname3(catalogPath));
|
|
976
|
+
writeFileSync2(catalogPath, serialized, "utf8");
|
|
977
|
+
changes.push({
|
|
978
|
+
platform: PLATFORM2,
|
|
979
|
+
action: existing == null ? "create" : "update",
|
|
980
|
+
path: catalogPath,
|
|
981
|
+
detail: `regenerated shared marketplace catalog (${plugins.length} plugin(s))`
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
function stageCodexBundle(connector, changes) {
|
|
985
|
+
const stagingRoot = codexStagingRoot();
|
|
986
|
+
const pluginDir = join4(stagingRoot, connector.id);
|
|
987
|
+
const existed = existsSync4(pluginDir);
|
|
988
|
+
const result = packageConnector(connector, { outDir: stagingRoot, format: "codex-plugin" });
|
|
989
|
+
changes.push({
|
|
990
|
+
platform: PLATFORM2,
|
|
991
|
+
action: existed ? "update" : "create",
|
|
992
|
+
path: pluginDir,
|
|
993
|
+
detail: `staged marketplace bundle (${result.files.length} files, codex-plugin)`
|
|
994
|
+
});
|
|
995
|
+
regenerateCodexCatalog(stagingRoot, changes);
|
|
996
|
+
return { pluginDir, contentHash: hashDirectory(pluginDir) };
|
|
997
|
+
}
|
|
998
|
+
async function driveInstall(connectorId) {
|
|
999
|
+
const stagingRoot = codexStagingRoot();
|
|
1000
|
+
const changes = [];
|
|
1001
|
+
const pluginKey = codexPluginKey(connectorId);
|
|
1002
|
+
const bin = codexBinary();
|
|
1003
|
+
if (!bin) {
|
|
1004
|
+
changes.push(
|
|
1005
|
+
warn2(
|
|
1006
|
+
`codex CLI not found on PATH \u2014 bundle staged but not installed. Install manually: ${codexManualInstallCommands(connectorId, stagingRoot)}`,
|
|
1007
|
+
stagingRoot
|
|
1008
|
+
)
|
|
1009
|
+
);
|
|
1010
|
+
return { changes, ok: false };
|
|
1011
|
+
}
|
|
1012
|
+
const registeredAt = codexMarketplaceSource(MARKETPLACE_NAME);
|
|
1013
|
+
if (samePath(registeredAt, stagingRoot)) {
|
|
1014
|
+
changes.push({
|
|
1015
|
+
platform: PLATFORM2,
|
|
1016
|
+
action: "skip",
|
|
1017
|
+
path: stagingRoot,
|
|
1018
|
+
detail: `marketplace "${MARKETPLACE_NAME}" already registered`
|
|
1019
|
+
});
|
|
1020
|
+
} else if (registeredAt != null) {
|
|
1021
|
+
changes.push(
|
|
1022
|
+
warn2(
|
|
1023
|
+
`a marketplace named "${MARKETPLACE_NAME}" is already registered at ${registeredAt} (not ours) \u2014 refusing to touch it. Remove it first (codex plugin marketplace remove ${MARKETPLACE_NAME}) or install manually: ` + codexManualInstallCommands(connectorId, stagingRoot)
|
|
1024
|
+
)
|
|
1025
|
+
);
|
|
1026
|
+
return { changes, ok: false };
|
|
1027
|
+
} else {
|
|
1028
|
+
const add2 = await runHostCommand(bin, ["plugin", "marketplace", "add", stagingRoot]);
|
|
1029
|
+
if (!samePath(codexMarketplaceSource(MARKETPLACE_NAME), stagingRoot)) {
|
|
1030
|
+
changes.push(
|
|
1031
|
+
warn2(
|
|
1032
|
+
`could not register the local marketplace \u2014 ` + failDetail2("codex plugin marketplace add", add2) + `. Install manually: ${codexManualInstallCommands(connectorId, stagingRoot)}`,
|
|
1033
|
+
stagingRoot
|
|
1034
|
+
)
|
|
1035
|
+
);
|
|
1036
|
+
return { changes, ok: false };
|
|
1037
|
+
}
|
|
1038
|
+
changes.push({
|
|
1039
|
+
platform: PLATFORM2,
|
|
1040
|
+
action: "create",
|
|
1041
|
+
path: stagingRoot,
|
|
1042
|
+
detail: `registered local marketplace "${MARKETPLACE_NAME}" (codex plugin marketplace add)`
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
if (codexPluginInstalled(connectorId)) {
|
|
1046
|
+
changes.push({
|
|
1047
|
+
platform: PLATFORM2,
|
|
1048
|
+
action: "skip",
|
|
1049
|
+
detail: `plugin ${pluginKey} already installed`
|
|
1050
|
+
});
|
|
1051
|
+
return { changes, ok: true };
|
|
1052
|
+
}
|
|
1053
|
+
const add = await runHostCommand(bin, ["plugin", "add", pluginKey]);
|
|
1054
|
+
if (!add.ok || !codexPluginInstalled(connectorId)) {
|
|
1055
|
+
changes.push(
|
|
1056
|
+
warn2(`plugin add did not complete \u2014 ` + failDetail2(`codex plugin add ${pluginKey}`, add))
|
|
1057
|
+
);
|
|
1058
|
+
return { changes, ok: false };
|
|
1059
|
+
}
|
|
1060
|
+
changes.push({
|
|
1061
|
+
platform: PLATFORM2,
|
|
1062
|
+
action: "create",
|
|
1063
|
+
detail: `installed plugin ${pluginKey} (scope user)`
|
|
1064
|
+
});
|
|
1065
|
+
return { changes, ok: true };
|
|
1066
|
+
}
|
|
1067
|
+
async function driveUninstall(connectorId) {
|
|
1068
|
+
const changes = [];
|
|
1069
|
+
const pluginKey = codexPluginKey(connectorId);
|
|
1070
|
+
if (!codexPluginInstalled(connectorId)) {
|
|
1071
|
+
changes.push({
|
|
1072
|
+
platform: PLATFORM2,
|
|
1073
|
+
action: "skip",
|
|
1074
|
+
detail: `plugin ${pluginKey} not installed on codex`
|
|
1075
|
+
});
|
|
1076
|
+
return { changes, ok: true };
|
|
1077
|
+
}
|
|
1078
|
+
const bin = codexBinary();
|
|
1079
|
+
if (!bin) {
|
|
1080
|
+
changes.push(
|
|
1081
|
+
warn2(
|
|
1082
|
+
`codex CLI not found on PATH \u2014 cannot drive the uninstall. Run manually: codex plugin remove ${pluginKey}`
|
|
1083
|
+
)
|
|
1084
|
+
);
|
|
1085
|
+
return { changes, ok: false };
|
|
1086
|
+
}
|
|
1087
|
+
const remove = await runHostCommand(bin, ["plugin", "remove", pluginKey]);
|
|
1088
|
+
if (!remove.ok || codexPluginInstalled(connectorId)) {
|
|
1089
|
+
changes.push(
|
|
1090
|
+
warn2(`plugin remove did not complete \u2014 ` + failDetail2(`codex plugin remove ${pluginKey}`, remove))
|
|
1091
|
+
);
|
|
1092
|
+
return { changes, ok: false };
|
|
1093
|
+
}
|
|
1094
|
+
changes.push({
|
|
1095
|
+
platform: PLATFORM2,
|
|
1096
|
+
action: "remove",
|
|
1097
|
+
detail: `uninstalled plugin ${pluginKey} (codex plugin remove)`
|
|
1098
|
+
});
|
|
1099
|
+
return { changes, ok: true };
|
|
1100
|
+
}
|
|
1101
|
+
async function driveMarketplaceRemove(stagingRoot) {
|
|
1102
|
+
const bin = codexBinary();
|
|
1103
|
+
if (!bin) {
|
|
1104
|
+
return [
|
|
1105
|
+
warn2(
|
|
1106
|
+
`codex CLI not found on PATH \u2014 marketplace registration "${MARKETPLACE_NAME}" left behind. Run manually: codex plugin marketplace remove ${MARKETPLACE_NAME}`,
|
|
1107
|
+
stagingRoot
|
|
1108
|
+
)
|
|
1109
|
+
];
|
|
1110
|
+
}
|
|
1111
|
+
const remove = await runHostCommand(bin, ["plugin", "marketplace", "remove", MARKETPLACE_NAME]);
|
|
1112
|
+
if (!remove.ok && codexMarketplaceSource(MARKETPLACE_NAME) != null) {
|
|
1113
|
+
return [
|
|
1114
|
+
warn2(
|
|
1115
|
+
`could not remove the marketplace registration \u2014 ` + failDetail2(`codex plugin marketplace remove ${MARKETPLACE_NAME}`, remove),
|
|
1116
|
+
stagingRoot
|
|
1117
|
+
)
|
|
1118
|
+
];
|
|
1119
|
+
}
|
|
1120
|
+
return [
|
|
1121
|
+
{
|
|
1122
|
+
platform: PLATFORM2,
|
|
1123
|
+
action: "remove",
|
|
1124
|
+
path: stagingRoot,
|
|
1125
|
+
detail: `removed marketplace registration "${MARKETPLACE_NAME}" (no plugins remain)`
|
|
1126
|
+
}
|
|
1127
|
+
];
|
|
1128
|
+
}
|
|
1129
|
+
var codexDriver = {
|
|
1130
|
+
platform: PLATFORM2,
|
|
1131
|
+
format: "codex-plugin",
|
|
1132
|
+
binary: codexBinary,
|
|
1133
|
+
stagingRoot: codexStagingRoot,
|
|
1134
|
+
pluginDir(id) {
|
|
1135
|
+
return join4(codexStagingRoot(), id);
|
|
1136
|
+
},
|
|
1137
|
+
installed: codexPluginInstalled,
|
|
1138
|
+
stage(connector, changes) {
|
|
1139
|
+
return stageCodexBundle(connector, changes).contentHash;
|
|
1140
|
+
},
|
|
1141
|
+
planInstall(connector, changes) {
|
|
1142
|
+
const stagingRoot = codexStagingRoot();
|
|
1143
|
+
const registered = samePath(codexMarketplaceSource(MARKETPLACE_NAME), stagingRoot);
|
|
1144
|
+
changes.push({
|
|
1145
|
+
platform: PLATFORM2,
|
|
1146
|
+
action: registered ? "skip" : "create",
|
|
1147
|
+
path: stagingRoot,
|
|
1148
|
+
detail: registered ? `marketplace "${MARKETPLACE_NAME}" already registered` : `run: codex plugin marketplace add ${stagingRoot}`
|
|
1149
|
+
});
|
|
1150
|
+
changes.push({
|
|
1151
|
+
platform: PLATFORM2,
|
|
1152
|
+
action: codexPluginInstalled(connector.id) ? "skip" : "create",
|
|
1153
|
+
detail: codexPluginInstalled(connector.id) ? `plugin ${codexPluginKey(connector.id)} already installed` : `run: codex plugin add ${codexPluginKey(connector.id)}`
|
|
1154
|
+
});
|
|
1155
|
+
},
|
|
1156
|
+
planUninstall(id, changes) {
|
|
1157
|
+
const stagingRoot = codexStagingRoot();
|
|
1158
|
+
const pluginDir = join4(stagingRoot, id);
|
|
1159
|
+
const pluginKey = codexPluginKey(id);
|
|
1160
|
+
changes.push({
|
|
1161
|
+
platform: PLATFORM2,
|
|
1162
|
+
action: codexPluginInstalled(id) ? "remove" : "skip",
|
|
1163
|
+
detail: codexPluginInstalled(id) ? `run: codex plugin remove ${pluginKey}` : `plugin ${pluginKey} not installed on codex`
|
|
1164
|
+
});
|
|
1165
|
+
if (existsSync4(pluginDir)) {
|
|
1166
|
+
changes.push({
|
|
1167
|
+
platform: PLATFORM2,
|
|
1168
|
+
action: "remove",
|
|
1169
|
+
path: pluginDir,
|
|
1170
|
+
detail: "remove staged marketplace bundle"
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
const othersStaged = stagedCodexPlugins(stagingRoot).some((n) => n !== id);
|
|
1174
|
+
if (!othersStaged && samePath(codexMarketplaceSource(MARKETPLACE_NAME), stagingRoot)) {
|
|
1175
|
+
changes.push({
|
|
1176
|
+
platform: PLATFORM2,
|
|
1177
|
+
action: "remove",
|
|
1178
|
+
path: stagingRoot,
|
|
1179
|
+
detail: `run: codex plugin marketplace remove ${MARKETPLACE_NAME} (when no plugins remain)`
|
|
1180
|
+
});
|
|
1181
|
+
}
|
|
1182
|
+
},
|
|
1183
|
+
driveInstall,
|
|
1184
|
+
driveUninstall,
|
|
1185
|
+
// Codex has no `plugin update` verb — update IS re-stage (done by the caller)
|
|
1186
|
+
// + `plugin add`, which is idempotent and version-cached.
|
|
1187
|
+
async driveUpdate(id) {
|
|
1188
|
+
return driveInstall(id);
|
|
1189
|
+
},
|
|
1190
|
+
async finishUninstall(id, changes) {
|
|
1191
|
+
const stagingRoot = codexStagingRoot();
|
|
1192
|
+
const pluginDir = join4(stagingRoot, id);
|
|
1193
|
+
if (existsSync4(pluginDir)) {
|
|
1194
|
+
try {
|
|
1195
|
+
rmSync3(pluginDir, { recursive: true, force: true });
|
|
1196
|
+
changes.push({
|
|
1197
|
+
platform: PLATFORM2,
|
|
1198
|
+
action: "remove",
|
|
1199
|
+
path: pluginDir,
|
|
1200
|
+
detail: "removed staged marketplace bundle"
|
|
1201
|
+
});
|
|
1202
|
+
} catch (err) {
|
|
1203
|
+
changes.push({
|
|
1204
|
+
platform: PLATFORM2,
|
|
1205
|
+
action: "warn",
|
|
1206
|
+
path: pluginDir,
|
|
1207
|
+
detail: `could not remove staged bundle: ${errMessage3(err)}`
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
if (existsSync4(stagingRoot)) regenerateCodexCatalog(stagingRoot, changes);
|
|
1212
|
+
const nothingStaged = stagedCodexPlugins(stagingRoot).length === 0;
|
|
1213
|
+
if (nothingStaged && !anyCodexAgentConnectorPlugins() && samePath(codexMarketplaceSource(MARKETPLACE_NAME), stagingRoot)) {
|
|
1214
|
+
changes.push(...await driveMarketplaceRemove(stagingRoot));
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
};
|
|
1218
|
+
|
|
1219
|
+
// src/core/marketplace-drivers/droid.ts
|
|
1220
|
+
import { existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync3, rmSync as rmSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
1221
|
+
import { join as join5 } from "path";
|
|
1222
|
+
var PLATFORM3 = "droid";
|
|
1223
|
+
function droidBinary() {
|
|
1224
|
+
return findOnPath("droid");
|
|
1225
|
+
}
|
|
1226
|
+
function droidManualInstallCommands(connectorId, stagingRoot) {
|
|
1227
|
+
return `droid plugin marketplace add ${stagingRoot} && droid plugin install ${droidPluginKey(connectorId)}`;
|
|
1228
|
+
}
|
|
1229
|
+
function errMessage4(err) {
|
|
1230
|
+
return err instanceof Error ? err.message : String(err);
|
|
1231
|
+
}
|
|
1232
|
+
function warn3(detail, path) {
|
|
1233
|
+
return { platform: PLATFORM3, action: "warn", detail, ...path ? { path } : {} };
|
|
1234
|
+
}
|
|
1235
|
+
function failDetail3(step, r) {
|
|
1236
|
+
if (r.timedOut) return `${step} timed out`;
|
|
1237
|
+
if (r.error) return `${step} failed to spawn: ${r.error}`;
|
|
1238
|
+
const line = firstLine(r.stderr) || firstLine(r.stdout);
|
|
1239
|
+
return `${step} exited ${r.code}${line ? `: ${line}` : ""}`;
|
|
1240
|
+
}
|
|
1241
|
+
function stagedDroidPlugins(stagingRoot) {
|
|
1242
|
+
if (!existsSync5(stagingRoot)) return [];
|
|
1243
|
+
try {
|
|
1244
|
+
return readdirSync4(stagingRoot).filter(
|
|
1245
|
+
(name) => existsSync5(join5(stagingRoot, name, ".factory-plugin", "plugin.json"))
|
|
1246
|
+
).sort();
|
|
1247
|
+
} catch {
|
|
1248
|
+
return [];
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
function droidCatalogPath(stagingRoot) {
|
|
1252
|
+
return join5(stagingRoot, "marketplace.json");
|
|
1253
|
+
}
|
|
1254
|
+
function regenerateDroidCatalog(stagingRoot, changes) {
|
|
1255
|
+
const catalogPath = droidCatalogPath(stagingRoot);
|
|
1256
|
+
const plugins = stagedDroidPlugins(stagingRoot).map((name) => {
|
|
1257
|
+
let description = `${name} \u2014 connector emitted by agent-connector`;
|
|
1258
|
+
try {
|
|
1259
|
+
const manifest = JSON.parse(
|
|
1260
|
+
readFileSync3(join5(stagingRoot, name, ".factory-plugin", "plugin.json"), "utf8")
|
|
1261
|
+
);
|
|
1262
|
+
if (typeof manifest.description === "string") description = manifest.description;
|
|
1263
|
+
} catch {
|
|
1264
|
+
}
|
|
1265
|
+
return { name, source: `./${name}`, description };
|
|
1266
|
+
});
|
|
1267
|
+
const catalog = { name: MARKETPLACE_NAME, owner: { name: MARKETPLACE_NAME }, plugins };
|
|
1268
|
+
const serialized = `${JSON.stringify(catalog, null, 2)}
|
|
1269
|
+
`;
|
|
1270
|
+
let existing = null;
|
|
1271
|
+
try {
|
|
1272
|
+
existing = readFileSync3(catalogPath, "utf8");
|
|
1273
|
+
} catch {
|
|
1274
|
+
}
|
|
1275
|
+
if (existing === serialized) return;
|
|
1276
|
+
ensureDir(stagingRoot);
|
|
1277
|
+
writeFileSync3(catalogPath, serialized, "utf8");
|
|
1278
|
+
changes.push({
|
|
1279
|
+
platform: PLATFORM3,
|
|
1280
|
+
action: existing == null ? "create" : "update",
|
|
1281
|
+
path: catalogPath,
|
|
1282
|
+
detail: `regenerated shared marketplace catalog (${plugins.length} plugin(s))`
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
function stageDroidBundle(connector, changes) {
|
|
1286
|
+
const stagingRoot = droidStagingRoot();
|
|
1287
|
+
const pluginDir = join5(stagingRoot, connector.id);
|
|
1288
|
+
const existed = existsSync5(pluginDir);
|
|
1289
|
+
const result = packageConnector(connector, { outDir: stagingRoot, format: "factory-plugin" });
|
|
1290
|
+
changes.push({
|
|
1291
|
+
platform: PLATFORM3,
|
|
1292
|
+
action: existed ? "update" : "create",
|
|
1293
|
+
path: pluginDir,
|
|
1294
|
+
detail: `staged marketplace bundle (${result.files.length} files, factory-plugin)`
|
|
1295
|
+
});
|
|
1296
|
+
regenerateDroidCatalog(stagingRoot, changes);
|
|
1297
|
+
return { pluginDir, contentHash: hashDirectory(pluginDir) };
|
|
1298
|
+
}
|
|
1299
|
+
async function driveInstall2(connectorId) {
|
|
1300
|
+
const stagingRoot = droidStagingRoot();
|
|
1301
|
+
const changes = [];
|
|
1302
|
+
const pluginKey = droidPluginKey(connectorId);
|
|
1303
|
+
const bin = droidBinary();
|
|
1304
|
+
if (!bin) {
|
|
1305
|
+
changes.push(
|
|
1306
|
+
warn3(
|
|
1307
|
+
`droid CLI not found on PATH \u2014 bundle staged but not installed. Install manually: ${droidManualInstallCommands(connectorId, stagingRoot)}`,
|
|
1308
|
+
stagingRoot
|
|
1309
|
+
)
|
|
1310
|
+
);
|
|
1311
|
+
return { changes, ok: false };
|
|
1312
|
+
}
|
|
1313
|
+
const registeredAt = droidMarketplaceSource(MARKETPLACE_NAME);
|
|
1314
|
+
if (samePath(registeredAt, stagingRoot)) {
|
|
1315
|
+
changes.push({
|
|
1316
|
+
platform: PLATFORM3,
|
|
1317
|
+
action: "skip",
|
|
1318
|
+
path: stagingRoot,
|
|
1319
|
+
detail: `marketplace "${MARKETPLACE_NAME}" already registered`
|
|
1320
|
+
});
|
|
1321
|
+
} else if (registeredAt != null) {
|
|
1322
|
+
changes.push(
|
|
1323
|
+
warn3(
|
|
1324
|
+
`a marketplace named "${MARKETPLACE_NAME}" is already registered at ${registeredAt} (not ours) \u2014 refusing to touch it. Remove it first (droid plugin marketplace remove ${MARKETPLACE_NAME}) or install manually: ` + droidManualInstallCommands(connectorId, stagingRoot)
|
|
1325
|
+
)
|
|
1326
|
+
);
|
|
1327
|
+
return { changes, ok: false };
|
|
1328
|
+
} else {
|
|
1329
|
+
const add = await runHostCommand(bin, ["plugin", "marketplace", "add", stagingRoot]);
|
|
1330
|
+
if (!samePath(droidMarketplaceSource(MARKETPLACE_NAME), stagingRoot)) {
|
|
1331
|
+
changes.push(
|
|
1332
|
+
warn3(
|
|
1333
|
+
`could not register the local marketplace \u2014 ` + failDetail3("droid plugin marketplace add", add) + `. Install manually: ${droidManualInstallCommands(connectorId, stagingRoot)}`,
|
|
1334
|
+
stagingRoot
|
|
1335
|
+
)
|
|
1336
|
+
);
|
|
1337
|
+
return { changes, ok: false };
|
|
1338
|
+
}
|
|
1339
|
+
changes.push({
|
|
1340
|
+
platform: PLATFORM3,
|
|
1341
|
+
action: "create",
|
|
1342
|
+
path: stagingRoot,
|
|
1343
|
+
detail: `registered local marketplace "${MARKETPLACE_NAME}" (droid plugin marketplace add)`
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
1346
|
+
if (droidPluginInstalled(connectorId)) {
|
|
1347
|
+
changes.push({
|
|
1348
|
+
platform: PLATFORM3,
|
|
1349
|
+
action: "skip",
|
|
1350
|
+
detail: `plugin ${pluginKey} already installed`
|
|
1351
|
+
});
|
|
1352
|
+
return { changes, ok: true };
|
|
1353
|
+
}
|
|
1354
|
+
const install = await runHostCommand(bin, ["plugin", "install", pluginKey]);
|
|
1355
|
+
if (!install.ok || !droidPluginInstalled(connectorId)) {
|
|
1356
|
+
changes.push(
|
|
1357
|
+
warn3(`plugin install did not complete \u2014 ` + failDetail3(`droid plugin install ${pluginKey}`, install))
|
|
1358
|
+
);
|
|
1359
|
+
return { changes, ok: false };
|
|
1360
|
+
}
|
|
1361
|
+
changes.push({
|
|
1362
|
+
platform: PLATFORM3,
|
|
1363
|
+
action: "create",
|
|
1364
|
+
detail: `installed plugin ${pluginKey} (scope user)`
|
|
1365
|
+
});
|
|
1366
|
+
return { changes, ok: true };
|
|
1367
|
+
}
|
|
1368
|
+
async function driveUninstall2(connectorId) {
|
|
1369
|
+
const changes = [];
|
|
1370
|
+
const pluginKey = droidPluginKey(connectorId);
|
|
1371
|
+
if (!droidPluginInstalled(connectorId)) {
|
|
1372
|
+
changes.push({
|
|
1373
|
+
platform: PLATFORM3,
|
|
1374
|
+
action: "skip",
|
|
1375
|
+
detail: `plugin ${pluginKey} not installed on droid`
|
|
1376
|
+
});
|
|
1377
|
+
return { changes, ok: true };
|
|
1378
|
+
}
|
|
1379
|
+
const bin = droidBinary();
|
|
1380
|
+
if (!bin) {
|
|
1381
|
+
changes.push(
|
|
1382
|
+
warn3(
|
|
1383
|
+
`droid CLI not found on PATH \u2014 cannot drive the uninstall. Run manually: droid plugin uninstall ${pluginKey}`
|
|
1384
|
+
)
|
|
1385
|
+
);
|
|
1386
|
+
return { changes, ok: false };
|
|
1387
|
+
}
|
|
1388
|
+
const remove = await runHostCommand(bin, ["plugin", "uninstall", pluginKey]);
|
|
1389
|
+
if (!remove.ok || droidPluginInstalled(connectorId)) {
|
|
1390
|
+
changes.push(
|
|
1391
|
+
warn3(`plugin uninstall did not complete \u2014 ` + failDetail3(`droid plugin uninstall ${pluginKey}`, remove))
|
|
1392
|
+
);
|
|
1393
|
+
return { changes, ok: false };
|
|
1394
|
+
}
|
|
1395
|
+
changes.push({
|
|
1396
|
+
platform: PLATFORM3,
|
|
1397
|
+
action: "remove",
|
|
1398
|
+
detail: `uninstalled plugin ${pluginKey} (droid plugin uninstall)`
|
|
1399
|
+
});
|
|
1400
|
+
return { changes, ok: true };
|
|
1401
|
+
}
|
|
1402
|
+
async function driveMarketplaceRemove2(stagingRoot) {
|
|
1403
|
+
const bin = droidBinary();
|
|
1404
|
+
if (!bin) {
|
|
1405
|
+
return [
|
|
1406
|
+
warn3(
|
|
1407
|
+
`droid CLI not found on PATH \u2014 marketplace registration "${MARKETPLACE_NAME}" left behind. Run manually: droid plugin marketplace remove ${MARKETPLACE_NAME}`,
|
|
1408
|
+
stagingRoot
|
|
1409
|
+
)
|
|
1410
|
+
];
|
|
1411
|
+
}
|
|
1412
|
+
const remove = await runHostCommand(bin, ["plugin", "marketplace", "remove", MARKETPLACE_NAME]);
|
|
1413
|
+
if (!remove.ok && droidMarketplaceSource(MARKETPLACE_NAME) != null) {
|
|
1414
|
+
return [
|
|
1415
|
+
warn3(
|
|
1416
|
+
`could not remove the marketplace registration \u2014 ` + failDetail3(`droid plugin marketplace remove ${MARKETPLACE_NAME}`, remove),
|
|
1417
|
+
stagingRoot
|
|
1418
|
+
)
|
|
1419
|
+
];
|
|
1420
|
+
}
|
|
1421
|
+
return [
|
|
1422
|
+
{
|
|
1423
|
+
platform: PLATFORM3,
|
|
1424
|
+
action: "remove",
|
|
1425
|
+
path: stagingRoot,
|
|
1426
|
+
detail: `removed marketplace registration "${MARKETPLACE_NAME}" (no plugins remain)`
|
|
1427
|
+
}
|
|
1428
|
+
];
|
|
1429
|
+
}
|
|
1430
|
+
var droidDriver = {
|
|
1431
|
+
platform: PLATFORM3,
|
|
1432
|
+
format: "factory-plugin",
|
|
1433
|
+
binary: droidBinary,
|
|
1434
|
+
stagingRoot: droidStagingRoot,
|
|
1435
|
+
pluginDir(id) {
|
|
1436
|
+
return join5(droidStagingRoot(), id);
|
|
1437
|
+
},
|
|
1438
|
+
installed: droidPluginInstalled,
|
|
1439
|
+
stage(connector, changes) {
|
|
1440
|
+
return stageDroidBundle(connector, changes).contentHash;
|
|
1441
|
+
},
|
|
1442
|
+
planInstall(connector, changes) {
|
|
1443
|
+
const stagingRoot = droidStagingRoot();
|
|
1444
|
+
const registered = samePath(droidMarketplaceSource(MARKETPLACE_NAME), stagingRoot);
|
|
1445
|
+
changes.push({
|
|
1446
|
+
platform: PLATFORM3,
|
|
1447
|
+
action: registered ? "skip" : "create",
|
|
1448
|
+
path: stagingRoot,
|
|
1449
|
+
detail: registered ? `marketplace "${MARKETPLACE_NAME}" already registered` : `run: droid plugin marketplace add ${stagingRoot}`
|
|
1450
|
+
});
|
|
1451
|
+
changes.push({
|
|
1452
|
+
platform: PLATFORM3,
|
|
1453
|
+
action: droidPluginInstalled(connector.id) ? "skip" : "create",
|
|
1454
|
+
detail: droidPluginInstalled(connector.id) ? `plugin ${droidPluginKey(connector.id)} already installed` : `run: droid plugin install ${droidPluginKey(connector.id)}`
|
|
1455
|
+
});
|
|
1456
|
+
},
|
|
1457
|
+
planUninstall(id, changes) {
|
|
1458
|
+
const stagingRoot = droidStagingRoot();
|
|
1459
|
+
const pluginDir = join5(stagingRoot, id);
|
|
1460
|
+
const pluginKey = droidPluginKey(id);
|
|
1461
|
+
changes.push({
|
|
1462
|
+
platform: PLATFORM3,
|
|
1463
|
+
action: droidPluginInstalled(id) ? "remove" : "skip",
|
|
1464
|
+
detail: droidPluginInstalled(id) ? `run: droid plugin uninstall ${pluginKey}` : `plugin ${pluginKey} not installed on droid`
|
|
1465
|
+
});
|
|
1466
|
+
if (existsSync5(pluginDir)) {
|
|
1467
|
+
changes.push({
|
|
1468
|
+
platform: PLATFORM3,
|
|
1469
|
+
action: "remove",
|
|
1470
|
+
path: pluginDir,
|
|
1471
|
+
detail: "remove staged marketplace bundle"
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
const othersStaged = stagedDroidPlugins(stagingRoot).some((n) => n !== id);
|
|
1475
|
+
if (!othersStaged && samePath(droidMarketplaceSource(MARKETPLACE_NAME), stagingRoot)) {
|
|
1476
|
+
changes.push({
|
|
1477
|
+
platform: PLATFORM3,
|
|
1478
|
+
action: "remove",
|
|
1479
|
+
path: stagingRoot,
|
|
1480
|
+
detail: `run: droid plugin marketplace remove ${MARKETPLACE_NAME} (when no plugins remain)`
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
},
|
|
1484
|
+
driveInstall: driveInstall2,
|
|
1485
|
+
driveUninstall: driveUninstall2,
|
|
1486
|
+
// droid has no documented `plugin update` verb — update IS re-stage (done by
|
|
1487
|
+
// the caller) + `plugin install`, assumed idempotent + version-cached (DOCS-only).
|
|
1488
|
+
async driveUpdate(id) {
|
|
1489
|
+
return driveInstall2(id);
|
|
1490
|
+
},
|
|
1491
|
+
async finishUninstall(id, changes) {
|
|
1492
|
+
const stagingRoot = droidStagingRoot();
|
|
1493
|
+
const pluginDir = join5(stagingRoot, id);
|
|
1494
|
+
if (existsSync5(pluginDir)) {
|
|
1495
|
+
try {
|
|
1496
|
+
rmSync4(pluginDir, { recursive: true, force: true });
|
|
1497
|
+
changes.push({
|
|
1498
|
+
platform: PLATFORM3,
|
|
1499
|
+
action: "remove",
|
|
1500
|
+
path: pluginDir,
|
|
1501
|
+
detail: "removed staged marketplace bundle"
|
|
1502
|
+
});
|
|
1503
|
+
} catch (err) {
|
|
1504
|
+
changes.push({
|
|
1505
|
+
platform: PLATFORM3,
|
|
1506
|
+
action: "warn",
|
|
1507
|
+
path: pluginDir,
|
|
1508
|
+
detail: `could not remove staged bundle: ${errMessage4(err)}`
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
if (existsSync5(stagingRoot)) regenerateDroidCatalog(stagingRoot, changes);
|
|
1513
|
+
const nothingStaged = stagedDroidPlugins(stagingRoot).length === 0;
|
|
1514
|
+
if (nothingStaged && !anyDroidAgentConnectorPlugins() && samePath(droidMarketplaceSource(MARKETPLACE_NAME), stagingRoot)) {
|
|
1515
|
+
changes.push(...await driveMarketplaceRemove2(stagingRoot));
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
};
|
|
1519
|
+
|
|
1520
|
+
// src/core/marketplace-drivers/agy.ts
|
|
1521
|
+
import { existsSync as existsSync6, rmSync as rmSync5 } from "fs";
|
|
1522
|
+
import { join as join6 } from "path";
|
|
1523
|
+
function agyBinary() {
|
|
1524
|
+
return findOnPath("agy");
|
|
1525
|
+
}
|
|
1526
|
+
function errMessage5(err) {
|
|
1527
|
+
return err instanceof Error ? err.message : String(err);
|
|
1528
|
+
}
|
|
1529
|
+
function failDetail4(step, r) {
|
|
1530
|
+
if (r.timedOut) return `${step} timed out`;
|
|
1531
|
+
if (r.error) return `${step} failed to spawn: ${r.error}`;
|
|
1532
|
+
const line = firstLine(r.stderr) || firstLine(r.stdout);
|
|
1533
|
+
return `${step} exited ${r.code}${line ? `: ${line}` : ""}`;
|
|
1534
|
+
}
|
|
1535
|
+
function stageAgyBundle(connector, platform, changes) {
|
|
1536
|
+
const stagingRoot = agyStagingRoot();
|
|
1537
|
+
const pluginDir = join6(stagingRoot, connector.id);
|
|
1538
|
+
const existed = existsSync6(pluginDir);
|
|
1539
|
+
const result = packageConnector(connector, { outDir: stagingRoot, format: "agy-plugin" });
|
|
1540
|
+
changes.push({
|
|
1541
|
+
platform,
|
|
1542
|
+
action: existed ? "update" : "create",
|
|
1543
|
+
path: pluginDir,
|
|
1544
|
+
detail: `staged marketplace bundle (${result.files.length} files, agy-plugin)`
|
|
1545
|
+
});
|
|
1546
|
+
return { pluginDir, contentHash: hashDirectory(pluginDir) };
|
|
1547
|
+
}
|
|
1548
|
+
function makeAgyDriver(platform) {
|
|
1549
|
+
const warn6 = (detail, path) => ({
|
|
1550
|
+
platform,
|
|
1551
|
+
action: "warn",
|
|
1552
|
+
detail,
|
|
1553
|
+
...path ? { path } : {}
|
|
1554
|
+
});
|
|
1555
|
+
const manualInstall3 = (pluginDir) => `agy plugin install ${pluginDir}`;
|
|
1556
|
+
return {
|
|
1557
|
+
platform,
|
|
1558
|
+
format: "agy-plugin",
|
|
1559
|
+
binary: agyBinary,
|
|
1560
|
+
stagingRoot: agyStagingRoot,
|
|
1561
|
+
pluginDir(id) {
|
|
1562
|
+
return join6(agyStagingRoot(), id);
|
|
1563
|
+
},
|
|
1564
|
+
installed: agyPluginInstalled,
|
|
1565
|
+
stage(connector, changes) {
|
|
1566
|
+
return stageAgyBundle(connector, platform, changes).contentHash;
|
|
1567
|
+
},
|
|
1568
|
+
planInstall(connector, changes) {
|
|
1569
|
+
const pluginDir = join6(agyStagingRoot(), connector.id);
|
|
1570
|
+
changes.push({
|
|
1571
|
+
platform,
|
|
1572
|
+
action: agyPluginInstalled(connector.id) ? "skip" : "create",
|
|
1573
|
+
detail: agyPluginInstalled(connector.id) ? `plugin ${connector.id} already installed` : `run: agy plugin install ${pluginDir}`
|
|
1574
|
+
});
|
|
1575
|
+
},
|
|
1576
|
+
planUninstall(id, changes) {
|
|
1577
|
+
const pluginDir = join6(agyStagingRoot(), id);
|
|
1578
|
+
changes.push({
|
|
1579
|
+
platform,
|
|
1580
|
+
action: agyPluginInstalled(id) ? "remove" : "skip",
|
|
1581
|
+
detail: agyPluginInstalled(id) ? `run: agy plugin uninstall ${id}` : `plugin ${id} not installed on ${platform}`
|
|
1582
|
+
});
|
|
1583
|
+
if (existsSync6(pluginDir)) {
|
|
1584
|
+
changes.push({
|
|
1585
|
+
platform,
|
|
1586
|
+
action: "remove",
|
|
1587
|
+
path: pluginDir,
|
|
1588
|
+
detail: "remove staged marketplace bundle"
|
|
1589
|
+
});
|
|
1590
|
+
}
|
|
1591
|
+
},
|
|
1592
|
+
async driveInstall(id) {
|
|
1593
|
+
const changes = [];
|
|
1594
|
+
const pluginDir = join6(agyStagingRoot(), id);
|
|
1595
|
+
const bin = agyBinary();
|
|
1596
|
+
if (!bin) {
|
|
1597
|
+
changes.push(
|
|
1598
|
+
warn6(
|
|
1599
|
+
`agy CLI not found on PATH \u2014 bundle staged but not installed. Install manually: ${manualInstall3(pluginDir)}`,
|
|
1600
|
+
pluginDir
|
|
1601
|
+
)
|
|
1602
|
+
);
|
|
1603
|
+
return { changes, ok: false };
|
|
1604
|
+
}
|
|
1605
|
+
if (agyPluginInstalled(id)) {
|
|
1606
|
+
changes.push({
|
|
1607
|
+
platform,
|
|
1608
|
+
action: "skip",
|
|
1609
|
+
detail: `plugin ${id} already installed`
|
|
1610
|
+
});
|
|
1611
|
+
return { changes, ok: true };
|
|
1612
|
+
}
|
|
1613
|
+
await runHostCommand(bin, ["plugin", "validate", pluginDir]);
|
|
1614
|
+
const install = await runHostCommand(bin, ["plugin", "install", pluginDir]);
|
|
1615
|
+
if (!install.ok || !agyPluginInstalled(id)) {
|
|
1616
|
+
changes.push(
|
|
1617
|
+
warn6(`plugin install did not complete \u2014 ` + failDetail4(`agy plugin install ${pluginDir}`, install))
|
|
1618
|
+
);
|
|
1619
|
+
return { changes, ok: false };
|
|
1620
|
+
}
|
|
1621
|
+
changes.push({
|
|
1622
|
+
platform,
|
|
1623
|
+
action: "create",
|
|
1624
|
+
detail: `installed plugin ${id} (scope user)`
|
|
1625
|
+
});
|
|
1626
|
+
return { changes, ok: true };
|
|
1627
|
+
},
|
|
1628
|
+
async driveUninstall(id) {
|
|
1629
|
+
const changes = [];
|
|
1630
|
+
if (!agyPluginInstalled(id)) {
|
|
1631
|
+
changes.push({
|
|
1632
|
+
platform,
|
|
1633
|
+
action: "skip",
|
|
1634
|
+
detail: `plugin ${id} not installed on ${platform}`
|
|
1635
|
+
});
|
|
1636
|
+
return { changes, ok: true };
|
|
1637
|
+
}
|
|
1638
|
+
const bin = agyBinary();
|
|
1639
|
+
if (!bin) {
|
|
1640
|
+
changes.push(
|
|
1641
|
+
warn6(
|
|
1642
|
+
`agy CLI not found on PATH \u2014 cannot drive the uninstall. Run manually: agy plugin uninstall ${id}`
|
|
1643
|
+
)
|
|
1644
|
+
);
|
|
1645
|
+
return { changes, ok: false };
|
|
1646
|
+
}
|
|
1647
|
+
const uninstall = await runHostCommand(bin, ["plugin", "uninstall", id]);
|
|
1648
|
+
if (!uninstall.ok || agyPluginInstalled(id)) {
|
|
1649
|
+
changes.push(
|
|
1650
|
+
warn6(`plugin uninstall did not complete \u2014 ` + failDetail4(`agy plugin uninstall ${id}`, uninstall))
|
|
1651
|
+
);
|
|
1652
|
+
return { changes, ok: false };
|
|
1653
|
+
}
|
|
1654
|
+
changes.push({
|
|
1655
|
+
platform,
|
|
1656
|
+
action: "remove",
|
|
1657
|
+
detail: `uninstalled plugin ${id} (agy plugin uninstall)`
|
|
1658
|
+
});
|
|
1659
|
+
return { changes, ok: true };
|
|
1660
|
+
},
|
|
1661
|
+
// No `plugin update` verb — update = re-stage (done by the caller) +
|
|
1662
|
+
// `plugin install` (idempotent overwrite). Force a fresh install even when
|
|
1663
|
+
// the probe already shows it present (the bundle on disk changed).
|
|
1664
|
+
async driveUpdate(id) {
|
|
1665
|
+
const changes = [];
|
|
1666
|
+
const pluginDir = join6(agyStagingRoot(), id);
|
|
1667
|
+
const bin = agyBinary();
|
|
1668
|
+
if (!bin) {
|
|
1669
|
+
changes.push(
|
|
1670
|
+
warn6(
|
|
1671
|
+
`agy CLI not found on PATH \u2014 bundle re-staged but the installed copy was not updated. Run manually: ${manualInstall3(pluginDir)}`,
|
|
1672
|
+
pluginDir
|
|
1673
|
+
)
|
|
1674
|
+
);
|
|
1675
|
+
return { changes, ok: false };
|
|
1676
|
+
}
|
|
1677
|
+
const install = await runHostCommand(bin, ["plugin", "install", pluginDir]);
|
|
1678
|
+
if (!install.ok || !agyPluginInstalled(id)) {
|
|
1679
|
+
changes.push(
|
|
1680
|
+
warn6(`plugin install did not complete \u2014 ` + failDetail4(`agy plugin install ${pluginDir}`, install))
|
|
1681
|
+
);
|
|
1682
|
+
return { changes, ok: false };
|
|
1683
|
+
}
|
|
1684
|
+
changes.push({
|
|
1685
|
+
platform,
|
|
1686
|
+
action: "update",
|
|
1687
|
+
detail: `updated plugin ${id} (agy plugin install \u2014 idempotent overwrite)`
|
|
1688
|
+
});
|
|
1689
|
+
return { changes, ok: true };
|
|
1690
|
+
},
|
|
1691
|
+
// Direct driver: no catalog, no marketplace de-registration. Cleanup is just
|
|
1692
|
+
// removing the staged bundle dir (the host copied it into its own store).
|
|
1693
|
+
async finishUninstall(id, changes) {
|
|
1694
|
+
const pluginDir = join6(agyStagingRoot(), id);
|
|
1695
|
+
if (existsSync6(pluginDir)) {
|
|
1696
|
+
try {
|
|
1697
|
+
rmSync5(pluginDir, { recursive: true, force: true });
|
|
1698
|
+
changes.push({
|
|
1699
|
+
platform,
|
|
1700
|
+
action: "remove",
|
|
1701
|
+
path: pluginDir,
|
|
1702
|
+
detail: "removed staged marketplace bundle"
|
|
1703
|
+
});
|
|
1704
|
+
} catch (err) {
|
|
1705
|
+
changes.push({
|
|
1706
|
+
platform,
|
|
1707
|
+
action: "warn",
|
|
1708
|
+
path: pluginDir,
|
|
1709
|
+
detail: `could not remove staged bundle: ${errMessage5(err)}`
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
// src/core/marketplace-drivers/gemini.ts
|
|
1718
|
+
import { existsSync as existsSync7, rmSync as rmSync6 } from "fs";
|
|
1719
|
+
import { join as join7 } from "path";
|
|
1720
|
+
var PLATFORM4 = "gemini-cli";
|
|
1721
|
+
function geminiBinary() {
|
|
1722
|
+
return findOnPath("gemini");
|
|
1723
|
+
}
|
|
1724
|
+
function errMessage6(err) {
|
|
1725
|
+
return err instanceof Error ? err.message : String(err);
|
|
1726
|
+
}
|
|
1727
|
+
function warn4(detail, path) {
|
|
1728
|
+
return { platform: PLATFORM4, action: "warn", detail, ...path ? { path } : {} };
|
|
1729
|
+
}
|
|
1730
|
+
function failDetail5(step, r) {
|
|
1731
|
+
if (r.timedOut) return `${step} timed out`;
|
|
1732
|
+
if (r.error) return `${step} failed to spawn: ${r.error}`;
|
|
1733
|
+
const line = firstLine(r.stderr) || firstLine(r.stdout);
|
|
1734
|
+
return `${step} exited ${r.code}${line ? `: ${line}` : ""}`;
|
|
1735
|
+
}
|
|
1736
|
+
function manualInstall(pluginDir) {
|
|
1737
|
+
return `gemini extensions install ${pluginDir} --consent`;
|
|
1738
|
+
}
|
|
1739
|
+
function stageGeminiBundle(connector, changes) {
|
|
1740
|
+
const stagingRoot = geminiStagingRoot();
|
|
1741
|
+
const pluginDir = join7(stagingRoot, connector.id);
|
|
1742
|
+
const existed = existsSync7(pluginDir);
|
|
1743
|
+
const result = packageConnector(connector, { outDir: stagingRoot, format: "gemini-extension" });
|
|
1744
|
+
changes.push({
|
|
1745
|
+
platform: PLATFORM4,
|
|
1746
|
+
action: existed ? "update" : "create",
|
|
1747
|
+
path: pluginDir,
|
|
1748
|
+
detail: `staged marketplace bundle (${result.files.length} files, gemini-extension)`
|
|
1749
|
+
});
|
|
1750
|
+
return { pluginDir, contentHash: hashDirectory(pluginDir) };
|
|
1751
|
+
}
|
|
1752
|
+
var geminiDriver = {
|
|
1753
|
+
platform: PLATFORM4,
|
|
1754
|
+
format: "gemini-extension",
|
|
1755
|
+
binary: geminiBinary,
|
|
1756
|
+
stagingRoot: geminiStagingRoot,
|
|
1757
|
+
pluginDir(id) {
|
|
1758
|
+
return join7(geminiStagingRoot(), id);
|
|
1759
|
+
},
|
|
1760
|
+
installed: geminiExtensionInstalled,
|
|
1761
|
+
stage(connector, changes) {
|
|
1762
|
+
return stageGeminiBundle(connector, changes).contentHash;
|
|
1763
|
+
},
|
|
1764
|
+
planInstall(connector, changes) {
|
|
1765
|
+
const pluginDir = join7(geminiStagingRoot(), connector.id);
|
|
1766
|
+
changes.push({
|
|
1767
|
+
platform: PLATFORM4,
|
|
1768
|
+
action: geminiExtensionInstalled(connector.id) ? "skip" : "create",
|
|
1769
|
+
detail: geminiExtensionInstalled(connector.id) ? `extension ${connector.id} already installed` : `run: ${manualInstall(pluginDir)}`
|
|
1770
|
+
});
|
|
1771
|
+
},
|
|
1772
|
+
planUninstall(id, changes) {
|
|
1773
|
+
const pluginDir = join7(geminiStagingRoot(), id);
|
|
1774
|
+
changes.push({
|
|
1775
|
+
platform: PLATFORM4,
|
|
1776
|
+
action: geminiExtensionInstalled(id) ? "remove" : "skip",
|
|
1777
|
+
detail: geminiExtensionInstalled(id) ? `run: gemini extensions uninstall ${id}` : `extension ${id} not installed on ${PLATFORM4}`
|
|
1778
|
+
});
|
|
1779
|
+
if (existsSync7(pluginDir)) {
|
|
1780
|
+
changes.push({
|
|
1781
|
+
platform: PLATFORM4,
|
|
1782
|
+
action: "remove",
|
|
1783
|
+
path: pluginDir,
|
|
1784
|
+
detail: "remove staged marketplace bundle"
|
|
1785
|
+
});
|
|
1786
|
+
}
|
|
1787
|
+
},
|
|
1788
|
+
async driveInstall(id) {
|
|
1789
|
+
const changes = [];
|
|
1790
|
+
const pluginDir = join7(geminiStagingRoot(), id);
|
|
1791
|
+
const bin = geminiBinary();
|
|
1792
|
+
if (!bin) {
|
|
1793
|
+
changes.push(
|
|
1794
|
+
warn4(
|
|
1795
|
+
`gemini CLI not found on PATH \u2014 bundle staged but not installed. Install manually: ${manualInstall(pluginDir)}`,
|
|
1796
|
+
pluginDir
|
|
1797
|
+
)
|
|
1798
|
+
);
|
|
1799
|
+
return { changes, ok: false };
|
|
1800
|
+
}
|
|
1801
|
+
if (geminiExtensionInstalled(id)) {
|
|
1802
|
+
changes.push({
|
|
1803
|
+
platform: PLATFORM4,
|
|
1804
|
+
action: "skip",
|
|
1805
|
+
detail: `extension ${id} already installed`
|
|
1806
|
+
});
|
|
1807
|
+
return { changes, ok: true };
|
|
1808
|
+
}
|
|
1809
|
+
await runHostCommand(bin, ["extensions", "validate", pluginDir]);
|
|
1810
|
+
const install = await runHostCommand(bin, ["extensions", "install", pluginDir, "--consent"]);
|
|
1811
|
+
if (!geminiExtensionInstalled(id)) {
|
|
1812
|
+
const trustGated = /trust (the |this )?(files|folder)/i.test(
|
|
1813
|
+
`${install.stdout}
|
|
1814
|
+
${install.stderr}`
|
|
1815
|
+
);
|
|
1816
|
+
changes.push(
|
|
1817
|
+
warn4(
|
|
1818
|
+
trustGated ? `extension install blocked by gemini's folder-trust prompt (newer gemini gates local-path installs and --consent does not bypass it). Trust the folder once, then re-run: run \`gemini extensions install ${pluginDir} --consent\` interactively (answer "y" to trust), or set \`security.folderTrust.enabled: false\` in ~/.gemini/settings.json. The bundle is staged and ready.` : `extension install did not complete \u2014 ` + failDetail5(`gemini extensions install ${pluginDir} --consent`, install),
|
|
1819
|
+
pluginDir
|
|
1820
|
+
)
|
|
1821
|
+
);
|
|
1822
|
+
return { changes, ok: false };
|
|
1823
|
+
}
|
|
1824
|
+
changes.push({
|
|
1825
|
+
platform: PLATFORM4,
|
|
1826
|
+
action: "create",
|
|
1827
|
+
detail: `installed extension ${id} (scope user)`
|
|
1828
|
+
});
|
|
1829
|
+
return { changes, ok: true };
|
|
1830
|
+
},
|
|
1831
|
+
async driveUninstall(id) {
|
|
1832
|
+
const changes = [];
|
|
1833
|
+
if (!geminiExtensionInstalled(id)) {
|
|
1834
|
+
changes.push({
|
|
1835
|
+
platform: PLATFORM4,
|
|
1836
|
+
action: "skip",
|
|
1837
|
+
detail: `extension ${id} not installed on ${PLATFORM4}`
|
|
1838
|
+
});
|
|
1839
|
+
return { changes, ok: true };
|
|
1840
|
+
}
|
|
1841
|
+
const bin = geminiBinary();
|
|
1842
|
+
if (!bin) {
|
|
1843
|
+
changes.push(
|
|
1844
|
+
warn4(
|
|
1845
|
+
`gemini CLI not found on PATH \u2014 cannot drive the uninstall. Run manually: gemini extensions uninstall ${id}`
|
|
1846
|
+
)
|
|
1847
|
+
);
|
|
1848
|
+
return { changes, ok: false };
|
|
1849
|
+
}
|
|
1850
|
+
const uninstall = await runHostCommand(bin, ["extensions", "uninstall", id]);
|
|
1851
|
+
if (geminiExtensionInstalled(id)) {
|
|
1852
|
+
changes.push(
|
|
1853
|
+
warn4(
|
|
1854
|
+
`extension uninstall did not complete \u2014 ` + failDetail5(`gemini extensions uninstall ${id}`, uninstall)
|
|
1855
|
+
)
|
|
1856
|
+
);
|
|
1857
|
+
return { changes, ok: false };
|
|
1858
|
+
}
|
|
1859
|
+
changes.push({
|
|
1860
|
+
platform: PLATFORM4,
|
|
1861
|
+
action: "remove",
|
|
1862
|
+
detail: `uninstalled extension ${id} (gemini extensions uninstall)`
|
|
1863
|
+
});
|
|
1864
|
+
return { changes, ok: true };
|
|
1865
|
+
},
|
|
1866
|
+
// gemini has NO overwrite-install (re-install REFUSES), so update is
|
|
1867
|
+
// uninstall-THEN-install. The bundle was already re-staged by the caller.
|
|
1868
|
+
async driveUpdate(id) {
|
|
1869
|
+
const changes = [];
|
|
1870
|
+
const pluginDir = join7(geminiStagingRoot(), id);
|
|
1871
|
+
const bin = geminiBinary();
|
|
1872
|
+
if (!bin) {
|
|
1873
|
+
changes.push(
|
|
1874
|
+
warn4(
|
|
1875
|
+
`gemini CLI not found on PATH \u2014 bundle re-staged but the installed copy was not updated. Run manually: gemini extensions uninstall ${id} && ${manualInstall(pluginDir)}`,
|
|
1876
|
+
pluginDir
|
|
1877
|
+
)
|
|
1878
|
+
);
|
|
1879
|
+
return { changes, ok: false };
|
|
1880
|
+
}
|
|
1881
|
+
if (geminiExtensionInstalled(id)) {
|
|
1882
|
+
const uninstall = await runHostCommand(bin, ["extensions", "uninstall", id]);
|
|
1883
|
+
if (geminiExtensionInstalled(id)) {
|
|
1884
|
+
changes.push(
|
|
1885
|
+
warn4(
|
|
1886
|
+
`extension update could not remove the old copy \u2014 ` + failDetail5(`gemini extensions uninstall ${id}`, uninstall)
|
|
1887
|
+
)
|
|
1888
|
+
);
|
|
1889
|
+
return { changes, ok: false };
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
await runHostCommand(bin, ["extensions", "validate", pluginDir]);
|
|
1893
|
+
const install = await runHostCommand(bin, ["extensions", "install", pluginDir, "--consent"]);
|
|
1894
|
+
if (!geminiExtensionInstalled(id)) {
|
|
1895
|
+
changes.push(
|
|
1896
|
+
warn4(
|
|
1897
|
+
`extension install did not complete \u2014 ` + failDetail5(`gemini extensions install ${pluginDir} --consent`, install)
|
|
1898
|
+
)
|
|
1899
|
+
);
|
|
1900
|
+
return { changes, ok: false };
|
|
1901
|
+
}
|
|
1902
|
+
changes.push({
|
|
1903
|
+
platform: PLATFORM4,
|
|
1904
|
+
action: "update",
|
|
1905
|
+
detail: `updated extension ${id} (gemini extensions uninstall + install)`
|
|
1906
|
+
});
|
|
1907
|
+
return { changes, ok: true };
|
|
1908
|
+
},
|
|
1909
|
+
// Direct driver: no catalog, no marketplace de-registration. Cleanup is just
|
|
1910
|
+
// removing the staged bundle dir (the host copied it into its own store).
|
|
1911
|
+
async finishUninstall(id, changes) {
|
|
1912
|
+
const pluginDir = join7(geminiStagingRoot(), id);
|
|
1913
|
+
if (existsSync7(pluginDir)) {
|
|
1914
|
+
try {
|
|
1915
|
+
rmSync6(pluginDir, { recursive: true, force: true });
|
|
1916
|
+
changes.push({
|
|
1917
|
+
platform: PLATFORM4,
|
|
1918
|
+
action: "remove",
|
|
1919
|
+
path: pluginDir,
|
|
1920
|
+
detail: "removed staged marketplace bundle"
|
|
1921
|
+
});
|
|
1922
|
+
} catch (err) {
|
|
1923
|
+
changes.push({
|
|
1924
|
+
platform: PLATFORM4,
|
|
1925
|
+
action: "warn",
|
|
1926
|
+
path: pluginDir,
|
|
1927
|
+
detail: `could not remove staged bundle: ${errMessage6(err)}`
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
};
|
|
1933
|
+
|
|
1934
|
+
// src/core/marketplace-drivers/npm-local.ts
|
|
1935
|
+
import { existsSync as existsSync8, mkdirSync, rmSync as rmSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
1936
|
+
import { dirname as dirname4, join as join8 } from "path";
|
|
1937
|
+
import { pathToFileURL } from "url";
|
|
1938
|
+
function errMessage7(err) {
|
|
1939
|
+
return err instanceof Error ? err.message : String(err);
|
|
1940
|
+
}
|
|
1941
|
+
function failDetail6(step, r) {
|
|
1942
|
+
if (r.timedOut) return `${step} timed out`;
|
|
1943
|
+
if (r.error) return `${step} failed to spawn: ${r.error}`;
|
|
1944
|
+
const line = firstLine(r.stderr) || firstLine(r.stdout);
|
|
1945
|
+
return `${step} exited ${r.code}${line ? `: ${line}` : ""}`;
|
|
1946
|
+
}
|
|
1947
|
+
function fileUrlFor(connectorId) {
|
|
1948
|
+
return pathToFileURL(join8(npmStagingRoot(), connectorId)).href;
|
|
1949
|
+
}
|
|
1950
|
+
function makeNpmLocalDriver(platform, opts) {
|
|
1951
|
+
const { binaryName } = opts;
|
|
1952
|
+
const binary = () => findOnPath(binaryName);
|
|
1953
|
+
const warn6 = (detail, path) => ({
|
|
1954
|
+
platform,
|
|
1955
|
+
action: "warn",
|
|
1956
|
+
detail,
|
|
1957
|
+
...path ? { path } : {}
|
|
1958
|
+
});
|
|
1959
|
+
const manualInstall3 = (connectorId) => `${binaryName} plugin --global ${fileUrlFor(connectorId)}`;
|
|
1960
|
+
const stageBundle = (connector, changes) => {
|
|
1961
|
+
const stagingRoot = npmStagingRoot();
|
|
1962
|
+
const pluginDir = join8(stagingRoot, connector.id);
|
|
1963
|
+
const existed = existsSync8(pluginDir);
|
|
1964
|
+
const result = packageConnector(connector, { outDir: stagingRoot, format: "npm-plugin" });
|
|
1965
|
+
changes.push({
|
|
1966
|
+
platform,
|
|
1967
|
+
action: existed ? "update" : "create",
|
|
1968
|
+
path: pluginDir,
|
|
1969
|
+
detail: `staged marketplace bundle (${result.files.length} files, npm-plugin)`
|
|
1970
|
+
});
|
|
1971
|
+
return { pluginDir, contentHash: hashDirectory(pluginDir) };
|
|
1972
|
+
};
|
|
1973
|
+
const driveInstall3 = async (id) => {
|
|
1974
|
+
const changes = [];
|
|
1975
|
+
if (npmPluginInstalled(platform, id)) {
|
|
1976
|
+
changes.push({
|
|
1977
|
+
platform,
|
|
1978
|
+
action: "skip",
|
|
1979
|
+
detail: `plugin ${id} already installed`
|
|
1980
|
+
});
|
|
1981
|
+
return { changes, ok: true };
|
|
1982
|
+
}
|
|
1983
|
+
const bin = binary();
|
|
1984
|
+
if (!bin) {
|
|
1985
|
+
changes.push(
|
|
1986
|
+
warn6(
|
|
1987
|
+
`${binaryName} CLI not found on PATH \u2014 bundle staged but not installed. Install manually: ${manualInstall3(id)}`,
|
|
1988
|
+
join8(npmStagingRoot(), id)
|
|
1989
|
+
)
|
|
1990
|
+
);
|
|
1991
|
+
return { changes, ok: false };
|
|
1992
|
+
}
|
|
1993
|
+
const stagingRoot = npmStagingRoot();
|
|
1994
|
+
try {
|
|
1995
|
+
mkdirSync(stagingRoot, { recursive: true });
|
|
1996
|
+
} catch {
|
|
1997
|
+
}
|
|
1998
|
+
const install = await runHostCommand(bin, ["plugin", "--global", fileUrlFor(id)], {
|
|
1999
|
+
cwd: stagingRoot
|
|
2000
|
+
});
|
|
2001
|
+
if (!npmPluginInstalled(platform, id)) {
|
|
2002
|
+
changes.push(
|
|
2003
|
+
warn6(`plugin install did not complete \u2014 ` + failDetail6(manualInstall3(id), install))
|
|
2004
|
+
);
|
|
2005
|
+
return { changes, ok: false };
|
|
2006
|
+
}
|
|
2007
|
+
changes.push({
|
|
2008
|
+
platform,
|
|
2009
|
+
action: "create",
|
|
2010
|
+
detail: `installed plugin ${id} (scope user)`
|
|
2011
|
+
});
|
|
2012
|
+
return { changes, ok: true };
|
|
2013
|
+
};
|
|
2014
|
+
return {
|
|
2015
|
+
platform,
|
|
2016
|
+
format: "npm-plugin",
|
|
2017
|
+
binary,
|
|
2018
|
+
stagingRoot: npmStagingRoot,
|
|
2019
|
+
pluginDir(id) {
|
|
2020
|
+
return join8(npmStagingRoot(), id);
|
|
2021
|
+
},
|
|
2022
|
+
installed: (id) => npmPluginInstalled(platform, id),
|
|
2023
|
+
stage(connector, changes) {
|
|
2024
|
+
return stageBundle(connector, changes).contentHash;
|
|
2025
|
+
},
|
|
2026
|
+
planInstall(connector, changes) {
|
|
2027
|
+
changes.push({
|
|
2028
|
+
platform,
|
|
2029
|
+
action: npmPluginInstalled(platform, connector.id) ? "skip" : "create",
|
|
2030
|
+
detail: npmPluginInstalled(platform, connector.id) ? `plugin ${connector.id} already installed` : `run: ${manualInstall3(connector.id)}`
|
|
2031
|
+
});
|
|
2032
|
+
},
|
|
2033
|
+
planUninstall(id, changes) {
|
|
2034
|
+
const pluginDir = join8(npmStagingRoot(), id);
|
|
2035
|
+
const present = npmPluginInstalled(platform, id);
|
|
2036
|
+
changes.push({
|
|
2037
|
+
platform,
|
|
2038
|
+
action: present ? "remove" : "skip",
|
|
2039
|
+
detail: present ? `edit ${npmConfigFilePath(platform)}: drop the file:// plugin entry for ${id}` : `plugin ${id} not installed on ${platform}`
|
|
2040
|
+
});
|
|
2041
|
+
if (existsSync8(pluginDir)) {
|
|
2042
|
+
changes.push({
|
|
2043
|
+
platform,
|
|
2044
|
+
action: "remove",
|
|
2045
|
+
path: pluginDir,
|
|
2046
|
+
detail: "remove staged marketplace bundle"
|
|
2047
|
+
});
|
|
2048
|
+
}
|
|
2049
|
+
},
|
|
2050
|
+
driveInstall: driveInstall3,
|
|
2051
|
+
// No host uninstall verb (`<bin> uninstall` removes the HOST). Removal is an
|
|
2052
|
+
// EDIT of the config `plugin` array: drop the entry referencing our staged
|
|
2053
|
+
// dir, delete the `plugin` key when empty, preserve every other key.
|
|
2054
|
+
async driveUninstall(id) {
|
|
2055
|
+
const changes = [];
|
|
2056
|
+
const entry = npmPluginArrayEntry(platform, id);
|
|
2057
|
+
if (entry == null) {
|
|
2058
|
+
changes.push({
|
|
2059
|
+
platform,
|
|
2060
|
+
action: "skip",
|
|
2061
|
+
detail: `plugin ${id} not installed on ${platform}`
|
|
2062
|
+
});
|
|
2063
|
+
return { changes, ok: true };
|
|
2064
|
+
}
|
|
2065
|
+
const configPath = npmConfigFilePath(platform);
|
|
2066
|
+
const cfg = readNpmConfig(platform);
|
|
2067
|
+
if (!cfg) {
|
|
2068
|
+
changes.push(
|
|
2069
|
+
warn6(
|
|
2070
|
+
`could not parse ${platform} config ${configPath} \u2014 leaving the plugin entry in place. Remove this entry from the "plugin" array by hand: ${entry}`,
|
|
2071
|
+
configPath
|
|
2072
|
+
)
|
|
2073
|
+
);
|
|
2074
|
+
return { changes, ok: false };
|
|
2075
|
+
}
|
|
2076
|
+
const arr = Array.isArray(cfg["plugin"]) ? cfg["plugin"] : [];
|
|
2077
|
+
const filtered = arr.filter((e) => e !== entry);
|
|
2078
|
+
if (filtered.length === 0) delete cfg["plugin"];
|
|
2079
|
+
else cfg["plugin"] = filtered;
|
|
2080
|
+
try {
|
|
2081
|
+
mkdirSync(dirname4(configPath), { recursive: true });
|
|
2082
|
+
writeFileSync4(configPath, `${JSON.stringify(cfg, null, 2)}
|
|
2083
|
+
`, "utf8");
|
|
2084
|
+
} catch (err) {
|
|
2085
|
+
changes.push(
|
|
2086
|
+
warn6(`could not rewrite ${platform} config: ${errMessage7(err)}`, configPath)
|
|
2087
|
+
);
|
|
2088
|
+
return { changes, ok: false };
|
|
2089
|
+
}
|
|
2090
|
+
if (npmPluginInstalled(platform, id)) {
|
|
2091
|
+
changes.push(
|
|
2092
|
+
warn6(`plugin entry persisted after the config edit \u2014 ${configPath}`, configPath)
|
|
2093
|
+
);
|
|
2094
|
+
return { changes, ok: false };
|
|
2095
|
+
}
|
|
2096
|
+
changes.push({
|
|
2097
|
+
platform,
|
|
2098
|
+
action: "remove",
|
|
2099
|
+
path: configPath,
|
|
2100
|
+
detail: `removed plugin ${id} from the ${platform} config "plugin" array`
|
|
2101
|
+
});
|
|
2102
|
+
return { changes, ok: true };
|
|
2103
|
+
},
|
|
2104
|
+
// The `file://` reference points at the LIVE staged dir, so an update is just
|
|
2105
|
+
// a re-run of install (the bundle was re-staged in place by the caller). When
|
|
2106
|
+
// the entry is already present, that is an idempotent `=` skip.
|
|
2107
|
+
async driveUpdate(id) {
|
|
2108
|
+
return driveInstall3(id);
|
|
2109
|
+
},
|
|
2110
|
+
// No catalog, no marketplace de-registration: cleanup is removing the staged
|
|
2111
|
+
// bundle dir (the host no longer references it after driveUninstall's edit).
|
|
2112
|
+
async finishUninstall(id, changes) {
|
|
2113
|
+
const pluginDir = join8(npmStagingRoot(), id);
|
|
2114
|
+
if (existsSync8(pluginDir)) {
|
|
2115
|
+
try {
|
|
2116
|
+
rmSync7(pluginDir, { recursive: true, force: true });
|
|
2117
|
+
changes.push({
|
|
2118
|
+
platform,
|
|
2119
|
+
action: "remove",
|
|
2120
|
+
path: pluginDir,
|
|
2121
|
+
detail: "removed staged marketplace bundle"
|
|
2122
|
+
});
|
|
2123
|
+
} catch (err) {
|
|
2124
|
+
changes.push({
|
|
2125
|
+
platform,
|
|
2126
|
+
action: "warn",
|
|
2127
|
+
path: pluginDir,
|
|
2128
|
+
detail: `could not remove staged bundle: ${errMessage7(err)}`
|
|
2129
|
+
});
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
};
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
// src/core/marketplace-drivers/qwen.ts
|
|
2137
|
+
import { existsSync as existsSync9, rmSync as rmSync8 } from "fs";
|
|
2138
|
+
import { join as join9 } from "path";
|
|
2139
|
+
var PLATFORM5 = "qwen-code";
|
|
2140
|
+
function qwenBinary() {
|
|
2141
|
+
return findOnPath("qwen");
|
|
2142
|
+
}
|
|
2143
|
+
function errMessage8(err) {
|
|
2144
|
+
return err instanceof Error ? err.message : String(err);
|
|
2145
|
+
}
|
|
2146
|
+
function warn5(detail, path) {
|
|
2147
|
+
return { platform: PLATFORM5, action: "warn", detail, ...path ? { path } : {} };
|
|
2148
|
+
}
|
|
2149
|
+
function failDetail7(step, r) {
|
|
2150
|
+
if (r.timedOut) return `${step} timed out`;
|
|
2151
|
+
if (r.error) return `${step} failed to spawn: ${r.error}`;
|
|
2152
|
+
const line = firstLine(r.stderr) || firstLine(r.stdout);
|
|
2153
|
+
return `${step} exited ${r.code}${line ? `: ${line}` : ""}`;
|
|
2154
|
+
}
|
|
2155
|
+
function manualInstall2(pluginDir) {
|
|
2156
|
+
return `qwen extensions install ${pluginDir}`;
|
|
2157
|
+
}
|
|
2158
|
+
function stageQwenBundle(connector, changes) {
|
|
2159
|
+
const stagingRoot = qwenStagingRoot();
|
|
2160
|
+
const pluginDir = join9(stagingRoot, connector.id);
|
|
2161
|
+
const existed = existsSync9(pluginDir);
|
|
2162
|
+
const result = packageConnector(connector, { outDir: stagingRoot, format: "qwen-extension" });
|
|
2163
|
+
changes.push({
|
|
2164
|
+
platform: PLATFORM5,
|
|
2165
|
+
action: existed ? "update" : "create",
|
|
2166
|
+
path: pluginDir,
|
|
2167
|
+
detail: `staged marketplace bundle (${result.files.length} files, qwen-extension)`
|
|
2168
|
+
});
|
|
2169
|
+
return { pluginDir, contentHash: hashDirectory(pluginDir) };
|
|
2170
|
+
}
|
|
2171
|
+
var qwenDriver = {
|
|
2172
|
+
platform: PLATFORM5,
|
|
2173
|
+
format: "qwen-extension",
|
|
2174
|
+
binary: qwenBinary,
|
|
2175
|
+
stagingRoot: qwenStagingRoot,
|
|
2176
|
+
pluginDir(id) {
|
|
2177
|
+
return join9(qwenStagingRoot(), id);
|
|
2178
|
+
},
|
|
2179
|
+
installed: qwenExtensionInstalled,
|
|
2180
|
+
stage(connector, changes) {
|
|
2181
|
+
return stageQwenBundle(connector, changes).contentHash;
|
|
2182
|
+
},
|
|
2183
|
+
planInstall(connector, changes) {
|
|
2184
|
+
const pluginDir = join9(qwenStagingRoot(), connector.id);
|
|
2185
|
+
changes.push({
|
|
2186
|
+
platform: PLATFORM5,
|
|
2187
|
+
action: qwenExtensionInstalled(connector.id) ? "skip" : "create",
|
|
2188
|
+
detail: qwenExtensionInstalled(connector.id) ? `extension ${connector.id} already installed` : `run: ${manualInstall2(pluginDir)}`
|
|
2189
|
+
});
|
|
2190
|
+
},
|
|
2191
|
+
planUninstall(id, changes) {
|
|
2192
|
+
const pluginDir = join9(qwenStagingRoot(), id);
|
|
2193
|
+
changes.push({
|
|
2194
|
+
platform: PLATFORM5,
|
|
2195
|
+
action: qwenExtensionInstalled(id) ? "remove" : "skip",
|
|
2196
|
+
detail: qwenExtensionInstalled(id) ? `run: qwen extensions uninstall ${id}` : `extension ${id} not installed on ${PLATFORM5}`
|
|
2197
|
+
});
|
|
2198
|
+
if (existsSync9(pluginDir)) {
|
|
2199
|
+
changes.push({
|
|
2200
|
+
platform: PLATFORM5,
|
|
2201
|
+
action: "remove",
|
|
2202
|
+
path: pluginDir,
|
|
2203
|
+
detail: "remove staged marketplace bundle"
|
|
2204
|
+
});
|
|
2205
|
+
}
|
|
2206
|
+
},
|
|
2207
|
+
async driveInstall(id) {
|
|
2208
|
+
const changes = [];
|
|
2209
|
+
const pluginDir = join9(qwenStagingRoot(), id);
|
|
2210
|
+
const bin = qwenBinary();
|
|
2211
|
+
if (!bin) {
|
|
2212
|
+
changes.push(
|
|
2213
|
+
warn5(
|
|
2214
|
+
`qwen CLI not found on PATH \u2014 bundle staged but not installed. Install manually: ${manualInstall2(pluginDir)}`,
|
|
2215
|
+
pluginDir
|
|
2216
|
+
)
|
|
2217
|
+
);
|
|
2218
|
+
return { changes, ok: false };
|
|
2219
|
+
}
|
|
2220
|
+
if (qwenExtensionInstalled(id)) {
|
|
2221
|
+
changes.push({
|
|
2222
|
+
platform: PLATFORM5,
|
|
2223
|
+
action: "skip",
|
|
2224
|
+
detail: `extension ${id} already installed`
|
|
2225
|
+
});
|
|
2226
|
+
return { changes, ok: true };
|
|
2227
|
+
}
|
|
2228
|
+
const install = await runHostCommand(bin, ["extensions", "install", pluginDir]);
|
|
2229
|
+
if (!qwenExtensionInstalled(id)) {
|
|
2230
|
+
changes.push(
|
|
2231
|
+
warn5(
|
|
2232
|
+
`extension install did not complete \u2014 ` + failDetail7(`qwen extensions install ${pluginDir}`, install)
|
|
2233
|
+
)
|
|
2234
|
+
);
|
|
2235
|
+
return { changes, ok: false };
|
|
2236
|
+
}
|
|
2237
|
+
changes.push({
|
|
2238
|
+
platform: PLATFORM5,
|
|
2239
|
+
action: "create",
|
|
2240
|
+
detail: `installed extension ${id} (scope user)`
|
|
2241
|
+
});
|
|
2242
|
+
return { changes, ok: true };
|
|
2243
|
+
},
|
|
2244
|
+
async driveUninstall(id) {
|
|
2245
|
+
const changes = [];
|
|
2246
|
+
if (!qwenExtensionInstalled(id)) {
|
|
2247
|
+
changes.push({
|
|
2248
|
+
platform: PLATFORM5,
|
|
2249
|
+
action: "skip",
|
|
2250
|
+
detail: `extension ${id} not installed on ${PLATFORM5}`
|
|
2251
|
+
});
|
|
2252
|
+
return { changes, ok: true };
|
|
2253
|
+
}
|
|
2254
|
+
const bin = qwenBinary();
|
|
2255
|
+
if (!bin) {
|
|
2256
|
+
changes.push(
|
|
2257
|
+
warn5(
|
|
2258
|
+
`qwen CLI not found on PATH \u2014 cannot drive the uninstall. Run manually: qwen extensions uninstall ${id}`
|
|
2259
|
+
)
|
|
2260
|
+
);
|
|
2261
|
+
return { changes, ok: false };
|
|
2262
|
+
}
|
|
2263
|
+
const uninstall = await runHostCommand(bin, ["extensions", "uninstall", id]);
|
|
2264
|
+
if (qwenExtensionInstalled(id)) {
|
|
2265
|
+
changes.push(
|
|
2266
|
+
warn5(
|
|
2267
|
+
`extension uninstall did not complete \u2014 ` + failDetail7(`qwen extensions uninstall ${id}`, uninstall)
|
|
2268
|
+
)
|
|
2269
|
+
);
|
|
2270
|
+
return { changes, ok: false };
|
|
2271
|
+
}
|
|
2272
|
+
changes.push({
|
|
2273
|
+
platform: PLATFORM5,
|
|
2274
|
+
action: "remove",
|
|
2275
|
+
detail: `uninstalled extension ${id} (qwen extensions uninstall)`
|
|
2276
|
+
});
|
|
2277
|
+
return { changes, ok: true };
|
|
2278
|
+
},
|
|
2279
|
+
// qwen documents an `extensions update <id>` verb (UNLIKE gemini, which has no
|
|
2280
|
+
// overwrite path) — PREFER it when the extension is already installed, else
|
|
2281
|
+
// fall back to a fresh install. The bundle was already re-staged by the caller.
|
|
2282
|
+
// DOCS-only: the `update` verb + its exit semantics are unverified live.
|
|
2283
|
+
async driveUpdate(id) {
|
|
2284
|
+
const changes = [];
|
|
2285
|
+
const pluginDir = join9(qwenStagingRoot(), id);
|
|
2286
|
+
const bin = qwenBinary();
|
|
2287
|
+
if (!bin) {
|
|
2288
|
+
changes.push(
|
|
2289
|
+
warn5(
|
|
2290
|
+
`qwen CLI not found on PATH \u2014 bundle re-staged but the installed copy was not updated. Run manually: qwen extensions update ${id}`,
|
|
2291
|
+
pluginDir
|
|
2292
|
+
)
|
|
2293
|
+
);
|
|
2294
|
+
return { changes, ok: false };
|
|
2295
|
+
}
|
|
2296
|
+
if (qwenExtensionInstalled(id)) {
|
|
2297
|
+
const update = await runHostCommand(bin, ["extensions", "update", id]);
|
|
2298
|
+
if (!qwenExtensionInstalled(id)) {
|
|
2299
|
+
changes.push(
|
|
2300
|
+
warn5(
|
|
2301
|
+
`extension update did not complete \u2014 ` + failDetail7(`qwen extensions update ${id}`, update)
|
|
2302
|
+
)
|
|
2303
|
+
);
|
|
2304
|
+
return { changes, ok: false };
|
|
2305
|
+
}
|
|
2306
|
+
changes.push({
|
|
2307
|
+
platform: PLATFORM5,
|
|
2308
|
+
action: "update",
|
|
2309
|
+
detail: `updated extension ${id} (qwen extensions update)`
|
|
2310
|
+
});
|
|
2311
|
+
return { changes, ok: true };
|
|
2312
|
+
}
|
|
2313
|
+
const install = await runHostCommand(bin, ["extensions", "install", pluginDir]);
|
|
2314
|
+
if (!qwenExtensionInstalled(id)) {
|
|
2315
|
+
changes.push(
|
|
2316
|
+
warn5(
|
|
2317
|
+
`extension install did not complete \u2014 ` + failDetail7(`qwen extensions install ${pluginDir}`, install)
|
|
2318
|
+
)
|
|
2319
|
+
);
|
|
2320
|
+
return { changes, ok: false };
|
|
2321
|
+
}
|
|
2322
|
+
changes.push({
|
|
2323
|
+
platform: PLATFORM5,
|
|
2324
|
+
action: "update",
|
|
2325
|
+
detail: `updated extension ${id} (qwen extensions install)`
|
|
2326
|
+
});
|
|
2327
|
+
return { changes, ok: true };
|
|
2328
|
+
},
|
|
2329
|
+
// Direct driver: no catalog, no marketplace de-registration. Cleanup is just
|
|
2330
|
+
// removing the staged bundle dir (the host copied it into its own store).
|
|
2331
|
+
async finishUninstall(id, changes) {
|
|
2332
|
+
const pluginDir = join9(qwenStagingRoot(), id);
|
|
2333
|
+
if (existsSync9(pluginDir)) {
|
|
2334
|
+
try {
|
|
2335
|
+
rmSync8(pluginDir, { recursive: true, force: true });
|
|
2336
|
+
changes.push({
|
|
2337
|
+
platform: PLATFORM5,
|
|
2338
|
+
action: "remove",
|
|
2339
|
+
path: pluginDir,
|
|
2340
|
+
detail: "removed staged marketplace bundle"
|
|
2341
|
+
});
|
|
2342
|
+
} catch (err) {
|
|
2343
|
+
changes.push({
|
|
2344
|
+
platform: PLATFORM5,
|
|
2345
|
+
action: "warn",
|
|
2346
|
+
path: pluginDir,
|
|
2347
|
+
detail: `could not remove staged bundle: ${errMessage8(err)}`
|
|
2348
|
+
});
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
};
|
|
2353
|
+
|
|
2354
|
+
// src/core/marketplace-drivers/registry.ts
|
|
2355
|
+
var agyDrivers = /* @__PURE__ */ new Map();
|
|
2356
|
+
function agyDriver(platform) {
|
|
2357
|
+
let driver = agyDrivers.get(platform);
|
|
2358
|
+
if (!driver) {
|
|
2359
|
+
driver = makeAgyDriver(platform);
|
|
2360
|
+
agyDrivers.set(platform, driver);
|
|
2361
|
+
}
|
|
2362
|
+
return driver;
|
|
2363
|
+
}
|
|
2364
|
+
var NPM_LOCAL_BINARIES = {
|
|
2365
|
+
opencode: "opencode",
|
|
2366
|
+
kilo: "kilo",
|
|
2367
|
+
"kilo-cli": "kilo"
|
|
2368
|
+
};
|
|
2369
|
+
var npmLocalDrivers = /* @__PURE__ */ new Map();
|
|
2370
|
+
function npmLocalDriver(platform) {
|
|
2371
|
+
let driver = npmLocalDrivers.get(platform);
|
|
2372
|
+
if (!driver) {
|
|
2373
|
+
driver = makeNpmLocalDriver(platform, {
|
|
2374
|
+
binaryName: NPM_LOCAL_BINARIES[platform] ?? platform
|
|
2375
|
+
});
|
|
2376
|
+
npmLocalDrivers.set(platform, driver);
|
|
2377
|
+
}
|
|
2378
|
+
return driver;
|
|
2379
|
+
}
|
|
2380
|
+
function getMarketplaceDriver(platform) {
|
|
2381
|
+
switch (platform) {
|
|
2382
|
+
case "claude-code":
|
|
2383
|
+
return claudeDriver;
|
|
2384
|
+
case "codex":
|
|
2385
|
+
return codexDriver;
|
|
2386
|
+
case "antigravity":
|
|
2387
|
+
case "antigravity-cli":
|
|
2388
|
+
return agyDriver(platform);
|
|
2389
|
+
case "gemini-cli":
|
|
2390
|
+
return geminiDriver;
|
|
2391
|
+
case "qwen-code":
|
|
2392
|
+
return qwenDriver;
|
|
2393
|
+
case "droid":
|
|
2394
|
+
return droidDriver;
|
|
2395
|
+
case "opencode":
|
|
2396
|
+
case "kilo":
|
|
2397
|
+
case "kilo-cli":
|
|
2398
|
+
return npmLocalDriver(platform);
|
|
2399
|
+
default:
|
|
2400
|
+
return null;
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
// src/core/marketplace.ts
|
|
2405
|
+
function parseInstallMethod(value) {
|
|
2406
|
+
if (value == null || value === "direct" || value === "marketplace") {
|
|
2407
|
+
return value ?? "direct";
|
|
2408
|
+
}
|
|
2409
|
+
return null;
|
|
2410
|
+
}
|
|
2411
|
+
function parseUninstallMethod(value) {
|
|
2412
|
+
if (value == null || value === "auto" || value === "direct" || value === "marketplace") {
|
|
2413
|
+
return value ?? "auto";
|
|
2414
|
+
}
|
|
2415
|
+
return null;
|
|
2416
|
+
}
|
|
2417
|
+
var MARKETPLACE_FORMAT_BY_PLATFORM = {
|
|
2418
|
+
"claude-code": "claude-plugin",
|
|
2419
|
+
codex: "codex-plugin",
|
|
2420
|
+
droid: "factory-plugin",
|
|
2421
|
+
"gemini-cli": "gemini-extension",
|
|
2422
|
+
"qwen-code": "qwen-extension",
|
|
2423
|
+
antigravity: "agy-plugin",
|
|
2424
|
+
"antigravity-cli": "agy-plugin",
|
|
2425
|
+
cursor: "cursor-plugin",
|
|
2426
|
+
kimi: "kimi-plugin",
|
|
2427
|
+
opencode: "npm-plugin",
|
|
2428
|
+
kilo: "npm-plugin",
|
|
2429
|
+
"kilo-cli": "npm-plugin",
|
|
2430
|
+
pi: "npm-plugin",
|
|
2431
|
+
"vscode-copilot": "claude-plugin",
|
|
2432
|
+
openclaw: "claude-plugin",
|
|
2433
|
+
omp: "claude-plugin"
|
|
2434
|
+
};
|
|
2435
|
+
var DRIVABLE_MARKETPLACE_PLATFORMS = /* @__PURE__ */ new Set([
|
|
2436
|
+
"claude-code",
|
|
2437
|
+
"codex",
|
|
2438
|
+
"antigravity",
|
|
2439
|
+
"antigravity-cli",
|
|
2440
|
+
"gemini-cli",
|
|
2441
|
+
"qwen-code",
|
|
2442
|
+
"droid",
|
|
2443
|
+
"opencode",
|
|
2444
|
+
"kilo",
|
|
2445
|
+
"kilo-cli"
|
|
2446
|
+
]);
|
|
2447
|
+
function newResult2(connectorId, dryRun) {
|
|
2448
|
+
return { connectorId, dryRun, changes: [], warnings: [] };
|
|
2449
|
+
}
|
|
2450
|
+
function errMessage9(err) {
|
|
2451
|
+
return err instanceof Error ? err.message : String(err);
|
|
2452
|
+
}
|
|
2453
|
+
function manualHint(platform, format, id) {
|
|
2454
|
+
const steps = installInstructions(format, id, "<out-dir>").join("; ");
|
|
2455
|
+
return `agent-connector cannot drive the ${platform} marketplace flow yet \u2014 install manually: \`agent-connector package --format ${format} --out <out-dir>\` then: ${steps}`;
|
|
2456
|
+
}
|
|
2457
|
+
async function directInstallPresent(connector, platform, scope, projectDir) {
|
|
2458
|
+
try {
|
|
2459
|
+
const adapter = await loadAdapter(platform);
|
|
2460
|
+
if (!adapter) return false;
|
|
2461
|
+
const ctx = {
|
|
2462
|
+
connector,
|
|
2463
|
+
scope: connector.platforms[platform]?.scope ?? scope,
|
|
2464
|
+
projectDir,
|
|
2465
|
+
homeBinPath: homeBinPath(),
|
|
2466
|
+
dataRoot: dataRoot(),
|
|
2467
|
+
dryRun: true
|
|
2468
|
+
};
|
|
2469
|
+
const escaped = connector.id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2470
|
+
const serverKey = new RegExp(`"${escaped}"\\s*:|\\.${escaped}\\]`);
|
|
2471
|
+
const hookCommand = new RegExp(`--connector\\s+${escaped}(?![\\w-])`);
|
|
2472
|
+
const matches = (path, re) => {
|
|
2473
|
+
try {
|
|
2474
|
+
return re.test(readFileSync4(path, "utf8"));
|
|
2475
|
+
} catch {
|
|
2476
|
+
return false;
|
|
2477
|
+
}
|
|
2478
|
+
};
|
|
2479
|
+
try {
|
|
2480
|
+
if (matches(adapter.getServerConfigPath(ctx), serverKey)) return true;
|
|
2481
|
+
} catch {
|
|
2482
|
+
}
|
|
2483
|
+
try {
|
|
2484
|
+
if (matches(adapter.getHookConfigPath(ctx), hookCommand)) return true;
|
|
2485
|
+
} catch {
|
|
2486
|
+
}
|
|
2487
|
+
return false;
|
|
2488
|
+
} catch {
|
|
2489
|
+
return false;
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
async function installViaMarketplace(opts) {
|
|
2493
|
+
const { connector, modulePath, scope, projectDir, dryRun } = opts;
|
|
2494
|
+
const result = newResult2(connector.id, dryRun);
|
|
2495
|
+
if (scope !== "user") {
|
|
2496
|
+
result.warnings.push(
|
|
2497
|
+
"--method marketplace supports --scope user only in this version (project-scope plugin installs are deferred)"
|
|
2498
|
+
);
|
|
2499
|
+
return result;
|
|
2500
|
+
}
|
|
2501
|
+
if (!dryRun) {
|
|
2502
|
+
try {
|
|
2503
|
+
ensureHomeBin(resolveCliEntry());
|
|
2504
|
+
} catch (err) {
|
|
2505
|
+
log.warn(`ensureHomeBin failed: ${errMessage9(err)}`);
|
|
2506
|
+
}
|
|
2507
|
+
try {
|
|
2508
|
+
registerConnector(connector, modulePath);
|
|
2509
|
+
} catch (err) {
|
|
2510
|
+
log.warn(`registerConnector failed: ${errMessage9(err)}`);
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
const explicit = opts.targets != null && opts.targets.length > 0;
|
|
2514
|
+
const targets = await resolveTargets(opts.targets, connector.targets, projectDir);
|
|
2515
|
+
if (targets.length === 0) {
|
|
2516
|
+
result.warnings.push(
|
|
2517
|
+
"no target platforms resolved (none installed / detected, or all filtered out)"
|
|
2518
|
+
);
|
|
2519
|
+
return result;
|
|
2520
|
+
}
|
|
2521
|
+
for (const id of targets) {
|
|
2522
|
+
const format = MARKETPLACE_FORMAT_BY_PLATFORM[id];
|
|
2523
|
+
if (!format) {
|
|
2524
|
+
result.changes.push({
|
|
2525
|
+
platform: id,
|
|
2526
|
+
action: explicit ? "warn" : "skip",
|
|
2527
|
+
detail: `no marketplace/plugin distribution path exists for ${id} \u2014 use the direct method: \`agent-connector install --targets ${id}\``
|
|
2528
|
+
});
|
|
2529
|
+
continue;
|
|
2530
|
+
}
|
|
2531
|
+
if (!DRIVABLE_MARKETPLACE_PLATFORMS.has(id)) {
|
|
2532
|
+
result.changes.push({
|
|
2533
|
+
platform: id,
|
|
2534
|
+
action: explicit ? "warn" : "skip",
|
|
2535
|
+
detail: manualHint(id, format, connector.id)
|
|
2536
|
+
});
|
|
2537
|
+
continue;
|
|
2538
|
+
}
|
|
2539
|
+
const driver = getMarketplaceDriver(id);
|
|
2540
|
+
if (!driver) {
|
|
2541
|
+
result.changes.push({
|
|
2542
|
+
platform: id,
|
|
2543
|
+
action: explicit ? "warn" : "skip",
|
|
2544
|
+
detail: manualHint(id, format, connector.id)
|
|
2545
|
+
});
|
|
2546
|
+
continue;
|
|
2547
|
+
}
|
|
2548
|
+
if (await directInstallPresent(connector, id, scope, projectDir)) {
|
|
2549
|
+
result.changes.push({
|
|
2550
|
+
platform: id,
|
|
2551
|
+
action: "warn",
|
|
2552
|
+
detail: `"${connector.id}" is already installed DIRECTLY on ${id} \u2014 refusing the marketplace install (both at once duplicates hooks + the MCP server and corrupts telemetry). Run \`agent-connector uninstall --targets ${id}\` first, or keep the direct install.`
|
|
2553
|
+
});
|
|
2554
|
+
continue;
|
|
2555
|
+
}
|
|
2556
|
+
const pluginDir = driver.pluginDir(connector.id);
|
|
2557
|
+
if (dryRun) {
|
|
2558
|
+
const planned = packageConnector(connector, {
|
|
2559
|
+
outDir: driver.stagingRoot(),
|
|
2560
|
+
format,
|
|
2561
|
+
dryRun: true
|
|
2562
|
+
});
|
|
2563
|
+
for (const file of planned.files) {
|
|
2564
|
+
result.changes.push({
|
|
2565
|
+
platform: id,
|
|
2566
|
+
action: "create",
|
|
2567
|
+
path: file,
|
|
2568
|
+
detail: "stage bundle file"
|
|
2569
|
+
});
|
|
2570
|
+
}
|
|
2571
|
+
driver.planInstall(connector, result.changes);
|
|
2572
|
+
continue;
|
|
2573
|
+
}
|
|
2574
|
+
const contentHash = driver.stage(connector, result.changes);
|
|
2575
|
+
const outcome = await driver.driveInstall(connector.id);
|
|
2576
|
+
result.changes.push(...outcome.changes);
|
|
2577
|
+
if (outcome.ok) {
|
|
2578
|
+
const installs = readMarketplaceInstalls(connector.id);
|
|
2579
|
+
const record = {
|
|
2580
|
+
format,
|
|
2581
|
+
bundleDir: pluginDir,
|
|
2582
|
+
marketplace: MARKETPLACE_NAME,
|
|
2583
|
+
scope,
|
|
2584
|
+
version: connector.version,
|
|
2585
|
+
contentHash,
|
|
2586
|
+
installedAt: installs[id]?.installedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
2587
|
+
};
|
|
2588
|
+
installs[id] = record;
|
|
2589
|
+
writeMarketplaceInstalls(connector.id, installs);
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
return result;
|
|
2593
|
+
}
|
|
2594
|
+
var MANUAL_UNINSTALL = {
|
|
2595
|
+
"gemini-cli": (id) => `gemini extensions uninstall ${id}`,
|
|
2596
|
+
antigravity: (id) => `agy plugin uninstall ${id}`,
|
|
2597
|
+
"antigravity-cli": (id) => `agy plugin uninstall ${id}`,
|
|
2598
|
+
codex: (id) => `codex plugin remove ${id}@${MARKETPLACE_NAME}`
|
|
2599
|
+
};
|
|
2600
|
+
async function uninstallViaMarketplace(opts) {
|
|
2601
|
+
const { connectorId, dryRun, purge } = opts;
|
|
2602
|
+
const result = newResult2(connectorId, dryRun);
|
|
2603
|
+
let targets;
|
|
2604
|
+
if (opts.targets && opts.targets.length > 0) {
|
|
2605
|
+
targets = [...new Set(opts.targets)];
|
|
2606
|
+
} else {
|
|
2607
|
+
targets = Object.keys(readMarketplaceInstalls(connectorId));
|
|
2608
|
+
for (const id of DRIVABLE_MARKETPLACE_PLATFORMS) {
|
|
2609
|
+
if (targets.includes(id)) continue;
|
|
2610
|
+
const driver = getMarketplaceDriver(id);
|
|
2611
|
+
if (driver?.installed(connectorId)) targets = [...targets, id];
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
if (targets.length === 0) {
|
|
2615
|
+
result.warnings.push(
|
|
2616
|
+
`no marketplace installs found for "${connectorId}" (nothing recorded, no host evidence)`
|
|
2617
|
+
);
|
|
2618
|
+
return result;
|
|
2619
|
+
}
|
|
2620
|
+
for (const id of targets) {
|
|
2621
|
+
const driver = getMarketplaceDriver(id);
|
|
2622
|
+
if (!driver) {
|
|
2623
|
+
const manual = MANUAL_UNINSTALL[id];
|
|
2624
|
+
const recorded = readMarketplaceInstalls(connectorId)[id] != null;
|
|
2625
|
+
result.changes.push({
|
|
2626
|
+
platform: id,
|
|
2627
|
+
action: recorded ? "warn" : "skip",
|
|
2628
|
+
detail: recorded ? `agent-connector cannot drive the ${id} marketplace uninstall yet \u2014 run manually${manual ? `: ${manual(connectorId)}` : ""} (record kept until then)` : `no marketplace install recorded for ${id}`
|
|
2629
|
+
});
|
|
2630
|
+
continue;
|
|
2631
|
+
}
|
|
2632
|
+
if (dryRun) {
|
|
2633
|
+
driver.planUninstall(connectorId, result.changes);
|
|
2634
|
+
continue;
|
|
2635
|
+
}
|
|
2636
|
+
const outcome = await driver.driveUninstall(connectorId);
|
|
2637
|
+
result.changes.push(...outcome.changes);
|
|
2638
|
+
if (!outcome.ok) continue;
|
|
2639
|
+
await driver.finishUninstall(connectorId, result.changes);
|
|
2640
|
+
const installs = readMarketplaceInstalls(connectorId);
|
|
2641
|
+
if (installs[id]) {
|
|
2642
|
+
delete installs[id];
|
|
2643
|
+
writeMarketplaceInstalls(connectorId, installs);
|
|
2644
|
+
result.changes.push({
|
|
2645
|
+
platform: id,
|
|
2646
|
+
action: "remove",
|
|
2647
|
+
path: marketplaceRoot(),
|
|
2648
|
+
detail: "removed marketplace install record"
|
|
2649
|
+
});
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
if (purge && !dryRun) {
|
|
2653
|
+
const seenRoots = /* @__PURE__ */ new Set();
|
|
2654
|
+
for (const id of DRIVABLE_MARKETPLACE_PLATFORMS) {
|
|
2655
|
+
const driver = getMarketplaceDriver(id);
|
|
2656
|
+
if (!driver) continue;
|
|
2657
|
+
const root2 = driver.stagingRoot();
|
|
2658
|
+
if (seenRoots.has(root2)) continue;
|
|
2659
|
+
seenRoots.add(root2);
|
|
2660
|
+
if (!existsSync10(root2)) continue;
|
|
2661
|
+
let bundleDirs;
|
|
2662
|
+
try {
|
|
2663
|
+
bundleDirs = readdirSync5(root2).filter((n) => !n.startsWith("."));
|
|
2664
|
+
} catch {
|
|
2665
|
+
bundleDirs = [];
|
|
2666
|
+
}
|
|
2667
|
+
if (bundleDirs.length > 0) continue;
|
|
2668
|
+
try {
|
|
2669
|
+
rmSync9(root2, { recursive: true, force: true });
|
|
2670
|
+
result.changes.push({
|
|
2671
|
+
platform: id,
|
|
2672
|
+
action: "remove",
|
|
2673
|
+
path: root2,
|
|
2674
|
+
detail: "removed empty marketplace staging root (--purge)"
|
|
2675
|
+
});
|
|
2676
|
+
} catch (err) {
|
|
2677
|
+
log.warn(`marketplace staging-root removal failed: ${errMessage9(err)}`);
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
const root = marketplaceRoot();
|
|
2681
|
+
try {
|
|
2682
|
+
if (existsSync10(root) && readdirSync5(root).length === 0) {
|
|
2683
|
+
rmSync9(root, { recursive: true, force: true });
|
|
2684
|
+
}
|
|
2685
|
+
} catch {
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
return result;
|
|
2689
|
+
}
|
|
2690
|
+
async function upgradeViaMarketplace(opts) {
|
|
2691
|
+
const { connector, scope, dryRun } = opts;
|
|
2692
|
+
const result = newResult2(connector.id, dryRun);
|
|
2693
|
+
if (scope !== "user") {
|
|
2694
|
+
result.warnings.push(
|
|
2695
|
+
"--method marketplace supports --scope user only in this version"
|
|
2696
|
+
);
|
|
2697
|
+
return result;
|
|
2698
|
+
}
|
|
2699
|
+
let targets;
|
|
2700
|
+
if (opts.targets && opts.targets.length > 0) {
|
|
2701
|
+
targets = [...new Set(opts.targets)];
|
|
2702
|
+
} else {
|
|
2703
|
+
targets = Object.keys(readMarketplaceInstalls(connector.id));
|
|
2704
|
+
for (const id of DRIVABLE_MARKETPLACE_PLATFORMS) {
|
|
2705
|
+
if (targets.includes(id)) continue;
|
|
2706
|
+
const driver = getMarketplaceDriver(id);
|
|
2707
|
+
if (driver?.installed(connector.id)) targets = [...targets, id];
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
if (targets.length === 0) {
|
|
2711
|
+
result.warnings.push(
|
|
2712
|
+
`no marketplace installs found for "${connector.id}" \u2014 run \`agent-connector install --method marketplace\` first`
|
|
2713
|
+
);
|
|
2714
|
+
return result;
|
|
2715
|
+
}
|
|
2716
|
+
for (const id of targets) {
|
|
2717
|
+
const driver = getMarketplaceDriver(id);
|
|
2718
|
+
if (!driver) {
|
|
2719
|
+
result.changes.push({
|
|
2720
|
+
platform: id,
|
|
2721
|
+
action: "warn",
|
|
2722
|
+
detail: `agent-connector cannot drive a ${id} marketplace upgrade yet \u2014 re-run \`agent-connector package\` and the host's own update flow`
|
|
2723
|
+
});
|
|
2724
|
+
continue;
|
|
2725
|
+
}
|
|
2726
|
+
const record = readMarketplaceInstalls(connector.id)[id];
|
|
2727
|
+
if (!record && !driver.installed(connector.id)) {
|
|
2728
|
+
result.changes.push({
|
|
2729
|
+
platform: id,
|
|
2730
|
+
action: "warn",
|
|
2731
|
+
detail: `"${connector.id}" is not marketplace-installed on ${id} \u2014 run \`agent-connector install --method marketplace --targets ${id}\``
|
|
2732
|
+
});
|
|
2733
|
+
continue;
|
|
2734
|
+
}
|
|
2735
|
+
if (record && record.version === connector.version) {
|
|
2736
|
+
result.changes.push({
|
|
2737
|
+
platform: id,
|
|
2738
|
+
action: "warn",
|
|
2739
|
+
detail: `connector.version is unchanged since the recorded install (${record.version}) \u2014 ${id} caches a versioned copy, so bump connector.version for the update to take effect`
|
|
2740
|
+
});
|
|
2741
|
+
}
|
|
2742
|
+
const pluginDir = driver.pluginDir(connector.id);
|
|
2743
|
+
if (dryRun) {
|
|
2744
|
+
result.changes.push({
|
|
2745
|
+
platform: id,
|
|
2746
|
+
action: "update",
|
|
2747
|
+
path: pluginDir,
|
|
2748
|
+
detail: "re-stage marketplace bundle in place"
|
|
2749
|
+
});
|
|
2750
|
+
result.changes.push({
|
|
2751
|
+
platform: id,
|
|
2752
|
+
action: "update",
|
|
2753
|
+
detail: `drive the ${id} host update flow`
|
|
2754
|
+
});
|
|
2755
|
+
continue;
|
|
2756
|
+
}
|
|
2757
|
+
const contentHash = driver.stage(connector, result.changes);
|
|
2758
|
+
const outcome = await driver.driveUpdate(connector.id);
|
|
2759
|
+
result.changes.push(...outcome.changes);
|
|
2760
|
+
if (outcome.ok) {
|
|
2761
|
+
const installs = readMarketplaceInstalls(connector.id);
|
|
2762
|
+
installs[id] = {
|
|
2763
|
+
format: record?.format ?? driver.format,
|
|
2764
|
+
bundleDir: pluginDir,
|
|
2765
|
+
marketplace: MARKETPLACE_NAME,
|
|
2766
|
+
scope,
|
|
2767
|
+
version: connector.version,
|
|
2768
|
+
contentHash,
|
|
2769
|
+
installedAt: record?.installedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
2770
|
+
};
|
|
2771
|
+
writeMarketplaceInstalls(connector.id, installs);
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
return result;
|
|
2775
|
+
}
|
|
2776
|
+
async function marketplaceDoctorChecks(connector, scope, projectDir) {
|
|
2777
|
+
const groups = [];
|
|
2778
|
+
const claude = await claudeMarketplaceGroup(connector, scope, projectDir);
|
|
2779
|
+
if (claude) groups.push(claude);
|
|
2780
|
+
const seenHosts = /* @__PURE__ */ new Set();
|
|
2781
|
+
const candidates = [
|
|
2782
|
+
"codex",
|
|
2783
|
+
"droid",
|
|
2784
|
+
"antigravity",
|
|
2785
|
+
"antigravity-cli",
|
|
2786
|
+
"gemini-cli",
|
|
2787
|
+
"qwen-code",
|
|
2788
|
+
"opencode",
|
|
2789
|
+
"kilo",
|
|
2790
|
+
"kilo-cli"
|
|
2791
|
+
].sort((a, b) => recordRank(connector.id, a) - recordRank(connector.id, b));
|
|
2792
|
+
for (const platform of candidates) {
|
|
2793
|
+
const driver = getMarketplaceDriver(platform);
|
|
2794
|
+
if (!driver) continue;
|
|
2795
|
+
const hostKey = doctorHostKey(platform, driver.stagingRoot());
|
|
2796
|
+
if (seenHosts.has(hostKey)) continue;
|
|
2797
|
+
const group = await genericMarketplaceGroup(connector, platform, scope, projectDir);
|
|
2798
|
+
if (group) {
|
|
2799
|
+
groups.push(group);
|
|
2800
|
+
seenHosts.add(hostKey);
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
groups.sort((a, b) => platformDoctorRank(a.platform) - platformDoctorRank(b.platform));
|
|
2804
|
+
return groups;
|
|
2805
|
+
}
|
|
2806
|
+
function doctorHostKey(platform, stagingRoot) {
|
|
2807
|
+
if (platform === "opencode" || platform === "kilo" || platform === "kilo-cli") {
|
|
2808
|
+
return npmConfigFilePath(platform);
|
|
2809
|
+
}
|
|
2810
|
+
return stagingRoot;
|
|
2811
|
+
}
|
|
2812
|
+
function recordRank(connectorId, platform) {
|
|
2813
|
+
return readMarketplaceInstalls(connectorId)[platform] ? 0 : 1;
|
|
2814
|
+
}
|
|
2815
|
+
function platformDoctorRank(platform) {
|
|
2816
|
+
const order = [
|
|
2817
|
+
"claude-code",
|
|
2818
|
+
"codex",
|
|
2819
|
+
"droid",
|
|
2820
|
+
"antigravity",
|
|
2821
|
+
"antigravity-cli",
|
|
2822
|
+
"gemini-cli",
|
|
2823
|
+
"qwen-code",
|
|
2824
|
+
"opencode",
|
|
2825
|
+
"kilo",
|
|
2826
|
+
"kilo-cli"
|
|
2827
|
+
];
|
|
2828
|
+
const i = order.indexOf(platform);
|
|
2829
|
+
return i === -1 ? order.length : i;
|
|
2830
|
+
}
|
|
2831
|
+
async function claudeMarketplaceGroup(connector, scope, projectDir) {
|
|
2832
|
+
const id = connector.id;
|
|
2833
|
+
const record = readMarketplaceInstalls(id)["claude-code"];
|
|
2834
|
+
const pluginPresent = claudePluginInstalled(id);
|
|
2835
|
+
if (!record && !pluginPresent) return null;
|
|
2836
|
+
const results = [];
|
|
2837
|
+
const stagingRoot = claudeStagingRoot();
|
|
2838
|
+
const pluginKey = claudePluginKey(id);
|
|
2839
|
+
const direct = await directInstallPresent(connector, "claude-code", scope, projectDir);
|
|
2840
|
+
if (direct && pluginPresent) {
|
|
2841
|
+
results.push({
|
|
2842
|
+
check: `${id}: marketplace duplicate-registration`,
|
|
2843
|
+
status: "fail",
|
|
2844
|
+
message: `installed BOTH directly and as plugin ${pluginKey} \u2014 hooks + MCP server are duplicated`,
|
|
2845
|
+
fix: `uninstall one method: \`agent-connector uninstall --method marketplace --targets claude-code\` or \`agent-connector uninstall --targets claude-code\``
|
|
2846
|
+
});
|
|
2847
|
+
}
|
|
2848
|
+
if (record) {
|
|
2849
|
+
const registeredAt = claudeKnownMarketplacePath(MARKETPLACE_NAME);
|
|
2850
|
+
const catalogPath = join10(stagingRoot, ".claude-plugin", "marketplace.json");
|
|
2851
|
+
let catalogListed = false;
|
|
2852
|
+
try {
|
|
2853
|
+
const catalog = JSON.parse(readFileSync4(catalogPath, "utf8"));
|
|
2854
|
+
catalogListed = (catalog.plugins ?? []).some((p) => p.name === id);
|
|
2855
|
+
} catch {
|
|
2856
|
+
catalogListed = false;
|
|
2857
|
+
}
|
|
2858
|
+
const intact = registeredAt === stagingRoot && existsSync10(record.bundleDir) && catalogListed;
|
|
2859
|
+
results.push({
|
|
2860
|
+
check: `${id}: marketplace registration`,
|
|
2861
|
+
status: intact ? "pass" : "fail",
|
|
2862
|
+
message: intact ? `local marketplace "${MARKETPLACE_NAME}" registered at ${stagingRoot}, bundle staged + cataloged` : `marketplace registration broken (registered at ${registeredAt ?? "nowhere"}, bundle ${existsSync10(record.bundleDir) ? "present" : "missing"}, catalog ${catalogListed ? "lists it" : "does not list it"})`,
|
|
2863
|
+
...intact ? {} : { fix: `agent-connector install --method marketplace --targets claude-code` }
|
|
2864
|
+
});
|
|
2865
|
+
}
|
|
2866
|
+
if (record && !pluginPresent) {
|
|
2867
|
+
results.push({
|
|
2868
|
+
check: `${id}: marketplace state drift`,
|
|
2869
|
+
status: "warn",
|
|
2870
|
+
message: `recorded as marketplace-installed but Claude's state shows ${pluginKey} absent`,
|
|
2871
|
+
fix: `re-run \`agent-connector install --method marketplace --targets claude-code\` or \`agent-connector uninstall --method marketplace --targets claude-code\` to reconcile`
|
|
2872
|
+
});
|
|
2873
|
+
} else if (!record && pluginPresent) {
|
|
2874
|
+
results.push({
|
|
2875
|
+
check: `${id}: marketplace state drift`,
|
|
2876
|
+
status: "warn",
|
|
2877
|
+
message: `Claude lists ${pluginKey} installed but agent-connector has no record of it (manual install?)`,
|
|
2878
|
+
fix: `adopt it: \`agent-connector install --method marketplace --targets claude-code\` (idempotent), or remove it: \`claude plugin uninstall ${pluginKey}\``
|
|
2879
|
+
});
|
|
2880
|
+
}
|
|
2881
|
+
if (record) {
|
|
2882
|
+
if (connector.version !== "0.0.0" && connector.version !== record.version) {
|
|
2883
|
+
results.push({
|
|
2884
|
+
check: `${id}: marketplace staleness`,
|
|
2885
|
+
status: "warn",
|
|
2886
|
+
message: `connector.version ${connector.version} \u2260 installed ${record.version}`,
|
|
2887
|
+
fix: `agent-connector upgrade --method marketplace --targets claude-code`
|
|
2888
|
+
});
|
|
2889
|
+
} else if (existsSync10(record.bundleDir) && record.contentHash !== "" && hashDirectory(record.bundleDir) !== record.contentHash) {
|
|
2890
|
+
results.push({
|
|
2891
|
+
check: `${id}: marketplace staleness`,
|
|
2892
|
+
status: "warn",
|
|
2893
|
+
message: "staged bundle changed since install \u2014 Claude's cached copy is stale",
|
|
2894
|
+
fix: `agent-connector upgrade --method marketplace --targets claude-code`
|
|
2895
|
+
});
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
if (pluginPresent && !existsSync10(homeBinPath())) {
|
|
2899
|
+
results.push({
|
|
2900
|
+
check: `${id}: marketplace home-bin`,
|
|
2901
|
+
status: "fail",
|
|
2902
|
+
message: `installed plugin hooks exec ${homeBinPath()} which does not exist`,
|
|
2903
|
+
fix: "agent-connector upgrade"
|
|
2904
|
+
});
|
|
2905
|
+
}
|
|
2906
|
+
if (record && claudeBinary() == null) {
|
|
2907
|
+
results.push({
|
|
2908
|
+
check: `${id}: marketplace host binary`,
|
|
2909
|
+
status: "warn",
|
|
2910
|
+
message: "claude CLI not found on PATH \u2014 the recorded marketplace install cannot be managed until it is reinstalled"
|
|
2911
|
+
});
|
|
2912
|
+
}
|
|
2913
|
+
if (results.every((r) => r.status === "pass")) {
|
|
2914
|
+
results.push({
|
|
2915
|
+
check: `${id}: marketplace install`,
|
|
2916
|
+
status: "pass",
|
|
2917
|
+
message: `${pluginKey} installed via marketplace (version ${record?.version ?? "unknown"})`
|
|
2918
|
+
});
|
|
2919
|
+
}
|
|
2920
|
+
return { platform: "claude-code", results };
|
|
2921
|
+
}
|
|
2922
|
+
async function genericMarketplaceGroup(connector, platform, scope, projectDir) {
|
|
2923
|
+
const driver = getMarketplaceDriver(platform);
|
|
2924
|
+
if (!driver) return null;
|
|
2925
|
+
const id = connector.id;
|
|
2926
|
+
const record = readMarketplaceInstalls(id)[platform];
|
|
2927
|
+
const installed = driver.installed(id);
|
|
2928
|
+
if (!record && !installed) return null;
|
|
2929
|
+
const results = [];
|
|
2930
|
+
const direct = await directInstallPresent(connector, platform, scope, projectDir);
|
|
2931
|
+
if (direct && installed) {
|
|
2932
|
+
results.push({
|
|
2933
|
+
check: `${id}: marketplace duplicate-registration`,
|
|
2934
|
+
status: "fail",
|
|
2935
|
+
message: `installed BOTH directly and as a marketplace plugin on ${platform} \u2014 hooks + MCP server are duplicated`,
|
|
2936
|
+
fix: `uninstall one method: \`agent-connector uninstall --method marketplace --targets ${platform}\` or \`agent-connector uninstall --targets ${platform}\``
|
|
2937
|
+
});
|
|
2938
|
+
}
|
|
2939
|
+
if (record && !installed) {
|
|
2940
|
+
results.push({
|
|
2941
|
+
check: `${id}: marketplace state drift`,
|
|
2942
|
+
status: "warn",
|
|
2943
|
+
message: `recorded as marketplace-installed but ${platform}'s state shows the plugin absent`,
|
|
2944
|
+
fix: `re-run \`agent-connector install --method marketplace --targets ${platform}\` or \`agent-connector uninstall --method marketplace --targets ${platform}\` to reconcile`
|
|
2945
|
+
});
|
|
2946
|
+
} else if (!record && installed) {
|
|
2947
|
+
results.push({
|
|
2948
|
+
check: `${id}: marketplace state drift`,
|
|
2949
|
+
status: "warn",
|
|
2950
|
+
message: `${platform} lists the plugin installed but agent-connector has no record of it (manual install?)`,
|
|
2951
|
+
fix: `adopt it: \`agent-connector install --method marketplace --targets ${platform}\` (idempotent), or remove it via the host's plugin uninstall`
|
|
2952
|
+
});
|
|
2953
|
+
}
|
|
2954
|
+
if (record) {
|
|
2955
|
+
if (connector.version !== "0.0.0" && connector.version !== record.version) {
|
|
2956
|
+
results.push({
|
|
2957
|
+
check: `${id}: marketplace staleness`,
|
|
2958
|
+
status: "warn",
|
|
2959
|
+
message: `connector.version ${connector.version} \u2260 installed ${record.version}`,
|
|
2960
|
+
fix: `agent-connector upgrade --method marketplace --targets ${platform}`
|
|
2961
|
+
});
|
|
2962
|
+
} else if (existsSync10(record.bundleDir) && record.contentHash !== "" && hashDirectory(record.bundleDir) !== record.contentHash) {
|
|
2963
|
+
results.push({
|
|
2964
|
+
check: `${id}: marketplace staleness`,
|
|
2965
|
+
status: "warn",
|
|
2966
|
+
message: `staged bundle changed since install \u2014 ${platform}'s cached copy is stale`,
|
|
2967
|
+
fix: `agent-connector upgrade --method marketplace --targets ${platform}`
|
|
2968
|
+
});
|
|
2969
|
+
}
|
|
2970
|
+
}
|
|
2971
|
+
if (installed && !existsSync10(homeBinPath())) {
|
|
2972
|
+
results.push({
|
|
2973
|
+
check: `${id}: marketplace home-bin`,
|
|
2974
|
+
status: "fail",
|
|
2975
|
+
message: `installed plugin hooks exec ${homeBinPath()} which does not exist`,
|
|
2976
|
+
fix: "agent-connector upgrade"
|
|
2977
|
+
});
|
|
2978
|
+
}
|
|
2979
|
+
if (record && driver.binary() == null) {
|
|
2980
|
+
results.push({
|
|
2981
|
+
check: `${id}: marketplace host binary`,
|
|
2982
|
+
status: "warn",
|
|
2983
|
+
message: `${platform} CLI not found on PATH \u2014 the recorded marketplace install cannot be managed until it is reinstalled`
|
|
2984
|
+
});
|
|
2985
|
+
}
|
|
2986
|
+
if (results.every((r) => r.status === "pass")) {
|
|
2987
|
+
results.push({
|
|
2988
|
+
check: `${id}: marketplace install`,
|
|
2989
|
+
status: "pass",
|
|
2990
|
+
message: `${id} installed via marketplace on ${platform} (version ${record?.version ?? "unknown"})`
|
|
2991
|
+
});
|
|
2992
|
+
}
|
|
2993
|
+
return { platform, results };
|
|
2994
|
+
}
|
|
2995
|
+
|
|
2996
|
+
export {
|
|
2997
|
+
installConnector,
|
|
2998
|
+
syncConnector,
|
|
2999
|
+
uninstallConnector,
|
|
3000
|
+
purgeFrameworkState,
|
|
3001
|
+
resolveCliEntry,
|
|
3002
|
+
resolveUninstallTargets,
|
|
3003
|
+
parseInstallMethod,
|
|
3004
|
+
parseUninstallMethod,
|
|
3005
|
+
installViaMarketplace,
|
|
3006
|
+
uninstallViaMarketplace,
|
|
3007
|
+
upgradeViaMarketplace,
|
|
3008
|
+
marketplaceDoctorChecks
|
|
3009
|
+
};
|
|
3010
|
+
//# sourceMappingURL=chunk-KMAKDD3Z.js.map
|