@iseoane/agent-depot 0.4.1 → 0.5.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.
Files changed (56) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.es.md +969 -0
  3. package/README.md +244 -23
  4. package/dist/src/app-flow.js +52 -51
  5. package/dist/src/app-recipe-removal.js +63 -0
  6. package/dist/src/cli.js +122 -11
  7. package/dist/src/executable-resolution.js +54 -0
  8. package/dist/src/git-source.js +150 -6
  9. package/dist/src/package-bundles.js +614 -0
  10. package/dist/src/package-cli.js +412 -0
  11. package/dist/src/package-content.js +158 -0
  12. package/dist/src/package-flow.js +645 -0
  13. package/dist/src/package-host-plans.js +243 -0
  14. package/dist/src/package-host-state.js +473 -0
  15. package/dist/src/package-model.js +33 -0
  16. package/dist/src/package-owned-skills.js +124 -0
  17. package/dist/src/package-state.js +404 -0
  18. package/dist/src/process-runner.js +9 -1
  19. package/dist/src/profile-cli.js +15 -9
  20. package/dist/src/profile-export.js +44 -9
  21. package/dist/src/profile-import.js +80 -17
  22. package/dist/src/profile.js +78 -3
  23. package/dist/src/skill-discovery.js +7 -2
  24. package/dist/src/skill-install.js +16 -0
  25. package/dist/src/sources.js +11 -0
  26. package/dist/src/tui/app-actions.js +1 -1
  27. package/dist/src/tui/app.js +10 -11
  28. package/dist/src/tui/catalog-browse.js +46 -13
  29. package/dist/src/tui/catalog-ownership.js +12 -0
  30. package/dist/src/tui/catalog-panel.js +16 -4
  31. package/dist/src/tui/catalog-state.js +62 -6
  32. package/dist/src/tui/catalog-tree.js +58 -12
  33. package/dist/src/tui/catalog-view.js +53 -7
  34. package/dist/src/tui/environment.js +8 -1
  35. package/dist/src/tui/installations-browse.js +20 -2
  36. package/dist/src/tui/installations-rows.js +2 -1
  37. package/dist/src/tui/installations-tree.js +14 -1
  38. package/dist/src/tui/installations-view.js +8 -2
  39. package/dist/src/tui/installations.js +12 -2
  40. package/dist/src/tui/manage-actions.js +2 -0
  41. package/dist/src/tui/manage-panel.js +5 -0
  42. package/dist/src/tui/package-actions.js +165 -0
  43. package/dist/src/tui/package-rows.js +91 -0
  44. package/dist/src/tui/profile-view.js +36 -14
  45. package/dist/src/tui/render.js +1 -1
  46. package/dist/src/tui/sources-mode.js +2 -0
  47. package/dist/src/tui/sources-recipes.js +116 -53
  48. package/dist/src/tui/sources-view.js +57 -19
  49. package/dist/src/tui/tree.js +3 -4
  50. package/dist/src/tui/updates-flow.js +4 -1
  51. package/dist/src/tui/updates-panel.js +2 -2
  52. package/dist/src/tui/updates.js +72 -4
  53. package/dist/src/tui/window.js +3 -4
  54. package/dist/src/user-global-skill-inventory.js +7 -2
  55. package/package.json +2 -1
  56. package/skills/agent-depot-apprecipe/SKILL.md +29 -5
