@schilderlabs/pitown 0.1.1 → 0.2.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/README.md +27 -0
- package/dist/{config-pnmTwoZh.mjs → config-CUpe9o0x.mjs} +30 -43
- package/dist/config-CUpe9o0x.mjs.map +1 -0
- package/dist/{controller-DbfbWdIq.mjs → controller-9ihAZj3V.mjs} +211 -10
- package/dist/controller-9ihAZj3V.mjs.map +1 -0
- package/dist/doctor.d.mts +8 -0
- package/dist/doctor.mjs +42 -0
- package/dist/doctor.mjs.map +1 -0
- package/dist/entrypoint-CyJDLudQ.mjs +61 -0
- package/dist/entrypoint-CyJDLudQ.mjs.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1235 -6
- package/dist/index.mjs.map +1 -1
- package/dist/pi-C0fURZj7.mjs +12 -0
- package/dist/pi-C0fURZj7.mjs.map +1 -0
- package/dist/run.d.mts +3 -0
- package/dist/run.mjs +8 -3
- package/dist/run.mjs.map +1 -1
- package/dist/status.mjs +2 -1
- package/dist/status.mjs.map +1 -1
- package/dist/watch.mjs +2 -1
- package/dist/watch.mjs.map +1 -1
- package/package.json +7 -5
- package/dist/config-pnmTwoZh.mjs.map +0 -1
- package/dist/controller-DbfbWdIq.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -12,16 +12,43 @@ For the full project overview, roadmap, and architecture context, see the main r
|
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
npm install -g @schilderlabs/pitown
|
|
15
|
+
npm install -g @mariozechner/pi-coding-agent
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`pitown run` requires Pi to be installed and authenticated.
|
|
19
|
+
Verify Pi first:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pitown doctor
|
|
23
|
+
pi -p "hello"
|
|
15
24
|
```
|
|
16
25
|
|
|
17
26
|
## Usage
|
|
18
27
|
|
|
19
28
|
```bash
|
|
20
29
|
pitown --help
|
|
30
|
+
pitown
|
|
31
|
+
pitown mayor
|
|
32
|
+
pitown mayor "plan the next milestones"
|
|
21
33
|
pitown run --repo /path/to/repo --plan /path/to/private/plans --goal "continue from current scaffold state"
|
|
22
34
|
pitown status
|
|
23
35
|
```
|
|
24
36
|
|
|
37
|
+
If you are already inside a repo, `pitown` and `pitown mayor` use the current working repo by default.
|
|
38
|
+
|
|
39
|
+
The main workflow is:
|
|
40
|
+
|
|
41
|
+
1. `cd` into a repo
|
|
42
|
+
2. run `pitown` or `pitown mayor`
|
|
43
|
+
3. use `/plan` inside the mayor session when you want a read-only plan first
|
|
44
|
+
4. use `pitown board`, `pitown peek mayor`, or `pitown msg mayor "..."` as needed
|
|
45
|
+
|
|
46
|
+
Inside the mayor session:
|
|
47
|
+
|
|
48
|
+
- `/plan` toggles read-only planning mode
|
|
49
|
+
- `/todos` shows the captured numbered plan
|
|
50
|
+
- leaving `/plan` returns the mayor to normal execution and delegation mode
|
|
51
|
+
|
|
25
52
|
## Runtime storage
|
|
26
53
|
|
|
27
54
|
By default, Pi Town stores local runtime state under `~/.pi-town` and keeps private plans outside the target repo.
|
|
@@ -1,48 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as runCommandSync, r as assertSuccess } from "./entrypoint-CyJDLudQ.mjs";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
3
|
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { createHash } from "node:crypto";
|
|
6
|
-
import { spawnSync } from "node:child_process";
|
|
7
6
|
|
|
8
|
-
//#region src/entrypoint.ts
|
|
9
|
-
function normalizePath(path) {
|
|
10
|
-
if (!path) return null;
|
|
11
|
-
try {
|
|
12
|
-
return realpathSync(path);
|
|
13
|
-
} catch {
|
|
14
|
-
return resolve(path);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
function isDirectExecution(fileUrl, argv1 = process.argv[1]) {
|
|
18
|
-
const modulePath = normalizePath(fileURLToPath(fileUrl));
|
|
19
|
-
const invokedPath = normalizePath(argv1);
|
|
20
|
-
return modulePath !== null && invokedPath !== null && modulePath === invokedPath;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
//#endregion
|
|
24
|
-
//#region ../core/src/shell.ts
|
|
25
|
-
function runCommandSync(command, args, options) {
|
|
26
|
-
const result = spawnSync(command, args, {
|
|
27
|
-
cwd: options?.cwd,
|
|
28
|
-
env: options?.env,
|
|
29
|
-
encoding: "utf-8"
|
|
30
|
-
});
|
|
31
|
-
const errorText = result.error instanceof Error ? `${result.error.message}
|
|
32
|
-
` : "";
|
|
33
|
-
return {
|
|
34
|
-
stdout: result.stdout ?? "",
|
|
35
|
-
stderr: `${errorText}${result.stderr ?? ""}`,
|
|
36
|
-
exitCode: result.status ?? 1
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
function assertSuccess(result, context) {
|
|
40
|
-
if (result.exitCode === 0) return;
|
|
41
|
-
const details = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n");
|
|
42
|
-
throw new Error(`${context} failed${details ? `\n${details}` : ""}`);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
//#endregion
|
|
46
7
|
//#region ../core/src/repo.ts
|
|
47
8
|
function gitResult(cwd, args) {
|
|
48
9
|
return runCommandSync("git", args, { cwd });
|
|
@@ -108,6 +69,9 @@ function getReposRootDir() {
|
|
|
108
69
|
function getRepoArtifactsDir(repoSlug) {
|
|
109
70
|
return join(getReposRootDir(), repoSlug);
|
|
110
71
|
}
|
|
72
|
+
function getRepoAgentsDir(repoSlug) {
|
|
73
|
+
return join(getRepoArtifactsDir(repoSlug), "agents");
|
|
74
|
+
}
|
|
111
75
|
function getLatestRunPointerPath() {
|
|
112
76
|
return join(getTownHomeDir(), "latest-run.json");
|
|
113
77
|
}
|
|
@@ -176,6 +140,29 @@ function parseCliFlags(argv) {
|
|
|
176
140
|
}
|
|
177
141
|
return flags;
|
|
178
142
|
}
|
|
143
|
+
function parseOptionalRepoFlag(argv) {
|
|
144
|
+
const rest = [];
|
|
145
|
+
let repo;
|
|
146
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
147
|
+
const arg = argv[index];
|
|
148
|
+
if (arg.startsWith("--repo=")) {
|
|
149
|
+
repo = arg.slice(7);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (arg === "--repo") {
|
|
153
|
+
const value = argv[index + 1];
|
|
154
|
+
if (!value) throw new Error("Missing value for --repo");
|
|
155
|
+
repo = value;
|
|
156
|
+
index += 1;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
rest.push(arg);
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
repo,
|
|
163
|
+
rest
|
|
164
|
+
};
|
|
165
|
+
}
|
|
179
166
|
function loadUserConfig() {
|
|
180
167
|
const configPath = getUserConfigPath();
|
|
181
168
|
if (!existsSync(configPath)) return {};
|
|
@@ -195,5 +182,5 @@ function resolveRunConfig(argv) {
|
|
|
195
182
|
}
|
|
196
183
|
|
|
197
184
|
//#endregion
|
|
198
|
-
export {
|
|
199
|
-
//# sourceMappingURL=config-
|
|
185
|
+
export { getRecommendedPlanDir as a, getRepoLatestRunPointerPath as c, createRepoSlug as d, getCurrentBranch as f, isGitRepo as h, getLatestRunPointerPath as i, getTownHomeDir as l, getRepoRoot as m, parseOptionalRepoFlag as n, getRepoAgentsDir as o, getRepoIdentity as p, resolveRunConfig as r, getRepoArtifactsDir as s, parseCliFlags as t, getUserConfigPath as u };
|
|
186
|
+
//# sourceMappingURL=config-CUpe9o0x.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-CUpe9o0x.mjs","names":[],"sources":["../../core/src/repo.ts","../src/paths.ts","../src/config.ts"],"sourcesContent":["import { createHash } from \"node:crypto\"\nimport { existsSync } from \"node:fs\"\nimport { basename, resolve } from \"node:path\"\nimport { assertSuccess, runCommandSync } from \"./shell.js\"\n\nfunction gitResult(cwd: string, args: string[]) {\n\treturn runCommandSync(\"git\", args, { cwd })\n}\n\nfunction sanitize(value: string): string {\n\treturn value.replace(/[^a-zA-Z0-9._-]+/g, \"-\").replace(/^-+|-+$/g, \"\") || \"repo\"\n}\n\nexport function isGitRepo(cwd: string): boolean {\n\tconst result = gitResult(cwd, [\"rev-parse\", \"--is-inside-work-tree\"])\n\treturn result.exitCode === 0 && result.stdout.trim() === \"true\"\n}\n\nexport function getRepoRoot(cwd: string): string {\n\tif (!isGitRepo(cwd)) return resolve(cwd)\n\tconst result = gitResult(cwd, [\"rev-parse\", \"--show-toplevel\"])\n\tassertSuccess(result, \"git rev-parse --show-toplevel\")\n\treturn resolve(result.stdout.trim())\n}\n\nexport function getCurrentBranch(cwd: string): string | null {\n\tif (!isGitRepo(cwd)) return null\n\tconst result = gitResult(cwd, [\"rev-parse\", \"--abbrev-ref\", \"HEAD\"])\n\tif (result.exitCode !== 0) return null\n\tconst branch = result.stdout.trim()\n\treturn branch || null\n}\n\nexport function getRepoIdentity(cwd: string): string {\n\tif (!isGitRepo(cwd)) return resolve(cwd)\n\n\tconst remote = gitResult(cwd, [\"config\", \"--get\", \"remote.origin.url\"])\n\tconst remoteValue = remote.stdout.trim()\n\tif (remote.exitCode === 0 && remoteValue) return remoteValue\n\n\tconst root = gitResult(cwd, [\"rev-parse\", \"--show-toplevel\"])\n\tassertSuccess(root, \"git rev-parse --show-toplevel\")\n\tconst commonDir = gitResult(cwd, [\"rev-parse\", \"--git-common-dir\"])\n\tassertSuccess(commonDir, \"git rev-parse --git-common-dir\")\n\n\tconst rootPath = resolve(root.stdout.trim())\n\tconst commonDirPath = commonDir.stdout.trim()\n\treturn `${basename(rootPath)}:${rootPath}:${existsSync(commonDirPath) ? resolve(commonDirPath) : commonDirPath}`\n}\n\nexport function createRepoSlug(repoId: string, repoRoot: string): string {\n\tconst name = sanitize(basename(repoRoot))\n\tconst digest = createHash(\"sha1\").update(repoId).digest(\"hex\").slice(0, 8)\n\treturn `${name}-${digest}`\n}\n","import { homedir } from \"node:os\"\nimport { join } from \"node:path\"\n\nexport function getTownHomeDir(): string {\n\treturn join(homedir(), \".pi-town\")\n}\n\nexport function getUserConfigPath(): string {\n\treturn join(getTownHomeDir(), \"config.json\")\n}\n\nexport function getPlansRootDir(): string {\n\treturn join(getTownHomeDir(), \"plans\")\n}\n\nexport function getReposRootDir(): string {\n\treturn join(getTownHomeDir(), \"repos\")\n}\n\nexport function getRepoArtifactsDir(repoSlug: string): string {\n\treturn join(getReposRootDir(), repoSlug)\n}\n\nexport function getRepoAgentsDir(repoSlug: string): string {\n\treturn join(getRepoArtifactsDir(repoSlug), \"agents\")\n}\n\nexport function getLatestRunPointerPath(): string {\n\treturn join(getTownHomeDir(), \"latest-run.json\")\n}\n\nexport function getRepoLatestRunPointerPath(repoSlug: string): string {\n\treturn join(getRepoArtifactsDir(repoSlug), \"latest-run.json\")\n}\n\nexport function getRecommendedPlanDir(repoSlug: string): string {\n\treturn join(getPlansRootDir(), repoSlug)\n}\n","import { existsSync, readFileSync } from \"node:fs\"\nimport { dirname, isAbsolute, resolve } from \"node:path\"\nimport { homedir } from \"node:os\"\nimport { getUserConfigPath } from \"./paths.js\"\n\nconst DEFAULT_GOAL = \"continue from current scaffold state\"\n\nexport interface CliFlags {\n\trepo?: string\n\tplan?: string\n\tgoal?: string\n\thelp: boolean\n}\n\nexport interface OptionalRepoFlagResult {\n\trepo?: string\n\trest: string[]\n}\n\ninterface UserConfig {\n\trepo?: string\n\tplan?: string\n\tgoal?: string\n}\n\nexport interface ResolvedRunConfig {\n\trepo: string\n\tplan: string | null\n\tgoal: string\n\tconfigPath: string\n}\n\nfunction expandHome(value: string): string {\n\tif (value === \"~\") return homedir()\n\tif (value.startsWith(\"~/\")) return resolve(homedir(), value.slice(2))\n\treturn value\n}\n\nfunction resolvePathValue(value: string | undefined, baseDir: string): string | undefined {\n\tif (!value) return undefined\n\tconst expanded = expandHome(value)\n\treturn isAbsolute(expanded) ? resolve(expanded) : resolve(baseDir, expanded)\n}\n\nexport function parseCliFlags(argv: string[]): CliFlags {\n\tconst flags: CliFlags = { help: false }\n\n\tfor (let index = 0; index < argv.length; index += 1) {\n\t\tconst arg = argv[index]\n\n\t\tif (arg === \"--help\" || arg === \"-h\") {\n\t\t\tflags.help = true\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg.startsWith(\"--repo=\")) {\n\t\t\tflags.repo = arg.slice(\"--repo=\".length)\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg === \"--repo\") {\n\t\t\tconst value = argv[index + 1]\n\t\t\tif (!value) throw new Error(\"Missing value for --repo\")\n\t\t\tflags.repo = value\n\t\t\tindex += 1\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg.startsWith(\"--plan=\")) {\n\t\t\tflags.plan = arg.slice(\"--plan=\".length)\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg === \"--plan\") {\n\t\t\tconst value = argv[index + 1]\n\t\t\tif (!value) throw new Error(\"Missing value for --plan\")\n\t\t\tflags.plan = value\n\t\t\tindex += 1\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg.startsWith(\"--goal=\")) {\n\t\t\tflags.goal = arg.slice(\"--goal=\".length)\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg === \"--goal\") {\n\t\t\tconst value = argv[index + 1]\n\t\t\tif (!value) throw new Error(\"Missing value for --goal\")\n\t\t\tflags.goal = value\n\t\t\tindex += 1\n\t\t\tcontinue\n\t\t}\n\n\t\tthrow new Error(`Unknown argument: ${arg}`)\n\t}\n\n\treturn flags\n}\n\nexport function parseOptionalRepoFlag(argv: string[]): OptionalRepoFlagResult {\n\tconst rest: string[] = []\n\tlet repo: string | undefined\n\n\tfor (let index = 0; index < argv.length; index += 1) {\n\t\tconst arg = argv[index]\n\n\t\tif (arg.startsWith(\"--repo=\")) {\n\t\t\trepo = arg.slice(\"--repo=\".length)\n\t\t\tcontinue\n\t\t}\n\n\t\tif (arg === \"--repo\") {\n\t\t\tconst value = argv[index + 1]\n\t\t\tif (!value) throw new Error(\"Missing value for --repo\")\n\t\t\trepo = value\n\t\t\tindex += 1\n\t\t\tcontinue\n\t\t}\n\n\t\trest.push(arg)\n\t}\n\n\treturn { repo, rest }\n}\n\nexport function loadUserConfig(): UserConfig {\n\tconst configPath = getUserConfigPath()\n\tif (!existsSync(configPath)) return {}\n\treturn JSON.parse(readFileSync(configPath, \"utf-8\")) as UserConfig\n}\n\nexport function resolveRunConfig(argv: string[]): ResolvedRunConfig {\n\tconst flags = parseCliFlags(argv)\n\tconst configPath = getUserConfigPath()\n\tconst userConfig = loadUserConfig()\n\tconst configDir = dirname(configPath)\n\n\tconst repo =\n\t\tresolvePathValue(flags.repo, process.cwd()) ??\n\t\tresolvePathValue(userConfig.repo, configDir) ??\n\t\tresolve(process.cwd())\n\tconst plan = resolvePathValue(flags.plan, process.cwd()) ?? resolvePathValue(userConfig.plan, configDir) ?? null\n\tconst goal = flags.goal ?? userConfig.goal ?? DEFAULT_GOAL\n\n\treturn {\n\t\trepo,\n\t\tplan,\n\t\tgoal,\n\t\tconfigPath,\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { n as detectPiAuthFailure, t as createPiAuthHelpMessage } from "./pi-C0fURZj7.mjs";
|
|
2
|
+
import { a as runCommandSync, n as assertCommandAvailable } from "./entrypoint-CyJDLudQ.mjs";
|
|
3
|
+
import { d as createRepoSlug, f as getCurrentBranch, m as getRepoRoot, p as getRepoIdentity } from "./config-CUpe9o0x.mjs";
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
3
5
|
import { dirname, join } from "node:path";
|
|
4
6
|
import { homedir, hostname } from "node:os";
|
|
5
7
|
|
|
@@ -11,6 +13,127 @@ function appendJsonl(filePath, value) {
|
|
|
11
13
|
flag: "a"
|
|
12
14
|
});
|
|
13
15
|
}
|
|
16
|
+
function readJsonl(filePath) {
|
|
17
|
+
try {
|
|
18
|
+
return readFileSync(filePath, "utf-8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => JSON.parse(line));
|
|
19
|
+
} catch {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region ../core/src/agents.ts
|
|
26
|
+
function writeJson$1(path, value) {
|
|
27
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
28
|
+
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, "utf-8");
|
|
29
|
+
}
|
|
30
|
+
function ensureMailbox(path) {
|
|
31
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
32
|
+
if (!existsSync(path)) writeFileSync(path, "", "utf-8");
|
|
33
|
+
}
|
|
34
|
+
function getAgentsDir(artifactsDir) {
|
|
35
|
+
return join(artifactsDir, "agents");
|
|
36
|
+
}
|
|
37
|
+
function getAgentDir(artifactsDir, agentId) {
|
|
38
|
+
return join(getAgentsDir(artifactsDir), agentId);
|
|
39
|
+
}
|
|
40
|
+
function getAgentStatePath(artifactsDir, agentId) {
|
|
41
|
+
return join(getAgentDir(artifactsDir, agentId), "state.json");
|
|
42
|
+
}
|
|
43
|
+
function getAgentSessionPath(artifactsDir, agentId) {
|
|
44
|
+
return join(getAgentDir(artifactsDir, agentId), "session.json");
|
|
45
|
+
}
|
|
46
|
+
function getAgentMailboxPath(artifactsDir, agentId, box) {
|
|
47
|
+
return join(getAgentDir(artifactsDir, agentId), `${box}.jsonl`);
|
|
48
|
+
}
|
|
49
|
+
function getAgentSessionsDir(artifactsDir, agentId) {
|
|
50
|
+
return join(getAgentDir(artifactsDir, agentId), "sessions");
|
|
51
|
+
}
|
|
52
|
+
function getSessionIdFromPath(sessionPath) {
|
|
53
|
+
if (!sessionPath) return null;
|
|
54
|
+
return /_([0-9a-f-]+)\.jsonl$/i.exec(sessionPath)?.[1] ?? null;
|
|
55
|
+
}
|
|
56
|
+
function createAgentSessionRecord(input) {
|
|
57
|
+
return {
|
|
58
|
+
runtime: "pi",
|
|
59
|
+
persisted: true,
|
|
60
|
+
sessionDir: input?.sessionDir ?? null,
|
|
61
|
+
sessionId: input?.sessionId ?? null,
|
|
62
|
+
sessionPath: input?.sessionPath ?? null,
|
|
63
|
+
lastAttachedAt: input?.lastAttachedAt ?? null
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function createAgentState(input) {
|
|
67
|
+
return {
|
|
68
|
+
agentId: input.agentId,
|
|
69
|
+
role: input.role,
|
|
70
|
+
status: input.status,
|
|
71
|
+
taskId: input.taskId ?? null,
|
|
72
|
+
task: input.task ?? null,
|
|
73
|
+
branch: input.branch ?? null,
|
|
74
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
75
|
+
lastMessage: input.lastMessage ?? null,
|
|
76
|
+
waitingOn: input.waitingOn ?? null,
|
|
77
|
+
blocked: input.blocked ?? false,
|
|
78
|
+
runId: input.runId ?? null,
|
|
79
|
+
session: input.session ?? createAgentSessionRecord()
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function writeAgentState(artifactsDir, state) {
|
|
83
|
+
mkdirSync(getAgentDir(artifactsDir, state.agentId), { recursive: true });
|
|
84
|
+
ensureMailbox(getAgentMailboxPath(artifactsDir, state.agentId, "inbox"));
|
|
85
|
+
ensureMailbox(getAgentMailboxPath(artifactsDir, state.agentId, "outbox"));
|
|
86
|
+
writeJson$1(getAgentStatePath(artifactsDir, state.agentId), state);
|
|
87
|
+
writeJson$1(getAgentSessionPath(artifactsDir, state.agentId), state.session);
|
|
88
|
+
}
|
|
89
|
+
function readAgentState(artifactsDir, agentId) {
|
|
90
|
+
const statePath = getAgentStatePath(artifactsDir, agentId);
|
|
91
|
+
try {
|
|
92
|
+
return JSON.parse(readFileSync(statePath, "utf-8"));
|
|
93
|
+
} catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function listAgentStates(artifactsDir) {
|
|
98
|
+
const agentsDir = getAgentsDir(artifactsDir);
|
|
99
|
+
let entries;
|
|
100
|
+
try {
|
|
101
|
+
entries = readdirSync(agentsDir);
|
|
102
|
+
} catch {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
return entries.map((entry) => readAgentState(artifactsDir, entry)).filter((state) => state !== null).sort((left, right) => left.agentId.localeCompare(right.agentId));
|
|
106
|
+
}
|
|
107
|
+
function appendAgentMessage(input) {
|
|
108
|
+
const record = {
|
|
109
|
+
box: input.box,
|
|
110
|
+
from: input.from,
|
|
111
|
+
body: input.body,
|
|
112
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
113
|
+
};
|
|
114
|
+
appendJsonl(getAgentMailboxPath(input.artifactsDir, input.agentId, input.box), record);
|
|
115
|
+
return record;
|
|
116
|
+
}
|
|
117
|
+
function readAgentMessages(artifactsDir, agentId, box) {
|
|
118
|
+
return readJsonl(getAgentMailboxPath(artifactsDir, agentId, box));
|
|
119
|
+
}
|
|
120
|
+
function getLatestAgentSession(artifactsDir, agentId) {
|
|
121
|
+
const sessionDir = getAgentSessionsDir(artifactsDir, agentId);
|
|
122
|
+
let entries;
|
|
123
|
+
try {
|
|
124
|
+
entries = readdirSync(sessionDir);
|
|
125
|
+
} catch {
|
|
126
|
+
return createAgentSessionRecord({ sessionDir });
|
|
127
|
+
}
|
|
128
|
+
const latestSessionPath = entries.filter((entry) => entry.endsWith(".jsonl")).sort().at(-1) ?? null;
|
|
129
|
+
if (latestSessionPath === null) return createAgentSessionRecord({ sessionDir });
|
|
130
|
+
const sessionPath = join(sessionDir, latestSessionPath);
|
|
131
|
+
return createAgentSessionRecord({
|
|
132
|
+
sessionDir,
|
|
133
|
+
sessionPath,
|
|
134
|
+
sessionId: getSessionIdFromPath(sessionPath)
|
|
135
|
+
});
|
|
136
|
+
}
|
|
14
137
|
|
|
15
138
|
//#endregion
|
|
16
139
|
//#region ../core/src/lease.ts
|
|
@@ -117,6 +240,16 @@ function computeMetrics(input) {
|
|
|
117
240
|
function createRunId() {
|
|
118
241
|
return `run-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
119
242
|
}
|
|
243
|
+
function createPiInvocationArgs(input) {
|
|
244
|
+
const args = [];
|
|
245
|
+
if (input.extensionPath) args.push("--extension", input.extensionPath);
|
|
246
|
+
if (input.appendedSystemPrompt) args.push("--append-system-prompt", input.appendedSystemPrompt);
|
|
247
|
+
if (input.sessionPath) args.push("--session", input.sessionPath);
|
|
248
|
+
else if (input.sessionDir) args.push("--session-dir", input.sessionDir);
|
|
249
|
+
else throw new Error("Pi invocation requires a session path or session directory");
|
|
250
|
+
args.push("-p", input.prompt);
|
|
251
|
+
return args;
|
|
252
|
+
}
|
|
120
253
|
function writeJson(path, value) {
|
|
121
254
|
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, "utf-8");
|
|
122
255
|
}
|
|
@@ -126,6 +259,9 @@ function writeText(path, value) {
|
|
|
126
259
|
function createPiPrompt(input) {
|
|
127
260
|
const goal = input.goal ?? "continue from current scaffold state";
|
|
128
261
|
if (input.planPath) return [
|
|
262
|
+
"You are the Pi Town leader agent for this repository.",
|
|
263
|
+
"Coordinate the next bounded unit of work, keep updates concise, and leave a durable artifact trail.",
|
|
264
|
+
"",
|
|
129
265
|
"Read the private plans in:",
|
|
130
266
|
`- ${input.planPath}`,
|
|
131
267
|
"",
|
|
@@ -137,6 +273,9 @@ function createPiPrompt(input) {
|
|
|
137
273
|
"Keep any persisted run artifacts high-signal and avoid copying private plan contents into them."
|
|
138
274
|
].join("\n");
|
|
139
275
|
return [
|
|
276
|
+
"You are the Pi Town leader agent for this repository.",
|
|
277
|
+
"Coordinate the next bounded unit of work, keep updates concise, and leave a durable artifact trail.",
|
|
278
|
+
"",
|
|
140
279
|
`Work in the repository at: ${input.repoRoot}`,
|
|
141
280
|
`Goal: ${goal}`,
|
|
142
281
|
"No private plan path is configured for this run.",
|
|
@@ -144,6 +283,23 @@ function createPiPrompt(input) {
|
|
|
144
283
|
"Continue from the current scaffold state."
|
|
145
284
|
].join("\n");
|
|
146
285
|
}
|
|
286
|
+
function assertPiRuntimeAvailable(piCommand) {
|
|
287
|
+
try {
|
|
288
|
+
assertCommandAvailable(piCommand);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
if (piCommand === "pi") throw new Error([
|
|
291
|
+
"Pi Town requires the `pi` CLI to run `pitown run`.",
|
|
292
|
+
"Install Pi: npm install -g @mariozechner/pi-coding-agent",
|
|
293
|
+
"Then authenticate Pi and verify it works: pi -p \"hello\"",
|
|
294
|
+
`Details: ${error.message}`
|
|
295
|
+
].join("\n"));
|
|
296
|
+
throw new Error([
|
|
297
|
+
`Pi Town could not execute the configured Pi command: ${piCommand}`,
|
|
298
|
+
"Make sure the command exists on PATH or points to an executable file.",
|
|
299
|
+
`Details: ${error.message}`
|
|
300
|
+
].join("\n"));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
147
303
|
function createManifest(input) {
|
|
148
304
|
return {
|
|
149
305
|
runId: input.runId,
|
|
@@ -169,12 +325,13 @@ function createManifest(input) {
|
|
|
169
325
|
function createSummary(input) {
|
|
170
326
|
const success = input.exitCode === 0;
|
|
171
327
|
const recommendation = input.recommendedPlanDir === null ? "" : ` No plan path was configured. Recommended private plans location: ${input.recommendedPlanDir}.`;
|
|
328
|
+
const authHelp = success || !detectPiAuthFailure(input.stderr, input.stdout) ? "" : ` ${createPiAuthHelpMessage()}`;
|
|
172
329
|
return {
|
|
173
330
|
runId: input.runId,
|
|
174
331
|
mode: input.mode,
|
|
175
332
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
176
333
|
success,
|
|
177
|
-
message: success ? `Pi invocation completed.${recommendation}` : `Pi invocation failed.${recommendation}`,
|
|
334
|
+
message: success ? `Pi invocation completed.${recommendation}` : `Pi invocation failed.${authHelp}${recommendation}`,
|
|
178
335
|
piExitCode: input.exitCode,
|
|
179
336
|
recommendedPlanDir: input.recommendedPlanDir
|
|
180
337
|
};
|
|
@@ -202,6 +359,24 @@ function runController(options) {
|
|
|
202
359
|
goal,
|
|
203
360
|
recommendedPlanDir
|
|
204
361
|
});
|
|
362
|
+
const existingLeaderState = readAgentState(artifactsDir, "leader");
|
|
363
|
+
const existingLeaderSession = existingLeaderState?.session.sessionPath || existingLeaderState?.session.sessionDir ? existingLeaderState.session : getLatestAgentSession(artifactsDir, "leader");
|
|
364
|
+
const leaderSessionDir = existingLeaderSession.sessionDir ?? getAgentSessionsDir(artifactsDir, "leader");
|
|
365
|
+
const leaderState = createAgentState({
|
|
366
|
+
agentId: "leader",
|
|
367
|
+
role: "leader",
|
|
368
|
+
status: "starting",
|
|
369
|
+
task: goal,
|
|
370
|
+
branch,
|
|
371
|
+
lastMessage: goal ? `Starting leader run for goal: ${goal}` : "Starting leader run",
|
|
372
|
+
runId,
|
|
373
|
+
session: createAgentSessionRecord({
|
|
374
|
+
sessionDir: leaderSessionDir,
|
|
375
|
+
sessionId: existingLeaderSession.sessionId,
|
|
376
|
+
sessionPath: existingLeaderSession.sessionPath
|
|
377
|
+
})
|
|
378
|
+
});
|
|
379
|
+
assertPiRuntimeAvailable(piCommand);
|
|
205
380
|
mkdirSync(runDir, { recursive: true });
|
|
206
381
|
mkdirSync(latestDir, { recursive: true });
|
|
207
382
|
writeText(join(runDir, "questions.jsonl"), "");
|
|
@@ -210,6 +385,7 @@ function runController(options) {
|
|
|
210
385
|
status: "starting",
|
|
211
386
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
212
387
|
});
|
|
388
|
+
writeAgentState(artifactsDir, leaderState);
|
|
213
389
|
const lease = acquireRepoLease(runId, repoId, branch);
|
|
214
390
|
try {
|
|
215
391
|
const manifest = createManifest({
|
|
@@ -239,15 +415,23 @@ function runController(options) {
|
|
|
239
415
|
command: piCommand,
|
|
240
416
|
createdAt: piStartedAt
|
|
241
417
|
});
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
"
|
|
245
|
-
|
|
246
|
-
|
|
418
|
+
writeAgentState(artifactsDir, createAgentState({
|
|
419
|
+
...leaderState,
|
|
420
|
+
status: "running",
|
|
421
|
+
lastMessage: goal ? `Leader working on: ${goal}` : "Leader working"
|
|
422
|
+
}));
|
|
423
|
+
const piResult = runCommandSync(piCommand, createPiInvocationArgs({
|
|
424
|
+
sessionDir: leaderState.session.sessionPath === null ? leaderSessionDir : null,
|
|
425
|
+
sessionPath: leaderState.session.sessionPath,
|
|
426
|
+
prompt,
|
|
427
|
+
appendedSystemPrompt: options.appendedSystemPrompt,
|
|
428
|
+
extensionPath: options.extensionPath
|
|
429
|
+
}), {
|
|
247
430
|
cwd: repoRoot,
|
|
248
431
|
env: process.env
|
|
249
432
|
});
|
|
250
433
|
const piEndedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
434
|
+
const latestLeaderSession = getLatestAgentSession(artifactsDir, "leader");
|
|
251
435
|
writeText(stdoutPath, piResult.stdout);
|
|
252
436
|
writeText(stderrPath, piResult.stderr);
|
|
253
437
|
const piInvocation = {
|
|
@@ -256,6 +440,9 @@ function runController(options) {
|
|
|
256
440
|
repoRoot,
|
|
257
441
|
planPath,
|
|
258
442
|
goal,
|
|
443
|
+
sessionDir: latestLeaderSession.sessionDir,
|
|
444
|
+
sessionId: latestLeaderSession.sessionId,
|
|
445
|
+
sessionPath: latestLeaderSession.sessionPath,
|
|
259
446
|
startedAt: piStartedAt,
|
|
260
447
|
endedAt: piEndedAt,
|
|
261
448
|
exitCode: piResult.exitCode,
|
|
@@ -279,6 +466,8 @@ function runController(options) {
|
|
|
279
466
|
runId,
|
|
280
467
|
mode,
|
|
281
468
|
exitCode: piInvocation.exitCode,
|
|
469
|
+
stdout: piResult.stdout,
|
|
470
|
+
stderr: piResult.stderr,
|
|
282
471
|
recommendedPlanDir
|
|
283
472
|
});
|
|
284
473
|
const finalManifest = {
|
|
@@ -298,6 +487,18 @@ function runController(options) {
|
|
|
298
487
|
writeJson(join(latestDir, "manifest.json"), finalManifest);
|
|
299
488
|
writeJson(join(latestDir, "metrics.json"), metrics);
|
|
300
489
|
writeJson(join(latestDir, "run-summary.json"), summary);
|
|
490
|
+
writeAgentState(artifactsDir, createAgentState({
|
|
491
|
+
...leaderState,
|
|
492
|
+
status: piInvocation.exitCode === 0 ? "idle" : "blocked",
|
|
493
|
+
lastMessage: piInvocation.exitCode === 0 ? "Leader run completed and is ready for the next instruction" : `Leader run stopped with exit code ${piInvocation.exitCode}`,
|
|
494
|
+
blocked: piInvocation.exitCode !== 0,
|
|
495
|
+
waitingOn: piInvocation.exitCode === 0 ? null : "human-or-follow-up-run",
|
|
496
|
+
session: createAgentSessionRecord({
|
|
497
|
+
sessionDir: latestLeaderSession.sessionDir,
|
|
498
|
+
sessionId: latestLeaderSession.sessionId,
|
|
499
|
+
sessionPath: latestLeaderSession.sessionPath
|
|
500
|
+
})
|
|
501
|
+
}));
|
|
301
502
|
appendJsonl(join(runDir, "events.jsonl"), {
|
|
302
503
|
type: "run_finished",
|
|
303
504
|
runId,
|
|
@@ -320,5 +521,5 @@ function runController(options) {
|
|
|
320
521
|
}
|
|
321
522
|
|
|
322
523
|
//#endregion
|
|
323
|
-
export { runController as t };
|
|
324
|
-
//# sourceMappingURL=controller-
|
|
524
|
+
export { createAgentState as a, getLatestAgentSession as c, readAgentState as d, writeAgentState as f, createAgentSessionRecord as i, listAgentStates as l, computeMetrics as n, getAgentDir as o, appendJsonl as p, appendAgentMessage as r, getAgentSessionsDir as s, runController as t, readAgentMessages as u };
|
|
525
|
+
//# sourceMappingURL=controller-9ihAZj3V.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controller-9ihAZj3V.mjs","names":["writeJson"],"sources":["../../core/src/events.ts","../../core/src/agents.ts","../../core/src/lease.ts","../../core/src/metrics.ts","../../core/src/controller.ts"],"sourcesContent":["import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\"\nimport { dirname } from \"node:path\"\n\nexport function appendJsonl(filePath: string, value: unknown) {\n\tmkdirSync(dirname(filePath), { recursive: true })\n\twriteFileSync(filePath, `${JSON.stringify(value)}\\n`, { encoding: \"utf-8\", flag: \"a\" })\n}\n\nexport function readJsonl<T>(filePath: string): T[] {\n\ttry {\n\t\tconst raw = readFileSync(filePath, \"utf-8\")\n\t\treturn raw\n\t\t\t.split(/\\r?\\n/)\n\t\t\t.map((line) => line.trim())\n\t\t\t.filter(Boolean)\n\t\t\t.map((line) => JSON.parse(line) as T)\n\t} catch {\n\t\treturn []\n\t}\n}\n","import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from \"node:fs\"\nimport { dirname, join } from \"node:path\"\nimport { appendJsonl, readJsonl } from \"./events.js\"\nimport type {\n\tAgentMailbox,\n\tAgentMessageRecord,\n\tAgentSessionRecord,\n\tAgentStateSnapshot,\n\tAgentStatus,\n} from \"./types.js\"\n\nfunction writeJson(path: string, value: unknown) {\n\tmkdirSync(dirname(path), { recursive: true })\n\twriteFileSync(path, `${JSON.stringify(value, null, 2)}\\n`, \"utf-8\")\n}\n\nfunction ensureMailbox(path: string) {\n\tmkdirSync(dirname(path), { recursive: true })\n\tif (!existsSync(path)) writeFileSync(path, \"\", \"utf-8\")\n}\n\nexport function getAgentsDir(artifactsDir: string): string {\n\treturn join(artifactsDir, \"agents\")\n}\n\nexport function getAgentDir(artifactsDir: string, agentId: string): string {\n\treturn join(getAgentsDir(artifactsDir), agentId)\n}\n\nexport function getAgentStatePath(artifactsDir: string, agentId: string): string {\n\treturn join(getAgentDir(artifactsDir, agentId), \"state.json\")\n}\n\nexport function getAgentSessionPath(artifactsDir: string, agentId: string): string {\n\treturn join(getAgentDir(artifactsDir, agentId), \"session.json\")\n}\n\nexport function getAgentMailboxPath(artifactsDir: string, agentId: string, box: AgentMailbox): string {\n\treturn join(getAgentDir(artifactsDir, agentId), `${box}.jsonl`)\n}\n\nexport function getAgentSessionsDir(artifactsDir: string, agentId: string): string {\n\treturn join(getAgentDir(artifactsDir, agentId), \"sessions\")\n}\n\nexport function getSessionIdFromPath(sessionPath: string | null | undefined): string | null {\n\tif (!sessionPath) return null\n\tconst match = /_([0-9a-f-]+)\\.jsonl$/i.exec(sessionPath)\n\treturn match?.[1] ?? null\n}\n\nexport function createAgentSessionRecord(\n\tinput?: Partial<Pick<AgentSessionRecord, \"sessionDir\" | \"sessionId\" | \"sessionPath\" | \"lastAttachedAt\">>,\n): AgentSessionRecord {\n\treturn {\n\t\truntime: \"pi\",\n\t\tpersisted: true,\n\t\tsessionDir: input?.sessionDir ?? null,\n\t\tsessionId: input?.sessionId ?? null,\n\t\tsessionPath: input?.sessionPath ?? null,\n\t\tlastAttachedAt: input?.lastAttachedAt ?? null,\n\t}\n}\n\nexport function createAgentState(input: {\n\tagentId: string\n\trole: string\n\tstatus: AgentStatus\n\ttaskId?: string | null\n\ttask?: string | null\n\tbranch?: string | null\n\tlastMessage?: string | null\n\twaitingOn?: string | null\n\tblocked?: boolean\n\trunId?: string | null\n\tsession?: AgentSessionRecord\n}): AgentStateSnapshot {\n\treturn {\n\t\tagentId: input.agentId,\n\t\trole: input.role,\n\t\tstatus: input.status,\n\t\ttaskId: input.taskId ?? null,\n\t\ttask: input.task ?? null,\n\t\tbranch: input.branch ?? null,\n\t\tupdatedAt: new Date().toISOString(),\n\t\tlastMessage: input.lastMessage ?? null,\n\t\twaitingOn: input.waitingOn ?? null,\n\t\tblocked: input.blocked ?? false,\n\t\trunId: input.runId ?? null,\n\t\tsession: input.session ?? createAgentSessionRecord(),\n\t}\n}\n\nexport function writeAgentState(artifactsDir: string, state: AgentStateSnapshot) {\n\tmkdirSync(getAgentDir(artifactsDir, state.agentId), { recursive: true })\n\tensureMailbox(getAgentMailboxPath(artifactsDir, state.agentId, \"inbox\"))\n\tensureMailbox(getAgentMailboxPath(artifactsDir, state.agentId, \"outbox\"))\n\twriteJson(getAgentStatePath(artifactsDir, state.agentId), state)\n\twriteJson(getAgentSessionPath(artifactsDir, state.agentId), state.session)\n}\n\nexport function readAgentState(artifactsDir: string, agentId: string): AgentStateSnapshot | null {\n\tconst statePath = getAgentStatePath(artifactsDir, agentId)\n\ttry {\n\t\treturn JSON.parse(readFileSync(statePath, \"utf-8\")) as AgentStateSnapshot\n\t} catch {\n\t\treturn null\n\t}\n}\n\nexport function listAgentStates(artifactsDir: string): AgentStateSnapshot[] {\n\tconst agentsDir = getAgentsDir(artifactsDir)\n\tlet entries: string[]\n\ttry {\n\t\tentries = readdirSync(agentsDir)\n\t} catch {\n\t\treturn []\n\t}\n\n\treturn entries\n\t\t.map((entry) => readAgentState(artifactsDir, entry))\n\t\t.filter((state): state is AgentStateSnapshot => state !== null)\n\t\t.sort((left, right) => left.agentId.localeCompare(right.agentId))\n}\n\nexport function appendAgentMessage(input: {\n\tartifactsDir: string\n\tagentId: string\n\tbox: AgentMailbox\n\tfrom: string\n\tbody: string\n}): AgentMessageRecord {\n\tconst record: AgentMessageRecord = {\n\t\tbox: input.box,\n\t\tfrom: input.from,\n\t\tbody: input.body,\n\t\tcreatedAt: new Date().toISOString(),\n\t}\n\tappendJsonl(getAgentMailboxPath(input.artifactsDir, input.agentId, input.box), record)\n\treturn record\n}\n\nexport function readAgentMessages(artifactsDir: string, agentId: string, box: AgentMailbox): AgentMessageRecord[] {\n\treturn readJsonl<AgentMessageRecord>(getAgentMailboxPath(artifactsDir, agentId, box))\n}\n\nexport function getLatestAgentSession(artifactsDir: string, agentId: string): AgentSessionRecord {\n\tconst sessionDir = getAgentSessionsDir(artifactsDir, agentId)\n\tlet entries: string[]\n\ttry {\n\t\tentries = readdirSync(sessionDir)\n\t} catch {\n\t\treturn createAgentSessionRecord({ sessionDir })\n\t}\n\n\tconst latestSessionPath =\n\t\tentries\n\t\t\t.filter((entry) => entry.endsWith(\".jsonl\"))\n\t\t\t.sort()\n\t\t\t.at(-1) ?? null\n\n\tif (latestSessionPath === null) return createAgentSessionRecord({ sessionDir })\n\n\tconst sessionPath = join(sessionDir, latestSessionPath)\n\treturn createAgentSessionRecord({\n\t\tsessionDir,\n\t\tsessionPath,\n\t\tsessionId: getSessionIdFromPath(sessionPath),\n\t})\n}\n","import { mkdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\"\nimport { homedir, hostname } from \"node:os\"\nimport { join } from \"node:path\"\n\ninterface LeaseData {\n\trunId: string\n\trepoId: string\n\tbranch: string\n\tpid: number\n\thostname: string\n\tstartedAt: string\n}\n\nfunction sanitize(value: string): string {\n\treturn value.replace(/[^a-zA-Z0-9._-]+/g, \"_\")\n}\n\nfunction processAlive(pid: number): boolean {\n\tif (!Number.isFinite(pid) || pid <= 0) return false\n\ttry {\n\t\tprocess.kill(pid, 0)\n\t\treturn true\n\t} catch {\n\t\treturn false\n\t}\n}\n\nexport function acquireRepoLease(runId: string, repoId: string, branch: string): { path: string; release: () => void } {\n\tconst locksDir = join(homedir(), \".pi-town\", \"locks\")\n\tmkdirSync(locksDir, { recursive: true })\n\n\tconst leasePath = join(locksDir, `pi-town-${sanitize(repoId)}-${sanitize(branch)}.json`)\n\tconst nextData: LeaseData = {\n\t\trunId,\n\t\trepoId,\n\t\tbranch,\n\t\tpid: process.pid,\n\t\thostname: hostname(),\n\t\tstartedAt: new Date().toISOString(),\n\t}\n\n\ttry {\n\t\tconst current = JSON.parse(readFileSync(leasePath, \"utf-8\")) as LeaseData\n\t\tif (processAlive(current.pid)) {\n\t\t\tthrow new Error(`Pi Town lease already held by pid ${current.pid} on ${current.hostname} for run ${current.runId}.`)\n\t\t}\n\t\trmSync(leasePath, { force: true })\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code !== \"ENOENT\") {\n\t\t\tif (error instanceof Error && error.message.startsWith(\"Pi Town lease already held\")) throw error\n\t\t}\n\t}\n\n\twriteFileSync(leasePath, `${JSON.stringify(nextData, null, 2)}\\n`, \"utf-8\")\n\n\treturn {\n\t\tpath: leasePath,\n\t\trelease: () => {\n\t\t\ttry {\n\t\t\t\tconst current = JSON.parse(readFileSync(leasePath, \"utf-8\")) as LeaseData\n\t\t\t\tif (current.runId === runId) rmSync(leasePath, { force: true })\n\t\t\t} catch {\n\t\t\t\t// ignore cleanup failures\n\t\t\t}\n\t\t},\n\t}\n}\n","import type { FeedbackCycle, InterruptRecord, MetricsSnapshot, TaskAttempt } from \"./types.js\"\n\nfunction round(value: number): number {\n\treturn Math.round(value * 1000) / 1000\n}\n\nfunction diffHours(start: string, end: string): number {\n\treturn (Date.parse(end) - Date.parse(start)) / 3_600_000\n}\n\nfunction average(values: number[]): number | null {\n\tif (values.length === 0) return null\n\treturn values.reduce((sum, value) => sum + value, 0) / values.length\n}\n\nexport function computeInterruptRate(interrupts: InterruptRecord[], taskAttempts: TaskAttempt[]): number {\n\tif (taskAttempts.length === 0) return 0\n\treturn round(interrupts.length / taskAttempts.length)\n}\n\nexport function computeAutonomousCompletionRate(taskAttempts: TaskAttempt[]): number {\n\tconst completed = taskAttempts.filter((task) => task.status === \"completed\")\n\tif (completed.length === 0) return 0\n\tconst autonomous = completed.filter((task) => !task.interrupted)\n\treturn round(autonomous.length / completed.length)\n}\n\nexport function computeContextCoverageScore(interrupts: InterruptRecord[]): number {\n\tconst observed = new Set(interrupts.map((interrupt) => interrupt.category))\n\tif (observed.size === 0) return 0\n\n\tconst covered = new Set(\n\t\tinterrupts.filter((interrupt) => interrupt.fixType).map((interrupt) => interrupt.category),\n\t)\n\n\treturn round(covered.size / observed.size)\n}\n\nexport function computeMeanTimeToCorrect(interrupts: InterruptRecord[]): number | null {\n\tconst resolved = interrupts.filter((interrupt) => interrupt.resolvedAt)\n\tconst hours = resolved.map((interrupt) => diffHours(interrupt.createdAt, interrupt.resolvedAt!))\n\tconst value = average(hours)\n\treturn value === null ? null : round(value)\n}\n\nexport function computeFeedbackToDemoCycleTime(feedbackCycles: FeedbackCycle[]): number | null {\n\tconst hours = feedbackCycles.map((cycle) => diffHours(cycle.feedbackAt, cycle.demoReadyAt))\n\tconst value = average(hours)\n\treturn value === null ? null : round(value)\n}\n\nexport function computeMetrics(input: {\n\ttaskAttempts: TaskAttempt[]\n\tinterrupts: InterruptRecord[]\n\tfeedbackCycles?: FeedbackCycle[]\n}): MetricsSnapshot {\n\tconst observedCategories = new Set(input.interrupts.map((interrupt) => interrupt.category))\n\tconst coveredCategories = new Set(\n\t\tinput.interrupts.filter((interrupt) => interrupt.fixType).map((interrupt) => interrupt.category),\n\t)\n\tconst completedTasks = input.taskAttempts.filter((task) => task.status === \"completed\").length\n\n\treturn {\n\t\tinterruptRate: computeInterruptRate(input.interrupts, input.taskAttempts),\n\t\tautonomousCompletionRate: computeAutonomousCompletionRate(input.taskAttempts),\n\t\tcontextCoverageScore: computeContextCoverageScore(input.interrupts),\n\t\tmeanTimeToCorrectHours: computeMeanTimeToCorrect(input.interrupts),\n\t\tfeedbackToDemoCycleTimeHours: computeFeedbackToDemoCycleTime(input.feedbackCycles ?? []),\n\t\ttotals: {\n\t\t\ttaskAttempts: input.taskAttempts.length,\n\t\t\tcompletedTasks,\n\t\t\tinterrupts: input.interrupts.length,\n\t\t\tobservedInterruptCategories: observedCategories.size,\n\t\t\tcoveredInterruptCategories: coveredCategories.size,\n\t\t},\n\t}\n}\n","import { mkdirSync, writeFileSync } from \"node:fs\"\nimport { join } from \"node:path\"\nimport {\n\tcreateAgentSessionRecord,\n\tcreateAgentState,\n\tgetAgentSessionsDir,\n\tgetLatestAgentSession,\n\treadAgentState,\n\twriteAgentState,\n} from \"./agents.js\"\nimport { appendJsonl } from \"./events.js\"\nimport { acquireRepoLease } from \"./lease.js\"\nimport { computeMetrics } from \"./metrics.js\"\nimport { createPiAuthHelpMessage, detectPiAuthFailure } from \"./pi.js\"\nimport { createRepoSlug, getCurrentBranch, getRepoIdentity, getRepoRoot } from \"./repo.js\"\nimport { assertCommandAvailable, runCommandSync } from \"./shell.js\"\nimport type { ControllerRunResult, PiInvocationRecord, RunManifest, RunOptions, RunSummary } from \"./types.js\"\n\nfunction createRunId(): string {\n\treturn `run-${new Date().toISOString().replace(/[:.]/g, \"-\")}`\n}\n\nfunction createPiInvocationArgs(input: {\n\tsessionDir?: string | null\n\tsessionPath?: string | null\n\tprompt: string\n\tappendedSystemPrompt?: string | null\n\textensionPath?: string | null\n}) {\n\tconst args: string[] = []\n\n\tif (input.extensionPath) args.push(\"--extension\", input.extensionPath)\n\tif (input.appendedSystemPrompt) args.push(\"--append-system-prompt\", input.appendedSystemPrompt)\n\tif (input.sessionPath) args.push(\"--session\", input.sessionPath)\n\telse if (input.sessionDir) args.push(\"--session-dir\", input.sessionDir)\n\telse throw new Error(\"Pi invocation requires a session path or session directory\")\n\targs.push(\"-p\", input.prompt)\n\n\treturn args\n}\n\nfunction writeJson(path: string, value: unknown) {\n\twriteFileSync(path, `${JSON.stringify(value, null, 2)}\\n`, \"utf-8\")\n}\n\nfunction writeText(path: string, value: string) {\n\twriteFileSync(path, value, \"utf-8\")\n}\n\nfunction createPiPrompt(input: {\n\trepoRoot: string\n\tplanPath: string | null\n\tgoal: string | null\n\trecommendedPlanDir: string | null\n}): string {\n\tconst goal = input.goal ?? \"continue from current scaffold state\"\n\n\tif (input.planPath) {\n\t\treturn [\n\t\t\t\"You are the Pi Town leader agent for this repository.\",\n\t\t\t\"Coordinate the next bounded unit of work, keep updates concise, and leave a durable artifact trail.\",\n\t\t\t\"\",\n\t\t\t\"Read the private plans in:\",\n\t\t\t`- ${input.planPath}`,\n\t\t\t\"\",\n\t\t\t\"and the current code in:\",\n\t\t\t`- ${input.repoRoot}`,\n\t\t\t\"\",\n\t\t\t`Goal: ${goal}`,\n\t\t\t\"Continue from the current scaffold state.\",\n\t\t\t\"Keep any persisted run artifacts high-signal and avoid copying private plan contents into them.\",\n\t\t].join(\"\\n\")\n\t}\n\n\treturn [\n\t\t\"You are the Pi Town leader agent for this repository.\",\n\t\t\"Coordinate the next bounded unit of work, keep updates concise, and leave a durable artifact trail.\",\n\t\t\"\",\n\t\t`Work in the repository at: ${input.repoRoot}`,\n\t\t`Goal: ${goal}`,\n\t\t\"No private plan path is configured for this run.\",\n\t\tinput.recommendedPlanDir\n\t\t\t? `If you need private plans, use a user-owned location such as: ${input.recommendedPlanDir}`\n\t\t\t: \"If you need private plans, keep them in a user-owned location outside the repo.\",\n\t\t\"Continue from the current scaffold state.\",\n\t].join(\"\\n\")\n}\n\nfunction assertPiRuntimeAvailable(piCommand: string) {\n\ttry {\n\t\tassertCommandAvailable(piCommand)\n\t} catch (error) {\n\t\tif (piCommand === \"pi\") {\n\t\t\tthrow new Error(\n\t\t\t\t[\n\t\t\t\t\t\"Pi Town requires the `pi` CLI to run `pitown run`.\",\n\t\t\t\t\t\"Install Pi: npm install -g @mariozechner/pi-coding-agent\",\n\t\t\t\t\t\"Then authenticate Pi and verify it works: pi -p \\\"hello\\\"\",\n\t\t\t\t\t`Details: ${(error as Error).message}`,\n\t\t\t\t].join(\"\\n\"),\n\t\t\t)\n\t\t}\n\n\t\tthrow new Error(\n\t\t\t[\n\t\t\t\t`Pi Town could not execute the configured Pi command: ${piCommand}`,\n\t\t\t\t\"Make sure the command exists on PATH or points to an executable file.\",\n\t\t\t\t`Details: ${(error as Error).message}`,\n\t\t\t].join(\"\\n\"),\n\t\t)\n\t}\n}\n\nfunction createManifest(input: {\n\trunId: string\n\trepoId: string\n\trepoSlug: string\n\trepoRoot: string\n\tbranch: string\n\tgoal: string | null\n\tplanPath: string | null\n\trecommendedPlanDir: string | null\n\tmode: \"single-pi\"\n\tleasePath: string\n}): RunManifest {\n\treturn {\n\t\trunId: input.runId,\n\t\trepoId: input.repoId,\n\t\trepoSlug: input.repoSlug,\n\t\trepoRoot: input.repoRoot,\n\t\tbranch: input.branch,\n\t\tgoal: input.goal,\n\t\tplanPath: input.planPath,\n\t\trecommendedPlanDir: input.recommendedPlanDir,\n\t\tmode: input.mode,\n\t\tstartedAt: new Date().toISOString(),\n\t\tendedAt: null,\n\t\tstopReason: null,\n\t\tleasePath: input.leasePath,\n\t\tpiExitCode: null,\n\t\tcompletedTaskCount: 0,\n\t\tblockedTaskCount: 0,\n\t\tskippedTaskCount: 0,\n\t\ttotalCostUsd: 0,\n\t}\n}\n\nfunction createSummary(input: {\n\trunId: string\n\tmode: \"single-pi\"\n\texitCode: number\n\tstdout: string\n\tstderr: string\n\trecommendedPlanDir: string | null\n}): RunSummary {\n\tconst success = input.exitCode === 0\n\tconst recommendation =\n\t\tinput.recommendedPlanDir === null\n\t\t\t? \"\"\n\t\t\t: ` No plan path was configured. Recommended private plans location: ${input.recommendedPlanDir}.`\n\tconst authHelp =\n\t\tsuccess || !detectPiAuthFailure(input.stderr, input.stdout) ? \"\" : ` ${createPiAuthHelpMessage()}`\n\n\treturn {\n\t\trunId: input.runId,\n\t\tmode: input.mode,\n\t\tcreatedAt: new Date().toISOString(),\n\t\tsuccess,\n\t\tmessage: success\n\t\t\t? `Pi invocation completed.${recommendation}`\n\t\t\t: `Pi invocation failed.${authHelp}${recommendation}`,\n\t\tpiExitCode: input.exitCode,\n\t\trecommendedPlanDir: input.recommendedPlanDir,\n\t}\n}\n\nexport function runController(options: RunOptions): ControllerRunResult {\n\tconst cwd = options.cwd ?? process.cwd()\n\tconst artifactsDir = options.artifactsDir\n\tconst repoRoot = getRepoRoot(cwd)\n\tconst repoId = getRepoIdentity(repoRoot)\n\tconst repoSlug = createRepoSlug(repoId, repoRoot)\n\tconst branch = options.branch ?? getCurrentBranch(repoRoot) ?? \"workspace\"\n\tconst goal = options.goal ?? null\n\tconst planPath = options.planPath ?? null\n\tconst recommendedPlanDir = planPath ? null : (options.recommendedPlanDir ?? null)\n\tconst mode = options.mode ?? \"single-pi\"\n\tconst piCommand = options.piCommand ?? \"pi\"\n\tconst runId = createRunId()\n\tconst runDir = join(artifactsDir, \"runs\", runId)\n\tconst latestDir = join(artifactsDir, \"latest\")\n\tconst stdoutPath = join(runDir, \"stdout.txt\")\n\tconst stderrPath = join(runDir, \"stderr.txt\")\n\tconst prompt = createPiPrompt({ repoRoot, planPath, goal, recommendedPlanDir })\n\tconst existingLeaderState = readAgentState(artifactsDir, \"leader\")\n\tconst existingLeaderSession =\n\t\texistingLeaderState?.session.sessionPath || existingLeaderState?.session.sessionDir\n\t\t\t? existingLeaderState.session\n\t\t\t: getLatestAgentSession(artifactsDir, \"leader\")\n\tconst leaderSessionDir = existingLeaderSession.sessionDir ?? getAgentSessionsDir(artifactsDir, \"leader\")\n\tconst leaderState = createAgentState({\n\t\tagentId: \"leader\",\n\t\trole: \"leader\",\n\t\tstatus: \"starting\",\n\t\ttask: goal,\n\t\tbranch,\n\t\tlastMessage: goal ? `Starting leader run for goal: ${goal}` : \"Starting leader run\",\n\t\trunId,\n\t\tsession: createAgentSessionRecord({\n\t\t\tsessionDir: leaderSessionDir,\n\t\t\tsessionId: existingLeaderSession.sessionId,\n\t\t\tsessionPath: existingLeaderSession.sessionPath,\n\t\t}),\n\t})\n\n\tassertPiRuntimeAvailable(piCommand)\n\n\tmkdirSync(runDir, { recursive: true })\n\tmkdirSync(latestDir, { recursive: true })\n\n\twriteText(join(runDir, \"questions.jsonl\"), \"\")\n\twriteText(join(runDir, \"interventions.jsonl\"), \"\")\n\twriteJson(join(runDir, \"agent-state.json\"), {\n\t\tstatus: \"starting\",\n\t\tupdatedAt: new Date().toISOString(),\n\t})\n\twriteAgentState(artifactsDir, leaderState)\n\n\tconst lease = acquireRepoLease(runId, repoId, branch)\n\n\ttry {\n\t\tconst manifest = createManifest({\n\t\t\trunId,\n\t\t\trepoId,\n\t\t\trepoSlug,\n\t\t\trepoRoot,\n\t\t\tbranch,\n\t\t\tgoal,\n\t\t\tplanPath,\n\t\t\trecommendedPlanDir,\n\t\t\tmode,\n\t\t\tleasePath: lease.path,\n\t\t})\n\n\t\tappendJsonl(join(runDir, \"events.jsonl\"), {\n\t\t\ttype: \"run_started\",\n\t\t\trunId,\n\t\t\trepoId,\n\t\t\trepoSlug,\n\t\t\tbranch,\n\t\t\tcreatedAt: manifest.startedAt,\n\t\t})\n\n\t\tconst piStartedAt = new Date().toISOString()\n\t\tappendJsonl(join(runDir, \"events.jsonl\"), {\n\t\t\ttype: \"pi_invocation_started\",\n\t\t\trunId,\n\t\t\tcommand: piCommand,\n\t\t\tcreatedAt: piStartedAt,\n\t\t})\n\n\t\twriteAgentState(\n\t\t\tartifactsDir,\n\t\t\tcreateAgentState({\n\t\t\t\t...leaderState,\n\t\t\t\tstatus: \"running\",\n\t\t\t\tlastMessage: goal ? `Leader working on: ${goal}` : \"Leader working\",\n\t\t\t}),\n\t\t)\n\n\t\tconst piArgs = createPiInvocationArgs({\n\t\t\tsessionDir: leaderState.session.sessionPath === null ? leaderSessionDir : null,\n\t\t\tsessionPath: leaderState.session.sessionPath,\n\t\t\tprompt,\n\t\t\tappendedSystemPrompt: options.appendedSystemPrompt,\n\t\t\textensionPath: options.extensionPath,\n\t\t})\n\t\tconst piResult = runCommandSync(piCommand, piArgs, {\n\t\t\tcwd: repoRoot,\n\t\t\tenv: process.env,\n\t\t})\n\t\tconst piEndedAt = new Date().toISOString()\n\t\tconst latestLeaderSession = getLatestAgentSession(artifactsDir, \"leader\")\n\n\t\twriteText(stdoutPath, piResult.stdout)\n\t\twriteText(stderrPath, piResult.stderr)\n\n\t\tconst piInvocation: PiInvocationRecord = {\n\t\t\tcommand: piCommand,\n\t\t\tcwd: repoRoot,\n\t\t\trepoRoot,\n\t\t\tplanPath,\n\t\t\tgoal,\n\t\t\tsessionDir: latestLeaderSession.sessionDir,\n\t\t\tsessionId: latestLeaderSession.sessionId,\n\t\t\tsessionPath: latestLeaderSession.sessionPath,\n\t\t\tstartedAt: piStartedAt,\n\t\t\tendedAt: piEndedAt,\n\t\t\texitCode: piResult.exitCode,\n\t\t\tstdoutPath,\n\t\t\tstderrPath,\n\t\t\tpromptSummary: planPath\n\t\t\t\t? \"Read private plan path and continue from current scaffold state.\"\n\t\t\t\t: \"Continue from current scaffold state without a configured private plan path.\",\n\t\t}\n\t\twriteJson(join(runDir, \"pi-invocation.json\"), piInvocation)\n\n\t\tappendJsonl(join(runDir, \"events.jsonl\"), {\n\t\t\ttype: \"pi_invocation_finished\",\n\t\t\trunId,\n\t\t\tcommand: piCommand,\n\t\t\texitCode: piInvocation.exitCode,\n\t\t\tcreatedAt: piEndedAt,\n\t\t})\n\n\t\tconst metrics = computeMetrics({\n\t\t\ttaskAttempts: [],\n\t\t\tinterrupts: [],\n\t\t})\n\t\tconst summary = createSummary({\n\t\t\trunId,\n\t\t\tmode,\n\t\t\texitCode: piInvocation.exitCode,\n\t\t\tstdout: piResult.stdout,\n\t\t\tstderr: piResult.stderr,\n\t\t\trecommendedPlanDir,\n\t\t})\n\t\tconst finalManifest: RunManifest = {\n\t\t\t...manifest,\n\t\t\tendedAt: piEndedAt,\n\t\t\tstopReason:\n\t\t\t\tpiInvocation.exitCode === 0\n\t\t\t\t\t? \"pi invocation completed\"\n\t\t\t\t\t: `pi invocation exited with code ${piInvocation.exitCode}`,\n\t\t\tpiExitCode: piInvocation.exitCode,\n\t\t}\n\n\t\twriteJson(join(runDir, \"manifest.json\"), finalManifest)\n\t\twriteJson(join(runDir, \"metrics.json\"), metrics)\n\t\twriteJson(join(runDir, \"run-summary.json\"), summary)\n\t\twriteJson(join(runDir, \"agent-state.json\"), {\n\t\t\tstatus: summary.success ? \"completed\" : \"failed\",\n\t\t\tupdatedAt: piEndedAt,\n\t\t\texitCode: piInvocation.exitCode,\n\t\t})\n\t\twriteJson(join(latestDir, \"manifest.json\"), finalManifest)\n\t\twriteJson(join(latestDir, \"metrics.json\"), metrics)\n\t\twriteJson(join(latestDir, \"run-summary.json\"), summary)\n\t\twriteAgentState(\n\t\t\tartifactsDir,\n\t\t\tcreateAgentState({\n\t\t\t\t...leaderState,\n\t\t\t\t\tstatus: piInvocation.exitCode === 0 ? \"idle\" : \"blocked\",\n\t\t\t\t\tlastMessage:\n\t\t\t\t\t\tpiInvocation.exitCode === 0\n\t\t\t\t\t\t\t? \"Leader run completed and is ready for the next instruction\"\n\t\t\t\t\t\t\t: `Leader run stopped with exit code ${piInvocation.exitCode}`,\n\t\t\t\t\tblocked: piInvocation.exitCode !== 0,\n\t\t\t\t\twaitingOn: piInvocation.exitCode === 0 ? null : \"human-or-follow-up-run\",\n\t\t\t\t\tsession: createAgentSessionRecord({\n\t\t\t\t\t\tsessionDir: latestLeaderSession.sessionDir,\n\t\t\t\t\t\tsessionId: latestLeaderSession.sessionId,\n\t\t\t\t\t\tsessionPath: latestLeaderSession.sessionPath,\n\t\t\t\t\t}),\n\t\t\t\t}),\n\t\t\t)\n\n\t\tappendJsonl(join(runDir, \"events.jsonl\"), {\n\t\t\ttype: \"run_finished\",\n\t\t\trunId,\n\t\t\tcreatedAt: finalManifest.endedAt,\n\t\t\tstopReason: finalManifest.stopReason,\n\t\t\tmetrics,\n\t\t})\n\n\t\treturn {\n\t\t\trunId,\n\t\t\trunDir,\n\t\t\tlatestDir,\n\t\t\tmanifest: finalManifest,\n\t\t\tmetrics,\n\t\t\tsummary,\n\t\t\tpiInvocation,\n\t\t}\n\t} finally {\n\t\tlease.release()\n\t}\n}\n"],"mappings":"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"}
|
package/dist/doctor.mjs
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { n as detectPiAuthFailure, t as createPiAuthHelpMessage } from "./pi-C0fURZj7.mjs";
|
|
2
|
+
import { a as runCommandSync, t as isDirectExecution } from "./entrypoint-CyJDLudQ.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/doctor.ts
|
|
5
|
+
function runDoctor() {
|
|
6
|
+
console.log("[pitown] doctor");
|
|
7
|
+
if (runCommandSync("pi", ["--help"]).exitCode !== 0) {
|
|
8
|
+
console.log("- pi cli: missing");
|
|
9
|
+
console.log("- install: npm install -g @mariozechner/pi-coding-agent");
|
|
10
|
+
console.log("- verify: pi -p \"hello\"");
|
|
11
|
+
return { ok: false };
|
|
12
|
+
}
|
|
13
|
+
const check = runCommandSync("pi", [
|
|
14
|
+
"--no-session",
|
|
15
|
+
"-p",
|
|
16
|
+
"hello"
|
|
17
|
+
]);
|
|
18
|
+
if (check.exitCode === 0) {
|
|
19
|
+
console.log("- pi cli: installed");
|
|
20
|
+
console.log("- pi auth: ready");
|
|
21
|
+
console.log("- status: ok");
|
|
22
|
+
return { ok: true };
|
|
23
|
+
}
|
|
24
|
+
if (detectPiAuthFailure(check.stderr, check.stdout)) {
|
|
25
|
+
console.log("- pi cli: installed");
|
|
26
|
+
console.log("- pi auth: not ready");
|
|
27
|
+
console.log(`- note: ${createPiAuthHelpMessage()}`);
|
|
28
|
+
return { ok: false };
|
|
29
|
+
}
|
|
30
|
+
console.log("- pi cli: installed");
|
|
31
|
+
console.log("- pi check: failed");
|
|
32
|
+
if (check.stderr.trim()) console.log(`- stderr: ${check.stderr.trim()}`);
|
|
33
|
+
else if (check.stdout.trim()) console.log(`- stdout: ${check.stdout.trim()}`);
|
|
34
|
+
return { ok: false };
|
|
35
|
+
}
|
|
36
|
+
if (isDirectExecution(import.meta.url)) {
|
|
37
|
+
if (!runDoctor().ok) process.exitCode = 1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
export { runDoctor };
|
|
42
|
+
//# sourceMappingURL=doctor.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.mjs","names":[],"sources":["../src/doctor.ts"],"sourcesContent":["import { createPiAuthHelpMessage, detectPiAuthFailure, runCommandSync } from \"../../core/src/index.js\"\nimport { isDirectExecution } from \"./entrypoint.js\"\n\nexport interface DoctorResult {\n\tok: boolean\n}\n\nexport function runDoctor(): DoctorResult {\n\tconsole.log(\"[pitown] doctor\")\n\n\tconst availability = runCommandSync(\"pi\", [\"--help\"])\n\tif (availability.exitCode !== 0) {\n\t\tconsole.log(\"- pi cli: missing\")\n\t\tconsole.log(\"- install: npm install -g @mariozechner/pi-coding-agent\")\n\t\tconsole.log('- verify: pi -p \"hello\"')\n\t\treturn { ok: false }\n\t}\n\n\tconst check = runCommandSync(\"pi\", [\"--no-session\", \"-p\", \"hello\"])\n\tif (check.exitCode === 0) {\n\t\tconsole.log(\"- pi cli: installed\")\n\t\tconsole.log(\"- pi auth: ready\")\n\t\tconsole.log(\"- status: ok\")\n\t\treturn { ok: true }\n\t}\n\n\tif (detectPiAuthFailure(check.stderr, check.stdout)) {\n\t\tconsole.log(\"- pi cli: installed\")\n\t\tconsole.log(\"- pi auth: not ready\")\n\t\tconsole.log(`- note: ${createPiAuthHelpMessage()}`)\n\t\treturn { ok: false }\n\t}\n\n\tconsole.log(\"- pi cli: installed\")\n\tconsole.log(\"- pi check: failed\")\n\tif (check.stderr.trim()) console.log(`- stderr: ${check.stderr.trim()}`)\n\telse if (check.stdout.trim()) console.log(`- stdout: ${check.stdout.trim()}`)\n\treturn { ok: false }\n}\n\nif (isDirectExecution(import.meta.url)) {\n\tconst result = runDoctor()\n\tif (!result.ok) process.exitCode = 1\n}\n"],"mappings":";;;;AAOA,SAAgB,YAA0B;AACzC,SAAQ,IAAI,kBAAkB;AAG9B,KADqB,eAAe,MAAM,CAAC,SAAS,CAAC,CACpC,aAAa,GAAG;AAChC,UAAQ,IAAI,oBAAoB;AAChC,UAAQ,IAAI,0DAA0D;AACtE,UAAQ,IAAI,4BAA0B;AACtC,SAAO,EAAE,IAAI,OAAO;;CAGrB,MAAM,QAAQ,eAAe,MAAM;EAAC;EAAgB;EAAM;EAAQ,CAAC;AACnE,KAAI,MAAM,aAAa,GAAG;AACzB,UAAQ,IAAI,sBAAsB;AAClC,UAAQ,IAAI,mBAAmB;AAC/B,UAAQ,IAAI,eAAe;AAC3B,SAAO,EAAE,IAAI,MAAM;;AAGpB,KAAI,oBAAoB,MAAM,QAAQ,MAAM,OAAO,EAAE;AACpD,UAAQ,IAAI,sBAAsB;AAClC,UAAQ,IAAI,uBAAuB;AACnC,UAAQ,IAAI,WAAW,yBAAyB,GAAG;AACnD,SAAO,EAAE,IAAI,OAAO;;AAGrB,SAAQ,IAAI,sBAAsB;AAClC,SAAQ,IAAI,qBAAqB;AACjC,KAAI,MAAM,OAAO,MAAM,CAAE,SAAQ,IAAI,aAAa,MAAM,OAAO,MAAM,GAAG;UAC/D,MAAM,OAAO,MAAM,CAAE,SAAQ,IAAI,aAAa,MAAM,OAAO,MAAM,GAAG;AAC7E,QAAO,EAAE,IAAI,OAAO;;AAGrB,IAAI,kBAAkB,OAAO,KAAK,IAAI,EAErC;KAAI,CADW,WAAW,CACd,GAAI,SAAQ,WAAW"}
|