agentsw 0.7.1 → 0.7.2
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 +30 -11
- package/README.zh-CN.md +26 -11
- package/dist/apps.js +48 -44
- package/dist/commands.js +221 -74
- package/dist/config-transaction.js +169 -84
- package/dist/discover.js +55 -17
- package/dist/envfile.js +101 -20
- package/dist/fsutil.js +109 -14
- package/dist/i18n.js +2 -1
- package/dist/import.js +2 -2
- package/dist/index.js +16 -1
- package/dist/jsonc.js +4 -10
- package/dist/modelsdev.js +7 -3
- package/dist/provider-identity.js +25 -0
- package/dist/remove.js +84 -38
- package/dist/rename.js +91 -58
- package/dist/store.js +38 -7
- package/dist/target-transaction.js +25 -0
- package/dist/targets/claudecode.js +12 -3
- package/dist/targets/codex.js +34 -3
- package/dist/targets/dsh.js +117 -65
- package/dist/targets/hermes.js +98 -49
- package/dist/targets/omp.js +11 -47
- package/dist/targets/opencode.js +27 -6
- package/dist/targets/pistyle.js +12 -8
- package/dist/targets/wire.js +15 -9
- package/dist/targets/workbuddy.js +151 -76
- package/dist/yaml.js +39 -0
- package/package.json +4 -2
package/dist/commands.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { parse as parseJsonc } from "jsonc-parser";
|
|
3
|
+
import { parse as parseToml } from "smol-toml";
|
|
4
|
+
import { parseDocument } from "yaml";
|
|
5
|
+
import { envAssignments } from "./envfile.js";
|
|
1
6
|
import pc from "picocolors";
|
|
2
7
|
import prompts from "prompts";
|
|
3
8
|
import { loadStore, saveStore, getProvider, configFile } from "./store.js";
|
|
@@ -5,7 +10,7 @@ import { scanCandidates, normalizeUrl, findMatchingProvider } from "./import.js"
|
|
|
5
10
|
import { enrichModels, loadCatalog, searchCatalog } from "./modelsdev.js";
|
|
6
11
|
import { resolveTargets, supportsProtocol, targets } from "./targets/index.js";
|
|
7
12
|
import { discoverProviderModels, probeProtocols } from "./discover.js";
|
|
8
|
-
import { appCommand, appPackages, installedVersion, isNewer, latestVersion, runShell } from "./apps.js";
|
|
13
|
+
import { appCommand, appPackages, installedVersion, isNewer, latestVersion, normalizeAppVersion, runShell } from "./apps.js";
|
|
9
14
|
import { drainPendingWrites, readTextIfExists, setDryRun } from "./fsutil.js";
|
|
10
15
|
import { applyModelFilter } from "./filter.js";
|
|
11
16
|
import { availableProviderId, providerIdFromBaseUrl, providerNameFromBaseUrl } from "./slug.js";
|
|
@@ -89,14 +94,15 @@ function guessProviderHint(catalog, baseUrl) {
|
|
|
89
94
|
}
|
|
90
95
|
return undefined;
|
|
91
96
|
}
|
|
92
|
-
function parseFilterOpts(opts) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
function parseFilterOpts(opts, previous) {
|
|
98
|
+
if (opts.include === undefined && opts.exclude === undefined && opts.dedup === undefined)
|
|
99
|
+
return previous;
|
|
100
|
+
return {
|
|
101
|
+
...previous,
|
|
102
|
+
...(opts.include !== undefined ? { include: opts.include.split(",").map((x) => x.trim()).filter(Boolean) } : {}),
|
|
103
|
+
...(opts.exclude !== undefined ? { exclude: opts.exclude.split(",").map((x) => x.trim()).filter(Boolean) } : {}),
|
|
104
|
+
...(opts.dedup !== undefined ? { dedup: opts.dedup } : {}),
|
|
105
|
+
};
|
|
100
106
|
}
|
|
101
107
|
function reportDropped(dropped) {
|
|
102
108
|
if (!dropped.length)
|
|
@@ -179,13 +185,16 @@ export async function cmdAdd(opts) {
|
|
|
179
185
|
if (protocol !== "openai" && protocol !== "anthropic")
|
|
180
186
|
fail(t("add.protocolInvalid"));
|
|
181
187
|
const wire = answers.openaiApi ?? opts.openaiApi;
|
|
182
|
-
if (wire && wire !== "completions" && wire !== "responses")
|
|
188
|
+
if (wire !== undefined && wire !== "completions" && wire !== "responses")
|
|
183
189
|
fail(t("add.openaiApiInvalid"));
|
|
184
|
-
const openaiApi = protocol === "openai" ? wire : undefined;
|
|
185
190
|
const baseUrl = normalizeUrl(answers.baseUrl);
|
|
191
|
+
const explicitId = answers.id || undefined;
|
|
192
|
+
const existing = explicitId ? store.providers[explicitId] : findMatchingProvider(Object.values(store.providers), { protocol, baseUrl, apiKey: answers.apiKey });
|
|
193
|
+
const id = explicitId ?? existing?.id ?? availableProviderId(providerIdFromBaseUrl(baseUrl, protocol), store.providers);
|
|
194
|
+
const openaiApi = protocol === "openai" ? wire ?? existing?.openaiApi : undefined;
|
|
186
195
|
const manualIds = (answers.models ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
187
196
|
let modelIds = manualIds;
|
|
188
|
-
const modelFilter = parseFilterOpts(opts);
|
|
197
|
+
const modelFilter = parseFilterOpts(opts, existing?.modelFilter);
|
|
189
198
|
const shouldDiscover = opts.discover || manualIds.length === 0;
|
|
190
199
|
if (shouldDiscover) {
|
|
191
200
|
if (!opts.discover) {
|
|
@@ -196,35 +205,35 @@ export async function cmdAdd(opts) {
|
|
|
196
205
|
console.log(t("add.providerLists", { count: discovered.length }));
|
|
197
206
|
modelIds = [...new Set([...manualIds, ...discovered])];
|
|
198
207
|
}
|
|
199
|
-
const
|
|
208
|
+
const retained = [opts.defaultModel === undefined ? existing?.defaultModel : undefined, opts.smallModel === undefined ? existing?.smallModel : undefined].filter((id) => !!id);
|
|
209
|
+
modelIds = [...new Set([...modelIds, ...retained])];
|
|
210
|
+
const pinned = [...manualIds, ...retained, ...(opts.defaultModel ? [opts.defaultModel] : []), ...(opts.smallModel ? [opts.smallModel] : [])];
|
|
200
211
|
const outcome = applyModelFilter(modelIds, modelFilter, pinned);
|
|
201
212
|
reportDropped(outcome.dropped);
|
|
202
213
|
modelIds = outcome.kept;
|
|
203
214
|
if (modelIds.length === 0)
|
|
204
215
|
fail(t("add.atLeastOne"));
|
|
205
216
|
const catalog = await loadCatalog();
|
|
206
|
-
const hint = opts.modelsDev ?? guessProviderHint(catalog, answers.baseUrl);
|
|
217
|
+
const hint = opts.modelsDev ?? existing?.modelsDevId ?? guessProviderHint(catalog, answers.baseUrl);
|
|
207
218
|
const models = enrichModels(catalog, modelIds, hint);
|
|
208
219
|
const matched = models.filter((m) => m.contextWindow !== undefined).length;
|
|
209
|
-
const defaultModel = opts.defaultModel ?? modelIds[0];
|
|
220
|
+
const defaultModel = opts.defaultModel ?? existing?.defaultModel ?? modelIds[0];
|
|
210
221
|
if (!modelIds.includes(defaultModel))
|
|
211
222
|
fail(t("add.defaultMissing", { model: defaultModel }));
|
|
212
223
|
if (opts.smallModel && !modelIds.includes(opts.smallModel))
|
|
213
224
|
fail(t("add.smallMissing", { model: opts.smallModel }));
|
|
214
|
-
const explicitId = answers.id || undefined;
|
|
215
|
-
const existing = explicitId ? undefined : findMatchingProvider(Object.values(store.providers), { protocol, baseUrl, apiKey: answers.apiKey });
|
|
216
|
-
const id = explicitId ?? existing?.id ?? availableProviderId(providerIdFromBaseUrl(baseUrl, protocol), store.providers);
|
|
217
225
|
const provider = {
|
|
226
|
+
...existing,
|
|
218
227
|
id,
|
|
219
228
|
name: answers.name || existing?.name || (explicitId ? explicitId : providerNameFromBaseUrl(baseUrl, protocol)),
|
|
220
229
|
protocol,
|
|
221
|
-
|
|
230
|
+
openaiApi,
|
|
222
231
|
baseUrl,
|
|
223
232
|
apiKey: answers.apiKey,
|
|
224
233
|
models,
|
|
225
234
|
defaultModel,
|
|
226
|
-
smallModel: opts.smallModel,
|
|
227
|
-
reasoningEffort: opts.reasoningEffort,
|
|
235
|
+
smallModel: opts.smallModel ?? existing?.smallModel,
|
|
236
|
+
reasoningEffort: opts.reasoningEffort ?? existing?.reasoningEffort,
|
|
228
237
|
modelsDevId: hint,
|
|
229
238
|
modelFilter,
|
|
230
239
|
};
|
|
@@ -244,23 +253,27 @@ export async function cmdAdd(opts) {
|
|
|
244
253
|
async function createProvider(opts) {
|
|
245
254
|
const { store, id, name, protocol, openaiApi, baseUrl, apiKey, modelIds, modelFilter } = opts;
|
|
246
255
|
const catalog = await loadCatalog();
|
|
247
|
-
const hint = guessProviderHint(catalog, baseUrl);
|
|
256
|
+
const hint = opts.modelsDev ?? opts.existing?.modelsDevId ?? guessProviderHint(catalog, baseUrl);
|
|
248
257
|
const models = enrichModels(catalog, modelIds, hint);
|
|
249
258
|
const matched = models.filter((m) => m.contextWindow !== undefined).length;
|
|
250
|
-
const defaultModel = opts.defaultModel ?? modelIds[0];
|
|
259
|
+
const defaultModel = opts.defaultModel ?? opts.existing?.defaultModel ?? modelIds[0];
|
|
251
260
|
if (!modelIds.includes(defaultModel))
|
|
252
261
|
fail(t("add.defaultMissing", { model: defaultModel }));
|
|
262
|
+
const smallModel = opts.smallModel ?? opts.existing?.smallModel;
|
|
263
|
+
if (smallModel && !modelIds.includes(smallModel))
|
|
264
|
+
fail(t("add.smallMissing", { model: smallModel }));
|
|
253
265
|
const provider = {
|
|
266
|
+
...opts.existing,
|
|
254
267
|
id,
|
|
255
268
|
name,
|
|
256
269
|
protocol,
|
|
257
|
-
|
|
270
|
+
openaiApi: protocol === "openai" ? openaiApi ?? opts.existing?.openaiApi : undefined,
|
|
258
271
|
baseUrl,
|
|
259
272
|
apiKey,
|
|
260
273
|
models,
|
|
261
274
|
defaultModel,
|
|
262
|
-
smallModel
|
|
263
|
-
reasoningEffort: opts.reasoningEffort,
|
|
275
|
+
smallModel,
|
|
276
|
+
reasoningEffort: opts.reasoningEffort ?? opts.existing?.reasoningEffort,
|
|
264
277
|
modelsDevId: hint,
|
|
265
278
|
modelFilter,
|
|
266
279
|
};
|
|
@@ -306,19 +319,22 @@ export async function cmdQuickAdd(opts) {
|
|
|
306
319
|
if (protocols.length === 0) {
|
|
307
320
|
fail(t("quick.noProtocol"));
|
|
308
321
|
}
|
|
309
|
-
|
|
322
|
+
if (opts.openaiApi !== undefined && opts.openaiApi !== "completions" && opts.openaiApi !== "responses")
|
|
323
|
+
fail(t("add.openaiApiInvalid"));
|
|
310
324
|
const multi = protocols.length > 1;
|
|
311
325
|
const createdIds = [];
|
|
312
326
|
for (const protocol of protocols) {
|
|
313
327
|
const explicitId = opts.id === undefined ? undefined : `${opts.id}${multi ? `-${protocol}` : ""}`;
|
|
314
|
-
const existing = explicitId === undefined ? findMatchingProvider(Object.values(store.providers), { protocol, baseUrl, apiKey }) :
|
|
328
|
+
const existing = explicitId === undefined ? findMatchingProvider(Object.values(store.providers), { protocol, baseUrl, apiKey }) : store.providers[explicitId];
|
|
315
329
|
const id = explicitId ?? existing?.id ?? availableProviderId(providerIdFromBaseUrl(baseUrl, protocol), store.providers);
|
|
316
|
-
const name = existing?.name ?? (opts.id === undefined ? providerNameFromBaseUrl(baseUrl, protocol) : multi ? `${opts.id} (${protocol})` : opts.id);
|
|
330
|
+
const name = opts.name ?? existing?.name ?? (opts.id === undefined ? providerNameFromBaseUrl(baseUrl, protocol) : multi ? `${opts.id} (${protocol})` : opts.id);
|
|
331
|
+
const modelFilter = parseFilterOpts(opts, existing?.modelFilter);
|
|
317
332
|
process.stderr.write(pc.dim(`${t("add.discovering", { url: baseUrl })} [${protocol}]\n`));
|
|
318
333
|
const discovered = await discoverProviderModels({ baseUrl, apiKey, protocol });
|
|
319
334
|
console.log(t("add.providerLists", { count: discovered.length }));
|
|
320
|
-
|
|
321
|
-
|
|
335
|
+
const retained = [opts.defaultModel === undefined ? existing?.defaultModel : undefined, opts.smallModel === undefined ? existing?.smallModel : undefined].filter((id) => !!id);
|
|
336
|
+
let modelIds = [...new Set([...discovered, ...retained])];
|
|
337
|
+
const pinned = [...retained, ...(opts.defaultModel ? [opts.defaultModel] : []), ...(opts.smallModel ? [opts.smallModel] : [])];
|
|
322
338
|
const outcome = applyModelFilter(modelIds, modelFilter, pinned);
|
|
323
339
|
reportDropped(outcome.dropped);
|
|
324
340
|
modelIds = outcome.kept;
|
|
@@ -338,6 +354,9 @@ export async function cmdQuickAdd(opts) {
|
|
|
338
354
|
smallModel: opts.smallModel,
|
|
339
355
|
reasoningEffort: opts.reasoningEffort,
|
|
340
356
|
modelFilter,
|
|
357
|
+
existing,
|
|
358
|
+
openaiApi: opts.openaiApi,
|
|
359
|
+
modelsDev: opts.modelsDev,
|
|
341
360
|
});
|
|
342
361
|
createdIds.push(provider.id);
|
|
343
362
|
console.log(pc.dim(`\n${t("add.next", { id: provider.id })}`));
|
|
@@ -384,7 +403,7 @@ function reportResults(results) {
|
|
|
384
403
|
console.log(` ${" ".repeat(9)} ${pc.dim(note)}`);
|
|
385
404
|
}
|
|
386
405
|
}
|
|
387
|
-
async function runTargets(op, provider, appsFilter) {
|
|
406
|
+
async function runTargets(op, provider, appsFilter, redactErrors = false) {
|
|
388
407
|
const selected = resolveTargets(appsFilter);
|
|
389
408
|
const explicit = appsFilter !== undefined && appsFilter !== "all";
|
|
390
409
|
const results = [];
|
|
@@ -398,48 +417,131 @@ async function runTargets(op, provider, appsFilter) {
|
|
|
398
417
|
});
|
|
399
418
|
continue;
|
|
400
419
|
}
|
|
401
|
-
if (!target.detect() && !explicit) {
|
|
402
|
-
results.push({ app: target.id, changed: [], notes: [], skipped: `${target.name} not detected (pass --apps ${target.id} to force)` });
|
|
403
|
-
continue;
|
|
404
|
-
}
|
|
405
420
|
try {
|
|
421
|
+
if (!explicit && !target.detect()) {
|
|
422
|
+
results.push({ app: target.id, changed: [], notes: [], skipped: `${target.name} not detected (pass --apps ${target.id} to force)` });
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
406
425
|
results.push(await target[op](provider));
|
|
407
426
|
}
|
|
408
427
|
catch (err) {
|
|
409
|
-
results.push({ app: target.id, changed: [], notes: [], skipped: pc.red(`failed: ${err.message}`) });
|
|
428
|
+
results.push({ app: target.id, changed: [], notes: [], skipped: pc.red(redactErrors ? "failed: configuration could not be previewed safely" : `failed: ${err.message}`) });
|
|
410
429
|
process.exitCode = 1;
|
|
411
430
|
}
|
|
412
431
|
}
|
|
413
432
|
return results;
|
|
414
433
|
}
|
|
415
|
-
/**
|
|
416
|
-
function
|
|
434
|
+
/** Parse first: an unknown format or malformed document must never fall back to raw secrets. */
|
|
435
|
+
function previewDocument(file, text) {
|
|
436
|
+
if (!text.trim())
|
|
437
|
+
return undefined;
|
|
438
|
+
if (path.basename(file) === ".env" || file.endsWith(".env")) {
|
|
439
|
+
return Object.fromEntries(envAssignments(file, text).map(({ name }) => [name, "[REDACTED]"]));
|
|
440
|
+
}
|
|
441
|
+
switch (path.extname(file).toLowerCase()) {
|
|
442
|
+
case ".json":
|
|
443
|
+
case ".jsonc": {
|
|
444
|
+
const errors = [];
|
|
445
|
+
const value = parseJsonc(text, errors, { allowTrailingComma: true });
|
|
446
|
+
if (errors.length)
|
|
447
|
+
throw new Error("invalid JSON configuration");
|
|
448
|
+
return value;
|
|
449
|
+
}
|
|
450
|
+
case ".toml": return parseToml(text);
|
|
451
|
+
case ".yaml":
|
|
452
|
+
case ".yml": {
|
|
453
|
+
const document = parseDocument(text);
|
|
454
|
+
if (document.errors.length)
|
|
455
|
+
throw new Error("invalid YAML configuration");
|
|
456
|
+
return document.toJS();
|
|
457
|
+
}
|
|
458
|
+
default: throw new Error("unsupported configuration format");
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
/** Normalized semantic diff: comments and unparseable raw input never enter output. */
|
|
462
|
+
function printFileDiff(file, next, apiKey) {
|
|
417
463
|
const before = readTextIfExists(file) ?? "";
|
|
418
464
|
if (before === next) {
|
|
419
465
|
console.log(`${pc.dim("unchanged")} ${file}`);
|
|
420
466
|
return;
|
|
421
467
|
}
|
|
422
|
-
console.log(pc.bold(`--- ${file}`));
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
468
|
+
console.log(pc.bold(`--- ${file} (redacted configuration)`));
|
|
469
|
+
let oldText;
|
|
470
|
+
let newText;
|
|
471
|
+
try {
|
|
472
|
+
const oldDocument = previewDocument(file, before);
|
|
473
|
+
const newDocument = previewDocument(file, next);
|
|
474
|
+
const sensitive = /key|token|secret|password|auth|credential|cookie|headers/i;
|
|
475
|
+
const credentialFile = path.basename(file) === ".credentials.yaml";
|
|
476
|
+
const secrets = new Set(apiKey ? [apiKey] : []);
|
|
477
|
+
const visited = new WeakSet();
|
|
478
|
+
const collect = (value, hidden = false) => {
|
|
479
|
+
if (typeof value === "string" && hidden && value)
|
|
480
|
+
secrets.add(value);
|
|
481
|
+
if (!value || typeof value !== "object" || visited.has(value))
|
|
482
|
+
return;
|
|
483
|
+
visited.add(value);
|
|
484
|
+
for (const [key, child] of Object.entries(value))
|
|
485
|
+
collect(child, hidden || sensitive.test(key));
|
|
486
|
+
};
|
|
487
|
+
collect(oldDocument, credentialFile);
|
|
488
|
+
collect(newDocument, credentialFile);
|
|
489
|
+
const orderedSecrets = [...secrets].sort((a, b) => b.length - a.length);
|
|
490
|
+
const redact = (key, value) => {
|
|
491
|
+
if (sensitive.test(key))
|
|
492
|
+
return "[REDACTED]";
|
|
493
|
+
if ((key === "" || credentialFile) && typeof value === "string")
|
|
494
|
+
return "[REDACTED]";
|
|
495
|
+
if (typeof value !== "string")
|
|
496
|
+
return value;
|
|
497
|
+
let safe = value;
|
|
498
|
+
if (/^https?:\/\//i.test(safe)) {
|
|
499
|
+
const url = new URL(safe);
|
|
500
|
+
if (url.username)
|
|
501
|
+
url.username = "[REDACTED]";
|
|
502
|
+
if (url.password)
|
|
503
|
+
url.password = "[REDACTED]";
|
|
504
|
+
for (const name of new Set(url.searchParams.keys()))
|
|
505
|
+
if (sensitive.test(name))
|
|
506
|
+
url.searchParams.set(name, "[REDACTED]");
|
|
507
|
+
url.hash = "";
|
|
508
|
+
safe = url.toString();
|
|
509
|
+
}
|
|
510
|
+
for (const secret of orderedSecrets)
|
|
511
|
+
safe = safe.split(secret).join("[REDACTED]");
|
|
512
|
+
return safe.replace(/\b(?:Bearer|Basic)\s+[^\s"',;]+/gi, "[REDACTED]");
|
|
513
|
+
};
|
|
514
|
+
oldText = JSON.stringify(oldDocument, redact, 2) ?? "";
|
|
515
|
+
newText = JSON.stringify(newDocument, redact, 2) ?? "";
|
|
516
|
+
}
|
|
517
|
+
catch {
|
|
518
|
+
console.log(pc.dim("[content withheld: unsupported or malformed configuration]"));
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
const oldLines = oldText.split("\n");
|
|
522
|
+
const newLines = newText.split("\n");
|
|
523
|
+
const oldSeen = new Map();
|
|
524
|
+
for (const line of oldLines)
|
|
525
|
+
oldSeen.set(line, (oldSeen.get(line) ?? 0) + 1);
|
|
526
|
+
const newSeen = new Map();
|
|
527
|
+
for (const line of newLines)
|
|
528
|
+
newSeen.set(line, (newSeen.get(line) ?? 0) + 1);
|
|
529
|
+
for (const line of oldLines) {
|
|
530
|
+
const count = newSeen.get(line) ?? 0;
|
|
531
|
+
if (count > 0)
|
|
532
|
+
newSeen.set(line, count - 1);
|
|
434
533
|
else
|
|
435
|
-
console.log(pc.red(`- ${
|
|
534
|
+
console.log(pc.red(`- ${line}`));
|
|
436
535
|
}
|
|
437
|
-
for (const
|
|
438
|
-
|
|
439
|
-
|
|
536
|
+
for (const line of newLines) {
|
|
537
|
+
const count = oldSeen.get(line) ?? 0;
|
|
538
|
+
if (count > 0)
|
|
539
|
+
oldSeen.set(line, count - 1);
|
|
440
540
|
else
|
|
441
|
-
console.log(pc.green(`+ ${
|
|
541
|
+
console.log(pc.green(`+ ${line}`));
|
|
442
542
|
}
|
|
543
|
+
if (oldText === newText)
|
|
544
|
+
console.log(pc.dim("[only redacted values or formatting changed]"));
|
|
443
545
|
}
|
|
444
546
|
async function runWithOptionalDryRun(op, provider, apps, dryRun) {
|
|
445
547
|
if (!dryRun) {
|
|
@@ -448,7 +550,7 @@ async function runWithOptionalDryRun(op, provider, apps, dryRun) {
|
|
|
448
550
|
}
|
|
449
551
|
setDryRun(true);
|
|
450
552
|
try {
|
|
451
|
-
const results = await runTargets(op, provider, apps);
|
|
553
|
+
const results = await runTargets(op, provider, apps, true);
|
|
452
554
|
const writes = drainPendingWrites();
|
|
453
555
|
for (const r of results) {
|
|
454
556
|
if (r.skipped)
|
|
@@ -456,13 +558,14 @@ async function runWithOptionalDryRun(op, provider, apps, dryRun) {
|
|
|
456
558
|
}
|
|
457
559
|
console.log(pc.bold(`\ndry run — ${writes.length} file(s) would be written:\n`));
|
|
458
560
|
for (const w of writes)
|
|
459
|
-
printFileDiff(w.file, w.content);
|
|
561
|
+
printFileDiff(w.file, w.content, provider.apiKey);
|
|
460
562
|
}
|
|
461
563
|
finally {
|
|
462
564
|
setDryRun(false);
|
|
463
565
|
}
|
|
464
566
|
}
|
|
465
567
|
export async function cmdUse(id, opts) {
|
|
568
|
+
resolveTargets(opts.apps);
|
|
466
569
|
const store = loadStore();
|
|
467
570
|
const provider = getProvider(store, id);
|
|
468
571
|
if (opts.model) {
|
|
@@ -479,6 +582,7 @@ export async function cmdUse(id, opts) {
|
|
|
479
582
|
await runWithOptionalDryRun("apply", provider, opts.apps, opts.dryRun);
|
|
480
583
|
}
|
|
481
584
|
export async function cmdSync(opts) {
|
|
585
|
+
resolveTargets(opts.apps);
|
|
482
586
|
const store = loadStore();
|
|
483
587
|
const id = opts.provider ?? store.active;
|
|
484
588
|
if (!id)
|
|
@@ -544,10 +648,11 @@ export async function cmdRefreshMeta() {
|
|
|
544
648
|
console.log(pc.dim("run `agentsw sync` to push updated metadata into app configs"));
|
|
545
649
|
}
|
|
546
650
|
export async function cmdDiscover(id, opts) {
|
|
651
|
+
resolveTargets(opts.apps);
|
|
547
652
|
const store = loadStore();
|
|
548
653
|
const provider = getProvider(store, id);
|
|
549
654
|
// flags override and re-persist the filter; --no-filter clears it
|
|
550
|
-
const flagFilter = parseFilterOpts(opts);
|
|
655
|
+
const flagFilter = parseFilterOpts(opts, provider.modelFilter);
|
|
551
656
|
if (opts.filter === false)
|
|
552
657
|
provider.modelFilter = undefined;
|
|
553
658
|
else if (flagFilter)
|
|
@@ -582,18 +687,29 @@ export async function cmdDiscover(id, opts) {
|
|
|
582
687
|
}
|
|
583
688
|
async function collectAppRows() {
|
|
584
689
|
return Promise.all(appPackages.map(async (app) => {
|
|
585
|
-
const [
|
|
586
|
-
Promise.resolve(installedVersion(app)),
|
|
690
|
+
const [installedResult, latestResult] = await Promise.allSettled([
|
|
691
|
+
Promise.resolve().then(() => installedVersion(app)),
|
|
587
692
|
latestVersion(app),
|
|
588
693
|
]);
|
|
589
|
-
const
|
|
694
|
+
const installed = installedResult.status === "fulfilled" ? installedResult.value : "?";
|
|
695
|
+
const latest = latestResult.status === "fulfilled" ? latestResult.value : undefined;
|
|
696
|
+
const knownInstalled = normalizeAppVersion(installed);
|
|
697
|
+
const knownLatest = normalizeAppVersion(latest);
|
|
698
|
+
const checkFailed = installedResult.status === "rejected"
|
|
699
|
+
? "installed version check failed"
|
|
700
|
+
: installed && !knownInstalled
|
|
701
|
+
? "installed version unknown"
|
|
702
|
+
: installed && !knownLatest
|
|
703
|
+
? "latest version unavailable"
|
|
704
|
+
: undefined;
|
|
590
705
|
return {
|
|
591
706
|
id: app.id,
|
|
592
707
|
name: app.name,
|
|
593
708
|
installed,
|
|
594
709
|
latest,
|
|
595
|
-
upgradable: knownInstalled && !!
|
|
710
|
+
upgradable: !!knownInstalled && !!knownLatest && isNewer(knownInstalled, knownLatest),
|
|
596
711
|
installable: !installed && !!appCommand(app, "install"),
|
|
712
|
+
checkFailed,
|
|
597
713
|
};
|
|
598
714
|
}));
|
|
599
715
|
}
|
|
@@ -610,7 +726,7 @@ export async function cmdApps() {
|
|
|
610
726
|
? r.installable
|
|
611
727
|
? pc.dim("installable")
|
|
612
728
|
: pc.dim("-")
|
|
613
|
-
: r.
|
|
729
|
+
: r.checkFailed || !normalizeAppVersion(r.latest)
|
|
614
730
|
? pc.dim("unknown")
|
|
615
731
|
: pc.green("up to date"),
|
|
616
732
|
]), ["APP", "INSTALLED", "LATEST", "STATUS"]));
|
|
@@ -632,38 +748,69 @@ export async function cmdInstall(id) {
|
|
|
632
748
|
}
|
|
633
749
|
console.log(`installing ${app.name}: ${pc.dim(installCmd)}`);
|
|
634
750
|
runShell(installCmd);
|
|
635
|
-
|
|
751
|
+
const detected = installedVersion(app);
|
|
752
|
+
if (!detected)
|
|
753
|
+
throw new Error(`${app.name}: installer completed but the app is still not detected; check the installation and PATH`);
|
|
754
|
+
const version = normalizeAppVersion(detected);
|
|
755
|
+
if (!version) {
|
|
756
|
+
console.log(pc.yellow(`${app.name}: installer completed; app detected but version unknown`));
|
|
757
|
+
process.exitCode = 1;
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
console.log(pc.green(`${app.name} installed: ${version}`));
|
|
636
761
|
}
|
|
637
762
|
export async function cmdUpgrade(ids) {
|
|
638
763
|
let selected = appPackages.filter((a) => ids.length === 0 || ids.includes(a.id));
|
|
639
764
|
const unknown = ids.filter((id) => !appPackages.some((a) => a.id === id));
|
|
640
765
|
if (unknown.length)
|
|
641
766
|
fail(`unknown app(s): ${unknown.join(", ")}`);
|
|
767
|
+
const expectedVersions = new Map();
|
|
642
768
|
if (ids.length === 0) {
|
|
643
769
|
// no args: upgrade everything that is installed and outdated
|
|
644
770
|
process.stderr.write(pc.dim("checking versions ...\n"));
|
|
645
771
|
const rows = await collectAppRows();
|
|
646
|
-
const
|
|
772
|
+
const managed = rows.filter((row) => !!appCommand(appPackages.find((app) => app.id === row.id), "upgrade"));
|
|
773
|
+
const failed = managed.filter((row) => row.checkFailed);
|
|
774
|
+
for (const row of failed)
|
|
775
|
+
console.log(pc.yellow(`${row.id}: ${row.checkFailed}; update status unknown`));
|
|
776
|
+
if (failed.length)
|
|
777
|
+
process.exitCode = 1;
|
|
778
|
+
const upgradable = managed.filter((row) => row.upgradable);
|
|
647
779
|
if (upgradable.length === 0) {
|
|
648
|
-
|
|
780
|
+
if (failed.length)
|
|
781
|
+
console.log("could not determine update status for every installed app");
|
|
782
|
+
else if (managed.some((row) => row.installed))
|
|
783
|
+
console.log("all checked apps are up to date");
|
|
784
|
+
else
|
|
785
|
+
console.log("no installed CLI-managed apps to upgrade");
|
|
649
786
|
return;
|
|
650
787
|
}
|
|
651
|
-
|
|
788
|
+
for (const row of upgradable)
|
|
789
|
+
expectedVersions.set(row.id, row.latest);
|
|
790
|
+
selected = appPackages.filter((app) => expectedVersions.has(app.id));
|
|
652
791
|
}
|
|
653
792
|
for (const app of selected) {
|
|
654
793
|
const cmd = appCommand(app, "upgrade");
|
|
655
794
|
if (!cmd) {
|
|
656
795
|
console.log(`${pc.yellow("skip")} ${app.id}: not CLI-upgradable`);
|
|
796
|
+
process.exitCode = 1;
|
|
657
797
|
continue;
|
|
658
798
|
}
|
|
659
|
-
if (!installedVersion(app)) {
|
|
660
|
-
console.log(`${pc.yellow("skip")} ${app.id}: not installed (use \`agentsw install ${app.id}\`)`);
|
|
661
|
-
continue;
|
|
662
|
-
}
|
|
663
|
-
console.log(`upgrading ${app.name}: ${pc.dim(cmd)}`);
|
|
664
799
|
try {
|
|
800
|
+
if (!installedVersion(app)) {
|
|
801
|
+
console.log(`${pc.yellow("skip")} ${app.id}: not installed (use \`agentsw install ${app.id}\`)`);
|
|
802
|
+
process.exitCode = 1;
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
console.log(`upgrading ${app.name}: ${pc.dim(cmd)}`);
|
|
665
806
|
runShell(cmd);
|
|
666
|
-
|
|
807
|
+
const version = normalizeAppVersion(installedVersion(app));
|
|
808
|
+
if (!version)
|
|
809
|
+
throw new Error("upgrade command completed but installed version is unknown or app is not detected");
|
|
810
|
+
const expected = expectedVersions.get(app.id);
|
|
811
|
+
if (expected && isNewer(version, expected))
|
|
812
|
+
throw new Error(`upgrade command completed but ${version} is older than available ${expected}`);
|
|
813
|
+
console.log(pc.green(`${app.id} -> ${version}`));
|
|
667
814
|
}
|
|
668
815
|
catch (err) {
|
|
669
816
|
console.log(pc.red(`${app.id} upgrade failed: ${err.message}`));
|