pitroom 0.6.8 → 0.6.10
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 +22 -0
- package/README.md +13 -4
- package/THIRD_PARTY_NOTICES.md +5 -1
- package/dist/lib.mjs +59 -12
- package/dist/pitroom.mjs +167 -65
- package/dist/ui/app.css +1 -1
- package/dist/ui/app.js +10 -10
- package/package.json +1 -1
- package/skills/pitroom-crew/SKILL.md +1 -1
- package/skills/using-pitroom/SKILL.md +1 -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.6.
|
|
4
|
+
"version": "0.6.10",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TahaAtas"
|
|
7
7
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pitroom",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.10",
|
|
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,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.10
|
|
4
|
+
|
|
5
|
+
### Verified answers
|
|
6
|
+
- A reference written as a bare file name or a partial path (`ipc.ts:218`, `src/main/ipc.ts:218`) is now looked up in the project, so it no longer counts as "file not found" just because the worker left out the directory. If several files match, one that holds up is enough.
|
|
7
|
+
- A call written like a reference (`Schema.parse:432`, `orchestrator.start:638`) is not a file reference and is no longer counted. On a real run this turned "7/18 verified" into "13/13"; the references that really did not match are still reported.
|
|
8
|
+
|
|
9
|
+
### Safety
|
|
10
|
+
- A run warns when the directory its worker runs in holds `.env` files, private keys (`*.pem`, `id_rsa`…) or `credentials.json`: the worker is told not to read them, but nothing stops it. The warning is on the report and when a run starts with `--bg`. For `-i` it names only the files the isolated copy would contain (a git-ignored `.env` is not copied). `PITROOM_NO_SECRET_WARNING=1` silences it.
|
|
11
|
+
|
|
12
|
+
### Smaller
|
|
13
|
+
- OpenCode with no pinned model no longer warns about `--effort` on every run: there is nothing to attach the level to, so it is left out quietly.
|
|
14
|
+
- A detached dashboard stops after an hour without a request (it was four hours).
|
|
15
|
+
- Every run card shows its worker's logo, and its animated mascot while it runs; the skills tell the agent to open the dashboard in the app's own browser pane (not with `--open`, which launches the system browser).
|
|
16
|
+
|
|
17
|
+
## 0.6.9
|
|
18
|
+
|
|
19
|
+
### Dashboard
|
|
20
|
+
- Every run card shows which worker ran it: its logo under the state icon, and while the worker is running its animated pixel mascot (Claude Code, Codex and OpenCode; the characters are from [CodeIsland](https://github.com/wxtsky/CodeIsland), MIT, credited in `THIRD_PARTY_NOTICES.md`).
|
|
21
|
+
|
|
22
|
+
### README
|
|
23
|
+
- A short screen recording of the dashboard at the top, and screenshots with the new cards. `scripts/demo-dash.mjs` records it too when `ffmpeg` is installed.
|
|
24
|
+
|
|
3
25
|
## 0.6.8
|
|
4
26
|
|
|
5
27
|
### Dashboard
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Model-agnostic · Verified answers · Receipts, not vibes · OpenCode / Codex /
|
|
|
11
11
|
<br />
|
|
12
12
|
|
|
13
13
|
[](https://github.com/ATASTECH/pitroom/releases/latest)
|
|
14
|
-
[](https://www.npmjs.com/package/pitroom)
|
|
15
15
|
[](https://www.npmjs.com/package/pitroom)
|
|
16
16
|
[](https://github.com/ATASTECH/pitroom/stargazers)
|
|
17
17
|
[](https://github.com/ATASTECH/pitroom/actions/workflows/ci.yml)
|
|
@@ -37,6 +37,12 @@ Model-agnostic · Verified answers · Receipts, not vibes · OpenCode / Codex /
|
|
|
37
37
|
|
|
38
38
|
**Your agent decides · Cheap workers do the typing · A second model reviews**
|
|
39
39
|
|
|
40
|
+
<br />
|
|
41
|
+
|
|
42
|
+
<img src="docs/dash-demo.gif" alt="The Pitroom dashboard: running workers with their animated mascots, a finished run opening into its task, steps, result and diff, then the History and Stats tabs (sample data)" width="82%" />
|
|
43
|
+
|
|
44
|
+
<sub>`pitroom dash`, with sample data</sub>
|
|
45
|
+
|
|
40
46
|
</div>
|
|
41
47
|
|
|
42
48
|
---
|
|
@@ -76,7 +82,7 @@ One group can mix them, and a fallback chain can cross them ([how adapters work]
|
|
|
76
82
|
|
|
77
83
|
### Verified answers
|
|
78
84
|
|
|
79
|
-
Every `path:line` a worker cites is checked on disk.
|
|
85
|
+
Every `path:line` a worker cites is checked on disk. A cite without its directory (`ipc.ts:218`, `src/main/ipc.ts:218`) is found in the project, and a call such as `Schema.parse:432` is not mistaken for a file.
|
|
80
86
|
|
|
81
87
|
Your agent trusts what checks out and skips re-reading it.
|
|
82
88
|
|
|
@@ -148,7 +154,7 @@ Every run prints a receipt, and `pitroom savings` adds them up (`--card` writes
|
|
|
148
154
|
|
|
149
155
|
The screenshots below show sample data (an imaginary `shop-api` project), not a real one.
|
|
150
156
|
|
|
151
|
-
**Live.** What is running now, and the latest runs. Running cards show a timer
|
|
157
|
+
**Live.** What is running now, and the latest runs. Running cards show a timer, the worker's last words and its animated pixel mascot (a finished card shows the worker's logo); finished ones show the result and, for reviews, the findings.
|
|
152
158
|
|
|
153
159
|
<p align="center"><img src="docs/dash-live.png" width="100%" alt="The Live tab: four running workers with timers and progress bars, then finished runs with approved and needs-fixes badges, step counts, tokens and savings"></p>
|
|
154
160
|
|
|
@@ -470,7 +476,7 @@ Two settings make every delegation visible, whether or not the agent mentions it
|
|
|
470
476
|
|
|
471
477
|
The Claude Code and Codex **apps** show neither hook messages nor a status line. For them there are two things that need no setup:
|
|
472
478
|
|
|
473
|
-
- `pitroom dash --detach` prints the address of a live dashboard with three tabs: **Live** (every running and recent run as an animated card; click one for the task, what the worker did step by step, its result, the diff and the details), **History** (search and filter everything Pitroom ever ran; every run opens as the same card) and **Stats** (success rate, time, tokens and savings per worker and model). It is read-only, listens on `127.0.0.1` only and stops itself after
|
|
479
|
+
- `pitroom dash --detach` prints the address of a live dashboard with three tabs: **Live** (every running and recent run as an animated card; click one for the task, what the worker did step by step, its result, the diff and the details), **History** (search and filter everything Pitroom ever ran; every run opens as the same card) and **Stats** (success rate, time, tokens and savings per worker and model). It is read-only, listens on `127.0.0.1` only and stops itself after an hour without a request (`pitroom dash --stop` ends it sooner). Open it in a browser or in the app's own browser pane. The skills tell the agent to start it and give you the address when it runs workers in the background.
|
|
474
480
|
- `pitroom watch -g NAME --brief` prints one card line when a worker starts and one when it ends. In Claude Code, the agent runs it through the Monitor tool and the lines appear in the app; in Codex the command's output block fills as it goes.
|
|
475
481
|
|
|
476
482
|
---
|
|
@@ -540,6 +546,7 @@ Fallbacks cross backends (e.g. `"fallback": ["codex:#low", "opencode"]`): a work
|
|
|
540
546
|
| `PITROOM_PRIMARY` | `sonnet` | Pricing preset for the savings estimate: `sonnet`, `opus`, `haiku`, `gpt-5` |
|
|
541
547
|
| `PITROOM_PRICE` | | Custom primary price, USD per 1M tokens: `"in,out[,cachedIn]"` |
|
|
542
548
|
| `PITROOM_HOME` | `~/.local/state/pitroom` | Where run records and the ledger live |
|
|
549
|
+
| `PITROOM_NO_SECRET_WARNING` | | Set to `1` to stop the warning about `.env` files and private keys in the worker's directory |
|
|
543
550
|
| `PITROOM_<WORKER>_BIN` | on PATH | Path to a worker CLI, e.g. `PITROOM_OPENCODE_BIN` |
|
|
544
551
|
|
|
545
552
|
</details>
|
|
@@ -575,6 +582,8 @@ Or put defaults in `~/.config/pitroom/config.json` (flags and env still win); `p
|
|
|
575
582
|
|
|
576
583
|
**Can the worker break my repo?** It can't touch git history, refs or your index (permission profile + git guard), can't run bulk deletes, and write-mode edits are revertible with one command. The guard is not an OS sandbox: git called by absolute path from inside a script, or non-git tools, are outside its reach, which is why `--isolate` exists: nothing reaches your tree until you apply it.
|
|
577
584
|
|
|
585
|
+
**Can the worker read my `.env`?** In the directory it runs in, yes: its prompt says not to, and nothing stops it, and a free model may be hosted by a third party. Pitroom warns when it sees `.env` files, private keys or `credentials.json` there (`PITROOM_NO_SECRET_WARNING=1` silences it). An isolated copy (`-i`) leaves out git-ignored files, so a git-ignored `.env` is not in it; a clean checkout is the safest place for a first try.
|
|
586
|
+
|
|
578
587
|
**What if the worker fails?** Pitroom exits non-zero with the real cause (for example a default model that no longer exists) and your agent simply continues on its own. `pitroom doctor` diagnoses setup problems.
|
|
579
588
|
|
|
580
589
|
---
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -39,6 +39,10 @@ The MIT License for the projects above is the one reproduced for superpowers, wi
|
|
|
39
39
|
|
|
40
40
|
Pitroom starts worker CLIs you install yourself (OpenCode, Codex CLI, Claude Code). They are not part of this package and keep their own licenses and terms.
|
|
41
41
|
|
|
42
|
+
## Pixel mascots (CodeIsland)
|
|
43
|
+
|
|
44
|
+
The animated Claude Code, Codex and OpenCode characters shown on a running card (`ui/src/assets/mascots`) are from [CodeIsland](https://github.com/wxtsky/CodeIsland), MIT License, Copyright (c) 2026 wxtsky. The license text is the one reproduced for superpowers above, with that copyright holder.
|
|
45
|
+
|
|
42
46
|
## Product names and marks
|
|
43
47
|
|
|
44
|
-
`docs/architecture.svg`
|
|
48
|
+
`docs/architecture.svg` and the dashboard's run cards show the logos of OpenCode, Codex (OpenAI) and Claude Code (Anthropic) only to say which tools Pitroom can drive. The files come from [svgl](https://svgl.app) (OpenCode) and the MIT-licensed [LobeHub icons](https://github.com/lobehub/lobe-icons) set (Codex, Claude Code). The names and logos belong to their owners. Pitroom is not affiliated with or endorsed by them.
|
package/dist/lib.mjs
CHANGED
|
@@ -1040,6 +1040,11 @@ var describeTarget = (t) => t.model ? formatTarget(t) : `${t.backend} (default m
|
|
|
1040
1040
|
// src/core/refs.ts
|
|
1041
1041
|
import fs5 from "node:fs";
|
|
1042
1042
|
import path5 from "node:path";
|
|
1043
|
+
var FILE_EXTENSIONS = new Set(
|
|
1044
|
+
"ts tsx mts cts js jsx mjs cjs json jsonc json5 md mdx txt rst py pyi rb go rs java kt kts swift c h cc cpp cxx hpp hh cs fs php lua r jl sh bash zsh fish ps1 bat yml yaml toml ini cfg conf env html htm css scss sass less vue svelte astro sql graphql gql proto lock xml svg csv tsv gradle tf hcl dart ex exs erl hs ml scala clj vim el mk cmake dockerfile gitignore gitattributes editorconfig npmrc nvmrc".split(" ")
|
|
1045
|
+
);
|
|
1046
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "out", ".next", ".nuxt", "target", "vendor", ".venv", "venv", "__pycache__", "coverage", ".turbo", ".cache"]);
|
|
1047
|
+
var MAX_INDEXED = 6e4;
|
|
1043
1048
|
var EXTENSIONLESS = "Makefile|Dockerfile|Containerfile|Gemfile|Rakefile|Procfile|Justfile|Vagrantfile|BUILD|WORKSPACE";
|
|
1044
1049
|
var REF = new RegExp(
|
|
1045
1050
|
String.raw`(?<![\w/:.-])(\.{0,2}/?(?:[\w@.+-]+/)*(?:[\w@+-][\w@.+-]*\.[A-Za-z][A-Za-z0-9]{0,7}|${EXTENSIONLESS})):(\d+)(?:[-–](\d+))?`,
|
|
@@ -1097,23 +1102,36 @@ function verifyRefs(refs, dirs) {
|
|
|
1097
1102
|
}
|
|
1098
1103
|
return lineCache.get(file);
|
|
1099
1104
|
};
|
|
1105
|
+
let index;
|
|
1106
|
+
const byName = () => index ??= indexFiles(roots);
|
|
1107
|
+
const candidates = (ref) => {
|
|
1108
|
+
const direct = resolve(ref.file, roots);
|
|
1109
|
+
if (direct) return [direct];
|
|
1110
|
+
if (path5.isAbsolute(ref.file)) return [];
|
|
1111
|
+
const wanted = ref.file.replace(/^(\.{1,2}\/)+/, "");
|
|
1112
|
+
return (byName().get(path5.basename(wanted)) ?? []).filter((f) => f.endsWith(`/${wanted}`) || path5.basename(f) === wanted).slice(0, 20);
|
|
1113
|
+
};
|
|
1114
|
+
const check = (file, ref) => {
|
|
1115
|
+
const lines = load(file);
|
|
1116
|
+
if (!lines) return void 0;
|
|
1117
|
+
if (ref.end > lines.length) return `file has ${lines.length} lines`;
|
|
1118
|
+
if (ref.symbol && !mentions(lines, ref) && !enclosedBy(lines, ref)) return `\`${ref.symbol}\` not near line ${ref.start}`;
|
|
1119
|
+
return void 0;
|
|
1120
|
+
};
|
|
1121
|
+
let total = 0;
|
|
1100
1122
|
for (const ref of refs) {
|
|
1101
|
-
const
|
|
1102
|
-
if (!
|
|
1123
|
+
const files = candidates(ref);
|
|
1124
|
+
if (!files.length) {
|
|
1125
|
+
if (!ref.file.includes("/") && !isFileName(ref.file)) continue;
|
|
1126
|
+
total++;
|
|
1103
1127
|
invalid.push({ ref: ref.text, reason: path5.isAbsolute(ref.file) && !inside(ref.file, roots) ? "outside the project" : "file not found" });
|
|
1104
1128
|
continue;
|
|
1105
1129
|
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
if (ref.
|
|
1109
|
-
invalid.push({ ref: ref.text, reason: `file has ${lines.length} lines` });
|
|
1110
|
-
continue;
|
|
1111
|
-
}
|
|
1112
|
-
if (ref.symbol && !mentions(lines, ref) && !enclosedBy(lines, ref)) {
|
|
1113
|
-
invalid.push({ ref: ref.text, reason: `\`${ref.symbol}\` not near line ${ref.start}` });
|
|
1114
|
-
}
|
|
1130
|
+
total++;
|
|
1131
|
+
const reasons = files.map((f) => check(f, ref));
|
|
1132
|
+
if (!reasons.some((r) => r === void 0)) invalid.push({ ref: ref.text, reason: files.length > 1 ? `${reasons[0]} (${files.length} files match)` : reasons[0] });
|
|
1115
1133
|
}
|
|
1116
|
-
return { total
|
|
1134
|
+
return { total, valid: total - invalid.length, invalid };
|
|
1117
1135
|
}
|
|
1118
1136
|
function mentions(lines, ref) {
|
|
1119
1137
|
const near = lines.slice(Math.max(0, ref.start - 1 - WINDOW), ref.end + WINDOW).join("\n");
|
|
@@ -1126,6 +1144,35 @@ function enclosedBy(lines, ref) {
|
|
|
1126
1144
|
);
|
|
1127
1145
|
return lines.slice(Math.max(0, ref.start - 1 - DEFINITION_LOOKBACK), ref.start).some((l) => def.test(l));
|
|
1128
1146
|
}
|
|
1147
|
+
var isFileName = (name) => {
|
|
1148
|
+
const base = path5.basename(name);
|
|
1149
|
+
return !base.includes(".") || new RegExp(`^(${EXTENSIONLESS})$`).test(base) || FILE_EXTENSIONS.has(base.split(".").pop().toLowerCase());
|
|
1150
|
+
};
|
|
1151
|
+
function indexFiles(roots) {
|
|
1152
|
+
const index = /* @__PURE__ */ new Map();
|
|
1153
|
+
let count = 0;
|
|
1154
|
+
const walk = (dir) => {
|
|
1155
|
+
let entries;
|
|
1156
|
+
try {
|
|
1157
|
+
entries = fs5.readdirSync(dir, { withFileTypes: true });
|
|
1158
|
+
} catch {
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
for (const e of entries) {
|
|
1162
|
+
if (count >= MAX_INDEXED) return;
|
|
1163
|
+
if (e.isDirectory()) {
|
|
1164
|
+
if (!SKIP_DIRS.has(e.name)) walk(path5.join(dir, e.name));
|
|
1165
|
+
} else if (e.isFile()) {
|
|
1166
|
+
count++;
|
|
1167
|
+
const list = index.get(e.name);
|
|
1168
|
+
if (list) list.push(path5.join(dir, e.name));
|
|
1169
|
+
else index.set(e.name, [path5.join(dir, e.name)]);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
};
|
|
1173
|
+
for (const r of roots) walk(r);
|
|
1174
|
+
return index;
|
|
1175
|
+
}
|
|
1129
1176
|
function resolve(ref, roots) {
|
|
1130
1177
|
const candidates = path5.isAbsolute(ref) ? inside(ref, roots) ? [ref] : [] : roots.map((r) => path5.join(r, ref));
|
|
1131
1178
|
return candidates.find((c) => {
|