@ken-jo/agent-connector 0.3.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 +12 -8
- package/dist/{chunk-DS3LUFVN.js → chunk-3RBLIHPZ.js} +15 -15
- package/dist/{chunk-44HUXXC6.js → chunk-G5CQHVQG.js} +232 -5
- package/dist/chunk-G5CQHVQG.js.map +1 -0
- package/dist/{chunk-QADSG2JY.js → chunk-KMAKDD3Z.js} +1130 -109
- package/dist/chunk-KMAKDD3Z.js.map +1 -0
- package/dist/cli/sdk.js +1 -1
- package/dist/cli.js +1 -1
- package/dist/{detect-MNDHGLSV.js → detect-U3E7NVNW.js} +2 -2
- package/dist/{doctor-N5KZDD55.js → doctor-DZTWED4O.js} +4 -4
- package/dist/{hook-7LUOKIXH.js → hook-EVY7LOUQ.js} +2 -2
- package/dist/{install-TQTT4F3I.js → install-OWAS5UIE.js} +4 -4
- package/dist/{leaderboard-MUJ7DURM.js → leaderboard-CSWHI4UY.js} +2 -2
- package/dist/{package-SYPA5POO.js → package-WFOS4QVU.js} +2 -2
- package/dist/{serve-XJOSA7BY.js → serve-3C2VFPAY.js} +2 -2
- package/dist/{status-DYSS6BOR.js → status-MKQYX5MW.js} +3 -3
- package/dist/{telemetry-IJ4GP2Q3.js → telemetry-3DN6XGYB.js} +2 -2
- package/dist/{uninstall-3DXIBRCV.js → uninstall-WG6PFWF3.js} +4 -4
- package/dist/{upgrade-GJEB3ITY.js → upgrade-32NC2RKI.js} +4 -4
- package/dist/{usage-2BJLM3GJ.js → usage-CJOM2WQM.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-44HUXXC6.js.map +0 -1
- package/dist/chunk-QADSG2JY.js.map +0 -1
- /package/dist/{chunk-DS3LUFVN.js.map → chunk-3RBLIHPZ.js.map} +0 -0
- /package/dist/{detect-MNDHGLSV.js.map → detect-U3E7NVNW.js.map} +0 -0
- /package/dist/{doctor-N5KZDD55.js.map → doctor-DZTWED4O.js.map} +0 -0
- /package/dist/{hook-7LUOKIXH.js.map → hook-EVY7LOUQ.js.map} +0 -0
- /package/dist/{install-TQTT4F3I.js.map → install-OWAS5UIE.js.map} +0 -0
- /package/dist/{leaderboard-MUJ7DURM.js.map → leaderboard-CSWHI4UY.js.map} +0 -0
- /package/dist/{package-SYPA5POO.js.map → package-WFOS4QVU.js.map} +0 -0
- /package/dist/{serve-XJOSA7BY.js.map → serve-3C2VFPAY.js.map} +0 -0
- /package/dist/{status-DYSS6BOR.js.map → status-MKQYX5MW.js.map} +0 -0
- /package/dist/{telemetry-IJ4GP2Q3.js.map → telemetry-3DN6XGYB.js.map} +0 -0
- /package/dist/{uninstall-3DXIBRCV.js.map → uninstall-WG6PFWF3.js.map} +0 -0
- /package/dist/{upgrade-GJEB3ITY.js.map → upgrade-32NC2RKI.js.map} +0 -0
- /package/dist/{usage-2BJLM3GJ.js.map → usage-CJOM2WQM.js.map} +0 -0
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
agyStagingRoot,
|
|
10
10
|
anyClaudeAgentConnectorPlugins,
|
|
11
11
|
anyCodexAgentConnectorPlugins,
|
|
12
|
+
anyDroidAgentConnectorPlugins,
|
|
12
13
|
anyMarketplaceRecordsRemain,
|
|
13
14
|
claudeKnownMarketplacePath,
|
|
14
15
|
claudePluginInstalled,
|
|
@@ -19,12 +20,25 @@ import {
|
|
|
19
20
|
codexPluginKey,
|
|
20
21
|
codexStagingRoot,
|
|
21
22
|
connectorHasMarketplaceRecords,
|
|
23
|
+
droidMarketplaceSource,
|
|
24
|
+
droidPluginInstalled,
|
|
25
|
+
droidPluginKey,
|
|
26
|
+
droidStagingRoot,
|
|
27
|
+
geminiExtensionInstalled,
|
|
28
|
+
geminiStagingRoot,
|
|
22
29
|
hashDirectory,
|
|
23
30
|
marketplaceEvidence,
|
|
24
31
|
marketplaceRoot,
|
|
32
|
+
npmConfigFilePath,
|
|
33
|
+
npmPluginArrayEntry,
|
|
34
|
+
npmPluginInstalled,
|
|
35
|
+
npmStagingRoot,
|
|
36
|
+
qwenExtensionInstalled,
|
|
37
|
+
qwenStagingRoot,
|
|
25
38
|
readMarketplaceInstalls,
|
|
39
|
+
readNpmConfig,
|
|
26
40
|
writeMarketplaceInstalls
|
|
27
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-G5CQHVQG.js";
|
|
28
42
|
import {
|
|
29
43
|
log,
|
|
30
44
|
spawnChild
|
|
@@ -409,15 +423,15 @@ function errMessage(err) {
|
|
|
409
423
|
}
|
|
410
424
|
|
|
411
425
|
// src/core/marketplace.ts
|
|
412
|
-
import { existsSync as
|
|
413
|
-
import { join as
|
|
426
|
+
import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync4, rmSync as rmSync9 } from "fs";
|
|
427
|
+
import { join as join10 } from "path";
|
|
414
428
|
|
|
415
429
|
// src/core/marketplace-drivers/claude.ts
|
|
416
430
|
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync, rmSync as rmSync2, writeFileSync } from "fs";
|
|
417
431
|
import { dirname as dirname2, join as join3 } from "path";
|
|
418
432
|
|
|
419
433
|
// src/core/marketplace-drivers/shared.ts
|
|
420
|
-
import { existsSync as existsSync2, statSync } from "fs";
|
|
434
|
+
import { existsSync as existsSync2, realpathSync, statSync } from "fs";
|
|
421
435
|
import { delimiter, join as join2, resolve } from "path";
|
|
422
436
|
var HOST_COMMAND_TIMEOUT_MS = 3e4;
|
|
423
437
|
function findOnPath(command) {
|
|
@@ -437,7 +451,10 @@ function findOnPath(command) {
|
|
|
437
451
|
}
|
|
438
452
|
return null;
|
|
439
453
|
}
|
|
440
|
-
function runHostCommand(command, args,
|
|
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;
|
|
441
458
|
return new Promise((resolvePromise) => {
|
|
442
459
|
let stdout = "";
|
|
443
460
|
let stderr = "";
|
|
@@ -453,7 +470,10 @@ function runHostCommand(command, args, timeoutMs = HOST_COMMAND_TIMEOUT_MS) {
|
|
|
453
470
|
try {
|
|
454
471
|
child = spawnChild(command, args, {
|
|
455
472
|
stdio: ["ignore", "pipe", "pipe"],
|
|
456
|
-
env: process.env
|
|
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 } : {}
|
|
457
477
|
});
|
|
458
478
|
} catch (err) {
|
|
459
479
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -512,7 +532,11 @@ function samePath(a, b) {
|
|
|
512
532
|
if (process.platform === "win32") {
|
|
513
533
|
s = s.replace(/^\\\\\?\\UNC\\/, "\\\\").replace(/^\\\\\?\\/, "");
|
|
514
534
|
}
|
|
515
|
-
|
|
535
|
+
try {
|
|
536
|
+
s = realpathSync.native(s);
|
|
537
|
+
} catch {
|
|
538
|
+
s = resolve(s);
|
|
539
|
+
}
|
|
516
540
|
return process.platform === "win32" ? s.toLowerCase() : s;
|
|
517
541
|
};
|
|
518
542
|
return norm(a) === norm(b);
|
|
@@ -1192,25 +1216,326 @@ var codexDriver = {
|
|
|
1192
1216
|
}
|
|
1193
1217
|
};
|
|
1194
1218
|
|
|
1195
|
-
// src/core/marketplace-drivers/
|
|
1196
|
-
import { existsSync as existsSync5, rmSync as rmSync4 } from "fs";
|
|
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";
|
|
1197
1221
|
import { join as join5 } from "path";
|
|
1198
|
-
|
|
1199
|
-
|
|
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)}`;
|
|
1200
1228
|
}
|
|
1201
1229
|
function errMessage4(err) {
|
|
1202
1230
|
return err instanceof Error ? err.message : String(err);
|
|
1203
1231
|
}
|
|
1232
|
+
function warn3(detail, path) {
|
|
1233
|
+
return { platform: PLATFORM3, action: "warn", detail, ...path ? { path } : {} };
|
|
1234
|
+
}
|
|
1204
1235
|
function failDetail3(step, r) {
|
|
1205
1236
|
if (r.timedOut) return `${step} timed out`;
|
|
1206
1237
|
if (r.error) return `${step} failed to spawn: ${r.error}`;
|
|
1207
1238
|
const line = firstLine(r.stderr) || firstLine(r.stdout);
|
|
1208
1239
|
return `${step} exited ${r.code}${line ? `: ${line}` : ""}`;
|
|
1209
1240
|
}
|
|
1210
|
-
function
|
|
1211
|
-
|
|
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();
|
|
1212
1287
|
const pluginDir = join5(stagingRoot, connector.id);
|
|
1213
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);
|
|
1214
1539
|
const result = packageConnector(connector, { outDir: stagingRoot, format: "agy-plugin" });
|
|
1215
1540
|
changes.push({
|
|
1216
1541
|
platform,
|
|
@@ -1221,27 +1546,27 @@ function stageAgyBundle(connector, platform, changes) {
|
|
|
1221
1546
|
return { pluginDir, contentHash: hashDirectory(pluginDir) };
|
|
1222
1547
|
}
|
|
1223
1548
|
function makeAgyDriver(platform) {
|
|
1224
|
-
const
|
|
1549
|
+
const warn6 = (detail, path) => ({
|
|
1225
1550
|
platform,
|
|
1226
1551
|
action: "warn",
|
|
1227
1552
|
detail,
|
|
1228
1553
|
...path ? { path } : {}
|
|
1229
1554
|
});
|
|
1230
|
-
const
|
|
1555
|
+
const manualInstall3 = (pluginDir) => `agy plugin install ${pluginDir}`;
|
|
1231
1556
|
return {
|
|
1232
1557
|
platform,
|
|
1233
1558
|
format: "agy-plugin",
|
|
1234
1559
|
binary: agyBinary,
|
|
1235
1560
|
stagingRoot: agyStagingRoot,
|
|
1236
1561
|
pluginDir(id) {
|
|
1237
|
-
return
|
|
1562
|
+
return join6(agyStagingRoot(), id);
|
|
1238
1563
|
},
|
|
1239
1564
|
installed: agyPluginInstalled,
|
|
1240
1565
|
stage(connector, changes) {
|
|
1241
1566
|
return stageAgyBundle(connector, platform, changes).contentHash;
|
|
1242
1567
|
},
|
|
1243
1568
|
planInstall(connector, changes) {
|
|
1244
|
-
const pluginDir =
|
|
1569
|
+
const pluginDir = join6(agyStagingRoot(), connector.id);
|
|
1245
1570
|
changes.push({
|
|
1246
1571
|
platform,
|
|
1247
1572
|
action: agyPluginInstalled(connector.id) ? "skip" : "create",
|
|
@@ -1249,13 +1574,13 @@ function makeAgyDriver(platform) {
|
|
|
1249
1574
|
});
|
|
1250
1575
|
},
|
|
1251
1576
|
planUninstall(id, changes) {
|
|
1252
|
-
const pluginDir =
|
|
1577
|
+
const pluginDir = join6(agyStagingRoot(), id);
|
|
1253
1578
|
changes.push({
|
|
1254
1579
|
platform,
|
|
1255
1580
|
action: agyPluginInstalled(id) ? "remove" : "skip",
|
|
1256
1581
|
detail: agyPluginInstalled(id) ? `run: agy plugin uninstall ${id}` : `plugin ${id} not installed on ${platform}`
|
|
1257
1582
|
});
|
|
1258
|
-
if (
|
|
1583
|
+
if (existsSync6(pluginDir)) {
|
|
1259
1584
|
changes.push({
|
|
1260
1585
|
platform,
|
|
1261
1586
|
action: "remove",
|
|
@@ -1266,12 +1591,12 @@ function makeAgyDriver(platform) {
|
|
|
1266
1591
|
},
|
|
1267
1592
|
async driveInstall(id) {
|
|
1268
1593
|
const changes = [];
|
|
1269
|
-
const pluginDir =
|
|
1594
|
+
const pluginDir = join6(agyStagingRoot(), id);
|
|
1270
1595
|
const bin = agyBinary();
|
|
1271
1596
|
if (!bin) {
|
|
1272
1597
|
changes.push(
|
|
1273
|
-
|
|
1274
|
-
`agy CLI not found on PATH \u2014 bundle staged but not installed. Install manually: ${
|
|
1598
|
+
warn6(
|
|
1599
|
+
`agy CLI not found on PATH \u2014 bundle staged but not installed. Install manually: ${manualInstall3(pluginDir)}`,
|
|
1275
1600
|
pluginDir
|
|
1276
1601
|
)
|
|
1277
1602
|
);
|
|
@@ -1289,7 +1614,7 @@ function makeAgyDriver(platform) {
|
|
|
1289
1614
|
const install = await runHostCommand(bin, ["plugin", "install", pluginDir]);
|
|
1290
1615
|
if (!install.ok || !agyPluginInstalled(id)) {
|
|
1291
1616
|
changes.push(
|
|
1292
|
-
|
|
1617
|
+
warn6(`plugin install did not complete \u2014 ` + failDetail4(`agy plugin install ${pluginDir}`, install))
|
|
1293
1618
|
);
|
|
1294
1619
|
return { changes, ok: false };
|
|
1295
1620
|
}
|
|
@@ -1313,7 +1638,7 @@ function makeAgyDriver(platform) {
|
|
|
1313
1638
|
const bin = agyBinary();
|
|
1314
1639
|
if (!bin) {
|
|
1315
1640
|
changes.push(
|
|
1316
|
-
|
|
1641
|
+
warn6(
|
|
1317
1642
|
`agy CLI not found on PATH \u2014 cannot drive the uninstall. Run manually: agy plugin uninstall ${id}`
|
|
1318
1643
|
)
|
|
1319
1644
|
);
|
|
@@ -1322,7 +1647,7 @@ function makeAgyDriver(platform) {
|
|
|
1322
1647
|
const uninstall = await runHostCommand(bin, ["plugin", "uninstall", id]);
|
|
1323
1648
|
if (!uninstall.ok || agyPluginInstalled(id)) {
|
|
1324
1649
|
changes.push(
|
|
1325
|
-
|
|
1650
|
+
warn6(`plugin uninstall did not complete \u2014 ` + failDetail4(`agy plugin uninstall ${id}`, uninstall))
|
|
1326
1651
|
);
|
|
1327
1652
|
return { changes, ok: false };
|
|
1328
1653
|
}
|
|
@@ -1338,12 +1663,12 @@ function makeAgyDriver(platform) {
|
|
|
1338
1663
|
// the probe already shows it present (the bundle on disk changed).
|
|
1339
1664
|
async driveUpdate(id) {
|
|
1340
1665
|
const changes = [];
|
|
1341
|
-
const pluginDir =
|
|
1666
|
+
const pluginDir = join6(agyStagingRoot(), id);
|
|
1342
1667
|
const bin = agyBinary();
|
|
1343
1668
|
if (!bin) {
|
|
1344
1669
|
changes.push(
|
|
1345
|
-
|
|
1346
|
-
`agy CLI not found on PATH \u2014 bundle re-staged but the installed copy was not updated. Run manually: ${
|
|
1670
|
+
warn6(
|
|
1671
|
+
`agy CLI not found on PATH \u2014 bundle re-staged but the installed copy was not updated. Run manually: ${manualInstall3(pluginDir)}`,
|
|
1347
1672
|
pluginDir
|
|
1348
1673
|
)
|
|
1349
1674
|
);
|
|
@@ -1352,7 +1677,7 @@ function makeAgyDriver(platform) {
|
|
|
1352
1677
|
const install = await runHostCommand(bin, ["plugin", "install", pluginDir]);
|
|
1353
1678
|
if (!install.ok || !agyPluginInstalled(id)) {
|
|
1354
1679
|
changes.push(
|
|
1355
|
-
|
|
1680
|
+
warn6(`plugin install did not complete \u2014 ` + failDetail4(`agy plugin install ${pluginDir}`, install))
|
|
1356
1681
|
);
|
|
1357
1682
|
return { changes, ok: false };
|
|
1358
1683
|
}
|
|
@@ -1366,10 +1691,10 @@ function makeAgyDriver(platform) {
|
|
|
1366
1691
|
// Direct driver: no catalog, no marketplace de-registration. Cleanup is just
|
|
1367
1692
|
// removing the staged bundle dir (the host copied it into its own store).
|
|
1368
1693
|
async finishUninstall(id, changes) {
|
|
1369
|
-
const pluginDir =
|
|
1370
|
-
if (
|
|
1694
|
+
const pluginDir = join6(agyStagingRoot(), id);
|
|
1695
|
+
if (existsSync6(pluginDir)) {
|
|
1371
1696
|
try {
|
|
1372
|
-
|
|
1697
|
+
rmSync5(pluginDir, { recursive: true, force: true });
|
|
1373
1698
|
changes.push({
|
|
1374
1699
|
platform,
|
|
1375
1700
|
action: "remove",
|
|
@@ -1381,7 +1706,7 @@ function makeAgyDriver(platform) {
|
|
|
1381
1706
|
platform,
|
|
1382
1707
|
action: "warn",
|
|
1383
1708
|
path: pluginDir,
|
|
1384
|
-
detail: `could not remove staged bundle: ${
|
|
1709
|
+
detail: `could not remove staged bundle: ${errMessage5(err)}`
|
|
1385
1710
|
});
|
|
1386
1711
|
}
|
|
1387
1712
|
}
|
|
@@ -1389,71 +1714,740 @@ function makeAgyDriver(platform) {
|
|
|
1389
1714
|
};
|
|
1390
1715
|
}
|
|
1391
1716
|
|
|
1392
|
-
// src/core/marketplace-drivers/
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
return
|
|
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);
|
|
1401
1726
|
}
|
|
1402
|
-
function
|
|
1403
|
-
|
|
1404
|
-
case "claude-code":
|
|
1405
|
-
return claudeDriver;
|
|
1406
|
-
case "codex":
|
|
1407
|
-
return codexDriver;
|
|
1408
|
-
case "antigravity":
|
|
1409
|
-
case "antigravity-cli":
|
|
1410
|
-
return agyDriver(platform);
|
|
1411
|
-
default:
|
|
1412
|
-
return null;
|
|
1413
|
-
}
|
|
1727
|
+
function warn4(detail, path) {
|
|
1728
|
+
return { platform: PLATFORM4, action: "warn", detail, ...path ? { path } : {} };
|
|
1414
1729
|
}
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
}
|
|
1421
|
-
return null;
|
|
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}` : ""}`;
|
|
1422
1735
|
}
|
|
1423
|
-
function
|
|
1424
|
-
|
|
1425
|
-
return value ?? "auto";
|
|
1426
|
-
}
|
|
1427
|
-
return null;
|
|
1736
|
+
function manualInstall(pluginDir) {
|
|
1737
|
+
return `gemini extensions install ${pluginDir} --consent`;
|
|
1428
1738
|
}
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
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",
|
|
1449
2437
|
"codex",
|
|
1450
2438
|
"antigravity",
|
|
1451
|
-
"antigravity-cli"
|
|
2439
|
+
"antigravity-cli",
|
|
2440
|
+
"gemini-cli",
|
|
2441
|
+
"qwen-code",
|
|
2442
|
+
"droid",
|
|
2443
|
+
"opencode",
|
|
2444
|
+
"kilo",
|
|
2445
|
+
"kilo-cli"
|
|
1452
2446
|
]);
|
|
1453
2447
|
function newResult2(connectorId, dryRun) {
|
|
1454
2448
|
return { connectorId, dryRun, changes: [], warnings: [] };
|
|
1455
2449
|
}
|
|
1456
|
-
function
|
|
2450
|
+
function errMessage9(err) {
|
|
1457
2451
|
return err instanceof Error ? err.message : String(err);
|
|
1458
2452
|
}
|
|
1459
2453
|
function manualHint(platform, format, id) {
|
|
@@ -1477,7 +2471,7 @@ async function directInstallPresent(connector, platform, scope, projectDir) {
|
|
|
1477
2471
|
const hookCommand = new RegExp(`--connector\\s+${escaped}(?![\\w-])`);
|
|
1478
2472
|
const matches = (path, re) => {
|
|
1479
2473
|
try {
|
|
1480
|
-
return re.test(
|
|
2474
|
+
return re.test(readFileSync4(path, "utf8"));
|
|
1481
2475
|
} catch {
|
|
1482
2476
|
return false;
|
|
1483
2477
|
}
|
|
@@ -1508,12 +2502,12 @@ async function installViaMarketplace(opts) {
|
|
|
1508
2502
|
try {
|
|
1509
2503
|
ensureHomeBin(resolveCliEntry());
|
|
1510
2504
|
} catch (err) {
|
|
1511
|
-
log.warn(`ensureHomeBin failed: ${
|
|
2505
|
+
log.warn(`ensureHomeBin failed: ${errMessage9(err)}`);
|
|
1512
2506
|
}
|
|
1513
2507
|
try {
|
|
1514
2508
|
registerConnector(connector, modulePath);
|
|
1515
2509
|
} catch (err) {
|
|
1516
|
-
log.warn(`registerConnector failed: ${
|
|
2510
|
+
log.warn(`registerConnector failed: ${errMessage9(err)}`);
|
|
1517
2511
|
}
|
|
1518
2512
|
}
|
|
1519
2513
|
const explicit = opts.targets != null && opts.targets.length > 0;
|
|
@@ -1663,16 +2657,16 @@ async function uninstallViaMarketplace(opts) {
|
|
|
1663
2657
|
const root2 = driver.stagingRoot();
|
|
1664
2658
|
if (seenRoots.has(root2)) continue;
|
|
1665
2659
|
seenRoots.add(root2);
|
|
1666
|
-
if (!
|
|
2660
|
+
if (!existsSync10(root2)) continue;
|
|
1667
2661
|
let bundleDirs;
|
|
1668
2662
|
try {
|
|
1669
|
-
bundleDirs =
|
|
2663
|
+
bundleDirs = readdirSync5(root2).filter((n) => !n.startsWith("."));
|
|
1670
2664
|
} catch {
|
|
1671
2665
|
bundleDirs = [];
|
|
1672
2666
|
}
|
|
1673
2667
|
if (bundleDirs.length > 0) continue;
|
|
1674
2668
|
try {
|
|
1675
|
-
|
|
2669
|
+
rmSync9(root2, { recursive: true, force: true });
|
|
1676
2670
|
result.changes.push({
|
|
1677
2671
|
platform: id,
|
|
1678
2672
|
action: "remove",
|
|
@@ -1680,13 +2674,13 @@ async function uninstallViaMarketplace(opts) {
|
|
|
1680
2674
|
detail: "removed empty marketplace staging root (--purge)"
|
|
1681
2675
|
});
|
|
1682
2676
|
} catch (err) {
|
|
1683
|
-
log.warn(`marketplace staging-root removal failed: ${
|
|
2677
|
+
log.warn(`marketplace staging-root removal failed: ${errMessage9(err)}`);
|
|
1684
2678
|
}
|
|
1685
2679
|
}
|
|
1686
2680
|
const root = marketplaceRoot();
|
|
1687
2681
|
try {
|
|
1688
|
-
if (
|
|
1689
|
-
|
|
2682
|
+
if (existsSync10(root) && readdirSync5(root).length === 0) {
|
|
2683
|
+
rmSync9(root, { recursive: true, force: true });
|
|
1690
2684
|
}
|
|
1691
2685
|
} catch {
|
|
1692
2686
|
}
|
|
@@ -1784,11 +2778,21 @@ async function marketplaceDoctorChecks(connector, scope, projectDir) {
|
|
|
1784
2778
|
const claude = await claudeMarketplaceGroup(connector, scope, projectDir);
|
|
1785
2779
|
if (claude) groups.push(claude);
|
|
1786
2780
|
const seenHosts = /* @__PURE__ */ new Set();
|
|
1787
|
-
const candidates = [
|
|
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));
|
|
1788
2792
|
for (const platform of candidates) {
|
|
1789
2793
|
const driver = getMarketplaceDriver(platform);
|
|
1790
2794
|
if (!driver) continue;
|
|
1791
|
-
const hostKey = driver.stagingRoot();
|
|
2795
|
+
const hostKey = doctorHostKey(platform, driver.stagingRoot());
|
|
1792
2796
|
if (seenHosts.has(hostKey)) continue;
|
|
1793
2797
|
const group = await genericMarketplaceGroup(connector, platform, scope, projectDir);
|
|
1794
2798
|
if (group) {
|
|
@@ -1799,11 +2803,28 @@ async function marketplaceDoctorChecks(connector, scope, projectDir) {
|
|
|
1799
2803
|
groups.sort((a, b) => platformDoctorRank(a.platform) - platformDoctorRank(b.platform));
|
|
1800
2804
|
return groups;
|
|
1801
2805
|
}
|
|
2806
|
+
function doctorHostKey(platform, stagingRoot) {
|
|
2807
|
+
if (platform === "opencode" || platform === "kilo" || platform === "kilo-cli") {
|
|
2808
|
+
return npmConfigFilePath(platform);
|
|
2809
|
+
}
|
|
2810
|
+
return stagingRoot;
|
|
2811
|
+
}
|
|
1802
2812
|
function recordRank(connectorId, platform) {
|
|
1803
2813
|
return readMarketplaceInstalls(connectorId)[platform] ? 0 : 1;
|
|
1804
2814
|
}
|
|
1805
2815
|
function platformDoctorRank(platform) {
|
|
1806
|
-
const order = [
|
|
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
|
+
];
|
|
1807
2828
|
const i = order.indexOf(platform);
|
|
1808
2829
|
return i === -1 ? order.length : i;
|
|
1809
2830
|
}
|
|
@@ -1826,19 +2847,19 @@ async function claudeMarketplaceGroup(connector, scope, projectDir) {
|
|
|
1826
2847
|
}
|
|
1827
2848
|
if (record) {
|
|
1828
2849
|
const registeredAt = claudeKnownMarketplacePath(MARKETPLACE_NAME);
|
|
1829
|
-
const catalogPath =
|
|
2850
|
+
const catalogPath = join10(stagingRoot, ".claude-plugin", "marketplace.json");
|
|
1830
2851
|
let catalogListed = false;
|
|
1831
2852
|
try {
|
|
1832
|
-
const catalog = JSON.parse(
|
|
2853
|
+
const catalog = JSON.parse(readFileSync4(catalogPath, "utf8"));
|
|
1833
2854
|
catalogListed = (catalog.plugins ?? []).some((p) => p.name === id);
|
|
1834
2855
|
} catch {
|
|
1835
2856
|
catalogListed = false;
|
|
1836
2857
|
}
|
|
1837
|
-
const intact = registeredAt === stagingRoot &&
|
|
2858
|
+
const intact = registeredAt === stagingRoot && existsSync10(record.bundleDir) && catalogListed;
|
|
1838
2859
|
results.push({
|
|
1839
2860
|
check: `${id}: marketplace registration`,
|
|
1840
2861
|
status: intact ? "pass" : "fail",
|
|
1841
|
-
message: intact ? `local marketplace "${MARKETPLACE_NAME}" registered at ${stagingRoot}, bundle staged + cataloged` : `marketplace registration broken (registered at ${registeredAt ?? "nowhere"}, bundle ${
|
|
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"})`,
|
|
1842
2863
|
...intact ? {} : { fix: `agent-connector install --method marketplace --targets claude-code` }
|
|
1843
2864
|
});
|
|
1844
2865
|
}
|
|
@@ -1865,7 +2886,7 @@ async function claudeMarketplaceGroup(connector, scope, projectDir) {
|
|
|
1865
2886
|
message: `connector.version ${connector.version} \u2260 installed ${record.version}`,
|
|
1866
2887
|
fix: `agent-connector upgrade --method marketplace --targets claude-code`
|
|
1867
2888
|
});
|
|
1868
|
-
} else if (
|
|
2889
|
+
} else if (existsSync10(record.bundleDir) && record.contentHash !== "" && hashDirectory(record.bundleDir) !== record.contentHash) {
|
|
1869
2890
|
results.push({
|
|
1870
2891
|
check: `${id}: marketplace staleness`,
|
|
1871
2892
|
status: "warn",
|
|
@@ -1874,7 +2895,7 @@ async function claudeMarketplaceGroup(connector, scope, projectDir) {
|
|
|
1874
2895
|
});
|
|
1875
2896
|
}
|
|
1876
2897
|
}
|
|
1877
|
-
if (pluginPresent && !
|
|
2898
|
+
if (pluginPresent && !existsSync10(homeBinPath())) {
|
|
1878
2899
|
results.push({
|
|
1879
2900
|
check: `${id}: marketplace home-bin`,
|
|
1880
2901
|
status: "fail",
|
|
@@ -1938,7 +2959,7 @@ async function genericMarketplaceGroup(connector, platform, scope, projectDir) {
|
|
|
1938
2959
|
message: `connector.version ${connector.version} \u2260 installed ${record.version}`,
|
|
1939
2960
|
fix: `agent-connector upgrade --method marketplace --targets ${platform}`
|
|
1940
2961
|
});
|
|
1941
|
-
} else if (
|
|
2962
|
+
} else if (existsSync10(record.bundleDir) && record.contentHash !== "" && hashDirectory(record.bundleDir) !== record.contentHash) {
|
|
1942
2963
|
results.push({
|
|
1943
2964
|
check: `${id}: marketplace staleness`,
|
|
1944
2965
|
status: "warn",
|
|
@@ -1947,7 +2968,7 @@ async function genericMarketplaceGroup(connector, platform, scope, projectDir) {
|
|
|
1947
2968
|
});
|
|
1948
2969
|
}
|
|
1949
2970
|
}
|
|
1950
|
-
if (installed && !
|
|
2971
|
+
if (installed && !existsSync10(homeBinPath())) {
|
|
1951
2972
|
results.push({
|
|
1952
2973
|
check: `${id}: marketplace home-bin`,
|
|
1953
2974
|
status: "fail",
|
|
@@ -1986,4 +3007,4 @@ export {
|
|
|
1986
3007
|
upgradeViaMarketplace,
|
|
1987
3008
|
marketplaceDoctorChecks
|
|
1988
3009
|
};
|
|
1989
|
-
//# sourceMappingURL=chunk-
|
|
3010
|
+
//# sourceMappingURL=chunk-KMAKDD3Z.js.map
|