@iseoane/agent-depot 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.es.md +78 -33
- package/README.md +66 -25
- package/dist/src/cli.js +55 -0
- package/dist/src/package-cli.js +2 -1
- package/dist/src/package-flow.js +1 -1
- package/dist/src/package-host-state.js +10 -6
- package/dist/src/self-update.js +141 -0
- package/dist/src/tui/package-rows.js +4 -2
- package/package.json +32 -12
- package/resources/pi/agents-panel/contract.ts +62 -0
- package/resources/pi/agents-panel/index.ts +471 -0
- package/skills/doctor-md-agents/SKILL.md +2 -1
- package/skills/doctor-md-agents/scripts/scan_corrections.py +141 -13
- package/skills/doctor-md-skill/references/supported-harnesses.md +2 -0
- package/skills/doctor-md-skill/scripts/collect_sessions.py +26 -2
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { lstat, realpath } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { compareAppVersions } from "./app-version.js";
|
|
4
|
+
import { createPortableExecutableResolver } from "./executable-resolution.js";
|
|
5
|
+
import { runProcess } from "./process-runner.js";
|
|
6
|
+
const REGISTRY_URL = "https://registry.npmjs.org";
|
|
7
|
+
const MAX_REGISTRY_RESPONSE_BYTES = 8 * 1024;
|
|
8
|
+
const MAX_NPM_STDERR_BYTES = 16 * 1024;
|
|
9
|
+
const COMMAND_TIMEOUT_MS = 30_000;
|
|
10
|
+
function compareVersions(left, right) {
|
|
11
|
+
const comparison = compareAppVersions(left, right);
|
|
12
|
+
if (comparison === undefined)
|
|
13
|
+
throw new Error(`Invalid semantic version comparison ${JSON.stringify(left)} and ${JSON.stringify(right)}`);
|
|
14
|
+
return comparison;
|
|
15
|
+
}
|
|
16
|
+
async function latestVersion(fetcher, packageName) {
|
|
17
|
+
const encodedName = encodeURIComponent(packageName).replace(/^%40/u, "@");
|
|
18
|
+
const response = await fetcher(`${REGISTRY_URL}/${encodedName}/latest`, {
|
|
19
|
+
signal: AbortSignal.timeout(10_000),
|
|
20
|
+
credentials: "omit",
|
|
21
|
+
redirect: "error",
|
|
22
|
+
});
|
|
23
|
+
if (!response.ok)
|
|
24
|
+
throw new Error(`npm registry returned HTTP ${response.status}`);
|
|
25
|
+
const contentLength = Number(response.headers.get("content-length"));
|
|
26
|
+
if (Number.isFinite(contentLength) && contentLength > MAX_REGISTRY_RESPONSE_BYTES) {
|
|
27
|
+
throw new Error("npm registry response exceeded the size limit");
|
|
28
|
+
}
|
|
29
|
+
if (!response.body)
|
|
30
|
+
throw new Error("npm registry response did not include a body");
|
|
31
|
+
const reader = response.body.getReader();
|
|
32
|
+
const chunks = [];
|
|
33
|
+
let responseBytes = 0;
|
|
34
|
+
try {
|
|
35
|
+
for (;;) {
|
|
36
|
+
const chunk = await reader.read();
|
|
37
|
+
if (chunk.done)
|
|
38
|
+
break;
|
|
39
|
+
responseBytes += chunk.value.byteLength;
|
|
40
|
+
if (responseBytes > MAX_REGISTRY_RESPONSE_BYTES) {
|
|
41
|
+
await reader.cancel();
|
|
42
|
+
throw new Error("npm registry response exceeded the size limit");
|
|
43
|
+
}
|
|
44
|
+
chunks.push(chunk.value);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
reader.releaseLock();
|
|
49
|
+
}
|
|
50
|
+
const body = Buffer.concat(chunks).toString("utf8");
|
|
51
|
+
let payload;
|
|
52
|
+
try {
|
|
53
|
+
payload = JSON.parse(body);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
throw new Error("npm registry returned invalid JSON");
|
|
57
|
+
}
|
|
58
|
+
if (typeof payload !== "object" || payload === null || !("version" in payload) || typeof payload.version !== "string") {
|
|
59
|
+
throw new Error("npm registry response did not contain a version");
|
|
60
|
+
}
|
|
61
|
+
compareVersions(payload.version, payload.version);
|
|
62
|
+
return payload.version;
|
|
63
|
+
}
|
|
64
|
+
function successfulStdout(result) {
|
|
65
|
+
if (result.code !== 0 || result.signal !== null || result.timedOut || result.outputTooLarge)
|
|
66
|
+
return undefined;
|
|
67
|
+
return result.stdout.toString("utf8").trim();
|
|
68
|
+
}
|
|
69
|
+
export function createSelfUpdateOperations(options) {
|
|
70
|
+
const resolveExecutable = options.resolveExecutable ?? createPortableExecutableResolver();
|
|
71
|
+
const run = options.run ?? runProcess;
|
|
72
|
+
const fetcher = options.fetch ?? globalThis.fetch;
|
|
73
|
+
async function npmGlobalExecutable() {
|
|
74
|
+
const npm = await resolveExecutable("npm");
|
|
75
|
+
if (npm.blocked || npm.executable === undefined)
|
|
76
|
+
return undefined;
|
|
77
|
+
const target = await realpath(npm.executable).catch(() => undefined);
|
|
78
|
+
if (target === undefined || path.basename(target) !== "npm-cli.js" || path.basename(path.dirname(target)) !== "bin" || path.basename(path.dirname(path.dirname(target))) !== "npm")
|
|
79
|
+
return undefined;
|
|
80
|
+
let rootResult;
|
|
81
|
+
try {
|
|
82
|
+
rootResult = await run(npm.executable, ["root", "--global", "--silent", "--logs-max=0", "--no-update-notifier"], {
|
|
83
|
+
captureStdout: true,
|
|
84
|
+
maxOutputBytes: 4096,
|
|
85
|
+
maxStderrBytes: MAX_NPM_STDERR_BYTES,
|
|
86
|
+
timeoutMs: COMMAND_TIMEOUT_MS,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
const globalRoot = successfulStdout(rootResult);
|
|
93
|
+
if (globalRoot === undefined || !path.isAbsolute(globalRoot))
|
|
94
|
+
return undefined;
|
|
95
|
+
const installedPackagePath = path.join(globalRoot, ...options.packageName.split("/"));
|
|
96
|
+
const installedPackage = await lstat(installedPackagePath).catch(() => undefined);
|
|
97
|
+
if (installedPackage === undefined || !installedPackage.isDirectory())
|
|
98
|
+
return undefined;
|
|
99
|
+
const actualRoot = await realpath(options.packageRoot).catch(() => undefined);
|
|
100
|
+
const reportedRoot = await realpath(installedPackagePath).catch(() => undefined);
|
|
101
|
+
return actualRoot !== undefined && actualRoot === reportedRoot ? npm.executable : undefined;
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
async check() {
|
|
105
|
+
compareVersions(options.packageVersion, options.packageVersion);
|
|
106
|
+
const npm = await npmGlobalExecutable();
|
|
107
|
+
if (npm === undefined) {
|
|
108
|
+
return { kind: "unsupported", reason: "this CLI is not verified in npm's global package directory" };
|
|
109
|
+
}
|
|
110
|
+
const latest = await latestVersion(fetcher, options.packageName);
|
|
111
|
+
const comparison = compareVersions(latest, options.packageVersion);
|
|
112
|
+
if (comparison === 0)
|
|
113
|
+
return { kind: "current", current: options.packageVersion, latest };
|
|
114
|
+
if (comparison < 0)
|
|
115
|
+
return { kind: "ahead", current: options.packageVersion, latest };
|
|
116
|
+
return { kind: "available", current: options.packageVersion, latest };
|
|
117
|
+
},
|
|
118
|
+
async apply(version) {
|
|
119
|
+
let comparison;
|
|
120
|
+
try {
|
|
121
|
+
comparison = compareVersions(version, options.packageVersion);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
throw new Error(`Refusing invalid Agent Depot version ${JSON.stringify(version)}`);
|
|
125
|
+
}
|
|
126
|
+
if (comparison <= 0)
|
|
127
|
+
throw new Error(`Refusing to downgrade Agent Depot from ${options.packageVersion} to ${version}`);
|
|
128
|
+
const npm = await npmGlobalExecutable();
|
|
129
|
+
if (npm === undefined)
|
|
130
|
+
throw new Error("this CLI is not verified in npm's global package directory");
|
|
131
|
+
const result = await run(npm, [
|
|
132
|
+
"install", "--global", "--no-audit", "--no-fund", "--no-update-notifier", "--logs-max=0", "--ignore-scripts",
|
|
133
|
+
"--registry=https://registry.npmjs.org/", `${options.packageName}@${version}`,
|
|
134
|
+
], { maxStderrBytes: MAX_NPM_STDERR_BYTES, timeoutMs: COMMAND_TIMEOUT_MS });
|
|
135
|
+
if (result.code !== 0 || result.signal !== null || result.timedOut || result.outputTooLarge) {
|
|
136
|
+
const detail = result.timedOut ? "timed out" : result.outputTooLarge ? "produced too much output" : result.signal ? `stopped by ${result.signal}` : `exited with code ${result.code ?? "unknown"}`;
|
|
137
|
+
throw new Error(`npm global update ${detail}${result.stderr ? `: ${result.stderr.trim()}` : ""}`);
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
@@ -10,12 +10,14 @@ export function packageVersionLabel(evidence) {
|
|
|
10
10
|
export function installedEvidenceLabel(evidence) {
|
|
11
11
|
switch (evidence.kind) {
|
|
12
12
|
case "installed": {
|
|
13
|
+
if (evidence.version !== undefined) {
|
|
14
|
+
const qualifier = evidence.reason === undefined ? "" : " (HEAD; local changes)";
|
|
15
|
+
return `installed ${evidence.version}${qualifier}`;
|
|
16
|
+
}
|
|
13
17
|
if (evidence.reason !== undefined) {
|
|
14
18
|
const reason = evidence.reason.includes("local modifications") ? "local changes" : evidence.reason;
|
|
15
19
|
return `installed (version unavailable: ${reason})`;
|
|
16
20
|
}
|
|
17
|
-
if (evidence.version !== undefined)
|
|
18
|
-
return `installed ${evidence.version}`;
|
|
19
21
|
if (evidence.commit !== undefined)
|
|
20
22
|
return `installed at ${evidence.commit.slice(0, 7)}`;
|
|
21
23
|
return "installed with no version evidence";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iseoane/agent-depot",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "A cross-platform catalog for reusable coding-agent skills.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "iseoane",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"files": [
|
|
27
27
|
"dist/src",
|
|
28
28
|
"!dist/src/**/*.map",
|
|
29
|
+
"resources/pi",
|
|
29
30
|
"skills",
|
|
30
31
|
"!skills/**/test_*.py",
|
|
31
32
|
"!skills/**/testdata",
|
|
@@ -41,10 +42,39 @@
|
|
|
41
42
|
"engines": {
|
|
42
43
|
"node": ">=24"
|
|
43
44
|
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@earendil-works/pi-tui": "^1.0.3",
|
|
47
|
+
"typebox": "^1.3.27"
|
|
48
|
+
},
|
|
49
|
+
"peerDependenciesMeta": {
|
|
50
|
+
"@earendil-works/pi-tui": {
|
|
51
|
+
"optional": true
|
|
52
|
+
},
|
|
53
|
+
"typebox": {
|
|
54
|
+
"optional": true
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@earendil-works/pi-tui": "1.0.3",
|
|
59
|
+
"@eslint/js": "^9.39.2",
|
|
60
|
+
"@homebridge/node-pty-prebuilt-multiarch": "^0.14.1",
|
|
61
|
+
"@types/node": "^24.19.0",
|
|
62
|
+
"@types/react": "^19.3.0",
|
|
63
|
+
"eslint": "^9.39.2",
|
|
64
|
+
"ink-testing-library": "^4.0.0",
|
|
65
|
+
"typebox": "1.3.27",
|
|
66
|
+
"typescript": "^5.7.2",
|
|
67
|
+
"typescript-eslint": "^8.70.1"
|
|
68
|
+
},
|
|
69
|
+
"pi": {
|
|
70
|
+
"extensions": [
|
|
71
|
+
"./resources/pi/agents-panel/index.ts"
|
|
72
|
+
]
|
|
73
|
+
},
|
|
44
74
|
"scripts": {
|
|
45
75
|
"build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc",
|
|
46
76
|
"typecheck": "tsc --noEmit",
|
|
47
|
-
"lint": "eslint \"src/**/*.{ts,tsx}\" \"tests/**/*.{ts,tsx}\"",
|
|
77
|
+
"lint": "eslint \"src/**/*.{ts,tsx}\" \"resources/pi/**/*.{ts,tsx}\" \"tests/**/*.{ts,tsx}\"",
|
|
48
78
|
"test": "pnpm build && node --test --import ./dist/tests/isolated-env.js \"dist/tests/*.test.js\"",
|
|
49
79
|
"test:single": "pnpm build && node -e \"const { spawnSync } = require('node:child_process'); const input = process.argv[1] || 'tests/version.test.ts'; const file = input.replace(/^tests[\\\\/]/, 'dist/tests/').replace(/\\\\.ts$/i, '.js'); process.exit(spawnSync(process.execPath, ['--import', './dist/tests/isolated-env.js', '--test', file], { stdio: 'inherit' }).status ?? 1);\"",
|
|
50
80
|
"test:coverage": "node -e \"require('node:fs').rmSync('coverage',{recursive:true,force:true})\" && tsc -p tsconfig.coverage.json && node -e \"const { spawnSync } = require('node:child_process'); process.exit(spawnSync(process.execPath, ['--enable-source-maps','--import','./dist/tests/isolated-env.js','--test','--test-reporter=dot','dist/tests/*.test.js'], { stdio: 'inherit', env: { ...process.env, NODE_V8_COVERAGE: 'coverage/v8' } }).status ?? 1);\"",
|
|
@@ -56,16 +86,6 @@
|
|
|
56
86
|
"prepublishOnly": "pnpm build && pnpm test",
|
|
57
87
|
"test:pack": "pnpm build && node scripts/pack-smoke.mjs"
|
|
58
88
|
},
|
|
59
|
-
"devDependencies": {
|
|
60
|
-
"@eslint/js": "^9.39.2",
|
|
61
|
-
"@homebridge/node-pty-prebuilt-multiarch": "^0.14.1",
|
|
62
|
-
"@types/node": "^24.19.0",
|
|
63
|
-
"@types/react": "^19.3.0",
|
|
64
|
-
"eslint": "^9.39.2",
|
|
65
|
-
"ink-testing-library": "^4.0.0",
|
|
66
|
-
"typescript": "^5.7.2",
|
|
67
|
-
"typescript-eslint": "^8.70.1"
|
|
68
|
-
},
|
|
69
89
|
"dependencies": {
|
|
70
90
|
"ink": "^7.1.1",
|
|
71
91
|
"react": "^19.3.0"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Type, type Static } from "typebox";
|
|
2
|
+
import { Value } from "typebox/value";
|
|
3
|
+
|
|
4
|
+
const agentActivitySchema = Type.Object({
|
|
5
|
+
label: Type.String(),
|
|
6
|
+
at: Type.Number(),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const agentUsageSchema = Type.Object({
|
|
10
|
+
input: Type.Optional(Type.Number()),
|
|
11
|
+
output: Type.Optional(Type.Number()),
|
|
12
|
+
cacheRead: Type.Optional(Type.Number()),
|
|
13
|
+
cacheWrite: Type.Optional(Type.Number()),
|
|
14
|
+
totalTokens: Type.Optional(Type.Number()),
|
|
15
|
+
cost: Type.Optional(Type.Number()),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const agentSchema = Type.Object({
|
|
19
|
+
id: Type.String(),
|
|
20
|
+
description: Type.String(),
|
|
21
|
+
subagentType: Type.String(),
|
|
22
|
+
model: Type.String(),
|
|
23
|
+
thinking: Type.Optional(Type.String()),
|
|
24
|
+
readonly: Type.Boolean(),
|
|
25
|
+
status: Type.Union([
|
|
26
|
+
Type.Literal("running"),
|
|
27
|
+
Type.Literal("completed"),
|
|
28
|
+
Type.Literal("failed"),
|
|
29
|
+
Type.Literal("stopped"),
|
|
30
|
+
]),
|
|
31
|
+
kind: Type.Union([Type.Literal("local"), Type.Literal("remote"), Type.Literal("ended")]),
|
|
32
|
+
pid: Type.Optional(Type.Number()),
|
|
33
|
+
startedAt: Type.String(),
|
|
34
|
+
endedAt: Type.Optional(Type.String()),
|
|
35
|
+
elapsedMs: Type.Number(),
|
|
36
|
+
observed: Type.Boolean(),
|
|
37
|
+
activity: Type.Optional(agentActivitySchema),
|
|
38
|
+
usage: Type.Optional(agentUsageSchema),
|
|
39
|
+
worktree: Type.Optional(Type.String()),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export const agentSnapshotRequestSchema = Type.Object({
|
|
43
|
+
version: Type.Literal(1),
|
|
44
|
+
requestId: Type.String(),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export const agentSnapshotResponseSchema = Type.Object({
|
|
48
|
+
version: Type.Literal(1),
|
|
49
|
+
requestId: Type.String(),
|
|
50
|
+
agents: Type.Array(agentSchema),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export type AgentSnapshot = Static<typeof agentSchema>;
|
|
54
|
+
export type AgentSnapshotRequest = Static<typeof agentSnapshotRequestSchema>;
|
|
55
|
+
export type AgentSnapshotResponse = Static<typeof agentSnapshotResponseSchema>;
|
|
56
|
+
|
|
57
|
+
export const AGENT_SNAPSHOT_REQUEST_EVENT = "agent-depot:agents:v1:request";
|
|
58
|
+
export const AGENT_SNAPSHOT_RESPONSE_EVENT = "agent-depot:agents:v1:snapshot";
|
|
59
|
+
|
|
60
|
+
export function parseAgentSnapshotResponse(value: unknown): AgentSnapshotResponse | undefined {
|
|
61
|
+
return Value.Check(agentSnapshotResponseSchema, value) ? value : undefined;
|
|
62
|
+
}
|