@iseoane/agent-depot 0.2.0 → 0.3.0
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 +47 -0
- package/README.md +117 -3
- package/dist/src/app-cli.js +172 -0
- package/dist/src/app-flow.js +457 -0
- package/dist/src/app-latest.js +63 -0
- package/dist/src/app-owned-skills.js +42 -0
- package/dist/src/app-recipes.js +116 -0
- package/dist/src/app-schema.js +45 -0
- package/dist/src/app-version.js +46 -0
- package/dist/src/atomic-file.js +26 -0
- package/dist/src/cli.js +1256 -0
- package/dist/src/git-source.js +409 -0
- package/dist/src/path-safety.js +31 -0
- package/dist/src/process-runner.js +41 -0
- package/dist/src/project-installation.js +1411 -0
- package/dist/src/project-manifest.js +511 -0
- package/dist/src/skill-adoption.js +77 -0
- package/dist/src/skill-discovery.js +467 -0
- package/dist/src/skill-hosts.js +103 -0
- package/dist/src/skill-install.js +374 -0
- package/dist/src/skill-removal.js +119 -0
- package/dist/src/skill-update.js +244 -0
- package/dist/src/source-state.js +240 -0
- package/dist/src/sources.js +391 -0
- package/dist/src/tui/adoption-actions.js +144 -0
- package/dist/src/tui/app-actions.js +157 -0
- package/dist/src/tui/app.js +63 -0
- package/dist/src/tui/batch.js +17 -0
- package/dist/src/tui/bulk-actions.js +243 -0
- package/dist/src/tui/catalog-actions.js +9 -0
- package/dist/src/tui/catalog-browse.js +59 -0
- package/dist/src/tui/catalog-filter.js +20 -0
- package/dist/src/tui/catalog-flow.js +107 -0
- package/dist/src/tui/catalog-installs.js +193 -0
- package/dist/src/tui/catalog-panel.js +47 -0
- package/dist/src/tui/catalog-state.js +92 -0
- package/dist/src/tui/catalog-tree.js +35 -0
- package/dist/src/tui/catalog-view.js +89 -0
- package/dist/src/tui/environment.js +1 -0
- package/dist/src/tui/installations-browse.js +136 -0
- package/dist/src/tui/installations-mode.js +1 -0
- package/dist/src/tui/installations-panel.js +27 -0
- package/dist/src/tui/installations-rows.js +11 -0
- package/dist/src/tui/installations-tree.js +80 -0
- package/dist/src/tui/installations-view.js +154 -0
- package/dist/src/tui/installations.js +172 -0
- package/dist/src/tui/keys.js +31 -0
- package/dist/src/tui/manage-actions.js +174 -0
- package/dist/src/tui/manage-panel.js +65 -0
- package/dist/src/tui/mode-keys.js +61 -0
- package/dist/src/tui/panel-parts.js +23 -0
- package/dist/src/tui/render.js +8 -0
- package/dist/src/tui/sources-mode.js +17 -0
- package/dist/src/tui/sources-view.js +276 -0
- package/dist/src/tui/theme.js +29 -0
- package/dist/src/tui/tree-navigation.js +68 -0
- package/dist/src/tui/tree.js +53 -0
- package/dist/src/tui/unmanaged-actions.js +178 -0
- package/dist/src/tui/updates-flow.js +89 -0
- package/dist/src/tui/updates-keys.js +104 -0
- package/dist/src/tui/updates-mode.js +1 -0
- package/dist/src/tui/updates-model.js +48 -0
- package/dist/src/tui/updates-panel.js +44 -0
- package/dist/src/tui/updates-view.js +101 -0
- package/dist/src/tui/updates.js +230 -0
- package/dist/src/tui/view-state.js +42 -0
- package/dist/src/tui/window.js +31 -0
- package/dist/src/unmanaged-removal.js +60 -0
- package/dist/src/update-batch.js +376 -0
- package/dist/src/update-flow.js +135 -0
- package/dist/src/usage-error.js +7 -0
- package/dist/src/user-global-skill-inventory.js +565 -0
- package/dist/src/user-method.js +14 -0
- package/package.json +7 -4
- package/skills/agent-depot-apprecipe/SKILL.md +44 -0
- package/skills/agent-depot-apprecipe/assets/example.md +25 -0
- package/skills/doctor-md-agents/scripts/test_collect_sessions.py +0 -780
- package/skills/doctor-md-agents/scripts/test_extract_rules.py +0 -530
- package/skills/doctor-md-agents/scripts/test_render_report.py +0 -383
- package/skills/doctor-md-agents/scripts/test_scan_corrections.py +0 -343
- package/skills/doctor-md-agents/scripts/test_verify_citations.py +0 -236
- package/skills/doctor-md-agents/scripts/testdata/old_extract_rules_pre_t1.py.txt +0 -507
- package/skills/doctor-md-skill/scripts/test_collect_sessions.py +0 -593
- package/skills/doctor-md-skill/scripts/test_detect_machine_sessions.py +0 -181
- package/skills/doctor-md-skill/scripts/test_render_report.py +0 -241
- package/skills/doctor-md-skill/scripts/test_verify_citations.py +0 -196
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { parsePortableInstallationMethod, PROJECT_HOSTS, } from "./project-manifest.js";
|
|
2
|
+
export const APP_GITHUB_PATTERN = "^[\\w-][\\w.-]*/[\\w-][\\w.-]*$";
|
|
3
|
+
export const APP_NPM_PATTERN = "^(?:@[a-z0-9~-][a-z0-9._~-]*/)?[a-z0-9~-][a-z0-9._~-]*$";
|
|
4
|
+
export const APP_SKILL_PATTERN = "^(?!\\*+$)(?!\\s*$)[^/\\\\\\u0000]+$";
|
|
5
|
+
function object(value, keys, field) {
|
|
6
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
7
|
+
throw new Error(`${field}: expected an object`);
|
|
8
|
+
}
|
|
9
|
+
const record = value;
|
|
10
|
+
const unsupported = Object.keys(record).find((key) => !keys.includes(key));
|
|
11
|
+
if (unsupported !== undefined)
|
|
12
|
+
throw new Error(`${field}.${unsupported}: unsupported recipe field`);
|
|
13
|
+
return record;
|
|
14
|
+
}
|
|
15
|
+
function text(value, field) {
|
|
16
|
+
if (typeof value !== "string" || !value.trim())
|
|
17
|
+
throw new Error(`${field}: expected non-empty text`);
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
function argv(value, field) {
|
|
21
|
+
return parsePortableInstallationMethod({ kind: "command", argv: value }, { label: `${field}:` }).argv;
|
|
22
|
+
}
|
|
23
|
+
function step(value, field) {
|
|
24
|
+
const item = object(value, ["argv", "manual"], field);
|
|
25
|
+
if (item.argv === undefined && item.manual === undefined) {
|
|
26
|
+
throw new Error(`${field}: step needs argv or manual`);
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
...(item.argv === undefined ? {} : { argv: argv(item.argv, `${field}.argv`) }),
|
|
30
|
+
...(item.manual === undefined ? {} : { manual: text(item.manual, `${field}.manual`) }),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function version(value, field) {
|
|
34
|
+
const item = object(value, ["argv", "pattern"], field);
|
|
35
|
+
const pattern = text(item.pattern, `${field}.pattern`);
|
|
36
|
+
try {
|
|
37
|
+
new RegExp(pattern);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
throw new Error(`${field}.pattern: ${error instanceof Error ? error.message : String(error)}`);
|
|
41
|
+
}
|
|
42
|
+
// Count captures while ignoring escapes, character classes and non-capturing/lookaround groups.
|
|
43
|
+
let captures = 0;
|
|
44
|
+
let inClass = false;
|
|
45
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
46
|
+
if (pattern[i] === "\\") {
|
|
47
|
+
i++;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (pattern[i] === "[")
|
|
51
|
+
inClass = true;
|
|
52
|
+
if (pattern[i] === "]")
|
|
53
|
+
inClass = false;
|
|
54
|
+
const namedCapture = /^\?<[^=!]/u.test(pattern.slice(i + 1));
|
|
55
|
+
if (!inClass && pattern[i] === "(" && (pattern[i + 1] !== "?" || namedCapture))
|
|
56
|
+
captures++;
|
|
57
|
+
}
|
|
58
|
+
if (captures !== 1)
|
|
59
|
+
throw new Error(`${field}.pattern: expected exactly one capture group`);
|
|
60
|
+
return { argv: argv(item.argv, `${field}.argv`), pattern };
|
|
61
|
+
}
|
|
62
|
+
function hosts(value, field) {
|
|
63
|
+
return Object.fromEntries(Object.entries(object(value, PROJECT_HOSTS, field)).map(([host, item]) => [host, step(item, `${field}.${host}`)]));
|
|
64
|
+
}
|
|
65
|
+
function latest(value) {
|
|
66
|
+
const item = object(value, ["github", "npm", "argv", "pattern"], "latest");
|
|
67
|
+
if (Object.keys(item).length === 1 && item.github !== undefined) {
|
|
68
|
+
const github = text(item.github, "latest.github");
|
|
69
|
+
if (!new RegExp(APP_GITHUB_PATTERN).test(github))
|
|
70
|
+
throw new Error("latest.github: expected owner/repo");
|
|
71
|
+
return { github };
|
|
72
|
+
}
|
|
73
|
+
if (Object.keys(item).length === 1 && item.npm !== undefined) {
|
|
74
|
+
const npm = text(item.npm, "latest.npm");
|
|
75
|
+
if (npm.length > 214 || !new RegExp(APP_NPM_PATTERN).test(npm) || ["node_modules", "favicon.ico"].includes(npm)) {
|
|
76
|
+
throw new Error("latest.npm: expected a valid npm package name");
|
|
77
|
+
}
|
|
78
|
+
return { npm };
|
|
79
|
+
}
|
|
80
|
+
return version(item, "latest");
|
|
81
|
+
}
|
|
82
|
+
export function parseAppRecipe(value) {
|
|
83
|
+
const item = object(value, [
|
|
84
|
+
"name", "platform", "homepage", "install", "update", "uninstall", "version",
|
|
85
|
+
"latest", "setup", "teardown", "skills",
|
|
86
|
+
], "recipe");
|
|
87
|
+
if (item.platform !== undefined && (typeof item.platform !== "string" || !["linux", "windows", "darwin"].includes(item.platform))) {
|
|
88
|
+
throw new Error("platform: unsupported platform");
|
|
89
|
+
}
|
|
90
|
+
let skills;
|
|
91
|
+
if (item.skills !== undefined) {
|
|
92
|
+
if (!Array.isArray(item.skills))
|
|
93
|
+
throw new Error("skills: expected a list");
|
|
94
|
+
skills = [];
|
|
95
|
+
for (const [index, skill] of item.skills.entries()) {
|
|
96
|
+
const pattern = text(skill, `skills[${index}]`);
|
|
97
|
+
if (!new RegExp(APP_SKILL_PATTERN, "u").test(pattern)) {
|
|
98
|
+
throw new Error(`skills[${index}]: expected a non-empty name pattern without path separators or NUL, and not only stars`);
|
|
99
|
+
}
|
|
100
|
+
skills.push(pattern);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
name: text(item.name, "name"),
|
|
105
|
+
install: step(item.install, "install"),
|
|
106
|
+
update: step(item.update, "update"),
|
|
107
|
+
uninstall: step(item.uninstall, "uninstall"),
|
|
108
|
+
version: version(item.version, "version"),
|
|
109
|
+
...(item.platform === undefined ? {} : { platform: item.platform }),
|
|
110
|
+
...(item.homepage === undefined ? {} : { homepage: text(item.homepage, "homepage") }),
|
|
111
|
+
...(item.latest === undefined ? {} : { latest: latest(item.latest) }),
|
|
112
|
+
...(item.setup === undefined ? {} : { setup: hosts(item.setup, "setup") }),
|
|
113
|
+
...(item.teardown === undefined ? {} : { teardown: hosts(item.teardown, "teardown") }),
|
|
114
|
+
...(skills === undefined ? {} : { skills }),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { APP_GITHUB_PATTERN, APP_NPM_PATTERN, APP_SKILL_PATTERN } from "./app-recipes.js";
|
|
2
|
+
import { PROJECT_HOSTS } from "./project-manifest.js";
|
|
3
|
+
const argv = {
|
|
4
|
+
type: "array", minItems: 1, items: { type: "string", minLength: 1 },
|
|
5
|
+
description: "Bare no-shell executable and arguments; user-method safety rules also apply.",
|
|
6
|
+
};
|
|
7
|
+
const text = { type: "string", minLength: 1 };
|
|
8
|
+
const step = {
|
|
9
|
+
type: "object", additionalProperties: false,
|
|
10
|
+
properties: { argv, manual: text },
|
|
11
|
+
anyOf: [{ required: ["argv"] }, { required: ["manual"] }],
|
|
12
|
+
};
|
|
13
|
+
const version = {
|
|
14
|
+
type: "object", additionalProperties: false, required: ["argv", "pattern"],
|
|
15
|
+
properties: {
|
|
16
|
+
argv, pattern: { ...text, description: "Valid JavaScript regex with exactly one capture group." },
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
const hosts = {
|
|
20
|
+
type: "object", additionalProperties: false,
|
|
21
|
+
properties: Object.fromEntries(PROJECT_HOSTS.map((host) => [host, step])),
|
|
22
|
+
};
|
|
23
|
+
export const APP_RECIPE_SCHEMA = {
|
|
24
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
25
|
+
title: "Agent Depot App recipe", type: "object", additionalProperties: false,
|
|
26
|
+
required: ["name", "install", "update", "uninstall", "version"],
|
|
27
|
+
properties: {
|
|
28
|
+
name: text, platform: { enum: ["linux", "windows", "darwin"] }, homepage: text,
|
|
29
|
+
install: step, update: step, uninstall: step, version,
|
|
30
|
+
latest: {
|
|
31
|
+
oneOf: [
|
|
32
|
+
version,
|
|
33
|
+
{
|
|
34
|
+
type: "object", additionalProperties: false, required: ["github"],
|
|
35
|
+
properties: { github: { ...text, pattern: APP_GITHUB_PATTERN } },
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
type: "object", additionalProperties: false, required: ["npm"],
|
|
39
|
+
properties: { npm: { ...text, pattern: APP_NPM_PATTERN, maxLength: 214, not: { enum: ["node_modules", "favicon.ico"] } } },
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
setup: hosts, teardown: hosts, skills: { type: "array", items: { ...text, pattern: APP_SKILL_PATTERN } },
|
|
44
|
+
},
|
|
45
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** Strict SemVer ordering; unlike Skill's permissive comparator, opaque inputs stay distinguishable. */
|
|
2
|
+
export function compareAppVersions(left, right) {
|
|
3
|
+
const parse = (value) => {
|
|
4
|
+
const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([\da-zA-Z-]+(?:\.[\da-zA-Z-]+)*))?(?:\+[\da-zA-Z-]+(?:\.[\da-zA-Z-]+)*)?$/u.exec(normalizeAppVersion(value));
|
|
5
|
+
if (!match)
|
|
6
|
+
return undefined;
|
|
7
|
+
const pre = match[4]?.split(".") ?? [];
|
|
8
|
+
if (pre.some(part => /^\d+$/u.test(part) && /^0\d/u.test(part)))
|
|
9
|
+
return undefined;
|
|
10
|
+
return { core: match.slice(1, 4).map(part => BigInt(part)), pre };
|
|
11
|
+
};
|
|
12
|
+
const a = parse(left);
|
|
13
|
+
const b = parse(right);
|
|
14
|
+
if (!a || !b)
|
|
15
|
+
return undefined;
|
|
16
|
+
for (let index = 0; index < 3; index++) {
|
|
17
|
+
if (a.core[index] !== b.core[index])
|
|
18
|
+
return a.core[index] < b.core[index] ? -1 : 1;
|
|
19
|
+
}
|
|
20
|
+
if (!a.pre.length || !b.pre.length)
|
|
21
|
+
return Math.sign(b.pre.length - a.pre.length);
|
|
22
|
+
for (let index = 0; index < Math.max(a.pre.length, b.pre.length); index++) {
|
|
23
|
+
const x = a.pre[index];
|
|
24
|
+
const y = b.pre[index];
|
|
25
|
+
if (x === y)
|
|
26
|
+
continue;
|
|
27
|
+
if (x === undefined)
|
|
28
|
+
return -1;
|
|
29
|
+
if (y === undefined)
|
|
30
|
+
return 1;
|
|
31
|
+
const numericX = /^\d+$/u.test(x);
|
|
32
|
+
const numericY = /^\d+$/u.test(y);
|
|
33
|
+
if (numericX && numericY)
|
|
34
|
+
return BigInt(x) < BigInt(y) ? -1 : 1;
|
|
35
|
+
if (numericX !== numericY)
|
|
36
|
+
return numericX ? -1 : 1;
|
|
37
|
+
return x < y ? -1 : 1;
|
|
38
|
+
}
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
export function normalizeAppVersion(version) {
|
|
42
|
+
return version.replace(/^v(?=\d)/u, "");
|
|
43
|
+
}
|
|
44
|
+
export function sameAppVersion(left, right) {
|
|
45
|
+
return compareAppVersions(left, right) === 0 || normalizeAppVersion(left) === normalizeAppVersion(right);
|
|
46
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { rename, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
/** Write a unique temporary sibling, then publish it with rename. */
|
|
4
|
+
export async function writeFileAtomically(file, contents, options = {}) {
|
|
5
|
+
const temporaryPath = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
6
|
+
try {
|
|
7
|
+
await writeFile(temporaryPath, contents, { encoding: "utf8", flag: "wx", mode: options.mode });
|
|
8
|
+
try {
|
|
9
|
+
await rename(temporaryPath, file);
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
const code = error.code;
|
|
13
|
+
if (!options.allowLockedReplaceFallback || !["EEXIST", "EPERM", "ENOTEMPTY"].includes(code ?? "")) {
|
|
14
|
+
throw error;
|
|
15
|
+
}
|
|
16
|
+
// Source state holds its read-modify-write lock while using this Windows fallback.
|
|
17
|
+
await writeFile(file, contents, "utf8");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
await unlink(temporaryPath).catch((error) => {
|
|
22
|
+
if (error.code !== "ENOENT")
|
|
23
|
+
throw error;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|