@@ -0,0 +1,243 @@
1
+ import { createHash } from "node:crypto";
2
+ import { compareAppVersions, sameAppVersion } from "./app-version.js";
3
+ import { claudeMarketplaceNamesForSource, piSourceIdentity, } from "./package-host-state.js";
4
+ import { parsePortableInstallationMethod } from "./project-manifest.js";
5
+ /** A plan that cannot be derived safely fails closed with its reason. */
6
+ export class PackagePlanError extends Error {
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = "PackagePlanError";
10
+ }
11
+ }
12
+ const NO_STEPS = Object.freeze([]);
13
+ /**
14
+ * The one place that knows each host CLI's grammar, verified against the
15
+ * installed CLIs in `.scratch/packages/arena/host-cli-verbs.md`. V1 uses the
16
+ * scoped verb for every action. Select and forget run nothing.
17
+ */
18
+ export const HOST_STEP_TEMPLATES = Object.freeze({
19
+ pi: Object.freeze({
20
+ install: (input) => Object.freeze([
21
+ Object.freeze(["pi", "install", input.installSpec]),
22
+ ]),
23
+ update: (input) => Object.freeze([
24
+ Object.freeze(["pi", "update", input.source]),
25
+ ]),
26
+ uninstall: (input) => Object.freeze([
27
+ Object.freeze(["pi", "remove", input.source]),
28
+ ]),
29
+ select: () => NO_STEPS,
30
+ forget: () => NO_STEPS,
31
+ }),
32
+ claude: Object.freeze({
33
+ install: (input) => Object.freeze([
34
+ ...(input.marketplaceKnown
35
+ ? []
36
+ : [Object.freeze(["claude", "plugin", "marketplace", "add", input.source])]),
37
+ Object.freeze(["claude", "plugin", "install", claudeInstallId(input)]),
38
+ ]),
39
+ update: (input) => Object.freeze([
40
+ Object.freeze(["claude", "plugin", "marketplace", "update", claudeMarketplaceName(input)]),
41
+ Object.freeze(["claude", "plugin", "update", claudeInstallId(input)]),
42
+ ]),
43
+ uninstall: (input) => Object.freeze([
44
+ Object.freeze(["claude", "plugin", "uninstall", claudeInstallId(input)]),
45
+ ]),
46
+ select: () => NO_STEPS,
47
+ forget: () => NO_STEPS,
48
+ }),
49
+ });
50
+ /**
51
+ * The exact host argv for one action. Pure. Every produced argv is re-validated
52
+ * through `parsePortableInstallationMethod`, so the recipe safety rules apply to
53
+ * host commands too.
54
+ *
55
+ * The synthesized design's four arguments cannot carry the Claude marketplace
56
+ * name, which lives in the Source snapshot's `marketplace.json` and never in the
57
+ * host state before the first `marketplace add`. `resolution` supplies it; a
58
+ * Claude plan that must name the marketplace and has no resolution fails closed.
59
+ */
60
+ export function planHostSteps(action, descriptor, selection, state, resolution = {}) {
61
+ assertHostCommand(action, descriptor, selection);
62
+ const input = stepTemplateInput(descriptor, selection, state, resolution);
63
+ const steps = HOST_STEP_TEMPLATES[descriptor.host][action](input);
64
+ return Object.freeze(steps.map((argv, index) => assertValidHostArgv(argv, `${descriptor.host} ${action} step ${index + 1}`)));
65
+ }
66
+ /**
67
+ * The host's own identity for one selection: `<plugin>@<marketplace>` for Claude
68
+ * and the Pi Source identity for Pi. The same value the argv templates carry, so
69
+ * a record can never name an identity the host was not given.
70
+ */
71
+ export function hostInstallId(descriptor, selection, state, resolution = {}) {
72
+ const input = stepTemplateInput(descriptor, selection, state, resolution);
73
+ return descriptor.host === "claude" ? claudeInstallId(input) : input.source;
74
+ }
75
+ function assertHostCommand(action, descriptor, selection) {
76
+ if (descriptor.host !== selection.host) {
77
+ throw new PackagePlanError(`The bundle descriptor host ${JSON.stringify(descriptor.host)} does not match the selection host ${JSON.stringify(selection.host)}`);
78
+ }
79
+ if (!runsHostCommand(action)) {
80
+ return;
81
+ }
82
+ if (!isInstallable(descriptor)) {
83
+ throw new PackagePlanError(`The bundle ${JSON.stringify(descriptor.name)} is not installable (${descriptor.warnings.join("; ")})`);
84
+ }
85
+ if (descriptor.host === "claude" && action !== "uninstall" && selection.version.policy === "fixed") {
86
+ throw new PackagePlanError(`A fixed-version Claude selection cannot be delegated: claude plugin install takes <plugin>@<marketplace> and cannot carry the pin ${JSON.stringify(selection.version.version)}`);
87
+ }
88
+ }
89
+ function stepTemplateInput(descriptor, selection, state, resolution) {
90
+ const source = hostSource(selection);
91
+ const marketplace = descriptor.host === "claude" ? resolveClaudeMarketplace(state, selection) : undefined;
92
+ const marketplaceName = resolution.marketplaceName ?? marketplace?.name;
93
+ return Object.freeze({
94
+ descriptor,
95
+ selection,
96
+ source,
97
+ installSpec: installSpec(source, selection),
98
+ ...(marketplaceName === undefined ? {} : { marketplaceName }),
99
+ marketplaceKnown: marketplace?.known === true,
100
+ });
101
+ }
102
+ /**
103
+ * Every recorded coordinate an action may change. V1 uses the scoped verb for
104
+ * every action, verified in `.scratch/packages/arena/host-cli-verbs.md`, so that
105
+ * is the selection's own coordinates. Flip `HOST_WIDE_UPDATE` for a host when a
106
+ * host-wide verb like `pi update --extensions` is chosen; this function then
107
+ * returns every recorded coordinate of that host.
108
+ */
109
+ export function affectedBy(action, selection, recorded) {
110
+ if (action === "update" && HOST_WIDE_UPDATE[selection.host]) {
111
+ return Object.freeze(recorded
112
+ .filter((candidate) => candidate.host === selection.host)
113
+ .map((candidate) => coordinatesOf(candidate)));
114
+ }
115
+ return Object.freeze([coordinatesOf(selection)]);
116
+ }
117
+ const HOST_WIDE_UPDATE = Object.freeze({
118
+ pi: false,
119
+ claude: false,
120
+ });
121
+ /** sha256 over the canonical serialization of the declaration. Exact argv order included. */
122
+ export function packageApprovalDigest(declaration) {
123
+ return createHash("sha256").update(canonicalJson(declaration), "utf8").digest("hex");
124
+ }
125
+ /**
126
+ * Compares installed evidence with available evidence. Manifest versions compare
127
+ * through `sameAppVersion` and `compareAppVersions`; differing commits compare only
128
+ * when cached Source history proves the installed commit is an ancestor.
129
+ */
130
+ export function classifyUpdate(installed, available, availableCommit, installedCommitIsAncestor) {
131
+ if (installed.kind !== "installed" || installed.reason !== undefined || available === undefined) {
132
+ return "unknown";
133
+ }
134
+ if (installed.commit !== undefined) {
135
+ const sourceCommit = availableCommit ?? (available.kind === "git-commit" ? available.commit : undefined);
136
+ if (sourceCommit === undefined)
137
+ return "unknown";
138
+ if (normalizeCommit(installed.commit) === normalizeCommit(sourceCommit))
139
+ return "current";
140
+ return installedCommitIsAncestor === true ? "update available" : "unknown";
141
+ }
142
+ if (installed.version !== undefined && available.kind === "manifest-version") {
143
+ if (sameAppVersion(installed.version, available.version)) {
144
+ return "current";
145
+ }
146
+ const order = compareAppVersions(installed.version, available.version);
147
+ if (order === undefined) {
148
+ return "unknown";
149
+ }
150
+ return order < 0 ? "update available" : "current";
151
+ }
152
+ return "unknown";
153
+ }
154
+ /** Validates one host argv with the shared portable-command rules. */
155
+ export function assertValidHostArgv(argv, label) {
156
+ try {
157
+ return parsePortableInstallationMethod({ kind: "command", argv }, { label }).argv;
158
+ }
159
+ catch (error) {
160
+ const reason = error instanceof Error ? error.message : "the argv is not a portable no-shell command";
161
+ throw new PackagePlanError(`Refused host command (${label}): ${reason}`);
162
+ }
163
+ }
164
+ function claudeInstallId(input) {
165
+ return `${claudePluginName(input)}@${claudeMarketplaceName(input)}`;
166
+ }
167
+ function claudePluginName(input) {
168
+ return input.selection.host === "claude" ? input.selection.pluginName : input.descriptor.name;
169
+ }
170
+ function claudeMarketplaceName(input) {
171
+ if (input.marketplaceName === undefined || input.marketplaceName === "") {
172
+ throw new PackagePlanError(`Naming the marketplace for ${JSON.stringify(claudePluginName(input))} requires the name declared in the Source's .claude-plugin/marketplace.json`);
173
+ }
174
+ return input.marketplaceName;
175
+ }
176
+ function resolveClaudeMarketplace(state, selection) {
177
+ if (selection.host !== "claude") {
178
+ return undefined;
179
+ }
180
+ const known = claudeMarketplaceNamesForSource(state.claudeMarketplaces, selection.source)[0];
181
+ if (known !== undefined) {
182
+ return { name: known, known: true };
183
+ }
184
+ const installed = state.claudePlugins.records.find((record) => !record.drifted && record.scope === "user" && record.pluginName === selection.pluginName && record.marketplace !== "");
185
+ return installed === undefined ? undefined : { name: installed.marketplace, known: false };
186
+ }
187
+ function isInstallable(descriptor) {
188
+ return descriptor.installable;
189
+ }
190
+ function runsHostCommand(action) {
191
+ return action === "install" || action === "update" || action === "uninstall";
192
+ }
193
+ function hostSource(selection) {
194
+ if (selection.host === "pi") {
195
+ const identity = piSourceIdentity(selection.source);
196
+ if (identity === undefined) {
197
+ throw new PackagePlanError("Pi cannot install a built-in or local path Source");
198
+ }
199
+ return identity;
200
+ }
201
+ if (selection.source.kind === "builtin" || "path" in selection.source) {
202
+ throw new PackagePlanError("Claude Code cannot install a built-in or local path Source");
203
+ }
204
+ return selection.source.url;
205
+ }
206
+ function installSpec(source, selection) {
207
+ return selection.version.policy === "fixed" && selection.version.version !== ""
208
+ ? `${source}@${selection.version.version}`
209
+ : source;
210
+ }
211
+ function normalizeCommit(commit) {
212
+ return commit.trim().toLowerCase();
213
+ }
214
+ function coordinatesOf(selection) {
215
+ return selection.host === "pi"
216
+ ? Object.freeze({ host: selection.host, root: selection.root })
217
+ : Object.freeze({ host: selection.host, marketplaceRoot: selection.marketplaceRoot, pluginName: selection.pluginName });
218
+ }
219
+ function canonicalJson(value) {
220
+ if (value === null) {
221
+ return "null";
222
+ }
223
+ if (typeof value === "string") {
224
+ return JSON.stringify(value);
225
+ }
226
+ if (typeof value === "boolean") {
227
+ return value ? "true" : "false";
228
+ }
229
+ if (typeof value === "number") {
230
+ return Number.isFinite(value) ? String(value) : "null";
231
+ }
232
+ if (Array.isArray(value)) {
233
+ return `[${value.map((entry) => canonicalJson(entry)).join(",")}]`;
234
+ }
235
+ if (isRecord(value)) {
236
+ const keys = Object.keys(value).filter((key) => value[key] !== undefined).sort();
237
+ return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
238
+ }
239
+ throw new PackagePlanError("The package declaration contains a value that cannot be canonicalized");
240
+ }
241
+ function isRecord(value) {
242
+ return typeof value === "object" && value !== null && !Array.isArray(value);
243
+ }