@iseoane/agent-depot 0.6.3 → 0.6.5
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/CHANGELOG.md +94 -0
- package/README.es.md +14 -10
- package/README.md +22 -8
- package/dist/src/app-cli.js +10 -5
- package/dist/src/app-flow.js +2 -2
- package/dist/src/atomic-file.js +9 -0
- package/dist/src/cli.js +98 -67
- package/dist/src/executable-resolution.js +9 -3
- package/dist/src/git-source.js +3 -5
- package/dist/src/package-cli.js +9 -4
- package/dist/src/package-flow.js +2 -1
- package/dist/src/package-host-state.js +44 -6
- package/dist/src/package-state.js +2 -5
- package/dist/src/process-runner.js +234 -16
- package/dist/src/profile-cli.js +8 -6
- package/dist/src/progress.js +157 -0
- package/dist/src/source-state.js +2 -5
- package/dist/src/tui/adoption-actions.js +4 -3
- package/dist/src/tui/app-actions.js +16 -9
- package/dist/src/tui/bulk-actions.js +7 -6
- package/dist/src/tui/catalog-flow.js +3 -2
- package/dist/src/tui/catalog-panel.js +2 -2
- package/dist/src/tui/catalog-view.js +26 -6
- package/dist/src/tui/file-picker.js +52 -20
- package/dist/src/tui/installations-view.js +44 -11
- package/dist/src/tui/manage-actions.js +7 -6
- package/dist/src/tui/manage-panel.js +2 -2
- package/dist/src/tui/package-actions.js +63 -31
- package/dist/src/tui/panel-parts.js +20 -0
- package/dist/src/tui/profile-view.js +84 -15
- package/dist/src/tui/sources-mode.js +2 -1
- package/dist/src/tui/sources-recipes.js +22 -10
- package/dist/src/tui/sources-view.js +13 -12
- package/dist/src/tui/text-wrap.js +13 -0
- package/dist/src/tui/unmanaged-actions.js +3 -2
- package/dist/src/tui/updates-flow.js +10 -7
- package/dist/src/tui/updates-keys.js +3 -2
- package/dist/src/tui/updates-model.js +2 -2
- package/dist/src/tui/updates-panel.js +3 -2
- package/dist/src/tui/updates-view.js +6 -3
- package/dist/src/tui/view-state.js +12 -0
- package/dist/src/tui/working-model.js +33 -0
- package/package.json +2 -2
package/dist/src/cli.js
CHANGED
|
@@ -6,12 +6,14 @@ import path from "node:path";
|
|
|
6
6
|
import { checkAppUpdates, createAppOperations } from "./app-flow.js";
|
|
7
7
|
import { describeAppResult, describeManualCompletion, runAppCommand } from "./app-cli.js";
|
|
8
8
|
import { createPackageOperations } from "./package-flow.js";
|
|
9
|
-
import { describeLifecyclePlan, describePackageResult, describePackageUpdateCheck, formatPackageSelection, packageSelectionMatches, runPackageCommand } from "./package-cli.js";
|
|
9
|
+
import { describeLifecyclePlan, describePackageResult, describePackageUpdateCheck, formatPackageSelection, packageSelectionMatches, planWithRefresh, runPackageCommand } from "./package-cli.js";
|
|
10
10
|
import { selectionKey } from "./package-model.js";
|
|
11
11
|
import { EXPORT_USAGE, IMPORT_USAGE, runProfileExport, runProfileImport } from "./profile-cli.js";
|
|
12
12
|
import { pathsOverlap } from "./path-safety.js";
|
|
13
|
+
import { withChildOutput } from "./process-runner.js";
|
|
14
|
+
import { createProgress, NO_PROGRESS } from "./progress.js";
|
|
13
15
|
import { skillTreeBaseline } from "./skill-discovery.js";
|
|
14
|
-
import { BuiltInSourceError, createSourceOperations, resolveProjectSource, SourceMetadataError, SourceNotFoundError, } from "./sources.js";
|
|
16
|
+
import { BUILT_IN_SOURCE, BuiltInSourceError, createSourceOperations, resolveProjectSource, SourceMetadataError, SourceNotFoundError, } from "./sources.js";
|
|
15
17
|
import { AGENT_DEPOT_PACKAGE_NAME, AGENT_DEPOT_PACKAGE_VERSION, builtinPinMismatch, defaultProjectManifestPath, parseProjectManifest, ProjectManifestStore, PROJECT_HOSTS, } from "./project-manifest.js";
|
|
16
18
|
import { inspectProjectSkillInstallation, assertNoOverlappingProjectSkillRemovalTargets, } from "./project-installation.js";
|
|
17
19
|
import { selectUpdateBatch, UpdateBatchSelectionError, } from "./update-batch.js";
|
|
@@ -57,6 +59,10 @@ const USAGE = [
|
|
|
57
59
|
` ${IMPORT_USAGE}`,
|
|
58
60
|
" agent-depot tui",
|
|
59
61
|
" agent-depot --version",
|
|
62
|
+
"",
|
|
63
|
+
"Options for every command, anywhere on the line:",
|
|
64
|
+
" --verbose stream the full child output (npm, pi, git, App commands) to stderr line by line as it runs,",
|
|
65
|
+
" instead of a spinner and the last few lines; stdout is unchanged.",
|
|
60
66
|
].join("\n");
|
|
61
67
|
const INSTALL_USAGE = "Usage: agent-depot install --scope <project|user-global> --source <id> --skill <path> --host <host>... --version <latest|version> [--ref <git-ref>] [--method <json>] --portable-v1 [--overwrite --yes] [--confirm-additional-host]";
|
|
62
68
|
const DISCOVER_USAGE = "Usage: agent-depot discover <source-id> [source-id...]\nSelect at least one Source ID explicitly; run `agent-depot source list` to see registered Sources";
|
|
@@ -74,11 +80,12 @@ const SOURCE_SUBCOMMANDS = new Map([
|
|
|
74
80
|
}],
|
|
75
81
|
["add", async (values, { operations, output }) => {
|
|
76
82
|
requireArgumentCount(values, 1, "source add <url>");
|
|
83
|
+
// Registration only: nothing is cloned until the first refresh, so there is no long step to report.
|
|
77
84
|
const source = await operations.addGitSource(values[0]);
|
|
78
85
|
output(`Added Git Source: ${source.id}\t${source.url}`);
|
|
79
86
|
}],
|
|
80
87
|
["refresh", runSourceRefresh],
|
|
81
|
-
["remove", (values, { operations, dependencies, output }) => runSourceRemoval(values, operations, dependencies, output)],
|
|
88
|
+
["remove", (values, { operations, dependencies, output, progress }) => runSourceRemoval(values, operations, dependencies, output, progress)],
|
|
82
89
|
["migrate", (values, { operations, dependencies, output }) => runSourceMigration(values, operations, dependencies, output)],
|
|
83
90
|
]);
|
|
84
91
|
function packageOperationsFor(dependencies, operations) {
|
|
@@ -89,34 +96,34 @@ function packageOperationsFor(dependencies, operations) {
|
|
|
89
96
|
});
|
|
90
97
|
}
|
|
91
98
|
const COMMANDS = new Map([
|
|
92
|
-
["import", (values, { operations, dependencies, output }) => runProfileImport(values, operations, dependencies.appOperations ?? createAppOperations({ homeDirectory: dependencies.homeDirectory }), output, { ...dependencies, packageOperations: packageOperationsFor(dependencies, operations) })],
|
|
93
|
-
["export", (values, { operations, dependencies, output }) => runProfileExport(values, operations, dependencies.appOperations ?? createAppOperations({ homeDirectory: dependencies.homeDirectory }), output,
|
|
94
|
-
["app", (values, { dependencies, output }) => runAppCommand(values, dependencies.appOperations ?? createAppOperations({ homeDirectory: dependencies.homeDirectory }), output)],
|
|
95
|
-
["package", (values, { operations, dependencies, output }) => runPackageCommand(values, packageOperationsFor(dependencies, operations), operations, output)],
|
|
96
|
-
["install", async (values, { operations, dependencies, output }) => {
|
|
97
|
-
await runInstall(values, operations, dependencies, output);
|
|
99
|
+
["import", (values, { operations, dependencies, output, progress }) => runProfileImport(values, operations, dependencies.appOperations ?? createAppOperations({ homeDirectory: dependencies.homeDirectory }), output, { ...dependencies, packageOperations: packageOperationsFor(dependencies, operations) }, progress)],
|
|
100
|
+
["export", (values, { operations, dependencies, output, errorOutput, progress }) => runProfileExport(values, operations, dependencies.appOperations ?? createAppOperations({ homeDirectory: dependencies.homeDirectory }), output, errorOutput, dependencies.homeDirectory, packageOperationsFor(dependencies, operations), progress)],
|
|
101
|
+
["app", (values, { dependencies, output, progress }) => runAppCommand(values, dependencies.appOperations ?? createAppOperations({ homeDirectory: dependencies.homeDirectory }), output, progress)],
|
|
102
|
+
["package", (values, { operations, dependencies, output, progress }) => runPackageCommand(values, packageOperationsFor(dependencies, operations), operations, output, progress)],
|
|
103
|
+
["install", async (values, { operations, dependencies, output, progress }) => {
|
|
104
|
+
await runInstall(values, operations, dependencies, output, progress);
|
|
98
105
|
return 0;
|
|
99
106
|
}],
|
|
100
|
-
["update", (values, { operations, dependencies, output }) => runUpdate(values, operations, dependencies, output)],
|
|
101
|
-
["self", async (values, { dependencies, output }) => {
|
|
107
|
+
["update", (values, { operations, dependencies, output, progress }) => runUpdate(values, operations, dependencies, output, progress)],
|
|
108
|
+
["self", async (values, { dependencies, output, progress }) => {
|
|
102
109
|
if (values[0] !== "update")
|
|
103
110
|
throw new CliUsageError("Usage: agent-depot self update [--yes]");
|
|
104
|
-
return runSelfUpdate(values.slice(1), dependencies, output);
|
|
111
|
+
return runSelfUpdate(values.slice(1), dependencies, output, progress);
|
|
105
112
|
}],
|
|
106
|
-
["skill", async (values, { operations, dependencies, output }) => {
|
|
113
|
+
["skill", async (values, { operations, dependencies, output, progress }) => {
|
|
107
114
|
if (values[0] === "remove") {
|
|
108
|
-
await runSkillRemoval(values.slice(1), operations, dependencies, output);
|
|
115
|
+
await runSkillRemoval(values.slice(1), operations, dependencies, output, progress);
|
|
109
116
|
}
|
|
110
117
|
else if (values[0] === "host" && values[1] === "add") {
|
|
111
|
-
await runSkillHostAdd(values.slice(2), operations, dependencies, output);
|
|
118
|
+
await runSkillHostAdd(values.slice(2), operations, dependencies, output, progress);
|
|
112
119
|
}
|
|
113
120
|
else {
|
|
114
121
|
throw new CliUsageError(USAGE);
|
|
115
122
|
}
|
|
116
123
|
return 0;
|
|
117
124
|
}],
|
|
118
|
-
["uninstall", async (values, { operations, dependencies, output }) => {
|
|
119
|
-
await runUninstall(values, operations, dependencies, output);
|
|
125
|
+
["uninstall", async (values, { operations, dependencies, output, progress }) => {
|
|
126
|
+
await runUninstall(values, operations, dependencies, output, progress);
|
|
120
127
|
return 0;
|
|
121
128
|
}],
|
|
122
129
|
["discover", async (values, { operations, output }) => {
|
|
@@ -162,7 +169,7 @@ const COMMANDS = new Map([
|
|
|
162
169
|
return 0;
|
|
163
170
|
}],
|
|
164
171
|
]);
|
|
165
|
-
async function runSourceRefresh(values, { operations, output }) {
|
|
172
|
+
async function runSourceRefresh(values, { operations, output, progress }) {
|
|
166
173
|
if (values.length < 1 || values.length > 2 || (values.length === 2 && values[1] !== "--yes")) {
|
|
167
174
|
throw new CliUsageError("Usage: agent-depot source refresh <id> [--yes]");
|
|
168
175
|
}
|
|
@@ -174,13 +181,21 @@ async function runSourceRefresh(values, { operations, output }) {
|
|
|
174
181
|
if (values[1] !== "--yes") {
|
|
175
182
|
throw new CliUsageError("Refresh not confirmed; rerun with --yes to continue");
|
|
176
183
|
}
|
|
177
|
-
|
|
184
|
+
// `git clone`/`fetch` may ask for credentials on the terminal, so this step never animates.
|
|
185
|
+
const refreshed = await progress.run(`Refreshing Git Source ${source.id}`, () => operations.refreshSource(source.id), { mayPrompt: true });
|
|
178
186
|
output(`Refreshed Git Source: ${refreshed.id}\t${refreshed.url}`);
|
|
179
187
|
}
|
|
180
188
|
/** Runs the CLI application and returns a process exit code. */
|
|
181
|
-
export async function runCli(
|
|
182
|
-
|
|
183
|
-
const
|
|
189
|
+
export async function runCli(rawArgv = process.argv.slice(2), dependencies = {}) {
|
|
190
|
+
// A global flag, so every command's own option parser never sees it.
|
|
191
|
+
const verbose = rawArgv.includes("--verbose");
|
|
192
|
+
const argv = verbose ? rawArgv.filter((argument) => argument !== "--verbose") : rawArgv;
|
|
193
|
+
const progress = dependencies.progress ?? (dependencies.stderr === undefined ? createProgress({ verbose, tapChildOutput: withChildOutput }) : NO_PROGRESS);
|
|
194
|
+
// Every line is written after erasing any spinner, so a step that prints while it runs is never garbled.
|
|
195
|
+
const writeStdout = dependencies.stdout ?? ((line) => console.log(line));
|
|
196
|
+
const writeStderr = dependencies.stderr ?? ((line) => console.error(line));
|
|
197
|
+
const output = (line) => { progress.clear(); writeStdout(line); };
|
|
198
|
+
const errorOutput = (line) => { progress.clear(); writeStderr(line); };
|
|
184
199
|
const operations = dependencies.operations ?? createSourceOperations({ homeDirectory: dependencies.homeDirectory });
|
|
185
200
|
try {
|
|
186
201
|
if (argv[0] === "--version" || argv[0] === "-v") {
|
|
@@ -201,7 +216,7 @@ export async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
201
216
|
if (!command) {
|
|
202
217
|
throw new CliUsageError(USAGE);
|
|
203
218
|
}
|
|
204
|
-
return await command(argv.slice(1), { operations, dependencies, output });
|
|
219
|
+
return await command(argv.slice(1), { operations, dependencies, output, errorOutput, progress });
|
|
205
220
|
}
|
|
206
221
|
catch (error) {
|
|
207
222
|
errorOutput(`Error: ${error instanceof Error ? error.message : "unknown error"}`);
|
|
@@ -210,7 +225,7 @@ export async function runCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
|
210
225
|
}
|
|
211
226
|
/** Alias retained as a discoverable public CLI seam for embedding and tests. */
|
|
212
227
|
export const main = runCli;
|
|
213
|
-
async function runSourceRemoval(argv, operations, dependencies, output) {
|
|
228
|
+
async function runSourceRemoval(argv, operations, dependencies, output, progress) {
|
|
214
229
|
const options = parseRemoveSourceOptions(argv);
|
|
215
230
|
const source = await findSource(operations, options.sourceId);
|
|
216
231
|
if (source.kind === "builtin") {
|
|
@@ -232,17 +247,19 @@ async function runSourceRemoval(argv, operations, dependencies, output) {
|
|
|
232
247
|
output(` [${index}] ${selection.path} -> ${location}${selected.includes(selection) ? " (selected for removal)" : " (kept)"}`);
|
|
233
248
|
}
|
|
234
249
|
output(" Default: keep all dependent Skills tracked by default after Source removal");
|
|
235
|
-
const plan = await inspectSkillRemovals(selected, removalOptions, (selection, inspection) => {
|
|
250
|
+
const plan = await progress.run(`Inspecting ${selected.length} Skill${selected.length === 1 ? "" : "s"} for removal`, () => inspectSkillRemovals(selected, removalOptions, (selection, inspection) => {
|
|
236
251
|
for (const line of describeSkillRemoval(selection, inspection, "Skill "))
|
|
237
252
|
output(line);
|
|
238
|
-
});
|
|
253
|
+
}));
|
|
239
254
|
assertNoOverlappingProjectSkillRemovalTargets(plan.inspections);
|
|
240
255
|
requireConfirmation(options.confirmed, "Removal not confirmed; rerun with --yes after reviewing every Source and Skill preview");
|
|
241
256
|
// Recheck every selected target before deleting the first one. A changed later
|
|
242
257
|
// target must never result in a partially removed batch.
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
258
|
+
await progress.run("Removing Skill files", async () => {
|
|
259
|
+
const rechecked = await recheckSkillRemoval(plan, removalOptions);
|
|
260
|
+
await removeSkillFiles(plan, rechecked, removalOptions);
|
|
261
|
+
});
|
|
262
|
+
await progress.run(`Removing Git Source ${source.id}`, () => operations.removeGitSource(source.id, selected));
|
|
246
263
|
output(`Removed Git Source: ${source.id}`);
|
|
247
264
|
if (selected.length > 0) {
|
|
248
265
|
output(`Removed ${selected.length} dependent user-global Skill${selected.length === 1 ? "" : "s"}; other dependent Skills remain tracked`);
|
|
@@ -418,7 +435,7 @@ function resolveRemovalSelections(dependent, requested) {
|
|
|
418
435
|
}
|
|
419
436
|
return Object.freeze(selected);
|
|
420
437
|
}
|
|
421
|
-
async function runSkillRemoval(argv, operations, dependencies, output) {
|
|
438
|
+
async function runSkillRemoval(argv, operations, dependencies, output, progress) {
|
|
422
439
|
const requested = [];
|
|
423
440
|
const hosts = [];
|
|
424
441
|
let confirmed = false;
|
|
@@ -446,7 +463,7 @@ async function runSkillRemoval(argv, operations, dependencies, output) {
|
|
|
446
463
|
const selected = selectManagedSkills(installations, requested);
|
|
447
464
|
const removalOptions = userGlobalRemovalOptions(path.resolve(dependencies.homeDirectory ?? homedir()), dependencies.installationOptions);
|
|
448
465
|
if (hosts.length > 0) {
|
|
449
|
-
await runSkillHostRemoval(selected[0], hosts, installations, confirmed, operations, removalOptions, output);
|
|
466
|
+
await runSkillHostRemoval(selected[0], hosts, installations, confirmed, operations, removalOptions, output, progress);
|
|
450
467
|
return;
|
|
451
468
|
}
|
|
452
469
|
output(`Preview: remove ${selected.length} user-global Skill${selected.length === 1 ? "" : "s"}`);
|
|
@@ -455,7 +472,7 @@ async function runSkillRemoval(argv, operations, dependencies, output) {
|
|
|
455
472
|
output(line);
|
|
456
473
|
output(" Installation records are reconciled after each successful Skill removal");
|
|
457
474
|
requireConfirmation(confirmed, "Removal not confirmed; rerun with --yes after reviewing every Skill preview");
|
|
458
|
-
await executeSkillRemoval(plan, operations, removalOptions, installations);
|
|
475
|
+
await progress.run(`Removing ${selected.length} user-global Skill${selected.length === 1 ? "" : "s"}`, () => executeSkillRemoval(plan, operations, removalOptions, installations));
|
|
459
476
|
output(`Removed ${selected.length} user-global Skill${selected.length === 1 ? "" : "s"}`);
|
|
460
477
|
}
|
|
461
478
|
/** Resolves `<id|path>` arguments to managed installations, reporting selection problems as usage errors. */
|
|
@@ -469,7 +486,7 @@ function selectManagedSkills(installations, requested) {
|
|
|
469
486
|
throw error;
|
|
470
487
|
}
|
|
471
488
|
}
|
|
472
|
-
async function runSkillHostRemoval(selection, hosts, installations, confirmed, operations, options, output) {
|
|
489
|
+
async function runSkillHostRemoval(selection, hosts, installations, confirmed, operations, options, output, progress) {
|
|
473
490
|
const plan = await planHostRemoval(selection, hosts, options).catch(rethrowSelectionError);
|
|
474
491
|
output(plan.kind === "full"
|
|
475
492
|
? "Preview: remove 1 user-global Skill (every recorded Host is removed)"
|
|
@@ -478,12 +495,12 @@ async function runSkillHostRemoval(selection, hosts, installations, confirmed, o
|
|
|
478
495
|
output(line);
|
|
479
496
|
output(" The installation record is reconciled after the locations are removed");
|
|
480
497
|
requireConfirmation(confirmed, "Removal not confirmed; rerun with --yes after reviewing the preview");
|
|
481
|
-
await executeHostRemoval(plan, operations, options, installations);
|
|
498
|
+
await progress.run(plan.kind === "full" ? "Removing 1 user-global Skill" : `Removing Hosts from ${selection.path}`, () => executeHostRemoval(plan, operations, options, installations));
|
|
482
499
|
output(plan.kind === "full"
|
|
483
500
|
? "Removed 1 user-global Skill"
|
|
484
501
|
: `Removed hosts ${plan.removed.join(", ")} from ${JSON.stringify(selection.path)}`);
|
|
485
502
|
}
|
|
486
|
-
async function runSkillHostAdd(argv, operations, dependencies, output) {
|
|
503
|
+
async function runSkillHostAdd(argv, operations, dependencies, output, progress) {
|
|
487
504
|
const requested = [];
|
|
488
505
|
const hosts = [];
|
|
489
506
|
let confirmed = false;
|
|
@@ -520,7 +537,7 @@ async function runSkillHostAdd(argv, operations, dependencies, output) {
|
|
|
520
537
|
output(line);
|
|
521
538
|
requireConfirmation(confirmed, "Host change not confirmed; rerun with --yes after reviewing the preview");
|
|
522
539
|
requireConfirmation(confirmAdditionalHost, "Adding a Host exposes the installed Skill to more hosts; rerun with --yes --confirm-additional-host");
|
|
523
|
-
await executeHostAddition(plan, operations, options, installations, true);
|
|
540
|
+
await progress.run(`Adding Hosts to ${plan.selection.path}`, () => executeHostAddition(plan, operations, options, installations, true));
|
|
524
541
|
output(`Added hosts ${plan.hosts.join(", ")} to ${JSON.stringify(plan.selection.path)}`);
|
|
525
542
|
}
|
|
526
543
|
function rethrowSelectionError(error) {
|
|
@@ -662,7 +679,7 @@ function outputUninstallSummary(options, removedManagedCount, output) {
|
|
|
662
679
|
output(" npx/pnpm dlx runs need no uninstall (only a cache entry)");
|
|
663
680
|
}
|
|
664
681
|
}
|
|
665
|
-
async function runUninstall(argv, operations, dependencies, output) {
|
|
682
|
+
async function runUninstall(argv, operations, dependencies, output, progress) {
|
|
666
683
|
const options = parseUninstallOptions(argv);
|
|
667
684
|
assertUninstallCapabilities(options, operations);
|
|
668
685
|
const homeDirectory = path.resolve(dependencies.homeDirectory ?? homedir());
|
|
@@ -685,7 +702,8 @@ async function runUninstall(argv, operations, dependencies, output) {
|
|
|
685
702
|
if (options.skills || options.unmanagedSkills.length > 0 || options.data) {
|
|
686
703
|
requireConfirmation(options.confirmed, "Uninstall not confirmed; rerun with --yes after reviewing every user-global deletion preview");
|
|
687
704
|
}
|
|
688
|
-
const
|
|
705
|
+
const recheckManaged = () => recheckSkillRemoval(managedPlan, removalOptions);
|
|
706
|
+
const managedRechecked = options.skills ? await progress.run("Rechecking Skills before removal", recheckManaged) : await recheckManaged();
|
|
689
707
|
const rechecked = managedRechecked.inspections;
|
|
690
708
|
const unmanagedRechecked = [];
|
|
691
709
|
for (const inspection of unmanagedInspections) {
|
|
@@ -697,11 +715,13 @@ async function runUninstall(argv, operations, dependencies, output) {
|
|
|
697
715
|
assertNoOverlappingUserGlobalSkillRemovals(unmanagedRechecked);
|
|
698
716
|
assertNoManagedUnmanagedOverlap(rechecked, unmanagedRechecked, () => "An unmanaged Skill target changed into an overlap with a selected managed Skill; no path was changed");
|
|
699
717
|
const expectedManagedInstallations = options.skills
|
|
700
|
-
? await removeSkillsAndRecords(managedPlan, managedRechecked, operations, removalOptions, selectedInstallations)
|
|
718
|
+
? await progress.run(`Removing ${selectedInstallations.length} managed Skill${selectedInstallations.length === 1 ? "" : "s"}`, () => removeSkillsAndRecords(managedPlan, managedRechecked, operations, removalOptions, selectedInstallations))
|
|
701
719
|
: selectedInstallations;
|
|
702
|
-
|
|
720
|
+
if (unmanagedInspections.length > 0) {
|
|
721
|
+
await progress.run(`Removing ${unmanagedInspections.length} unmanaged Skill${unmanagedInspections.length === 1 ? "" : "s"}`, () => removeInspectedUnmanagedSkills(context, unmanagedInspections, expectedManagedInstallations));
|
|
722
|
+
}
|
|
703
723
|
if (options.data) {
|
|
704
|
-
await operations.removeCatalogData();
|
|
724
|
+
await progress.run("Removing catalog data", () => operations.removeCatalogData());
|
|
705
725
|
}
|
|
706
726
|
outputUninstallSummary(options, selectedInstallations.length, output);
|
|
707
727
|
}
|
|
@@ -717,13 +737,13 @@ function selfUpdateOperationsFor(dependencies) {
|
|
|
717
737
|
packageRoot: path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../.."),
|
|
718
738
|
});
|
|
719
739
|
}
|
|
720
|
-
async function runSelfUpdate(argv, dependencies, output) {
|
|
740
|
+
async function runSelfUpdate(argv, dependencies, output, progress) {
|
|
721
741
|
if (argv.length > 1 || (argv.length === 1 && argv[0] !== "--yes")) {
|
|
722
742
|
throw new CliUsageError("Usage: agent-depot self update [--yes]");
|
|
723
743
|
}
|
|
724
744
|
const confirmed = argv[0] === "--yes";
|
|
725
745
|
const selfUpdate = selfUpdateOperationsFor(dependencies);
|
|
726
|
-
const status = await selfUpdate.check();
|
|
746
|
+
const status = await progress.run("Checking for Agent Depot updates", () => selfUpdate.check());
|
|
727
747
|
switch (status.kind) {
|
|
728
748
|
case "unsupported":
|
|
729
749
|
output(`Self-update is unavailable: ${status.reason}`);
|
|
@@ -744,7 +764,7 @@ async function runSelfUpdate(argv, dependencies, output) {
|
|
|
744
764
|
output(` command: npm install --global --no-audit --no-fund --no-update-notifier --logs-max=0 --ignore-scripts --registry=https://registry.npmjs.org/ ${AGENT_DEPOT_PACKAGE_NAME ?? "@iseoane/agent-depot"}@${status.latest}`);
|
|
745
765
|
output(" npm package scripts will not run; no elevated privileges will be requested");
|
|
746
766
|
requireConfirmation(confirmed, "Self-update not confirmed; rerun with --yes after reviewing the preview");
|
|
747
|
-
await selfUpdate.apply(status.latest);
|
|
767
|
+
await progress.run(`Updating Agent Depot to ${status.latest}`, () => selfUpdate.apply(status.latest));
|
|
748
768
|
output(`Updated Agent Depot to ${status.latest}; restart the CLI to use the new version`);
|
|
749
769
|
return 0;
|
|
750
770
|
default: {
|
|
@@ -753,7 +773,7 @@ async function runSelfUpdate(argv, dependencies, output) {
|
|
|
753
773
|
}
|
|
754
774
|
}
|
|
755
775
|
}
|
|
756
|
-
async function runUpdate(argv, operations, dependencies, output) {
|
|
776
|
+
async function runUpdate(argv, operations, dependencies, output, progress) {
|
|
757
777
|
const options = parseUpdateOptions(argv);
|
|
758
778
|
const apps = dependencies.appOperations ?? createAppOperations({ homeDirectory: dependencies.homeDirectory });
|
|
759
779
|
const packages = packageOperationsFor(dependencies, operations);
|
|
@@ -766,14 +786,14 @@ async function runUpdate(argv, operations, dependencies, output) {
|
|
|
766
786
|
let packageCheckFailed = false;
|
|
767
787
|
if (options.scope === "user-global") {
|
|
768
788
|
try {
|
|
769
|
-
appUpdates = await checkAppUpdates(apps);
|
|
789
|
+
appUpdates = await progress.run("Checking App updates", () => checkAppUpdates(apps));
|
|
770
790
|
}
|
|
771
791
|
catch (error) {
|
|
772
792
|
appCheckFailed = true;
|
|
773
793
|
output(`Error (Apps): ${error instanceof Error ? error.message : String(error)}`);
|
|
774
794
|
}
|
|
775
795
|
try {
|
|
776
|
-
packageUpdates = await packages.checkUpdates(requestedPackages.length === 0 ? undefined : requestedPackages);
|
|
796
|
+
packageUpdates = await progress.run("Checking Package updates", () => packages.checkUpdates(requestedPackages.length === 0 ? undefined : requestedPackages));
|
|
777
797
|
}
|
|
778
798
|
catch (error) {
|
|
779
799
|
packageCheckFailed = true;
|
|
@@ -782,7 +802,7 @@ async function runUpdate(argv, operations, dependencies, output) {
|
|
|
782
802
|
}
|
|
783
803
|
let loaded;
|
|
784
804
|
try {
|
|
785
|
-
loaded = await loadUpdates({
|
|
805
|
+
loaded = await progress.run(`Checking ${options.scope} Skill updates`, () => loadUpdates({
|
|
786
806
|
scope: options.scope,
|
|
787
807
|
operations,
|
|
788
808
|
...(dependencies.homeDirectory === undefined ? {} : { homeDirectory: dependencies.homeDirectory }),
|
|
@@ -790,7 +810,7 @@ async function runUpdate(argv, operations, dependencies, output) {
|
|
|
790
810
|
...(dependencies.sourceAccess === undefined ? {} : { sourceAccess: dependencies.sourceAccess }),
|
|
791
811
|
...(dependencies.projectManifestStore === undefined ? {} : { projectManifestStore: dependencies.projectManifestStore }),
|
|
792
812
|
...(dependencies.installationOptions?.fileSystem === undefined ? {} : { installationFileSystem: dependencies.installationOptions.fileSystem }),
|
|
793
|
-
});
|
|
813
|
+
}));
|
|
794
814
|
}
|
|
795
815
|
catch (error) {
|
|
796
816
|
output(`Error (Skills, ${options.scope}): ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -846,7 +866,7 @@ async function runUpdate(argv, operations, dependencies, output) {
|
|
|
846
866
|
}
|
|
847
867
|
for (const item of selectedPackages) {
|
|
848
868
|
try {
|
|
849
|
-
const plan = await packages
|
|
869
|
+
const plan = await planWithRefresh(packages, item.installed.selection, "update", progress);
|
|
850
870
|
const approval = await packages.approvalStatus(item.installed.selection, "update");
|
|
851
871
|
describeLifecyclePlan(plan, approval, output);
|
|
852
872
|
if (approval === "approved") {
|
|
@@ -863,7 +883,9 @@ async function runUpdate(argv, operations, dependencies, output) {
|
|
|
863
883
|
}
|
|
864
884
|
}
|
|
865
885
|
const selected = selectUpdateCandidates(assessment, options);
|
|
866
|
-
const { plans, failures: previewFailures, nonCanonicalPaths } =
|
|
886
|
+
const { plans, failures: previewFailures, nonCanonicalPaths } = selected.length === 0
|
|
887
|
+
? await previewUpdateCandidates(selected, context)
|
|
888
|
+
: await progress.run("Previewing Skill updates", () => previewUpdateCandidates(selected, context));
|
|
867
889
|
// Successes and failures are reported in candidate order, as the preview runs.
|
|
868
890
|
const failedIds = new Set(previewFailures.map(({ item }) => item.id));
|
|
869
891
|
const plansById = new Map(plans.map((plan) => [plan.item.id, plan]));
|
|
@@ -882,7 +904,9 @@ async function runUpdate(argv, operations, dependencies, output) {
|
|
|
882
904
|
const pathProblem = findPathConfirmationProblem(nonCanonicalPaths, options.confirmedPaths);
|
|
883
905
|
if (pathProblem !== undefined)
|
|
884
906
|
throw new CliUsageError(pathProblem);
|
|
885
|
-
const result =
|
|
907
|
+
const result = selected.length === 0
|
|
908
|
+
? await applyConfirmedUpdates(selected, context, options.confirmedPaths)
|
|
909
|
+
: await progress.run(`Updating ${selected.length} Skill${selected.length === 1 ? "" : "s"}`, () => applyConfirmedUpdates(selected, context, options.confirmedPaths));
|
|
886
910
|
for (const { item } of previewFailures) {
|
|
887
911
|
if (!result.failed.some((failed) => failed.id === item.id)) {
|
|
888
912
|
output(`Preview failure was not selected for application: ${JSON.stringify(item.id)}`);
|
|
@@ -897,7 +921,7 @@ async function runUpdate(argv, operations, dependencies, output) {
|
|
|
897
921
|
let appUpdated = 0;
|
|
898
922
|
for (const plan of appPlans) {
|
|
899
923
|
try {
|
|
900
|
-
const outcome = await apps.executeLifecycle(plan, true);
|
|
924
|
+
const outcome = await progress.run(`Updating App ${plan.entry.recipe.name}`, () => apps.executeLifecycle(plan, true));
|
|
901
925
|
output(`App ${plan.entry.recipe.name}: ${describeAppResult(outcome)}${outcome.reason ? `: ${outcome.reason}` : ""}`);
|
|
902
926
|
if (outcome.status === "installed")
|
|
903
927
|
appUpdated++;
|
|
@@ -917,7 +941,7 @@ async function runUpdate(argv, operations, dependencies, output) {
|
|
|
917
941
|
let packageUpdated = 0;
|
|
918
942
|
for (const plan of packagePlans) {
|
|
919
943
|
try {
|
|
920
|
-
const outcome = await packages.executeLifecycle(plan, true);
|
|
944
|
+
const outcome = await progress.run(`Updating Package ${formatPackageSelection(plan.selection)}`, () => packages.executeLifecycle(plan, true));
|
|
921
945
|
output(`Package ${formatPackageSelection(plan.selection)}: ${describePackageResult(outcome)}`);
|
|
922
946
|
if (outcome.status === "updated") {
|
|
923
947
|
packageUpdated++;
|
|
@@ -1097,16 +1121,16 @@ function outputUpdateItem(item, output) {
|
|
|
1097
1121
|
const selection = item.selection;
|
|
1098
1122
|
output(` [${item.index}] ${item.id}${typeof selection?.path === "string" ? ` path=${selection.path}` : ""}: ${item.reason}`);
|
|
1099
1123
|
}
|
|
1100
|
-
async function runInstall(argv, operations, dependencies, output) {
|
|
1124
|
+
async function runInstall(argv, operations, dependencies, output, progress) {
|
|
1101
1125
|
const options = parseInstallOptions(argv);
|
|
1102
1126
|
const sourceAccess = dependencies.sourceAccess ?? defaultProjectSkillTreeAccess();
|
|
1103
1127
|
if (options.scope === "user-global") {
|
|
1104
|
-
await runUserGlobalInstall(options, operations, dependencies, sourceAccess, output);
|
|
1128
|
+
await runUserGlobalInstall(options, operations, dependencies, sourceAccess, output, progress);
|
|
1105
1129
|
return;
|
|
1106
1130
|
}
|
|
1107
1131
|
const projectRoot = path.resolve(dependencies.projectRoot ?? process.cwd());
|
|
1108
1132
|
const manifestStore = dependencies.projectManifestStore ?? new ProjectManifestStore(defaultProjectManifestPath(projectRoot));
|
|
1109
|
-
const context = { options, operations, dependencies, sourceAccess, output, projectRoot, manifestStore };
|
|
1133
|
+
const context = { options, operations, dependencies, sourceAccess, output, progress, projectRoot, manifestStore };
|
|
1110
1134
|
if (options.fromManifest) {
|
|
1111
1135
|
await runManifestInstall(context);
|
|
1112
1136
|
return;
|
|
@@ -1114,7 +1138,7 @@ async function runInstall(argv, operations, dependencies, output) {
|
|
|
1114
1138
|
await runSingleProjectInstall(context);
|
|
1115
1139
|
}
|
|
1116
1140
|
async function runManifestInstall(context) {
|
|
1117
|
-
const { options, operations, dependencies, sourceAccess, output, projectRoot, manifestStore } = context;
|
|
1141
|
+
const { options, operations, dependencies, sourceAccess, output, progress, projectRoot, manifestStore } = context;
|
|
1118
1142
|
const manifest = await manifestStore.load();
|
|
1119
1143
|
if (manifest.skills.length === 0) {
|
|
1120
1144
|
throw new CliUsageError("The project manifest contains no selected Skills");
|
|
@@ -1141,7 +1165,7 @@ async function runManifestInstall(context) {
|
|
|
1141
1165
|
if (skipped > 0) {
|
|
1142
1166
|
output(`Skipped ${skipped} of ${manifest.skills.length} Skills pinned to another Agent Depot version`);
|
|
1143
1167
|
}
|
|
1144
|
-
const resolved = await resolveManifestSelections(installable, operations, sourceAccess, options.confirmed);
|
|
1168
|
+
const resolved = await progress.run(`Resolving ${installable.length} manifest Skill${installable.length === 1 ? "" : "s"}`, () => resolveManifestSelections(installable, operations, sourceAccess, options.confirmed), { mayPrompt: options.confirmed && installable.some((selection) => selection.source.kind !== "builtin") });
|
|
1145
1169
|
if (resolved.some((item) => item.method !== undefined) && resolved.length > 1) {
|
|
1146
1170
|
throw new SourceMetadataError("A batch install containing an external method is refused because command side effects cannot be rolled back safely; install that Skill separately");
|
|
1147
1171
|
}
|
|
@@ -1163,7 +1187,7 @@ async function runManifestInstall(context) {
|
|
|
1163
1187
|
try {
|
|
1164
1188
|
for (let index = 0; index < resolved.length; index += 1) {
|
|
1165
1189
|
const item = resolved[index];
|
|
1166
|
-
const transaction = await installOne(item.selection, item.source, sourceAccess, projectRoot, dependencies, item.method, options.portableV1, item.files, item.resolvedVersion, options.confirmAdditionalHostExposure, false, inspections[index]);
|
|
1190
|
+
const transaction = await progress.run(`Installing ${item.selection.path}`, () => installOne(item.selection, item.source, sourceAccess, projectRoot, dependencies, item.method, options.portableV1, item.files, item.resolvedVersion, options.confirmAdditionalHostExposure, false, inspections[index]));
|
|
1167
1191
|
if (transaction) {
|
|
1168
1192
|
transactions.push(transaction);
|
|
1169
1193
|
installed.push({ item, result: transaction.result });
|
|
@@ -1198,20 +1222,21 @@ async function runManifestInstall(context) {
|
|
|
1198
1222
|
}
|
|
1199
1223
|
}
|
|
1200
1224
|
async function runSingleProjectInstall(context) {
|
|
1201
|
-
const { options, operations, dependencies, sourceAccess, output, projectRoot, manifestStore } = context;
|
|
1225
|
+
const { options, operations, dependencies, sourceAccess, output, progress, projectRoot, manifestStore } = context;
|
|
1202
1226
|
if (!options.sourceId || !options.skillPath || !options.version || options.hosts.length === 0) {
|
|
1203
1227
|
throw new CliUsageError(INSTALL_USAGE);
|
|
1204
1228
|
}
|
|
1205
1229
|
const existing = await manifestStore.load();
|
|
1206
|
-
const
|
|
1230
|
+
const request = { ...options, scope: "project", sourceId: options.sourceId, skillPath: options.skillPath, version: options.version };
|
|
1231
|
+
const plan = await progress.run(`Resolving ${options.skillPath}`, () => planSingleInstall(request, { operations, sourceAccess, environment: dependencies, root: projectRoot, existing: existing.skills, recordedIn: manifestStore.path }), { mayPrompt: refreshesSource(options) });
|
|
1207
1232
|
outputSingleInstallPreview(plan, output);
|
|
1208
1233
|
requireConfirmation(options.confirmed, "Installation not confirmed; rerun with --yes to continue");
|
|
1209
|
-
await executeSingleInstall(plan, async (record) => {
|
|
1234
|
+
await progress.run(`Installing ${options.skillPath}`, () => executeSingleInstall(plan, async (record) => {
|
|
1210
1235
|
await manifestStore.save(parseProjectManifest({ version: 1, skills: [...existing.skills, record] }));
|
|
1211
|
-
}, output);
|
|
1236
|
+
}, output));
|
|
1212
1237
|
output(`Saved project manifest: ${manifestStore.path}`);
|
|
1213
1238
|
}
|
|
1214
|
-
async function runUserGlobalInstall(options, operations, dependencies, sourceAccess, output) {
|
|
1239
|
+
async function runUserGlobalInstall(options, operations, dependencies, sourceAccess, output, progress) {
|
|
1215
1240
|
if (options.fromManifest || !options.sourceId || !options.skillPath || !options.version || options.hosts.length === 0) {
|
|
1216
1241
|
throw new CliUsageError(INSTALL_USAGE);
|
|
1217
1242
|
}
|
|
@@ -1220,12 +1245,18 @@ async function runUserGlobalInstall(options, operations, dependencies, sourceAcc
|
|
|
1220
1245
|
throw new Error("Configured Source operations cannot manage user-global installation state");
|
|
1221
1246
|
}
|
|
1222
1247
|
const homeDirectory = path.resolve(dependencies.homeDirectory ?? homedir());
|
|
1223
|
-
const
|
|
1248
|
+
const existing = await listUserGlobalInstallations.call(operations);
|
|
1249
|
+
const request = { ...options, scope: "user-global", sourceId: options.sourceId, skillPath: options.skillPath, version: options.version };
|
|
1250
|
+
const plan = await progress.run(`Resolving ${options.skillPath}`, () => planSingleInstall(request, { operations, sourceAccess, environment: dependencies, root: homeDirectory, existing, recordedIn: "user-global state" }), { mayPrompt: refreshesSource(options) });
|
|
1224
1251
|
outputSingleInstallPreview(plan, output);
|
|
1225
1252
|
requireConfirmation(options.confirmed, "Installation not confirmed; rerun with --yes to continue");
|
|
1226
|
-
await executeSingleInstall(plan, (record) => addUserGlobalInstallation.call(operations, record), output);
|
|
1253
|
+
await progress.run(`Installing ${options.skillPath}`, () => executeSingleInstall(plan, (record) => addUserGlobalInstallation.call(operations, record), output));
|
|
1227
1254
|
output("Saved user-global installation state: shared Source state");
|
|
1228
1255
|
}
|
|
1256
|
+
/** A confirmed install of a non-built-in Source refreshes it with `git`, which may ask for credentials. */
|
|
1257
|
+
function refreshesSource(options) {
|
|
1258
|
+
return options.confirmed && options.sourceId !== BUILT_IN_SOURCE.id;
|
|
1259
|
+
}
|
|
1229
1260
|
/** Appends the comma-separated `--host` value, rejecting unknown or repeated Hosts. */
|
|
1230
1261
|
function collectHosts(hosts, value) {
|
|
1231
1262
|
for (const host of value.split(",")) {
|
|
@@ -25,9 +25,15 @@ export function createPortableExecutableResolver(options = {}) {
|
|
|
25
25
|
return { executable, blocked: target !== undefined && isWindowsExecutableOnWsl(target, wslEnvironment) };
|
|
26
26
|
}
|
|
27
27
|
let blockedExecutable;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
// On Windows only PATHEXT candidates are runnable: npm installs an
|
|
29
|
+
// extensionless sh shim beside `name.cmd`, and spawning it fails (ENOENT).
|
|
30
|
+
const configuredExtensions = (process.env.PATHEXT ?? "").split(";").map((extension) => extension.trim()).filter(Boolean);
|
|
31
|
+
const extensions = configuredExtensions.length > 0 ? configuredExtensions : [".COM", ".EXE", ".BAT", ".CMD"];
|
|
32
|
+
const suffixes = platform !== "win32"
|
|
33
|
+
? [""]
|
|
34
|
+
: extensions.some((extension) => name.toLowerCase().endsWith(extension.toLowerCase()))
|
|
35
|
+
? [""]
|
|
36
|
+
: extensions;
|
|
31
37
|
for (const directory of (process.env.PATH ?? "").split(path.delimiter)) {
|
|
32
38
|
if (!path.isAbsolute(directory)) {
|
|
33
39
|
continue;
|
package/dist/src/git-source.js
CHANGED
|
@@ -2,12 +2,13 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
2
2
|
import { lstat, mkdir, rename, rm, rmdir } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { isLockContention } from "./atomic-file.js";
|
|
5
6
|
import { assertNoSymlinkPath } from "./path-safety.js";
|
|
6
7
|
import { runProcess } from "./process-runner.js";
|
|
7
8
|
export const SKILL_TREE_LIMITS = Object.freeze({
|
|
8
9
|
maxFiles: 256,
|
|
9
10
|
maxFileBytes: 2 * 1024 * 1024,
|
|
10
|
-
maxTotalBytes:
|
|
11
|
+
maxTotalBytes: 32 * 1024 * 1024,
|
|
11
12
|
maxPathBytes: 4096,
|
|
12
13
|
maxDepth: 32,
|
|
13
14
|
maxListingBytes: 16 * 1024 * 1024,
|
|
@@ -424,7 +425,7 @@ async function acquireSourceLock(lockPath) {
|
|
|
424
425
|
};
|
|
425
426
|
}
|
|
426
427
|
catch (error) {
|
|
427
|
-
if (!
|
|
428
|
+
if (!isLockContention(error)) {
|
|
428
429
|
throw error;
|
|
429
430
|
}
|
|
430
431
|
if ((await pathType(lockPath)) === "symlink") {
|
|
@@ -481,9 +482,6 @@ function isSafeSourcePath(candidate) {
|
|
|
481
482
|
function isMissing(error) {
|
|
482
483
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
483
484
|
}
|
|
484
|
-
function isAlreadyExists(error) {
|
|
485
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
|
|
486
|
-
}
|
|
487
485
|
const GIT_PROTOCOLS = new Set(["git:", "http:", "https:", "ssh:"]);
|
|
488
486
|
export function canonicalizeGitSourceUrl(input) {
|
|
489
487
|
if (typeof input !== "string" || input.length === 0 || input.trim() !== input) {
|
package/dist/src/package-cli.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { sourceIdForUrl } from "./git-source.js";
|
|
2
2
|
import { PACKAGE_HOSTS, bundleRoot, describeCoordinates, selectionKey } from "./package-model.js";
|
|
3
|
+
import { NO_PROGRESS } from "./progress.js";
|
|
3
4
|
import { projectSourceOf } from "./sources.js";
|
|
4
5
|
import { CliUsageError } from "./usage-error.js";
|
|
5
6
|
export const PACKAGE_USAGE = "Usage: agent-depot package list; package discover <source-id>; package inspect <selection> --host <host>; package approve <selection> --host <host> [--action <install|update|uninstall>]; package install|update|uninstall <selection> --host <host> [--yes]; package forget <selection> --host <host> --yes";
|
|
6
|
-
export async function runPackageCommand(values, packages, sources, output) {
|
|
7
|
+
export async function runPackageCommand(values, packages, sources, output, progress = NO_PROGRESS) {
|
|
7
8
|
const [command, ...args] = values;
|
|
8
9
|
if (command === "list") {
|
|
9
10
|
requireNoArgs(args, "package list");
|
|
@@ -35,7 +36,7 @@ export async function runPackageCommand(values, packages, sources, output) {
|
|
|
35
36
|
throw new CliUsageError("Package approval only applies to install, update, and uninstall actions");
|
|
36
37
|
}
|
|
37
38
|
const selection = await resolveSelection(parsed, packages, sources);
|
|
38
|
-
const plan = await packages
|
|
39
|
+
const plan = await planWithRefresh(packages, selection, action, progress);
|
|
39
40
|
describeLifecyclePlan(plan, await packages.approvalStatus(selection, action), output);
|
|
40
41
|
await packages.approve(selection, action);
|
|
41
42
|
output(`Approved Package ${action}: ${formatPackageSelection(selection)}`);
|
|
@@ -44,7 +45,7 @@ export async function runPackageCommand(values, packages, sources, output) {
|
|
|
44
45
|
if (command === "install" || command === "update" || command === "uninstall") {
|
|
45
46
|
const parsed = parseSelectionArgs(args, { allowConfirmation: true });
|
|
46
47
|
const selection = await resolveSelection(parsed, packages, sources);
|
|
47
|
-
const plan = await packages
|
|
48
|
+
const plan = await planWithRefresh(packages, selection, command, progress);
|
|
48
49
|
const approval = await packages.approvalStatus(selection, command);
|
|
49
50
|
describeLifecyclePlan(plan, approval, output);
|
|
50
51
|
if (approval !== "approved") {
|
|
@@ -53,7 +54,7 @@ export async function runPackageCommand(values, packages, sources, output) {
|
|
|
53
54
|
if (!parsed.confirmed) {
|
|
54
55
|
throw new Error(`Package ${command} not confirmed; rerun with --yes after reviewing the preview`);
|
|
55
56
|
}
|
|
56
|
-
const result = await packages.executeLifecycle(plan, true);
|
|
57
|
+
const result = await progress.run(`${command === "install" ? "Installing" : command === "update" ? "Updating" : "Uninstalling"} Package ${formatPackageSelection(selection)}`, () => packages.executeLifecycle(plan, true));
|
|
57
58
|
output(`Package ${formatPackageSelection(selection)}: ${describePackageResult(result)}`);
|
|
58
59
|
return packageResultSucceeded(result) ? 0 : 1;
|
|
59
60
|
}
|
|
@@ -71,6 +72,10 @@ export async function runPackageCommand(values, packages, sources, output) {
|
|
|
71
72
|
}
|
|
72
73
|
throw new CliUsageError(PACKAGE_USAGE);
|
|
73
74
|
}
|
|
75
|
+
/** Planning refreshes the Package's Git Source (ADR 0004), and git may ask for credentials, so the step never animates. */
|
|
76
|
+
export function planWithRefresh(packages, selection, action, progress) {
|
|
77
|
+
return progress.run(`Refreshing Source for Package ${formatPackageSelection(selection)}`, () => packages.planLifecycle(selection, action), { mayPrompt: true });
|
|
78
|
+
}
|
|
74
79
|
export function formatPackageSelection(selection) {
|
|
75
80
|
return `${sourceToken(selection.source)}::${describeSelectionCoordinate(selection)}`;
|
|
76
81
|
}
|
package/dist/src/package-flow.js
CHANGED
|
@@ -357,13 +357,14 @@ export function createPackageOperations(environment = {}) {
|
|
|
357
357
|
result = await runner(resolution.executable, command.args, {
|
|
358
358
|
cwd: plan.cwd,
|
|
359
359
|
captureStdout: true,
|
|
360
|
+
streamStdout: true,
|
|
360
361
|
maxOutputBytes: PACKAGE_OUTPUT_LIMIT,
|
|
361
362
|
maxStderrBytes: PACKAGE_OUTPUT_LIMIT,
|
|
362
363
|
});
|
|
363
364
|
}
|
|
364
365
|
catch (error) {
|
|
365
366
|
const code = error?.code;
|
|
366
|
-
if (code === undefined || !["ENOENT", "EACCES", "ENOEXEC", "EPERM"].includes(code)) {
|
|
367
|
+
if (code === undefined || !["ENOENT", "EACCES", "ENOEXEC", "EPERM", "EINVAL"].includes(code)) {
|
|
367
368
|
throw error;
|
|
368
369
|
}
|
|
369
370
|
const detail = error instanceof Error ? error.message : String(error);
|