pitroom 0.7.0 → 0.8.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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +17 -0
- package/README.md +1 -1
- package/THIRD_PARTY_NOTICES.md +2 -1
- package/dist/pitroom.mjs +122 -11
- package/dist/ui/app.css +1 -1
- package/dist/ui/app.js +172 -16
- package/package.json +2 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pitroom",
|
|
3
3
|
"description": "A free pit crew for your expensive coding agent: the superpowers development workflow, run by cheap workers. Delegate research, fixes, tests, reviews and whole plans from Claude Code, Codex or any agent to OpenCode, Codex or Claude Code workers, with receipts that prove the savings.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.8.1",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TahaAtas"
|
|
7
7
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pitroom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "A free pit crew for your expensive coding agent: delegate research, fixes, tests, reviews and parallel investigations to cheap worker agents running your own models, with verified answers, exact diffs and cost receipts.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TahaAtas"
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.1
|
|
4
|
+
|
|
5
|
+
### Dashboard
|
|
6
|
+
- The Stats table shows each worker's logo next to its badge.
|
|
7
|
+
- Stats no longer lists an `unknown` worker with no runs: an old savings record (from before pluggable workers: no backend, a model without its provider) now counts for its own worker row, so the per-worker figures still add up to the total.
|
|
8
|
+
- The step list's scroller stays mounted so the list animates closed with its content, and a tap on a touch screen no longer leaves auto-follow off while you are at the bottom. A file path containing `", "` still matches its edit step.
|
|
9
|
+
|
|
10
|
+
## 0.8.0
|
|
11
|
+
|
|
12
|
+
### Dashboard
|
|
13
|
+
- An edited file in a run card opens as its diff: line numbers, added and removed rows, syntax highlighting (Shiki: TypeScript, JavaScript, TSX, JSX, JSON, CSS, HTML, Python, Bash, YAML) and a copy button. Messages and tool results get their own rows with a status, and the step list has a scroller with a rail and a "Last step" button that follows new steps while you are at the bottom.
|
|
14
|
+
- The dashboard files grow from about 0.7 MB to 1.9 MB for the highlighter; the npm package from 422 kB to 592 kB.
|
|
15
|
+
- A renamed file whose path starts with `a/` or `b/` keeps its real name in the diff.
|
|
16
|
+
|
|
17
|
+
### Fixes
|
|
18
|
+
- `pitroom init` asks OpenCode for its models twice: the first call can come back empty while OpenCode's service starts, which made init see no models and suggest no fallback.
|
|
19
|
+
|
|
3
20
|
## 0.7.0
|
|
4
21
|
|
|
5
22
|
### Codex
|
package/README.md
CHANGED
|
@@ -114,7 +114,7 @@ In isolate mode nothing reaches your tree until you apply it.
|
|
|
114
114
|
- **Zero repo pollution.** No `.pitroom/` folder, no `.gitignore` edits, no branches. Records live in `~/.local/state/pitroom`.
|
|
115
115
|
- **Works with any agent.** Fourteen [Agent Skills](https://agentskills.io): the superpowers development workflow run by workers, plus delegation (`using-pitroom`, `pitroom-research`, `-crew`, `-implement`), a CLI, and a Claude Code / Codex plugin whose session-start hook loads the workflow. Claude Code, Codex, Gemini CLI, Cursor, or anything that can run a shell command.
|
|
116
116
|
- **A live dashboard and a searchable history.** `pitroom dash` opens a local page of every run (the task, what the worker did step by step, its result and diff) with history and statistics, for the agent apps that show neither hooks nor a status line; `pitroom history` searches everything Pitroom ever ran. See [Dashboard](#dashboard).
|
|
117
|
-
- **Small.** About 6,
|
|
117
|
+
- **Small.** About 6,500 lines of TypeScript in the CLI, a ~210 KB CLI bundle (plus the ~1.9 MB of dashboard files it serves, with syntax highlighting for ten languages), zero runtime dependencies.
|
|
118
118
|
|
|
119
119
|
### How it stays safe
|
|
120
120
|
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -24,7 +24,8 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|
|
24
24
|
| [Base UI](https://github.com/mui/base-ui) | MIT | Material-UI SAS (2019) |
|
|
25
25
|
| [shadcn/ui](https://github.com/shadcn-ui/ui) components (`ui/src/components/ui`) | MIT | shadcn |
|
|
26
26
|
| [Shadix UI](https://github.com/apix-js/shadix-ui) expandable card (`ui/src/components/expandable-card.tsx`) | MIT | gihanrangana (2025) |
|
|
27
|
-
| [beUI](https://github.com/starc007/ui-components) agent activity (`ui/src/components/agents`, `motion`, `lib`) | MIT | Saurabh Chauhan (2026) |
|
|
27
|
+
| [beUI](https://github.com/starc007/ui-components) agent activity, file diff, tool result, message scroller and preview rail (`ui/src/components/agents`, `motion`, `lib`) | MIT | Saurabh Chauhan (2026) |
|
|
28
|
+
| [Shiki](https://github.com/shikijs/shiki) syntax highlighting | MIT | Pine Wu (2021), Anthony Fu (2023) |
|
|
28
29
|
| [Motion](https://github.com/motiondivision/motion) (with framer-motion) | MIT | Motion B.V. (2024), Framer B.V. (2018) |
|
|
29
30
|
| [Tailwind CSS](https://github.com/tailwindlabs/tailwindcss) | MIT | Tailwind Labs, Inc. |
|
|
30
31
|
| [tw-animate-css](https://github.com/Wombosvideo/tw-animate-css) | MIT | Wombosvideo (2025) |
|
package/dist/pitroom.mjs
CHANGED
|
@@ -1463,7 +1463,7 @@ function planInit(opts = {}) {
|
|
|
1463
1463
|
const b = getBackend("opencode");
|
|
1464
1464
|
let models = [];
|
|
1465
1465
|
try {
|
|
1466
|
-
models = b.listModels?.() ?? [];
|
|
1466
|
+
models = b.catalog ? b.catalog().models.map((m) => m.id) : b.listModels?.() ?? [];
|
|
1467
1467
|
} catch {
|
|
1468
1468
|
notes.push("could not list OpenCode models (is it logged in?)");
|
|
1469
1469
|
}
|
|
@@ -1950,7 +1950,7 @@ function archivedRun(id) {
|
|
|
1950
1950
|
at: s.at ?? void 0,
|
|
1951
1951
|
t: s.at && Number.isFinite(start) ? Math.max(0, Math.round((s.at - start) / 1e3)) : void 0
|
|
1952
1952
|
}));
|
|
1953
|
-
return { meta: JSON.parse(r.meta_json), answer: r.answer ?? "", patch: r.patch ?? void 0, steps };
|
|
1953
|
+
return { meta: JSON.parse(r.meta_json), answer: r.answer ?? "", patch: r.patch ?? void 0, patchTruncated: r.patch?.length >= PATCH_MAX, steps };
|
|
1954
1954
|
} catch {
|
|
1955
1955
|
return void 0;
|
|
1956
1956
|
}
|
|
@@ -2029,10 +2029,11 @@ function workerRows(rows, ledger) {
|
|
|
2029
2029
|
const key = (backend, model) => `${backend ?? ""}\0${model ?? ""}`;
|
|
2030
2030
|
const index = new Map(out.map((r) => [key(r.backend, r.model), r]));
|
|
2031
2031
|
for (const e of ledger) {
|
|
2032
|
-
|
|
2032
|
+
const backend = e.backend ?? "opencode";
|
|
2033
|
+
let row = index.get(key(backend, e.model)) ?? (e.model ? out.find((r) => r.backend === backend && r.model?.endsWith(`/${e.model}`)) : void 0);
|
|
2033
2034
|
if (!row) {
|
|
2034
|
-
row = { backend
|
|
2035
|
-
index.set(key(
|
|
2035
|
+
row = { backend, model: e.model, runs: 0, ok: 0, avgSeconds: null, avgTokens: null, saved: 0 };
|
|
2036
|
+
index.set(key(backend, e.model), row);
|
|
2036
2037
|
out.push(row);
|
|
2037
2038
|
}
|
|
2038
2039
|
row.saved += e.saved;
|
|
@@ -2969,6 +2970,118 @@ import http from "node:http";
|
|
|
2969
2970
|
import path16 from "node:path";
|
|
2970
2971
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2971
2972
|
|
|
2973
|
+
// src/core/file-diff.ts
|
|
2974
|
+
var PREVIEW_LINES = 300;
|
|
2975
|
+
function gitPath(value, prefix = true) {
|
|
2976
|
+
let path24 = value;
|
|
2977
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
2978
|
+
const bytes = [];
|
|
2979
|
+
const escapes = { t: " ", n: "\n", r: "\r", b: "\b", f: "\f", v: "\v" };
|
|
2980
|
+
for (const match of value.slice(1, -1).matchAll(/\\([0-7]{1,3}|.)|([^\\]+)/g)) {
|
|
2981
|
+
if (match[1] && /^[0-7]+$/.test(match[1])) bytes.push(parseInt(match[1], 8));
|
|
2982
|
+
else bytes.push(...new TextEncoder().encode(match[2] ?? escapes[match[1]] ?? match[1]));
|
|
2983
|
+
}
|
|
2984
|
+
path24 = new TextDecoder().decode(new Uint8Array(bytes));
|
|
2985
|
+
}
|
|
2986
|
+
return prefix ? path24.replace(/^[ab]\//, "") : path24;
|
|
2987
|
+
}
|
|
2988
|
+
function headerPath(header) {
|
|
2989
|
+
const quoted = header.match(/^diff --git ("(?:[^"\\]|\\.)*"|\S+) ("(?:[^"\\]|\\.)*"|\S+)$/);
|
|
2990
|
+
if (quoted) return gitPath(quoted[2]);
|
|
2991
|
+
return gitPath(header.slice(header.lastIndexOf(" b/") + 1));
|
|
2992
|
+
}
|
|
2993
|
+
function fileDiffs(patch, changes, sourceTruncated = false) {
|
|
2994
|
+
const files = [];
|
|
2995
|
+
let file;
|
|
2996
|
+
let oldLine = 0;
|
|
2997
|
+
let newLine = 0;
|
|
2998
|
+
let oldLeft = 0;
|
|
2999
|
+
let newLeft = 0;
|
|
3000
|
+
let inHunk = false;
|
|
3001
|
+
let row = 0;
|
|
3002
|
+
const finishHunk = () => {
|
|
3003
|
+
if (file && (oldLeft > 0 || newLeft > 0)) file.incomplete = true;
|
|
3004
|
+
oldLeft = newLeft = 0;
|
|
3005
|
+
inHunk = false;
|
|
3006
|
+
};
|
|
3007
|
+
const add = (line) => {
|
|
3008
|
+
if (!file) return;
|
|
3009
|
+
if (file.lines.length < PREVIEW_LINES) file.lines.push({ ...line, id: String(row) });
|
|
3010
|
+
else file.omittedLines++;
|
|
3011
|
+
row++;
|
|
3012
|
+
};
|
|
3013
|
+
for (const line of patch.split("\n")) {
|
|
3014
|
+
if (line.startsWith("diff --git ")) {
|
|
3015
|
+
finishHunk();
|
|
3016
|
+
file = { path: headerPath(line), status: "M", lines: [], additions: 0, deletions: 0, omittedLines: 0, binary: false, incomplete: false };
|
|
3017
|
+
files.push(file);
|
|
3018
|
+
row = 0;
|
|
3019
|
+
continue;
|
|
3020
|
+
}
|
|
3021
|
+
if (!file) continue;
|
|
3022
|
+
const hunk = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/.exec(line);
|
|
3023
|
+
if (hunk) {
|
|
3024
|
+
finishHunk();
|
|
3025
|
+
oldLine = Number(hunk[1]);
|
|
3026
|
+
newLine = Number(hunk[3]);
|
|
3027
|
+
oldLeft = Number(hunk[2] ?? 1);
|
|
3028
|
+
newLeft = Number(hunk[4] ?? 1);
|
|
3029
|
+
inHunk = true;
|
|
3030
|
+
add({ type: "hunk", content: line });
|
|
3031
|
+
} else if (inHunk && line.startsWith("\")) {
|
|
3032
|
+
add({ type: "meta", content: line });
|
|
3033
|
+
} else if (inHunk && (oldLeft > 0 || newLeft > 0)) {
|
|
3034
|
+
if (line.startsWith("+") && newLeft > 0) {
|
|
3035
|
+
file.additions++;
|
|
3036
|
+
newLeft--;
|
|
3037
|
+
add({ type: "added", newLine: newLine++, content: line.slice(1) });
|
|
3038
|
+
} else if (line.startsWith("-") && oldLeft > 0) {
|
|
3039
|
+
file.deletions++;
|
|
3040
|
+
oldLeft--;
|
|
3041
|
+
add({ type: "removed", oldLine: oldLine++, content: line.slice(1) });
|
|
3042
|
+
} else if (line.startsWith(" ") && oldLeft > 0 && newLeft > 0) {
|
|
3043
|
+
oldLeft--;
|
|
3044
|
+
newLeft--;
|
|
3045
|
+
add({ type: "context", oldLine: oldLine++, newLine: newLine++, content: line.slice(1) });
|
|
3046
|
+
} else {
|
|
3047
|
+
file.incomplete = true;
|
|
3048
|
+
}
|
|
3049
|
+
} else if (!inHunk) {
|
|
3050
|
+
if (line.startsWith("+++ ") && line !== "+++ /dev/null") file.path = gitPath(line.slice(4).replace(/\t$/, ""));
|
|
3051
|
+
else if (line.startsWith("--- ") && line !== "--- /dev/null") file.path = gitPath(line.slice(4).replace(/\t$/, ""));
|
|
3052
|
+
else if (/^(GIT binary patch|Binary files .* differ)$/.test(line)) file.binary = true;
|
|
3053
|
+
else if (/^(new file mode|deleted file mode|old mode|new mode|rename from|rename to|similarity index) /.test(line)) {
|
|
3054
|
+
if (line.startsWith("new file mode ")) file.status = "A";
|
|
3055
|
+
if (line.startsWith("deleted file mode ")) file.status = "D";
|
|
3056
|
+
if (line.startsWith("rename to ")) {
|
|
3057
|
+
file.path = gitPath(line.slice(10), false);
|
|
3058
|
+
file.status = "R";
|
|
3059
|
+
}
|
|
3060
|
+
add({ type: "meta", content: line });
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
finishHunk();
|
|
3065
|
+
if (sourceTruncated && file) file.incomplete = true;
|
|
3066
|
+
for (const f of files) {
|
|
3067
|
+
if (f.incomplete || f.binary) f.additions = f.deletions = void 0;
|
|
3068
|
+
}
|
|
3069
|
+
const byPath = new Map(files.map((f) => [f.path, f]));
|
|
3070
|
+
const result = changes.map((change) => {
|
|
3071
|
+
const diff = byPath.get(change.path);
|
|
3072
|
+
byPath.delete(change.path);
|
|
3073
|
+
return diff ? { ...diff, status: change.status } : {
|
|
3074
|
+
...change,
|
|
3075
|
+
lines: [],
|
|
3076
|
+
omittedLines: 0,
|
|
3077
|
+
binary: false,
|
|
3078
|
+
incomplete: sourceTruncated,
|
|
3079
|
+
unavailable: true
|
|
3080
|
+
};
|
|
3081
|
+
});
|
|
3082
|
+
return [...result, ...byPath.values()];
|
|
3083
|
+
}
|
|
3084
|
+
|
|
2972
3085
|
// src/core/dash-page.ts
|
|
2973
3086
|
var THEME_SCRIPT = "(function(){try{var t=localStorage.getItem('pitroom-theme');var d=t?t==='dark':matchMedia('(prefers-color-scheme: dark)').matches;document.documentElement.classList.toggle('dark',d)}catch(e){}})()";
|
|
2974
3087
|
var ICON3 = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='8' fill='%23ff6a2b'/%3E%3Cpath d='M9 8h4v4H9zm8 0h4v4h-4zm-4 4h4v4h-4zm8 0h4v4h-4zM9 16h4v4H9zm8 0h4v4h-4zm-4 4h4v4h-4zm8 0h4v4h-4z' fill='%23fff'/%3E%3C/svg%3E";
|
|
@@ -3041,10 +3154,6 @@ function dashState(opts = {}) {
|
|
|
3041
3154
|
}
|
|
3042
3155
|
var TASK_MAX = 6e3;
|
|
3043
3156
|
var PATCH_LINES = 300;
|
|
3044
|
-
function readFile(id, name) {
|
|
3045
|
-
const f = runFile(id, name);
|
|
3046
|
-
return fs19.existsSync(f) ? fs19.readFileSync(f, "utf8") : "";
|
|
3047
|
-
}
|
|
3048
3157
|
function runDetail(id) {
|
|
3049
3158
|
if (!RUN_ID2.test(id)) return void 0;
|
|
3050
3159
|
let m;
|
|
@@ -3069,7 +3178,8 @@ function runDetail(id) {
|
|
|
3069
3178
|
const answer = isActive(m.state) ? "" : readSummary(m).replace(/^SUMMARY:\s*SPEC[^\n]*\n+/i, "").replace(/^DETAILS:[ \t]*\n+/i, "").trim();
|
|
3070
3179
|
const lastSay = steps.at(-1);
|
|
3071
3180
|
if (lastSay?.kind === "say" && answer && (answer.includes(lastSay.text.slice(0, 80)) || lastSay.text.includes(answer.slice(0, 80)))) steps.pop();
|
|
3072
|
-
const
|
|
3181
|
+
const fullPatch = readRunFile(id, "changes.patch");
|
|
3182
|
+
const patch = fullPatch ?? kept?.patch ?? "";
|
|
3073
3183
|
const patchLines = patch.split("\n");
|
|
3074
3184
|
const u = m.usage;
|
|
3075
3185
|
return {
|
|
@@ -3080,6 +3190,7 @@ function runDetail(id) {
|
|
|
3080
3190
|
steps,
|
|
3081
3191
|
answer,
|
|
3082
3192
|
changes: m.changes ?? [],
|
|
3193
|
+
fileDiffs: fileDiffs(patch, m.changes ?? [], fullPatch === void 0 && kept?.patchTruncated),
|
|
3083
3194
|
patch: patch ? `${patchLines.slice(0, PATCH_LINES).join("\n")}${patchLines.length > PATCH_LINES ? `
|
|
3084
3195
|
\u2026 ${patchLines.length - PATCH_LINES} more lines (pitroom show ${id} --patch)` : ""}` : void 0,
|
|
3085
3196
|
info: {
|
|
@@ -3852,7 +3963,7 @@ function secretWarning(files, mode) {
|
|
|
3852
3963
|
}
|
|
3853
3964
|
|
|
3854
3965
|
// src/core/run.ts
|
|
3855
|
-
var VERSION2 = true ? "0.
|
|
3966
|
+
var VERSION2 = true ? "0.8.1" : "0.0.0-dev";
|
|
3856
3967
|
function planWork(o) {
|
|
3857
3968
|
if (!o.plan) return { task: o.task.trim(), tier: o.tier };
|
|
3858
3969
|
if (o.continueFrom) throw new UserError("a follow-up continues its parent's task; drop --plan/--step");
|