pitroom 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/CHANGELOG.md +10 -0
- package/README.md +1 -1
- package/THIRD_PARTY_NOTICES.md +2 -1
- package/dist/pitroom.mjs +118 -8
- 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.0",
|
|
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.0",
|
|
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,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Dashboard
|
|
6
|
+
- 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.
|
|
7
|
+
- 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.
|
|
8
|
+
- A renamed file whose path starts with `a/` or `b/` keeps its real name in the diff.
|
|
9
|
+
|
|
10
|
+
### Fixes
|
|
11
|
+
- `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.
|
|
12
|
+
|
|
3
13
|
## 0.7.0
|
|
4
14
|
|
|
5
15
|
### 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
|
}
|
|
@@ -2969,6 +2969,118 @@ import http from "node:http";
|
|
|
2969
2969
|
import path16 from "node:path";
|
|
2970
2970
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2971
2971
|
|
|
2972
|
+
// src/core/file-diff.ts
|
|
2973
|
+
var PREVIEW_LINES = 300;
|
|
2974
|
+
function gitPath(value, prefix = true) {
|
|
2975
|
+
let path24 = value;
|
|
2976
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
2977
|
+
const bytes = [];
|
|
2978
|
+
const escapes = { t: " ", n: "\n", r: "\r", b: "\b", f: "\f", v: "\v" };
|
|
2979
|
+
for (const match of value.slice(1, -1).matchAll(/\\([0-7]{1,3}|.)|([^\\]+)/g)) {
|
|
2980
|
+
if (match[1] && /^[0-7]+$/.test(match[1])) bytes.push(parseInt(match[1], 8));
|
|
2981
|
+
else bytes.push(...new TextEncoder().encode(match[2] ?? escapes[match[1]] ?? match[1]));
|
|
2982
|
+
}
|
|
2983
|
+
path24 = new TextDecoder().decode(new Uint8Array(bytes));
|
|
2984
|
+
}
|
|
2985
|
+
return prefix ? path24.replace(/^[ab]\//, "") : path24;
|
|
2986
|
+
}
|
|
2987
|
+
function headerPath(header) {
|
|
2988
|
+
const quoted = header.match(/^diff --git ("(?:[^"\\]|\\.)*"|\S+) ("(?:[^"\\]|\\.)*"|\S+)$/);
|
|
2989
|
+
if (quoted) return gitPath(quoted[2]);
|
|
2990
|
+
return gitPath(header.slice(header.lastIndexOf(" b/") + 1));
|
|
2991
|
+
}
|
|
2992
|
+
function fileDiffs(patch, changes, sourceTruncated = false) {
|
|
2993
|
+
const files = [];
|
|
2994
|
+
let file;
|
|
2995
|
+
let oldLine = 0;
|
|
2996
|
+
let newLine = 0;
|
|
2997
|
+
let oldLeft = 0;
|
|
2998
|
+
let newLeft = 0;
|
|
2999
|
+
let inHunk = false;
|
|
3000
|
+
let row = 0;
|
|
3001
|
+
const finishHunk = () => {
|
|
3002
|
+
if (file && (oldLeft > 0 || newLeft > 0)) file.incomplete = true;
|
|
3003
|
+
oldLeft = newLeft = 0;
|
|
3004
|
+
inHunk = false;
|
|
3005
|
+
};
|
|
3006
|
+
const add = (line) => {
|
|
3007
|
+
if (!file) return;
|
|
3008
|
+
if (file.lines.length < PREVIEW_LINES) file.lines.push({ ...line, id: String(row) });
|
|
3009
|
+
else file.omittedLines++;
|
|
3010
|
+
row++;
|
|
3011
|
+
};
|
|
3012
|
+
for (const line of patch.split("\n")) {
|
|
3013
|
+
if (line.startsWith("diff --git ")) {
|
|
3014
|
+
finishHunk();
|
|
3015
|
+
file = { path: headerPath(line), status: "M", lines: [], additions: 0, deletions: 0, omittedLines: 0, binary: false, incomplete: false };
|
|
3016
|
+
files.push(file);
|
|
3017
|
+
row = 0;
|
|
3018
|
+
continue;
|
|
3019
|
+
}
|
|
3020
|
+
if (!file) continue;
|
|
3021
|
+
const hunk = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/.exec(line);
|
|
3022
|
+
if (hunk) {
|
|
3023
|
+
finishHunk();
|
|
3024
|
+
oldLine = Number(hunk[1]);
|
|
3025
|
+
newLine = Number(hunk[3]);
|
|
3026
|
+
oldLeft = Number(hunk[2] ?? 1);
|
|
3027
|
+
newLeft = Number(hunk[4] ?? 1);
|
|
3028
|
+
inHunk = true;
|
|
3029
|
+
add({ type: "hunk", content: line });
|
|
3030
|
+
} else if (inHunk && line.startsWith("\")) {
|
|
3031
|
+
add({ type: "meta", content: line });
|
|
3032
|
+
} else if (inHunk && (oldLeft > 0 || newLeft > 0)) {
|
|
3033
|
+
if (line.startsWith("+") && newLeft > 0) {
|
|
3034
|
+
file.additions++;
|
|
3035
|
+
newLeft--;
|
|
3036
|
+
add({ type: "added", newLine: newLine++, content: line.slice(1) });
|
|
3037
|
+
} else if (line.startsWith("-") && oldLeft > 0) {
|
|
3038
|
+
file.deletions++;
|
|
3039
|
+
oldLeft--;
|
|
3040
|
+
add({ type: "removed", oldLine: oldLine++, content: line.slice(1) });
|
|
3041
|
+
} else if (line.startsWith(" ") && oldLeft > 0 && newLeft > 0) {
|
|
3042
|
+
oldLeft--;
|
|
3043
|
+
newLeft--;
|
|
3044
|
+
add({ type: "context", oldLine: oldLine++, newLine: newLine++, content: line.slice(1) });
|
|
3045
|
+
} else {
|
|
3046
|
+
file.incomplete = true;
|
|
3047
|
+
}
|
|
3048
|
+
} else if (!inHunk) {
|
|
3049
|
+
if (line.startsWith("+++ ") && line !== "+++ /dev/null") file.path = gitPath(line.slice(4).replace(/\t$/, ""));
|
|
3050
|
+
else if (line.startsWith("--- ") && line !== "--- /dev/null") file.path = gitPath(line.slice(4).replace(/\t$/, ""));
|
|
3051
|
+
else if (/^(GIT binary patch|Binary files .* differ)$/.test(line)) file.binary = true;
|
|
3052
|
+
else if (/^(new file mode|deleted file mode|old mode|new mode|rename from|rename to|similarity index) /.test(line)) {
|
|
3053
|
+
if (line.startsWith("new file mode ")) file.status = "A";
|
|
3054
|
+
if (line.startsWith("deleted file mode ")) file.status = "D";
|
|
3055
|
+
if (line.startsWith("rename to ")) {
|
|
3056
|
+
file.path = gitPath(line.slice(10), false);
|
|
3057
|
+
file.status = "R";
|
|
3058
|
+
}
|
|
3059
|
+
add({ type: "meta", content: line });
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3063
|
+
finishHunk();
|
|
3064
|
+
if (sourceTruncated && file) file.incomplete = true;
|
|
3065
|
+
for (const f of files) {
|
|
3066
|
+
if (f.incomplete || f.binary) f.additions = f.deletions = void 0;
|
|
3067
|
+
}
|
|
3068
|
+
const byPath = new Map(files.map((f) => [f.path, f]));
|
|
3069
|
+
const result = changes.map((change) => {
|
|
3070
|
+
const diff = byPath.get(change.path);
|
|
3071
|
+
byPath.delete(change.path);
|
|
3072
|
+
return diff ? { ...diff, status: change.status } : {
|
|
3073
|
+
...change,
|
|
3074
|
+
lines: [],
|
|
3075
|
+
omittedLines: 0,
|
|
3076
|
+
binary: false,
|
|
3077
|
+
incomplete: sourceTruncated,
|
|
3078
|
+
unavailable: true
|
|
3079
|
+
};
|
|
3080
|
+
});
|
|
3081
|
+
return [...result, ...byPath.values()];
|
|
3082
|
+
}
|
|
3083
|
+
|
|
2972
3084
|
// src/core/dash-page.ts
|
|
2973
3085
|
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
3086
|
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 +3153,6 @@ function dashState(opts = {}) {
|
|
|
3041
3153
|
}
|
|
3042
3154
|
var TASK_MAX = 6e3;
|
|
3043
3155
|
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
3156
|
function runDetail(id) {
|
|
3049
3157
|
if (!RUN_ID2.test(id)) return void 0;
|
|
3050
3158
|
let m;
|
|
@@ -3069,7 +3177,8 @@ function runDetail(id) {
|
|
|
3069
3177
|
const answer = isActive(m.state) ? "" : readSummary(m).replace(/^SUMMARY:\s*SPEC[^\n]*\n+/i, "").replace(/^DETAILS:[ \t]*\n+/i, "").trim();
|
|
3070
3178
|
const lastSay = steps.at(-1);
|
|
3071
3179
|
if (lastSay?.kind === "say" && answer && (answer.includes(lastSay.text.slice(0, 80)) || lastSay.text.includes(answer.slice(0, 80)))) steps.pop();
|
|
3072
|
-
const
|
|
3180
|
+
const fullPatch = readRunFile(id, "changes.patch");
|
|
3181
|
+
const patch = fullPatch ?? kept?.patch ?? "";
|
|
3073
3182
|
const patchLines = patch.split("\n");
|
|
3074
3183
|
const u = m.usage;
|
|
3075
3184
|
return {
|
|
@@ -3080,6 +3189,7 @@ function runDetail(id) {
|
|
|
3080
3189
|
steps,
|
|
3081
3190
|
answer,
|
|
3082
3191
|
changes: m.changes ?? [],
|
|
3192
|
+
fileDiffs: fileDiffs(patch, m.changes ?? [], fullPatch === void 0 && kept?.patchTruncated),
|
|
3083
3193
|
patch: patch ? `${patchLines.slice(0, PATCH_LINES).join("\n")}${patchLines.length > PATCH_LINES ? `
|
|
3084
3194
|
\u2026 ${patchLines.length - PATCH_LINES} more lines (pitroom show ${id} --patch)` : ""}` : void 0,
|
|
3085
3195
|
info: {
|
|
@@ -3852,7 +3962,7 @@ function secretWarning(files, mode) {
|
|
|
3852
3962
|
}
|
|
3853
3963
|
|
|
3854
3964
|
// src/core/run.ts
|
|
3855
|
-
var VERSION2 = true ? "0.
|
|
3965
|
+
var VERSION2 = true ? "0.8.0" : "0.0.0-dev";
|
|
3856
3966
|
function planWork(o) {
|
|
3857
3967
|
if (!o.plan) return { task: o.task.trim(), tier: o.tier };
|
|
3858
3968
|
if (o.continueFrom) throw new UserError("a follow-up continues its parent's task; drop --plan/--step");
|