@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.
- package/CHANGELOG.md +45 -0
- package/README.es.md +969 -0
- package/README.md +244 -23
- package/dist/src/app-flow.js +52 -51
- package/dist/src/app-recipe-removal.js +63 -0
- package/dist/src/cli.js +122 -11
- package/dist/src/executable-resolution.js +54 -0
- package/dist/src/git-source.js +150 -6
- package/dist/src/package-bundles.js +614 -0
- package/dist/src/package-cli.js +412 -0
- package/dist/src/package-content.js +158 -0
- package/dist/src/package-flow.js +645 -0
- package/dist/src/package-host-plans.js +243 -0
- package/dist/src/package-host-state.js +473 -0
- package/dist/src/package-model.js +33 -0
- package/dist/src/package-owned-skills.js +124 -0
- package/dist/src/package-state.js +404 -0
- package/dist/src/process-runner.js +9 -1
- package/dist/src/profile-cli.js +15 -9
- package/dist/src/profile-export.js +44 -9
- package/dist/src/profile-import.js +80 -17
- package/dist/src/profile.js +78 -3
- package/dist/src/skill-discovery.js +7 -2
- package/dist/src/skill-install.js +16 -0
- package/dist/src/sources.js +11 -0
- package/dist/src/tui/app-actions.js +1 -1
- package/dist/src/tui/app.js +10 -11
- package/dist/src/tui/catalog-browse.js +46 -13
- package/dist/src/tui/catalog-ownership.js +12 -0
- package/dist/src/tui/catalog-panel.js +16 -4
- package/dist/src/tui/catalog-state.js +62 -6
- package/dist/src/tui/catalog-tree.js +58 -12
- package/dist/src/tui/catalog-view.js +53 -7
- package/dist/src/tui/environment.js +8 -1
- package/dist/src/tui/installations-browse.js +20 -2
- package/dist/src/tui/installations-rows.js +2 -1
- package/dist/src/tui/installations-tree.js +14 -1
- package/dist/src/tui/installations-view.js +8 -2
- package/dist/src/tui/installations.js +12 -2
- package/dist/src/tui/manage-actions.js +2 -0
- package/dist/src/tui/manage-panel.js +5 -0
- package/dist/src/tui/package-actions.js +165 -0
- package/dist/src/tui/package-rows.js +91 -0
- package/dist/src/tui/profile-view.js +36 -14
- package/dist/src/tui/render.js +1 -1
- package/dist/src/tui/sources-mode.js +2 -0
- package/dist/src/tui/sources-recipes.js +116 -53
- package/dist/src/tui/sources-view.js +57 -19
- package/dist/src/tui/tree.js +3 -4
- package/dist/src/tui/updates-flow.js +4 -1
- package/dist/src/tui/updates-panel.js +2 -2
- package/dist/src/tui/updates.js +72 -4
- package/dist/src/tui/window.js +3 -4
- package/dist/src/user-global-skill-inventory.js +7 -2
- package/package.json +2 -1
- 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
|
+
}
|