@schilderlabs/pitown 0.1.2 → 0.2.6
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 +25 -0
- package/dist/{config-Bw-mNdF5.mjs → config-BG1v4iIi.mjs} +29 -50
- package/dist/config-BG1v4iIi.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-WBAQmFbT.mjs +61 -0
- package/dist/entrypoint-WBAQmFbT.mjs.map +1 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1265 -8
- package/dist/index.mjs.map +1 -1
- package/dist/loop-CocC9qO1.mjs +678 -0
- package/dist/loop-CocC9qO1.mjs.map +1 -0
- package/dist/pi-C7HRNjBG.mjs +12 -0
- package/dist/pi-C7HRNjBG.mjs.map +1 -0
- package/dist/repo-context-BuA2JqPm.mjs +45 -0
- package/dist/repo-context-BuA2JqPm.mjs.map +1 -0
- package/dist/run.d.mts +3 -69
- package/dist/run.mjs +39 -19
- package/dist/run.mjs.map +1 -1
- package/dist/status.mjs +2 -1
- package/dist/status.mjs.map +1 -1
- package/dist/tasks-De4IAy3x.mjs +195 -0
- package/dist/tasks-De4IAy3x.mjs.map +1 -0
- package/dist/types-COGNGvsY.d.mts +142 -0
- package/dist/watch.d.mts +35 -1
- package/dist/watch.mjs +129 -16
- package/dist/watch.mjs.map +1 -1
- package/package.json +21 -23
- package/dist/config-Bw-mNdF5.mjs.map +0 -1
- package/dist/controller-D7lezZjg.mjs +0 -342
- package/dist/controller-D7lezZjg.mjs.map +0 -1
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
//#region ../core/src/types.d.ts
|
|
2
|
+
type RunMode = "single-pi";
|
|
3
|
+
type AgentStatus = "queued" | "starting" | "running" | "idle" | "blocked" | "completed" | "failed" | "stopped";
|
|
4
|
+
type TaskStatus = "queued" | "running" | "blocked" | "completed" | "aborted";
|
|
5
|
+
interface AgentSessionRecord {
|
|
6
|
+
runtime: "pi";
|
|
7
|
+
persisted: boolean;
|
|
8
|
+
sessionDir: string | null;
|
|
9
|
+
sessionId: string | null;
|
|
10
|
+
sessionPath: string | null;
|
|
11
|
+
processId: number | null;
|
|
12
|
+
lastAttachedAt: string | null;
|
|
13
|
+
}
|
|
14
|
+
interface AgentStateSnapshot {
|
|
15
|
+
agentId: string;
|
|
16
|
+
role: string;
|
|
17
|
+
status: AgentStatus;
|
|
18
|
+
taskId: string | null;
|
|
19
|
+
task: string | null;
|
|
20
|
+
branch: string | null;
|
|
21
|
+
updatedAt: string;
|
|
22
|
+
lastMessage: string | null;
|
|
23
|
+
waitingOn: string | null;
|
|
24
|
+
blocked: boolean;
|
|
25
|
+
runId: string | null;
|
|
26
|
+
session: AgentSessionRecord;
|
|
27
|
+
}
|
|
28
|
+
interface TaskRecord {
|
|
29
|
+
taskId: string;
|
|
30
|
+
title: string;
|
|
31
|
+
status: TaskStatus;
|
|
32
|
+
role: string;
|
|
33
|
+
assignedAgentId: string;
|
|
34
|
+
createdBy: string;
|
|
35
|
+
createdAt: string;
|
|
36
|
+
updatedAt: string;
|
|
37
|
+
}
|
|
38
|
+
interface MetricsSnapshot {
|
|
39
|
+
interruptRate: number;
|
|
40
|
+
autonomousCompletionRate: number;
|
|
41
|
+
contextCoverageScore: number;
|
|
42
|
+
meanTimeToCorrectHours: number | null;
|
|
43
|
+
feedbackToDemoCycleTimeHours: number | null;
|
|
44
|
+
totals: {
|
|
45
|
+
taskAttempts: number;
|
|
46
|
+
completedTasks: number;
|
|
47
|
+
interrupts: number;
|
|
48
|
+
observedInterruptCategories: number;
|
|
49
|
+
coveredInterruptCategories: number;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
interface RunManifest {
|
|
53
|
+
runId: string;
|
|
54
|
+
repoId: string;
|
|
55
|
+
repoSlug: string;
|
|
56
|
+
repoRoot: string;
|
|
57
|
+
branch: string;
|
|
58
|
+
goal: string | null;
|
|
59
|
+
planPath: string | null;
|
|
60
|
+
recommendedPlanDir: string | null;
|
|
61
|
+
mode: RunMode;
|
|
62
|
+
startedAt: string;
|
|
63
|
+
endedAt: string | null;
|
|
64
|
+
stopReason: string | null;
|
|
65
|
+
leasePath: string;
|
|
66
|
+
piExitCode: number | null;
|
|
67
|
+
completedTaskCount: number;
|
|
68
|
+
blockedTaskCount: number;
|
|
69
|
+
skippedTaskCount: number;
|
|
70
|
+
totalCostUsd: number;
|
|
71
|
+
}
|
|
72
|
+
interface PiInvocationRecord {
|
|
73
|
+
command: string;
|
|
74
|
+
cwd: string;
|
|
75
|
+
repoRoot: string;
|
|
76
|
+
planPath: string | null;
|
|
77
|
+
goal: string | null;
|
|
78
|
+
sessionDir: string | null;
|
|
79
|
+
sessionId: string | null;
|
|
80
|
+
sessionPath: string | null;
|
|
81
|
+
startedAt: string;
|
|
82
|
+
endedAt: string;
|
|
83
|
+
exitCode: number;
|
|
84
|
+
stdoutPath: string;
|
|
85
|
+
stderrPath: string;
|
|
86
|
+
promptSummary: string;
|
|
87
|
+
}
|
|
88
|
+
interface RunSummary {
|
|
89
|
+
runId: string;
|
|
90
|
+
mode: RunMode;
|
|
91
|
+
createdAt: string;
|
|
92
|
+
success: boolean;
|
|
93
|
+
message: string;
|
|
94
|
+
piExitCode: number;
|
|
95
|
+
recommendedPlanDir: string | null;
|
|
96
|
+
}
|
|
97
|
+
interface ControllerRunResult {
|
|
98
|
+
runId: string;
|
|
99
|
+
runDir: string;
|
|
100
|
+
latestDir: string;
|
|
101
|
+
manifest: RunManifest;
|
|
102
|
+
metrics: MetricsSnapshot;
|
|
103
|
+
summary: RunSummary;
|
|
104
|
+
piInvocation: PiInvocationRecord;
|
|
105
|
+
}
|
|
106
|
+
type LoopStopReason = "all-tasks-completed" | "all-remaining-tasks-blocked" | "mayor-blocked" | "mayor-idle-no-work" | "max-iterations-reached" | "max-wall-time-reached" | "pi-exit-nonzero" | "high-interrupt-rate";
|
|
107
|
+
interface BoardSnapshot {
|
|
108
|
+
tasks: Array<{
|
|
109
|
+
taskId: string;
|
|
110
|
+
status: TaskStatus;
|
|
111
|
+
}>;
|
|
112
|
+
agents: Array<{
|
|
113
|
+
agentId: string;
|
|
114
|
+
status: AgentStatus;
|
|
115
|
+
blocked: boolean;
|
|
116
|
+
}>;
|
|
117
|
+
allTasksCompleted: boolean;
|
|
118
|
+
allRemainingTasksBlocked: boolean;
|
|
119
|
+
mayorBlocked: boolean;
|
|
120
|
+
hasQueuedOrRunningWork: boolean;
|
|
121
|
+
}
|
|
122
|
+
interface LoopIterationResult {
|
|
123
|
+
iteration: number;
|
|
124
|
+
controllerResult: ControllerRunResult;
|
|
125
|
+
boardSnapshot: BoardSnapshot;
|
|
126
|
+
metrics: MetricsSnapshot;
|
|
127
|
+
elapsedMs: number;
|
|
128
|
+
continueReason: string | null;
|
|
129
|
+
stopReason: LoopStopReason | null;
|
|
130
|
+
}
|
|
131
|
+
interface LoopRunResult {
|
|
132
|
+
loopId: string;
|
|
133
|
+
iterations: LoopIterationResult[];
|
|
134
|
+
stopReason: LoopStopReason;
|
|
135
|
+
totalIterations: number;
|
|
136
|
+
totalElapsedMs: number;
|
|
137
|
+
finalBoardSnapshot: BoardSnapshot;
|
|
138
|
+
aggregateMetrics: MetricsSnapshot;
|
|
139
|
+
}
|
|
140
|
+
//#endregion
|
|
141
|
+
export { LoopRunResult as n, TaskRecord as r, AgentStateSnapshot as t };
|
|
142
|
+
//# sourceMappingURL=types-COGNGvsY.d.mts.map
|
package/dist/watch.d.mts
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
|
+
import { r as TaskRecord, t as AgentStateSnapshot } from "./types-COGNGvsY.mjs";
|
|
2
|
+
|
|
3
|
+
//#region ../core/src/agents.d.ts
|
|
4
|
+
declare function listAgentStates(artifactsDir: string): AgentStateSnapshot[];
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region ../core/src/tasks.d.ts
|
|
7
|
+
declare function listTaskRecords(artifactsDir: string): TaskRecord[];
|
|
8
|
+
//#endregion
|
|
1
9
|
//#region src/watch.d.ts
|
|
10
|
+
interface WatchSummarySnapshot {
|
|
11
|
+
runId: string | null;
|
|
12
|
+
piExitCode: number | null;
|
|
13
|
+
mode: string | null;
|
|
14
|
+
message: string | null;
|
|
15
|
+
stopReason: string | null;
|
|
16
|
+
}
|
|
17
|
+
interface WatchManifestSnapshot {
|
|
18
|
+
branch: string | null;
|
|
19
|
+
goal: string | null;
|
|
20
|
+
planPath: string | null;
|
|
21
|
+
recommendedPlanDir: string | null;
|
|
22
|
+
}
|
|
23
|
+
interface WatchSnapshot {
|
|
24
|
+
repoRoot: string;
|
|
25
|
+
repoSlug: string;
|
|
26
|
+
repoName: string;
|
|
27
|
+
branch: string | null;
|
|
28
|
+
summary: WatchSummarySnapshot;
|
|
29
|
+
manifest: WatchManifestSnapshot;
|
|
30
|
+
agents: ReturnType<typeof listAgentStates>;
|
|
31
|
+
tasks: ReturnType<typeof listTaskRecords>;
|
|
32
|
+
}
|
|
33
|
+
declare function buildWatchSnapshot(argv?: string[]): WatchSnapshot;
|
|
34
|
+
declare function createWatchFingerprint(snapshot: WatchSnapshot): string;
|
|
35
|
+
declare function renderWatchSnapshot(snapshot: WatchSnapshot): string[];
|
|
2
36
|
declare function watchTown(argv?: string[]): void;
|
|
3
37
|
//#endregion
|
|
4
|
-
export { watchTown };
|
|
38
|
+
export { WatchSnapshot, buildWatchSnapshot, createWatchFingerprint, renderWatchSnapshot, watchTown };
|
|
5
39
|
//# sourceMappingURL=watch.d.mts.map
|
package/dist/watch.mjs
CHANGED
|
@@ -1,25 +1,138 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { d as listAgentStates, n as listTaskRecords } from "./tasks-De4IAy3x.mjs";
|
|
2
|
+
import { t as isDirectExecution } from "./entrypoint-WBAQmFbT.mjs";
|
|
3
|
+
import { f as getCurrentBranch, l as getTownHomeDir } from "./config-BG1v4iIi.mjs";
|
|
4
|
+
import { t as resolveRepoContext } from "./repo-context-BuA2JqPm.mjs";
|
|
5
|
+
import { resolveLatestRunPointer } from "./status.mjs";
|
|
6
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
7
|
+
import { basename } from "node:path";
|
|
4
8
|
|
|
5
9
|
//#region src/watch.ts
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (!metricsPath || !existsSync(metricsPath)) {
|
|
13
|
-
console.log("- metrics file does not exist yet; watch will activate after the first run");
|
|
14
|
-
return;
|
|
10
|
+
function readJsonIfExists(path) {
|
|
11
|
+
if (!existsSync(path)) return null;
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
15
16
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
}
|
|
18
|
+
function truncate(text, max) {
|
|
19
|
+
if (!text) return "—";
|
|
20
|
+
const single = text.replace(/\n/g, " ").trim();
|
|
21
|
+
if (single.length <= max) return single;
|
|
22
|
+
return `${single.slice(0, max - 1)}...`;
|
|
23
|
+
}
|
|
24
|
+
function buildWatchSnapshot(argv = process.argv.slice(2)) {
|
|
25
|
+
const repo = resolveRepoContext(argv);
|
|
26
|
+
const latest = resolveLatestRunPointer(["--repo", repo.repoRoot]);
|
|
27
|
+
const summary = latest ? readJsonIfExists(latest.summaryPath) ?? null : null;
|
|
28
|
+
const manifest = latest ? readJsonIfExists(latest.manifestPath) ?? null : null;
|
|
29
|
+
return {
|
|
30
|
+
repoRoot: repo.repoRoot,
|
|
31
|
+
repoSlug: repo.repoSlug,
|
|
32
|
+
repoName: basename(repo.repoRoot),
|
|
33
|
+
branch: getCurrentBranch(repo.repoRoot),
|
|
34
|
+
summary: {
|
|
35
|
+
runId: summary?.runId ?? null,
|
|
36
|
+
piExitCode: summary?.piExitCode ?? null,
|
|
37
|
+
mode: summary?.mode ?? null,
|
|
38
|
+
message: summary?.message ?? null,
|
|
39
|
+
stopReason: manifest?.stopReason ?? null
|
|
40
|
+
},
|
|
41
|
+
manifest: {
|
|
42
|
+
branch: manifest?.branch ?? null,
|
|
43
|
+
goal: manifest?.goal ?? null,
|
|
44
|
+
planPath: manifest?.planPath ?? null,
|
|
45
|
+
recommendedPlanDir: manifest?.recommendedPlanDir ?? null
|
|
46
|
+
},
|
|
47
|
+
agents: listAgentStates(repo.artifactsDir),
|
|
48
|
+
tasks: listTaskRecords(repo.artifactsDir)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function createWatchFingerprint(snapshot) {
|
|
52
|
+
return JSON.stringify({
|
|
53
|
+
summary: snapshot.summary,
|
|
54
|
+
manifest: snapshot.manifest,
|
|
55
|
+
agents: snapshot.agents.map((agent) => ({
|
|
56
|
+
agentId: agent.agentId,
|
|
57
|
+
role: agent.role,
|
|
58
|
+
status: agent.status,
|
|
59
|
+
taskId: agent.taskId,
|
|
60
|
+
task: agent.task,
|
|
61
|
+
lastMessage: agent.lastMessage,
|
|
62
|
+
waitingOn: agent.waitingOn,
|
|
63
|
+
blocked: agent.blocked,
|
|
64
|
+
updatedAt: agent.updatedAt
|
|
65
|
+
})),
|
|
66
|
+
tasks: snapshot.tasks.map((task) => ({
|
|
67
|
+
taskId: task.taskId,
|
|
68
|
+
title: task.title,
|
|
69
|
+
status: task.status,
|
|
70
|
+
assignedAgentId: task.assignedAgentId,
|
|
71
|
+
updatedAt: task.updatedAt
|
|
72
|
+
}))
|
|
19
73
|
});
|
|
20
74
|
}
|
|
75
|
+
function renderWatchSnapshot(snapshot) {
|
|
76
|
+
const branchLabel = snapshot.branch ? ` (${snapshot.branch})` : "";
|
|
77
|
+
const lines = [`[pitown] watch - ${snapshot.repoName}${branchLabel}`];
|
|
78
|
+
lines.push(`- town home: ${getTownHomeDir()}`);
|
|
79
|
+
lines.push(`- repo root: ${snapshot.repoRoot}`);
|
|
80
|
+
if (snapshot.summary.runId) lines.push(`- latest run: ${snapshot.summary.runId}`);
|
|
81
|
+
if (snapshot.summary.mode) lines.push(`- mode: ${snapshot.summary.mode}`);
|
|
82
|
+
if (snapshot.summary.piExitCode !== null) lines.push(`- latest pi exit code: ${snapshot.summary.piExitCode}`);
|
|
83
|
+
if (snapshot.summary.stopReason) lines.push(`- stop reason: ${snapshot.summary.stopReason}`);
|
|
84
|
+
if (snapshot.manifest.goal) lines.push(`- goal: ${snapshot.manifest.goal}`);
|
|
85
|
+
if (snapshot.manifest.planPath) lines.push(`- plan path: ${snapshot.manifest.planPath}`);
|
|
86
|
+
if (!snapshot.manifest.planPath && snapshot.manifest.recommendedPlanDir) lines.push(`- recommended plans: ${snapshot.manifest.recommendedPlanDir}`);
|
|
87
|
+
if (snapshot.summary.message) lines.push(`- note: ${snapshot.summary.message}`);
|
|
88
|
+
lines.push("");
|
|
89
|
+
lines.push("Agents:");
|
|
90
|
+
if (snapshot.agents.length === 0) lines.push(" (no agents)");
|
|
91
|
+
else for (const agent of snapshot.agents) {
|
|
92
|
+
const id = agent.agentId.padEnd(14);
|
|
93
|
+
const role = agent.role.padEnd(10);
|
|
94
|
+
const status = agent.status.padEnd(10);
|
|
95
|
+
const task = truncate(agent.task, 56);
|
|
96
|
+
const msg = agent.lastMessage ? ` | ${truncate(agent.lastMessage, 36)}` : "";
|
|
97
|
+
const waiting = agent.waitingOn ? ` | waiting on: ${agent.waitingOn}` : "";
|
|
98
|
+
lines.push(` ${id}${role}${status}${task}${msg}${waiting}`);
|
|
99
|
+
}
|
|
100
|
+
lines.push("");
|
|
101
|
+
lines.push("Tasks:");
|
|
102
|
+
if (snapshot.tasks.length === 0) lines.push(" (no tasks)");
|
|
103
|
+
else for (const task of snapshot.tasks) {
|
|
104
|
+
const id = task.taskId.padEnd(14);
|
|
105
|
+
const status = task.status.padEnd(12);
|
|
106
|
+
const assignee = task.assignedAgentId.padEnd(14);
|
|
107
|
+
lines.push(` ${id}${status}${assignee}${truncate(task.title, 56)}`);
|
|
108
|
+
}
|
|
109
|
+
return lines;
|
|
110
|
+
}
|
|
111
|
+
function printSnapshot(snapshot) {
|
|
112
|
+
console.log(renderWatchSnapshot(snapshot).join("\n"));
|
|
113
|
+
}
|
|
114
|
+
function watchTown(argv = process.argv.slice(2)) {
|
|
115
|
+
const initialSnapshot = buildWatchSnapshot(argv);
|
|
116
|
+
let previousFingerprint = createWatchFingerprint(initialSnapshot);
|
|
117
|
+
printSnapshot(initialSnapshot);
|
|
118
|
+
console.log("- press Ctrl+C to stop");
|
|
119
|
+
const interval = setInterval(() => {
|
|
120
|
+
const nextSnapshot = buildWatchSnapshot(argv);
|
|
121
|
+
const nextFingerprint = createWatchFingerprint(nextSnapshot);
|
|
122
|
+
if (nextFingerprint === previousFingerprint) return;
|
|
123
|
+
previousFingerprint = nextFingerprint;
|
|
124
|
+
console.log("");
|
|
125
|
+
console.log(`[pitown] watch update - ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
126
|
+
printSnapshot(nextSnapshot);
|
|
127
|
+
}, 1e3);
|
|
128
|
+
const stopWatching = () => {
|
|
129
|
+
clearInterval(interval);
|
|
130
|
+
};
|
|
131
|
+
process.once("SIGINT", stopWatching);
|
|
132
|
+
process.once("SIGTERM", stopWatching);
|
|
133
|
+
}
|
|
21
134
|
if (isDirectExecution(import.meta.url)) watchTown();
|
|
22
135
|
|
|
23
136
|
//#endregion
|
|
24
|
-
export { watchTown };
|
|
137
|
+
export { buildWatchSnapshot, createWatchFingerprint, renderWatchSnapshot, watchTown };
|
|
25
138
|
//# sourceMappingURL=watch.mjs.map
|
package/dist/watch.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watch.mjs","names":[],"sources":["../src/watch.ts"],"sourcesContent":["import { existsSync, watchFile } from \"node:fs\"\nimport { isDirectExecution } from \"./entrypoint.js\"\nimport { getTownHomeDir } from \"./paths.js\"\nimport { resolveLatestRunPointer, showTownStatus } from \"./status.js\"\n\nexport function watchTown(argv = process.argv.slice(2)) {\n\tconst latest = resolveLatestRunPointer(argv)\n\tconst metricsPath = latest?.metricsPath\n\n\tconsole.log(\"[pitown] watch\")\n\tconsole.log(`- town home: ${getTownHomeDir()}`)\n\tconsole.log(\"- press Ctrl+C to stop\")\n\tshowTownStatus(argv)\n\n\tif (!metricsPath || !existsSync(metricsPath)) {\n\t\tconsole.log(\"- metrics file does not exist yet; watch will activate after the first run\")\n\t\treturn\n\t}\n\n\twatchFile(metricsPath, { interval: 1000 }, () => {\n\t\tconsole.log(\"\\n[pitown] metrics updated\")\n\t\tshowTownStatus(argv)\n\t})\n}\n\nif (isDirectExecution(import.meta.url)) {\n\twatchTown()\n}\n"],"mappings":";;;;;AAKA,SAAgB,UAAU,OAAO,QAAQ,KAAK,MAAM,EAAE,EAAE;CAEvD,MAAM,cADS,wBAAwB,KAAK,EAChB;AAE5B,SAAQ,IAAI,iBAAiB;AAC7B,SAAQ,IAAI,gBAAgB,gBAAgB,GAAG;AAC/C,SAAQ,IAAI,yBAAyB;AACrC,gBAAe,KAAK;AAEpB,KAAI,CAAC,eAAe,CAAC,WAAW,YAAY,EAAE;AAC7C,UAAQ,IAAI,6EAA6E;AACzF;;AAGD,WAAU,aAAa,EAAE,UAAU,KAAM,QAAQ;AAChD,UAAQ,IAAI,6BAA6B;AACzC,iBAAe,KAAK;GACnB;;AAGH,IAAI,kBAAkB,OAAO,KAAK,IAAI,CACrC,YAAW"}
|
|
1
|
+
{"version":3,"file":"watch.mjs","names":[],"sources":["../src/watch.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\"\nimport { basename } from \"node:path\"\nimport { getCurrentBranch, listAgentStates, listTaskRecords } from \"../../core/src/index.js\"\nimport { isDirectExecution } from \"./entrypoint.js\"\nimport { getTownHomeDir } from \"./paths.js\"\nimport { resolveRepoContext } from \"./repo-context.js\"\nimport { resolveLatestRunPointer } from \"./status.js\"\n\ninterface WatchSummarySnapshot {\n\trunId: string | null\n\tpiExitCode: number | null\n\tmode: string | null\n\tmessage: string | null\n\tstopReason: string | null\n}\n\ninterface WatchManifestSnapshot {\n\tbranch: string | null\n\tgoal: string | null\n\tplanPath: string | null\n\trecommendedPlanDir: string | null\n}\n\nexport interface WatchSnapshot {\n\trepoRoot: string\n\trepoSlug: string\n\trepoName: string\n\tbranch: string | null\n\tsummary: WatchSummarySnapshot\n\tmanifest: WatchManifestSnapshot\n\tagents: ReturnType<typeof listAgentStates>\n\ttasks: ReturnType<typeof listTaskRecords>\n}\n\nfunction readJsonIfExists<T>(path: string): T | null {\n\tif (!existsSync(path)) return null\n\n\ttry {\n\t\treturn JSON.parse(readFileSync(path, \"utf-8\")) as T\n\t} catch {\n\t\treturn null\n\t}\n}\n\nfunction truncate(text: string | null | undefined, max: number): string {\n\tif (!text) return \"—\"\n\tconst single = text.replace(/\\n/g, \" \").trim()\n\tif (single.length <= max) return single\n\treturn `${single.slice(0, max - 1)}...`\n}\n\nexport function buildWatchSnapshot(argv = process.argv.slice(2)): WatchSnapshot {\n\tconst repo = resolveRepoContext(argv)\n\tconst latest = resolveLatestRunPointer([\"--repo\", repo.repoRoot])\n\tconst summary = latest\n\t\t? (readJsonIfExists<{ runId?: string; piExitCode?: number; mode?: string; message?: string }>(latest.summaryPath) ?? null)\n\t\t: null\n\tconst manifest = latest\n\t\t? (readJsonIfExists<{ branch?: string; goal?: string | null; planPath?: string | null; recommendedPlanDir?: string | null; stopReason?: string | null }>(\n\t\t\t\tlatest.manifestPath,\n\t\t\t) ?? null)\n\t\t: null\n\n\treturn {\n\t\trepoRoot: repo.repoRoot,\n\t\trepoSlug: repo.repoSlug,\n\t\trepoName: basename(repo.repoRoot),\n\t\tbranch: getCurrentBranch(repo.repoRoot),\n\t\tsummary: {\n\t\t\trunId: summary?.runId ?? null,\n\t\t\tpiExitCode: summary?.piExitCode ?? null,\n\t\t\tmode: summary?.mode ?? null,\n\t\t\tmessage: summary?.message ?? null,\n\t\t\tstopReason: manifest?.stopReason ?? null,\n\t\t},\n\t\tmanifest: {\n\t\t\tbranch: manifest?.branch ?? null,\n\t\t\tgoal: manifest?.goal ?? null,\n\t\t\tplanPath: manifest?.planPath ?? null,\n\t\t\trecommendedPlanDir: manifest?.recommendedPlanDir ?? null,\n\t\t},\n\t\tagents: listAgentStates(repo.artifactsDir),\n\t\ttasks: listTaskRecords(repo.artifactsDir),\n\t}\n}\n\nexport function createWatchFingerprint(snapshot: WatchSnapshot): string {\n\treturn JSON.stringify({\n\t\tsummary: snapshot.summary,\n\t\tmanifest: snapshot.manifest,\n\t\tagents: snapshot.agents.map((agent) => ({\n\t\t\tagentId: agent.agentId,\n\t\t\trole: agent.role,\n\t\t\tstatus: agent.status,\n\t\t\ttaskId: agent.taskId,\n\t\t\ttask: agent.task,\n\t\t\tlastMessage: agent.lastMessage,\n\t\t\twaitingOn: agent.waitingOn,\n\t\t\tblocked: agent.blocked,\n\t\t\tupdatedAt: agent.updatedAt,\n\t\t})),\n\t\ttasks: snapshot.tasks.map((task) => ({\n\t\t\ttaskId: task.taskId,\n\t\t\ttitle: task.title,\n\t\t\tstatus: task.status,\n\t\t\tassignedAgentId: task.assignedAgentId,\n\t\t\tupdatedAt: task.updatedAt,\n\t\t})),\n\t})\n}\n\nexport function renderWatchSnapshot(snapshot: WatchSnapshot): string[] {\n\tconst branchLabel = snapshot.branch ? ` (${snapshot.branch})` : \"\"\n\tconst lines = [`[pitown] watch - ${snapshot.repoName}${branchLabel}`]\n\n\tlines.push(`- town home: ${getTownHomeDir()}`)\n\tlines.push(`- repo root: ${snapshot.repoRoot}`)\n\tif (snapshot.summary.runId) lines.push(`- latest run: ${snapshot.summary.runId}`)\n\tif (snapshot.summary.mode) lines.push(`- mode: ${snapshot.summary.mode}`)\n\tif (snapshot.summary.piExitCode !== null) lines.push(`- latest pi exit code: ${snapshot.summary.piExitCode}`)\n\tif (snapshot.summary.stopReason) lines.push(`- stop reason: ${snapshot.summary.stopReason}`)\n\tif (snapshot.manifest.goal) lines.push(`- goal: ${snapshot.manifest.goal}`)\n\tif (snapshot.manifest.planPath) lines.push(`- plan path: ${snapshot.manifest.planPath}`)\n\tif (!snapshot.manifest.planPath && snapshot.manifest.recommendedPlanDir) {\n\t\tlines.push(`- recommended plans: ${snapshot.manifest.recommendedPlanDir}`)\n\t}\n\tif (snapshot.summary.message) lines.push(`- note: ${snapshot.summary.message}`)\n\n\tlines.push(\"\")\n\tlines.push(\"Agents:\")\n\tif (snapshot.agents.length === 0) {\n\t\tlines.push(\" (no agents)\")\n\t} else {\n\t\tfor (const agent of snapshot.agents) {\n\t\t\tconst id = agent.agentId.padEnd(14)\n\t\t\tconst role = agent.role.padEnd(10)\n\t\t\tconst status = agent.status.padEnd(10)\n\t\t\tconst task = truncate(agent.task, 56)\n\t\t\tconst msg = agent.lastMessage ? ` | ${truncate(agent.lastMessage, 36)}` : \"\"\n\t\t\tconst waiting = agent.waitingOn ? ` | waiting on: ${agent.waitingOn}` : \"\"\n\t\t\tlines.push(` ${id}${role}${status}${task}${msg}${waiting}`)\n\t\t}\n\t}\n\n\tlines.push(\"\")\n\tlines.push(\"Tasks:\")\n\tif (snapshot.tasks.length === 0) {\n\t\tlines.push(\" (no tasks)\")\n\t} else {\n\t\tfor (const task of snapshot.tasks) {\n\t\t\tconst id = task.taskId.padEnd(14)\n\t\t\tconst status = task.status.padEnd(12)\n\t\t\tconst assignee = task.assignedAgentId.padEnd(14)\n\t\t\tlines.push(` ${id}${status}${assignee}${truncate(task.title, 56)}`)\n\t\t}\n\t}\n\n\treturn lines\n}\n\nfunction printSnapshot(snapshot: WatchSnapshot) {\n\tconsole.log(renderWatchSnapshot(snapshot).join(\"\\n\"))\n}\n\nexport function watchTown(argv = process.argv.slice(2)) {\n\tconst initialSnapshot = buildWatchSnapshot(argv)\n\tlet previousFingerprint = createWatchFingerprint(initialSnapshot)\n\n\tprintSnapshot(initialSnapshot)\n\tconsole.log(\"- press Ctrl+C to stop\")\n\n\tconst interval = setInterval(() => {\n\t\tconst nextSnapshot = buildWatchSnapshot(argv)\n\t\tconst nextFingerprint = createWatchFingerprint(nextSnapshot)\n\t\tif (nextFingerprint === previousFingerprint) return\n\n\t\tpreviousFingerprint = nextFingerprint\n\t\tconsole.log(\"\")\n\t\tconsole.log(`[pitown] watch update - ${new Date().toISOString()}`)\n\t\tprintSnapshot(nextSnapshot)\n\t}, 1000)\n\n\tconst stopWatching = () => {\n\t\tclearInterval(interval)\n\t}\n\n\tprocess.once(\"SIGINT\", stopWatching)\n\tprocess.once(\"SIGTERM\", stopWatching)\n}\n\nif (isDirectExecution(import.meta.url)) {\n\twatchTown()\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,27 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@schilderlabs/pitown",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Globally installable CLI for Pi Town",
|
|
3
|
+
"version": "0.2.6",
|
|
5
4
|
"type": "module",
|
|
6
|
-
"
|
|
7
|
-
"pitown": "./dist/index.mjs"
|
|
8
|
-
},
|
|
5
|
+
"types": "./dist/index.d.mts",
|
|
9
6
|
"exports": {
|
|
10
7
|
".": {
|
|
11
8
|
"import": "./dist/index.mjs",
|
|
12
9
|
"types": "./dist/index.d.mts"
|
|
13
10
|
}
|
|
14
11
|
},
|
|
15
|
-
"types": "./dist/index.d.mts",
|
|
16
|
-
"files": [
|
|
17
|
-
"dist",
|
|
18
|
-
"README.md"
|
|
19
|
-
],
|
|
20
12
|
"engines": {
|
|
21
13
|
"node": ">=24"
|
|
22
14
|
},
|
|
23
15
|
"dependencies": {
|
|
24
|
-
"@schilderlabs/pitown-
|
|
16
|
+
"@schilderlabs/pitown-package": "0.2.6",
|
|
17
|
+
"@schilderlabs/pitown-core": "0.2.6"
|
|
25
18
|
},
|
|
26
19
|
"devDependencies": {
|
|
27
20
|
"eslint": "9.39.3",
|
|
@@ -30,29 +23,34 @@
|
|
|
30
23
|
"typescript": "5.9.2",
|
|
31
24
|
"vitest": "4.0.18"
|
|
32
25
|
},
|
|
26
|
+
"bin": {
|
|
27
|
+
"pitown": "./dist/index.mjs"
|
|
28
|
+
},
|
|
29
|
+
"bugs": "https://github.com/RobSchilderr/pitown/issues",
|
|
30
|
+
"description": "Globally installable CLI for Pi Town",
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"README.md"
|
|
34
|
+
],
|
|
35
|
+
"homepage": "https://github.com/RobSchilderr/pitown#readme",
|
|
33
36
|
"keywords": [
|
|
34
|
-
"
|
|
35
|
-
"pi-town",
|
|
37
|
+
"automation",
|
|
36
38
|
"cli",
|
|
37
39
|
"orchestration",
|
|
38
|
-
"
|
|
40
|
+
"pi-package",
|
|
41
|
+
"pi-town",
|
|
42
|
+
"pitown"
|
|
39
43
|
],
|
|
40
|
-
"
|
|
41
|
-
"type": "git",
|
|
42
|
-
"url": "git+https://github.com/schilderlabs/pitown.git"
|
|
43
|
-
},
|
|
44
|
-
"homepage": "https://github.com/schilderlabs/pitown#readme",
|
|
45
|
-
"bugs": {
|
|
46
|
-
"url": "https://github.com/schilderlabs/pitown/issues"
|
|
47
|
-
},
|
|
44
|
+
"license": "MIT",
|
|
48
45
|
"publishConfig": {
|
|
49
46
|
"access": "public"
|
|
50
47
|
},
|
|
51
|
-
"
|
|
48
|
+
"repository": "RobSchilderr/pitown.git",
|
|
52
49
|
"scripts": {
|
|
53
50
|
"build": "tsdown",
|
|
54
51
|
"lint": "eslint .",
|
|
55
52
|
"test": "vitest run --passWithNoTests",
|
|
53
|
+
"town:doctor": "tsx src/doctor.ts",
|
|
56
54
|
"town:run": "tsx src/run.ts",
|
|
57
55
|
"town:status": "tsx src/status.ts",
|
|
58
56
|
"town:watch": "tsx src/watch.ts",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config-Bw-mNdF5.mjs","names":[],"sources":["../src/entrypoint.ts","../../core/src/shell.ts","../../core/src/repo.ts","../src/paths.ts","../src/config.ts"],"sourcesContent":["import { realpathSync } from \"node:fs\"\nimport { resolve } from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\n\nfunction normalizePath(path: string | undefined): string | null {\n\tif (!path) return null\n\ttry {\n\t\treturn realpathSync(path)\n\t} catch {\n\t\treturn resolve(path)\n\t}\n}\n\nexport function isDirectExecution(fileUrl: string, argv1 = process.argv[1]): boolean {\n\tconst modulePath = normalizePath(fileURLToPath(fileUrl))\n\tconst invokedPath = normalizePath(argv1)\n\treturn modulePath !== null && invokedPath !== null && modulePath === invokedPath\n}\n","import { spawnSync } from \"node:child_process\"\n\nexport interface CommandResult {\n\tstdout: string\n\tstderr: string\n\texitCode: number\n}\n\nexport function runCommandSync(\n\tcommand: string,\n\targs: string[],\n\toptions?: { cwd?: string; env?: NodeJS.ProcessEnv },\n): CommandResult {\n\tconst result = spawnSync(command, args, {\n\t\tcwd: options?.cwd,\n\t\tenv: options?.env,\n\t\tencoding: \"utf-8\",\n\t})\n\tconst errorText = result.error instanceof Error ? `${result.error.message}\n` : \"\"\n\n\treturn {\n\t\tstdout: result.stdout ?? \"\",\n\t\tstderr: `${errorText}${result.stderr ?? \"\"}`,\n\t\texitCode: result.status ?? 1,\n\t}\n}\n\nexport function assertCommandAvailable(command: string) {\n\tconst result = spawnSync(command, [\"--help\"], {\n\t\tencoding: \"utf-8\",\n\t\tstdio: \"ignore\",\n\t})\n\n\tif (result.error instanceof Error) {\n\t\tthrow new Error(result.error.message)\n\t}\n}\n\nexport function assertSuccess(result: CommandResult, context: string) {\n\tif (result.exitCode === 0) return\n\tconst details = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join(\"\\n\")\n\tthrow new Error(`${context} failed${details ? `\\n${details}` : \"\"}`)\n}\n","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 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\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 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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"}
|