@pitboss/cli 0.4.0 → 0.4.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 +3 -1
- package/dist/cli.js +116 -19
- package/dist/event-worker.js +41 -3
- package/dist/mcp.js +59 -27
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ On the approval screen you pick:
|
|
|
19
19
|
| Runtime | Files written |
|
|
20
20
|
|---|---|
|
|
21
21
|
| Claude Code | `.mcp.json` (server `pitboss`), `.claude/settings.local.json` (approves the server and installs the wake-up hooks) |
|
|
22
|
-
| Cursor | `.cursor/mcp.json` (server `pitboss`), `.cursor/hooks.json` (stop
|
|
22
|
+
| Cursor | `.cursor/mcp.json` (server `pitboss`), `.cursor/hooks.json` (stop + prompt hooks), `.cursor/rules/pitboss.mdc` (wake-up rule) |
|
|
23
23
|
|
|
24
24
|
`.mcp.json` and `.cursor/mcp.json` hold a secret token. `join` adds every file it writes to `.git/info/exclude`, and refuses to write into a file git already tracks.
|
|
25
25
|
|
|
@@ -38,6 +38,8 @@ pitboss watch # stream events until Ctrl-C (for a human watching a te
|
|
|
38
38
|
|
|
39
39
|
`watch --once` reads the agent from the folder's `.mcp.json` or `.cursor/mcp.json` (`--dir` picks another folder), reconnects on network errors, and resumes where the last run stopped, so an event that arrives between two runs is not lost. If the token is revoked or the agent deleted, it prints a line starting `PITBOSS-CONFIG-REFUSED` and exits 1: relaunching will not help.
|
|
40
40
|
|
|
41
|
+
After three failed relaunches the hooks go quiet so a refused or offline watcher cannot loop. A human prompt re-arms them; a background-command wake does not.
|
|
42
|
+
|
|
41
43
|
## Commands
|
|
42
44
|
|
|
43
45
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -18343,7 +18343,7 @@ async function taskCreateCommand(options) {
|
|
|
18343
18343
|
if (options.reviewBy) {
|
|
18344
18344
|
verifyKind = "reviewer_agent";
|
|
18345
18345
|
} else if (options.verifyKind) {
|
|
18346
|
-
if (!["shell", "file_exists", "thread_concluded", "reviewer_agent"].includes(options.verifyKind)) {
|
|
18346
|
+
if (!["shell", "file_exists", "thread_concluded", "reviewer_agent", "human_approval"].includes(options.verifyKind)) {
|
|
18347
18347
|
console.error(source_default.red(`Invalid --verify-kind "${options.verifyKind}".`));
|
|
18348
18348
|
process.exit(1);
|
|
18349
18349
|
}
|
|
@@ -19078,7 +19078,35 @@ function registerWatcher(dir, key) {
|
|
|
19078
19078
|
|
|
19079
19079
|
// src/lib/runtimes.ts
|
|
19080
19080
|
import { join as join3 } from "node:path";
|
|
19081
|
-
|
|
19081
|
+
|
|
19082
|
+
// ../../shared/types/src/index.ts
|
|
19083
|
+
var WORKER_TYPES = ["claude", "copilot", "cursor", "windsurf", "gemini", "gpt", "mcp", "human"];
|
|
19084
|
+
var WORKER_TYPE_LABELS = {
|
|
19085
|
+
claude: "Claude Code",
|
|
19086
|
+
copilot: "Copilot",
|
|
19087
|
+
cursor: "Cursor",
|
|
19088
|
+
windsurf: "Devin Desktop (formerly Windsurf)",
|
|
19089
|
+
gemini: "Gemini",
|
|
19090
|
+
gpt: "GPT",
|
|
19091
|
+
mcp: "Other MCP client",
|
|
19092
|
+
human: "Human"
|
|
19093
|
+
};
|
|
19094
|
+
var RUNTIMES = [
|
|
19095
|
+
{ value: "claude", label: WORKER_TYPE_LABELS.claude },
|
|
19096
|
+
{ value: "cursor", label: WORKER_TYPE_LABELS.cursor },
|
|
19097
|
+
{ value: "gemini", label: WORKER_TYPE_LABELS.gemini },
|
|
19098
|
+
{ value: "copilot", label: WORKER_TYPE_LABELS.copilot },
|
|
19099
|
+
{ value: "windsurf", label: WORKER_TYPE_LABELS.windsurf },
|
|
19100
|
+
{ value: "gpt", label: WORKER_TYPE_LABELS.gpt },
|
|
19101
|
+
{ value: "mcp", label: WORKER_TYPE_LABELS.mcp }
|
|
19102
|
+
];
|
|
19103
|
+
function isWorkerType(value) {
|
|
19104
|
+
return typeof value === "string" && WORKER_TYPES.includes(value);
|
|
19105
|
+
}
|
|
19106
|
+
var DEFAULT_UNSTARTED_AFTER_MS = 4 * 60 * 60 * 1e3;
|
|
19107
|
+
|
|
19108
|
+
// src/lib/runtimes.ts
|
|
19109
|
+
var RUNTIMES2 = RUNTIMES.map((r) => r.value);
|
|
19082
19110
|
function runtimeTargets(workerType, { home, repo: repo2 }) {
|
|
19083
19111
|
switch (workerType) {
|
|
19084
19112
|
case "claude":
|
|
@@ -19135,7 +19163,7 @@ function dropLegacyPitbossEntry(config) {
|
|
|
19135
19163
|
return { ...config, mcpServers: rest };
|
|
19136
19164
|
}
|
|
19137
19165
|
function allRuntimeTargets({ home, repo: repo2 }) {
|
|
19138
|
-
const fromRuntimes =
|
|
19166
|
+
const fromRuntimes = RUNTIMES2.flatMap((w) => runtimeTargets(w, { home, repo: repo2 }));
|
|
19139
19167
|
return [.../* @__PURE__ */ new Set([...fromRuntimes, join3(home, ".claude.json")])];
|
|
19140
19168
|
}
|
|
19141
19169
|
function holdsRelaiToken(existing, token2) {
|
|
@@ -19415,7 +19443,6 @@ function sweepMissingWorkingDirs() {
|
|
|
19415
19443
|
}
|
|
19416
19444
|
|
|
19417
19445
|
// src/commands/invite.ts
|
|
19418
|
-
var WORKER_TYPES = ["claude", "copilot", "cursor", "windsurf", "gemini", "gpt", "mcp", "human"];
|
|
19419
19446
|
var SPECIALIZATION_CHOICES = [
|
|
19420
19447
|
{ value: "writer", name: "writer \u2014 implementation, code changes" },
|
|
19421
19448
|
{ value: "reviewer", name: "reviewer \u2014 code review, PR feedback" },
|
|
@@ -19502,7 +19529,7 @@ async function loginCommand(opts) {
|
|
|
19502
19529
|
process.exit(1);
|
|
19503
19530
|
}
|
|
19504
19531
|
if (opts.workerType !== void 0) {
|
|
19505
|
-
if (!
|
|
19532
|
+
if (!isWorkerType(opts.workerType)) {
|
|
19506
19533
|
console.error(source_default.red(`--worker-type must be one of: ${WORKER_TYPES.join(", ")}`));
|
|
19507
19534
|
process.exit(1);
|
|
19508
19535
|
}
|
|
@@ -19728,18 +19755,26 @@ function claudeLocalSettings(existing, cli) {
|
|
|
19728
19755
|
return {
|
|
19729
19756
|
...settings,
|
|
19730
19757
|
enabledMcpjsonServers: servers.includes("pitboss") ? servers : [...servers, "pitboss"],
|
|
19731
|
-
hooks: {
|
|
19758
|
+
hooks: {
|
|
19759
|
+
...hooks,
|
|
19760
|
+
SessionStart: withHook("SessionStart"),
|
|
19761
|
+
Stop: withHook("Stop"),
|
|
19762
|
+
UserPromptSubmit: withHook("UserPromptSubmit")
|
|
19763
|
+
}
|
|
19732
19764
|
};
|
|
19733
19765
|
}
|
|
19734
19766
|
function cursorHooks(existing, cli, dir) {
|
|
19735
19767
|
const file = base(existing);
|
|
19736
19768
|
const hooks = objectField(file, "hooks");
|
|
19737
|
-
const
|
|
19738
|
-
const command = `${cli} hook cursor-stop --dir ${shq(dir)}`;
|
|
19769
|
+
const keep = (list) => (Array.isArray(list) ? list : []).filter((h) => !isOurs(h.command));
|
|
19739
19770
|
return {
|
|
19740
19771
|
version: 1,
|
|
19741
19772
|
...file,
|
|
19742
|
-
hooks: {
|
|
19773
|
+
hooks: {
|
|
19774
|
+
...hooks,
|
|
19775
|
+
stop: [...keep(hooks.stop), { command: `${cli} hook cursor-stop --dir ${shq(dir)}`, loop_limit: null }],
|
|
19776
|
+
beforeSubmitPrompt: [...keep(hooks.beforeSubmitPrompt), { command: `${cli} hook cursor-prompt --dir ${shq(dir)}` }]
|
|
19777
|
+
}
|
|
19743
19778
|
};
|
|
19744
19779
|
}
|
|
19745
19780
|
function cursorRule(cli, dir) {
|
|
@@ -20252,11 +20287,21 @@ function summarize(e, bodyChars = 80, agentId) {
|
|
|
20252
20287
|
return `new thread: "${threadTitle}"`;
|
|
20253
20288
|
case "thread.concluded":
|
|
20254
20289
|
return `thread concluded: "${threadTitle}"`;
|
|
20290
|
+
case "thread.message_ceiling": {
|
|
20291
|
+
const count = e.payload.count;
|
|
20292
|
+
const ceiling = e.payload.ceiling;
|
|
20293
|
+
return `message ceiling on thread (${count ?? "?"}/${ceiling ?? "?"})`;
|
|
20294
|
+
}
|
|
20255
20295
|
case "agent.idle_unroutable": {
|
|
20256
20296
|
const name = e.payload.agentName ?? e.targetId;
|
|
20257
20297
|
const n = Array.isArray(e.payload.tasks) ? e.payload.tasks.length : 0;
|
|
20258
20298
|
return `idle agent ${name} cannot take ${n} ready task(s)`;
|
|
20259
20299
|
}
|
|
20300
|
+
case "agent.unavailable": {
|
|
20301
|
+
const name = e.payload.agentName ?? e.targetId;
|
|
20302
|
+
const reason = e.payload.reason ?? "quota";
|
|
20303
|
+
return `agent ${name} unavailable (${reason})`;
|
|
20304
|
+
}
|
|
20260
20305
|
default:
|
|
20261
20306
|
return `${e.targetType} ${e.targetId}`;
|
|
20262
20307
|
}
|
|
@@ -20276,7 +20321,8 @@ var KIND_STYLE = {
|
|
|
20276
20321
|
"task.review_submitted": source_default.magenta,
|
|
20277
20322
|
"task.review_overdue": source_default.yellow,
|
|
20278
20323
|
"thread.created": source_default.cyan,
|
|
20279
|
-
"thread.concluded": source_default.dim
|
|
20324
|
+
"thread.concluded": source_default.dim,
|
|
20325
|
+
"thread.message_ceiling": source_default.yellow
|
|
20280
20326
|
};
|
|
20281
20327
|
function clockOf(iso) {
|
|
20282
20328
|
const d = new Date(iso);
|
|
@@ -20409,6 +20455,30 @@ async function watchCommand(options) {
|
|
|
20409
20455
|
import { readFileSync as readFileSync10 } from "node:fs";
|
|
20410
20456
|
import { join as join11 } from "node:path";
|
|
20411
20457
|
var FOLLOWUP_CAP = 3;
|
|
20458
|
+
var CURSOR_BACKGROUND_TASK_PROMPT = "Briefly inform the user about the task result";
|
|
20459
|
+
var SYSTEM_REMINDER_ONLY = /^(?:<system-reminder>[\s\S]*?<\/system-reminder>\s*)+$/;
|
|
20460
|
+
function promptFromPayload(payload) {
|
|
20461
|
+
try {
|
|
20462
|
+
const p = JSON.parse(payload).prompt;
|
|
20463
|
+
return typeof p === "string" ? p : null;
|
|
20464
|
+
} catch {
|
|
20465
|
+
return null;
|
|
20466
|
+
}
|
|
20467
|
+
}
|
|
20468
|
+
function isHumanPrompt(prompt) {
|
|
20469
|
+
if (prompt === null) return false;
|
|
20470
|
+
const t = prompt.trim();
|
|
20471
|
+
if (!t) return false;
|
|
20472
|
+
if (t.includes("<task-notification>")) return false;
|
|
20473
|
+
if (SYSTEM_REMINDER_ONLY.test(t)) return false;
|
|
20474
|
+
if (t.includes(CURSOR_BACKGROUND_TASK_PROMPT)) return false;
|
|
20475
|
+
if (t.includes("Your pitboss watcher is not running")) return false;
|
|
20476
|
+
if (t.includes("The pitboss watcher is NOT RUNNING")) return false;
|
|
20477
|
+
return true;
|
|
20478
|
+
}
|
|
20479
|
+
function maybeResetOnPrompt(payload, reset) {
|
|
20480
|
+
if (isHumanPrompt(promptFromPayload(payload))) reset();
|
|
20481
|
+
}
|
|
20412
20482
|
function wakeInstructions(watch, opening) {
|
|
20413
20483
|
return `${opening}
|
|
20414
20484
|
|
|
@@ -20479,11 +20549,16 @@ function wakeStateFor(dir) {
|
|
|
20479
20549
|
return {
|
|
20480
20550
|
watcherAlive: () => watcherAlive(state, key),
|
|
20481
20551
|
followups: followupCount(state, key),
|
|
20482
|
-
asked: () => recordFollowup(state, key)
|
|
20552
|
+
asked: () => recordFollowup(state, key),
|
|
20553
|
+
reset: () => resetFollowups(state, key)
|
|
20483
20554
|
};
|
|
20484
20555
|
}
|
|
20556
|
+
function claudeEvent(raw) {
|
|
20557
|
+
if (raw === "Stop" || raw === "UserPromptSubmit") return raw;
|
|
20558
|
+
return "SessionStart";
|
|
20559
|
+
}
|
|
20485
20560
|
function hookClaudeCommand(opts) {
|
|
20486
|
-
const event = opts.event
|
|
20561
|
+
const event = claudeEvent(opts.event);
|
|
20487
20562
|
const payload = readStdin();
|
|
20488
20563
|
let cwd;
|
|
20489
20564
|
try {
|
|
@@ -20494,6 +20569,10 @@ function hookClaudeCommand(opts) {
|
|
|
20494
20569
|
const dir = process.env.CLAUDE_PROJECT_DIR || typeof cwd === "string" && cwd || process.cwd();
|
|
20495
20570
|
const wake = wakeStateFor(dir);
|
|
20496
20571
|
if (!wake) return;
|
|
20572
|
+
if (event === "UserPromptSubmit") {
|
|
20573
|
+
maybeResetOnPrompt(payload, wake.reset);
|
|
20574
|
+
return;
|
|
20575
|
+
}
|
|
20497
20576
|
const out = claudeHookOutput(event, payload, { cli: pinnedCli(CLI_VERSION), dir, ...wake });
|
|
20498
20577
|
if (out && event === "Stop") wake.asked();
|
|
20499
20578
|
if (out) process.stdout.write(JSON.stringify(out));
|
|
@@ -20509,6 +20588,12 @@ function hookCursorStopCommand(opts) {
|
|
|
20509
20588
|
if ("followup_message" in out) wake.asked();
|
|
20510
20589
|
process.stdout.write(JSON.stringify(out));
|
|
20511
20590
|
}
|
|
20591
|
+
function hookCursorPromptCommand(opts) {
|
|
20592
|
+
const dir = opts.dir ?? process.cwd();
|
|
20593
|
+
const wake = wakeStateFor(dir);
|
|
20594
|
+
if (wake) maybeResetOnPrompt(readStdin(), wake.reset);
|
|
20595
|
+
process.stdout.write("{}");
|
|
20596
|
+
}
|
|
20512
20597
|
|
|
20513
20598
|
// src/commands/node.ts
|
|
20514
20599
|
import { execFileSync as execFileSync6, spawn as spawnChild } from "node:child_process";
|
|
@@ -20545,6 +20630,7 @@ var BASE_TASK_LOOP = (agentId, repoPath) => `You are an AI worker agent (ID: ${a
|
|
|
20545
20630
|
with \`toAgent\` set to the sender and no \`threadId\`. To check whether someone answered *you*,
|
|
20546
20631
|
call \`mcp__relai__get_thread_messages\` on the \`threadId\` \`send_message\` gave you back \u2014 do not
|
|
20547
20632
|
trawl the unread feed for it.
|
|
20633
|
+
If \`mcp__relai__send_message\` returns 429, batch your updates into one message rather than retrying.
|
|
20548
20634
|
|
|
20549
20635
|
**If you are stuck on something a colleague would know, ask one.** Call
|
|
20550
20636
|
\`mcp__relai__list_agents\` to see who exists, pick by specialization or domain, prefer someone
|
|
@@ -21050,9 +21136,15 @@ import { spawn as spawn3 } from "child_process";
|
|
|
21050
21136
|
import { join as join13 } from "path";
|
|
21051
21137
|
|
|
21052
21138
|
// ../claude-worker/src/errors.ts
|
|
21053
|
-
var
|
|
21139
|
+
var QUOTA_PATTERNS = [
|
|
21054
21140
|
/credit balance is too low/i,
|
|
21055
21141
|
/insufficient[ _-]?credits?/i,
|
|
21142
|
+
/quota[ _-]?(?:exceeded|exhausted)/i,
|
|
21143
|
+
/exceeded your (?:usage |rate )?quota/i,
|
|
21144
|
+
/usage limit (?:reached|exceeded)/i,
|
|
21145
|
+
/daily (?:limit|quota|cap)/i
|
|
21146
|
+
];
|
|
21147
|
+
var CREDENTIAL_PATTERNS = [
|
|
21056
21148
|
/invalid[ _-]?(api[ _-]?key|x-api-key|bearer token)/i,
|
|
21057
21149
|
/authentication[ _-]?error/i,
|
|
21058
21150
|
/\bunauthorized\b/i,
|
|
@@ -21068,6 +21160,7 @@ var OVERFLOW_PATTERNS = [
|
|
|
21068
21160
|
];
|
|
21069
21161
|
function classifySessionError(text) {
|
|
21070
21162
|
if (!text) return "transient";
|
|
21163
|
+
if (QUOTA_PATTERNS.some((re) => re.test(text))) return "quota";
|
|
21071
21164
|
if (CREDENTIAL_PATTERNS.some((re) => re.test(text))) return "credentials";
|
|
21072
21165
|
if (OVERFLOW_PATTERNS.some((re) => re.test(text))) return "overflow";
|
|
21073
21166
|
return "transient";
|
|
@@ -21174,7 +21267,7 @@ var claudeAdapter = {
|
|
|
21174
21267
|
};
|
|
21175
21268
|
|
|
21176
21269
|
// ../claude-worker/src/adapters/index.ts
|
|
21177
|
-
var
|
|
21270
|
+
var RUNTIMES3 = ["claude", "cursor"];
|
|
21178
21271
|
var ADAPTERS = { claude: claudeAdapter, cursor: cursorAdapter };
|
|
21179
21272
|
function getAdapter(runtime) {
|
|
21180
21273
|
const adapter = ADAPTERS[runtime];
|
|
@@ -21182,6 +21275,9 @@ function getAdapter(runtime) {
|
|
|
21182
21275
|
return adapter;
|
|
21183
21276
|
}
|
|
21184
21277
|
|
|
21278
|
+
// ../claude-worker/src/unavailable.ts
|
|
21279
|
+
var DEFAULT_UNAVAILABLE_UNTIL_MS = 60 * 60 * 1e3;
|
|
21280
|
+
|
|
21185
21281
|
// ../claude-worker/src/config.ts
|
|
21186
21282
|
var VALID_SPECIALIZATIONS = ["reviewer", "architect", "writer", "tester", "devops"];
|
|
21187
21283
|
|
|
@@ -21208,8 +21304,8 @@ function parseSpec(text, specDir) {
|
|
|
21208
21304
|
const a = entry ?? {};
|
|
21209
21305
|
if (typeof a.name !== "string" || !NAME.test(a.name)) throw new Error(`agents[${i}].name must be letters, digits, '.', '_' or '-'`);
|
|
21210
21306
|
if (typeof a.worktree !== "string" || !a.worktree) throw new Error(`agents[${i}].worktree is required`);
|
|
21211
|
-
if (!
|
|
21212
|
-
throw new Error(`agents[${i}]: runtime "${String(a.runtime)}" is not supported (have: ${
|
|
21307
|
+
if (!RUNTIMES3.includes(a.runtime)) {
|
|
21308
|
+
throw new Error(`agents[${i}]: runtime "${String(a.runtime)}" is not supported (have: ${RUNTIMES3.join(", ")})`);
|
|
21213
21309
|
}
|
|
21214
21310
|
const specialization = a.specialization === void 0 ? "writer" : a.specialization;
|
|
21215
21311
|
if (!VALID_SPECIALIZATIONS.includes(specialization)) {
|
|
@@ -21908,11 +22004,12 @@ program2.command("status").description("Show your agent info, online agents, tas
|
|
|
21908
22004
|
program2.command("start").description("Show your session orientation: project context, your open tasks, unread messages, open threads").action(startCommand);
|
|
21909
22005
|
program2.command("watch").description("Stream live events you're subscribed to (new tasks, messages, reviews) until Ctrl-C").option("--kinds <list>", "Only show these event kinds (comma-separated, e.g. task.created,message.posted)").option("--once", "Block until the next event for this folder's agent, print it as JSON, and exit").option("--dir <path>", "Folder whose agent config --once uses (defaults to the current directory)").action(watchCommand);
|
|
21910
22006
|
var hook = program2.command("hook", { hidden: true }).description("Agent runtime hooks that keep the watcher running (installed by join)");
|
|
21911
|
-
hook.command("claude").description("Claude Code SessionStart/Stop hook").option("--event <event>", "SessionStart | Stop", "SessionStart").action(hookClaudeCommand);
|
|
22007
|
+
hook.command("claude").description("Claude Code SessionStart/Stop/UserPromptSubmit hook").option("--event <event>", "SessionStart | Stop | UserPromptSubmit", "SessionStart").action(hookClaudeCommand);
|
|
21912
22008
|
hook.command("cursor-stop").description("Cursor stop hook").option("--dir <path>", "The agent's folder").action(hookCursorStopCommand);
|
|
22009
|
+
hook.command("cursor-prompt").description("Cursor beforeSubmitPrompt hook").option("--dir <path>", "The agent's folder").action(hookCursorPromptCommand);
|
|
21913
22010
|
program2.command("tasks").description("List tasks assigned to you (default: assigned + in_progress)").option("-a, --all", "Show all tasks in the project, not just yours").option("-s, --status <status>", "Filter by status (comma-separated: pending,assigned,in_progress,pending_verification,completed,blocked)").action(tasksCommand);
|
|
21914
22011
|
var task = program2.command("task").description("Create or update a task");
|
|
21915
|
-
task.command("create").description("Create a new task").option("-t, --title <title>", "Task title").option("-d, --description <description>", "Task description").option("-p, --priority <priority>", "low|normal|high|urgent").option("--to <agent>", "Assign to an agent (id or name)").option("--domains <list>", "Comma-separated domain tags").option("--specialization <s>", "Required specialization").option("-v, --verify <cmd>", "Shell command that must exit 0 to gate the `completed` transition").option("--verify-cwd <path>", "Working directory for --verify (defaults to API server cwd)").option("--verify-kind <kind>", "Verifier kind: shell|file_exists|thread_concluded|reviewer_agent").option("--verify-path <path>", "Path that must exist (--verify-kind file_exists)").option("--verify-thread <id>", "Thread that must be concluded (--verify-kind thread_concluded)").option("--verify-reviewer <agent>", "Agent (id or name) who must approve (--verify-kind reviewer_agent)").option("--review-by <agent>", "Shorthand for --verify-kind reviewer_agent --verify-reviewer <agent>").action(taskCreateCommand);
|
|
22012
|
+
task.command("create").description("Create a new task").option("-t, --title <title>", "Task title").option("-d, --description <description>", "Task description").option("-p, --priority <priority>", "low|normal|high|urgent").option("--to <agent>", "Assign to an agent (id or name)").option("--domains <list>", "Comma-separated domain tags").option("--specialization <s>", "Required specialization").option("-v, --verify <cmd>", "Shell command that must exit 0 to gate the `completed` transition").option("--verify-cwd <path>", "Working directory for --verify (defaults to API server cwd)").option("--verify-kind <kind>", "Verifier kind: shell|file_exists|thread_concluded|reviewer_agent|human_approval").option("--verify-path <path>", "Path that must exist (--verify-kind file_exists)").option("--verify-thread <id>", "Thread that must be concluded (--verify-kind thread_concluded)").option("--verify-reviewer <agent>", "Agent (id or name) who must approve (--verify-kind reviewer_agent)").option("--review-by <agent>", "Shorthand for --verify-kind reviewer_agent --verify-reviewer <agent>").action(taskCreateCommand);
|
|
21916
22013
|
task.command("start <id>").description("Mark a task as in_progress").option("-n, --note <note>", "Optional note to attach").action((id, options) => taskUpdateCommand(id, "in_progress", options));
|
|
21917
22014
|
task.command("done <id>").description("Mark a task as completed").option("-n, --note <note>", "Optional note to attach").action((id, options) => taskUpdateCommand(id, "completed", options));
|
|
21918
22015
|
task.command("block <id>").description("Mark a task as blocked").option("-n, --note <note>", "Describe what's blocking you").action((id, options) => taskUpdateCommand(id, "blocked", options));
|
|
@@ -21928,7 +22025,7 @@ program2.command("send <threadId>").description("Send a message to a thread").op
|
|
|
21928
22025
|
program2.command("inbox").description("Show unread messages").option("-r, --read", "Mark all displayed messages as read").action(inboxCommand);
|
|
21929
22026
|
program2.command("repos").description("List all repos on this server").action(reposListCommand);
|
|
21930
22027
|
program2.command("agents").description("List agents in the current repo").action(agentsListCommand);
|
|
21931
|
-
program2.command("login").description("Log in to a repo on this machine using an invite code or an agent token").option("--invite <code>", "Invite code from `pitboss repo invite`").option("--token <token>", "Per-agent token (from the cloud dashboard)").option("--api <url>", "API URL (skips prompt)").option("--working-dir <path>", "Override the working directory (defaults to CWD)").option("--worker-type <type>",
|
|
22028
|
+
program2.command("login").description("Log in to a repo on this machine using an invite code or an agent token").option("--invite <code>", "Invite code from `pitboss repo invite`").option("--token <token>", "Per-agent token (from the cloud dashboard)").option("--api <url>", "API URL (skips prompt)").option("--working-dir <path>", "Override the working directory (defaults to CWD)").option("--worker-type <type>", `Agent runtime: ${WORKER_TYPES.join(", ")} (default: human)`).option("--name <name>", "Agent name (defaults to the invite's suggestion when not interactive)").option("--specialization <spec>", "Agent specialization: writer, reviewer, tester, architect, devops, orchestrator (defaults to the invite's suggestion)").action(loginCommand);
|
|
21932
22029
|
program2.command("logout").description("Release this directory's local agent claim (does not revoke the server token)").option("--all", "Sweep claims whose working directories no longer exist").action(logoutCommand);
|
|
21933
22030
|
var repo = program2.command("repo").description("Repo operations");
|
|
21934
22031
|
repo.command("show [id]").description("Show a repo's details (defaults to the current repo)").action(repoShowCommand);
|
package/dist/event-worker.js
CHANGED
|
@@ -26,6 +26,7 @@ var BASE_TASK_LOOP = (agentId, repoPath) => `You are an AI worker agent (ID: ${a
|
|
|
26
26
|
with \`toAgent\` set to the sender and no \`threadId\`. To check whether someone answered *you*,
|
|
27
27
|
call \`mcp__relai__get_thread_messages\` on the \`threadId\` \`send_message\` gave you back \u2014 do not
|
|
28
28
|
trawl the unread feed for it.
|
|
29
|
+
If \`mcp__relai__send_message\` returns 429, batch your updates into one message rather than retrying.
|
|
29
30
|
|
|
30
31
|
**If you are stuck on something a colleague would know, ask one.** Call
|
|
31
32
|
\`mcp__relai__list_agents\` to see who exists, pick by specialization or domain, prefer someone
|
|
@@ -531,9 +532,15 @@ import { spawn as spawn2 } from "child_process";
|
|
|
531
532
|
import { join as join2 } from "path";
|
|
532
533
|
|
|
533
534
|
// ../claude-worker/src/errors.ts
|
|
534
|
-
var
|
|
535
|
+
var QUOTA_PATTERNS = [
|
|
535
536
|
/credit balance is too low/i,
|
|
536
537
|
/insufficient[ _-]?credits?/i,
|
|
538
|
+
/quota[ _-]?(?:exceeded|exhausted)/i,
|
|
539
|
+
/exceeded your (?:usage |rate )?quota/i,
|
|
540
|
+
/usage limit (?:reached|exceeded)/i,
|
|
541
|
+
/daily (?:limit|quota|cap)/i
|
|
542
|
+
];
|
|
543
|
+
var CREDENTIAL_PATTERNS = [
|
|
537
544
|
/invalid[ _-]?(api[ _-]?key|x-api-key|bearer token)/i,
|
|
538
545
|
/authentication[ _-]?error/i,
|
|
539
546
|
/\bunauthorized\b/i,
|
|
@@ -549,6 +556,7 @@ var OVERFLOW_PATTERNS = [
|
|
|
549
556
|
];
|
|
550
557
|
function classifySessionError(text) {
|
|
551
558
|
if (!text) return "transient";
|
|
559
|
+
if (QUOTA_PATTERNS.some((re) => re.test(text))) return "quota";
|
|
552
560
|
if (CREDENTIAL_PATTERNS.some((re) => re.test(text))) return "credentials";
|
|
553
561
|
if (OVERFLOW_PATTERNS.some((re) => re.test(text))) return "overflow";
|
|
554
562
|
return "transient";
|
|
@@ -702,6 +710,28 @@ async function blockOverflowedTasks(conn, detail, logPrefix = "[claude-worker]")
|
|
|
702
710
|
return blocked;
|
|
703
711
|
}
|
|
704
712
|
|
|
713
|
+
// ../claude-worker/src/unavailable.ts
|
|
714
|
+
var DEFAULT_UNAVAILABLE_UNTIL_MS = 60 * 60 * 1e3;
|
|
715
|
+
function untilMs() {
|
|
716
|
+
const n = Number(process.env.UNAVAILABLE_UNTIL_MS);
|
|
717
|
+
return Number.isFinite(n) && n >= 1e3 ? n : DEFAULT_UNAVAILABLE_UNTIL_MS;
|
|
718
|
+
}
|
|
719
|
+
async function reportUnavailable(config2, reason, logPrefix = "[claude-worker]") {
|
|
720
|
+
const until = new Date(Date.now() + untilMs()).toISOString();
|
|
721
|
+
await fetch(`${config2.apiUrl}/agents/${config2.agentId}/unavailable`, {
|
|
722
|
+
method: "PUT",
|
|
723
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${config2.apiSecret}` },
|
|
724
|
+
body: JSON.stringify({ reason: reason.slice(0, 200), until })
|
|
725
|
+
}).catch((err) => console.warn(`${logPrefix} report unavailable failed:`, err.message));
|
|
726
|
+
}
|
|
727
|
+
async function clearUnavailable(config2, logPrefix = "[claude-worker]") {
|
|
728
|
+
await fetch(`${config2.apiUrl}/agents/${config2.agentId}/unavailable`, {
|
|
729
|
+
method: "PUT",
|
|
730
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${config2.apiSecret}` },
|
|
731
|
+
body: JSON.stringify({ until: null })
|
|
732
|
+
}).catch((err) => console.warn(`${logPrefix} clear unavailable failed:`, err.message));
|
|
733
|
+
}
|
|
734
|
+
|
|
705
735
|
// ../../shared/git/src/index.ts
|
|
706
736
|
import { execFileSync } from "node:child_process";
|
|
707
737
|
function getGitRoot(cwd) {
|
|
@@ -1389,7 +1419,8 @@ var EVENT_KINDS = [
|
|
|
1389
1419
|
"thread.created",
|
|
1390
1420
|
"thread.concluded",
|
|
1391
1421
|
"task.blocked_overdue",
|
|
1392
|
-
"artifact.published"
|
|
1422
|
+
"artifact.published",
|
|
1423
|
+
"agent.unavailable"
|
|
1393
1424
|
];
|
|
1394
1425
|
async function runEventWorker(config2) {
|
|
1395
1426
|
console.log(`[event-worker] Starting \u2014 agent ${config2.agentId}, watching ${config2.apiUrl}/events`);
|
|
@@ -1416,6 +1447,7 @@ async function runEventWorker(config2) {
|
|
|
1416
1447
|
try {
|
|
1417
1448
|
console.log("[event-worker] Event received \u2014 running session...");
|
|
1418
1449
|
await getAdapter(config2.runtime).runSession(config2);
|
|
1450
|
+
await clearUnavailable(config2, "[event-worker]");
|
|
1419
1451
|
} catch (err) {
|
|
1420
1452
|
const text = err instanceof Error ? err.message : String(err);
|
|
1421
1453
|
const failure = getAdapter(config2.runtime).classifyError(text);
|
|
@@ -1424,11 +1456,17 @@ async function runEventWorker(config2) {
|
|
|
1424
1456
|
console.error(
|
|
1425
1457
|
blocked.length > 0 ? `[event-worker] Session ran out of context \u2014 blocked ${blocked.join(", ")} pending a split.
|
|
1426
1458
|
${text}` : `[event-worker] Session ran out of context but no task was in flight to block.
|
|
1459
|
+
${text}`
|
|
1460
|
+
);
|
|
1461
|
+
} else if (failure === "quota") {
|
|
1462
|
+
await reportUnavailable(config2, "quota_exhausted", "[event-worker]");
|
|
1463
|
+
console.error(
|
|
1464
|
+
`[event-worker] FATAL error (quota or credit exhausted) \u2014 router will skip this agent until the until-time or a successful session.
|
|
1427
1465
|
${text}`
|
|
1428
1466
|
);
|
|
1429
1467
|
} else if (failure === "credentials") {
|
|
1430
1468
|
console.error(
|
|
1431
|
-
`[event-worker] FATAL error (likely
|
|
1469
|
+
`[event-worker] FATAL error (likely bad credentials) \u2014 fix it; the worker keeps listening and will recover on the next event.
|
|
1432
1470
|
${text}`
|
|
1433
1471
|
);
|
|
1434
1472
|
} else {
|
package/dist/mcp.js
CHANGED
|
@@ -12378,7 +12378,10 @@ var ApiClient = class {
|
|
|
12378
12378
|
return this.request("PUT", `/agents/${agentId}/heartbeat`, {});
|
|
12379
12379
|
}
|
|
12380
12380
|
getAgent(id) {
|
|
12381
|
-
return this.request(
|
|
12381
|
+
return this.request(
|
|
12382
|
+
"GET",
|
|
12383
|
+
`/agents/${id}`
|
|
12384
|
+
);
|
|
12382
12385
|
}
|
|
12383
12386
|
listAgents(repoId) {
|
|
12384
12387
|
const qs = repoId ? `?repoId=${encodeURIComponent(repoId)}` : "";
|
|
@@ -12562,7 +12565,7 @@ ${version.body}`
|
|
|
12562
12565
|
},
|
|
12563
12566
|
{
|
|
12564
12567
|
name: "create_task",
|
|
12565
|
-
description: "Create a new task in this project \u2014 use this to turn a plan into actionable work (e.g. an architect/planner breaking a design into tasks for workers). You are recorded as the task's creator automatically. Assign with assignedTo: an agent ID assigns directly, '@auto' lets the routing scheduler pick (task stays 'pending'), and omitting it means '@auto'. To keep a task yourself, pass your own agent id. Do NOT set status \u2014 it's derived (assigned when there's a concrete assignee, else pending). Optionally add a completion gate via verifyKind: 'reviewer_agent' (set verifyReviewerId \u2014 that agent must approve via submit_review), 'file_exists' (verifyPath),
|
|
12568
|
+
description: "Create a new task in this project \u2014 use this to turn a plan into actionable work (e.g. an architect/planner breaking a design into tasks for workers). You are recorded as the task's creator automatically. Assign with assignedTo: an agent ID assigns directly, '@auto' lets the routing scheduler pick (task stays 'pending'), and omitting it means '@auto'. To keep a task yourself, pass your own agent id. Do NOT set status \u2014 it's derived (assigned when there's a concrete assignee, else pending). Optionally add a completion gate via verifyKind: 'reviewer_agent' (set verifyReviewerId \u2014 that agent must approve via submit_review), 'file_exists' (verifyPath), 'thread_concluded' (verifyThreadId), or 'human_approval' (the dashboard owner must approve; no agent token can satisfy it). The 'shell' kind (verifyCommand) is restricted to orchestrator agents and 403s otherwise. Note: if you are a worker (not an orchestrator, not tier\u22652), this creates a *proposal* \u2014 the task lands in 'proposed' with your assignedTo kept only as a hint, and an orchestrator must commit it before it becomes real work. Orchestrators and trusted seniors (tier\u22652 only) commit on creation. commit_task stays orchestrator-only. When creating under an Epic (epicId), the result includes epicSiblings and may include a warning if a sibling already has this title \u2014 that is a warning, not a refusal; the task is still created. A failed create is returned as an error, never as a created-looking payload." + godNote,
|
|
12566
12569
|
inputSchema: external_exports.object({
|
|
12567
12570
|
...repoParam,
|
|
12568
12571
|
title: external_exports.string().min(1).describe("Short, action-oriented task title."),
|
|
@@ -12572,7 +12575,7 @@ ${version.body}`
|
|
|
12572
12575
|
domains: external_exports.array(external_exports.string()).optional().describe("Domain tags for rules-based routing, e.g. ['database','schema']."),
|
|
12573
12576
|
specialization: external_exports.string().optional().describe("Specialization the task needs, for routing (e.g. 'writer')."),
|
|
12574
12577
|
epicId: external_exports.string().optional().describe("Parent Epic (a plan thread). The response lists existing children and warns on a title match."),
|
|
12575
|
-
verifyKind: external_exports.enum(["shell", "file_exists", "thread_concluded", "reviewer_agent"]).optional().describe("Optional completion gate \u2014 see tool description."),
|
|
12578
|
+
verifyKind: external_exports.enum(["shell", "file_exists", "thread_concluded", "reviewer_agent", "human_approval"]).optional().describe("Optional completion gate \u2014 see tool description."),
|
|
12576
12579
|
verifyReviewerId: external_exports.string().optional().describe("For verifyKind='reviewer_agent': agent ID that must approve."),
|
|
12577
12580
|
verifyThreadId: external_exports.string().optional().describe("For verifyKind='thread_concluded': thread whose conclusion gates completion."),
|
|
12578
12581
|
verifyPath: external_exports.string().optional().describe("For verifyKind='file_exists': path that must exist."),
|
|
@@ -12686,7 +12689,7 @@ ${version.body}`
|
|
|
12686
12689
|
},
|
|
12687
12690
|
{
|
|
12688
12691
|
name: "send_message",
|
|
12689
|
-
description: "Send a message to the orchestrator or another agent. Use the correct type: 'handoff' when you finish a task and the next agent needs context; 'finding' when you discover something that affects other work (not your current task); 'decision' to record an agreed-upon decision that all agents should honor; 'question' when you are blocked and need input before proceeding; 'escalation' when a decision needs human judgment; 'status' for routine progress updates. Be specific in the body \u2014 the receiver has no other context. IMPORTANT: if you were asked something via a relai message or task (toAgent=you, or a task assigned to you), your answer belongs here, posted on that same threadId \u2014 not just written in your own session output. The requester only sees what you post through send_message. To start a conversation with a peer you have no thread with, give toAgent and omit threadId: that opens (or reuses) a private direct thread between the two of you. Find the agent with list_agents. A direct thread is readable only by its two participants.",
|
|
12692
|
+
description: "Send a message to the orchestrator or another agent. Use the correct type: 'handoff' when you finish a task and the next agent needs context; 'finding' when you discover something that affects other work (not your current task); 'decision' to record an agreed-upon decision that all agents should honor; 'question' when you are blocked and need input before proceeding; 'escalation' when a decision needs human judgment; 'status' for routine progress updates. Be specific in the body \u2014 the receiver has no other context. IMPORTANT: if you were asked something via a relai message or task (toAgent=you, or a task assigned to you), your answer belongs here, posted on that same threadId \u2014 not just written in your own session output. The requester only sees what you post through send_message. To start a conversation with a peer you have no thread with, give toAgent and omit threadId: that opens (or reuses) a private direct thread between the two of you. Find the agent with list_agents. A direct thread is readable only by its two participants. If the API returns 429, batch your updates into one message rather than retrying.",
|
|
12690
12693
|
inputSchema: external_exports.object({
|
|
12691
12694
|
threadId: external_exports.string().optional().describe(
|
|
12692
12695
|
"The thread to post to. Use list_threads to find or create one. Omit it (with toAgent set) to direct-message that agent \u2014 no thread needed."
|
|
@@ -12983,7 +12986,7 @@ ${version.body}`
|
|
|
12983
12986
|
priority: external_exports.enum(["low", "normal", "high", "urgent"]).optional().describe("Ratified priority."),
|
|
12984
12987
|
domains: external_exports.array(external_exports.string()).optional().describe("Ratified domain tags."),
|
|
12985
12988
|
specialization: external_exports.string().optional().describe("Ratified specialization."),
|
|
12986
|
-
verifyKind: external_exports.enum(["shell", "file_exists", "thread_concluded", "reviewer_agent"]).optional(),
|
|
12989
|
+
verifyKind: external_exports.enum(["shell", "file_exists", "thread_concluded", "reviewer_agent", "human_approval"]).optional(),
|
|
12987
12990
|
verifyReviewerId: external_exports.string().optional(),
|
|
12988
12991
|
verifyThreadId: external_exports.string().optional(),
|
|
12989
12992
|
verifyPath: external_exports.string().optional(),
|
|
@@ -13165,7 +13168,7 @@ function buildProvisioningTools(client, opts = {}) {
|
|
|
13165
13168
|
inputSchema: external_exports.object({
|
|
13166
13169
|
repoId: external_exports.string().describe("The repo to invite into. Must be one you own; see list_repos."),
|
|
13167
13170
|
name: external_exports.string().min(1).describe("Suggested name for the new agent, e.g. 'reviewer'."),
|
|
13168
|
-
workerType: external_exports.
|
|
13171
|
+
workerType: external_exports.string().optional().describe("The runtime this agent will run as. Defaults to 'human' at redeem time if omitted."),
|
|
13169
13172
|
specialization: external_exports.string().optional().describe("What this agent is for, e.g. 'review' or 'frontend'. Used for routing.")
|
|
13170
13173
|
}),
|
|
13171
13174
|
handler: async (input) => {
|
|
@@ -13659,6 +13662,41 @@ async function deliverAttention(prev, tasks, send) {
|
|
|
13659
13662
|
return next;
|
|
13660
13663
|
}
|
|
13661
13664
|
|
|
13665
|
+
// ../mcp-server/src/inbox-poll.ts
|
|
13666
|
+
var DEFAULT_INBOX_POLL_INTERVAL_MS = 6e4;
|
|
13667
|
+
function inboxPollIntervalMs(raw = process.env.INBOX_POLL_INTERVAL_MS) {
|
|
13668
|
+
const n = Number(raw);
|
|
13669
|
+
return Number.isFinite(n) && n >= 1 ? n : DEFAULT_INBOX_POLL_INTERVAL_MS;
|
|
13670
|
+
}
|
|
13671
|
+
function shouldSkipInboxPoll(agent) {
|
|
13672
|
+
return agent?.listening === true;
|
|
13673
|
+
}
|
|
13674
|
+
async function runInboxPoll(deps) {
|
|
13675
|
+
let agent;
|
|
13676
|
+
try {
|
|
13677
|
+
agent = await deps.getAgent();
|
|
13678
|
+
} catch {
|
|
13679
|
+
agent = null;
|
|
13680
|
+
}
|
|
13681
|
+
if (shouldSkipInboxPoll(agent)) return "skipped";
|
|
13682
|
+
const [tasks, messages] = await Promise.all([deps.getTasks(), deps.getUnread()]);
|
|
13683
|
+
for (const task of tasks) {
|
|
13684
|
+
if (deps.seenTaskIds.has(task.id)) continue;
|
|
13685
|
+
deps.seenTaskIds.add(task.id);
|
|
13686
|
+
await deps.notify(
|
|
13687
|
+
`\u{1F4CB} New task assigned: "${task.title}" [${task.id}] \u2014 call get_my_tasks to begin`
|
|
13688
|
+
);
|
|
13689
|
+
}
|
|
13690
|
+
for (const msg of messages) {
|
|
13691
|
+
if (deps.seenMessageIds.has(msg.id)) continue;
|
|
13692
|
+
deps.seenMessageIds.add(msg.id);
|
|
13693
|
+
await deps.notify(
|
|
13694
|
+
`\u{1F4AC} New ${msg.type} message from ${msg.fromAgent} in thread ${msg.threadId} \u2014 call get_unread_messages to read`
|
|
13695
|
+
);
|
|
13696
|
+
}
|
|
13697
|
+
return "polled";
|
|
13698
|
+
}
|
|
13699
|
+
|
|
13662
13700
|
// ../../shared/git/src/index.ts
|
|
13663
13701
|
import { execFileSync } from "node:child_process";
|
|
13664
13702
|
function getGitRoot(cwd) {
|
|
@@ -13867,31 +13905,25 @@ if (!OWNER_MODE) {
|
|
|
13867
13905
|
apiClient.heartbeat(AGENT_ID).catch(() => {
|
|
13868
13906
|
});
|
|
13869
13907
|
}, HEARTBEAT_INTERVAL_MS);
|
|
13870
|
-
const POLL_INTERVAL_MS =
|
|
13908
|
+
const POLL_INTERVAL_MS = inboxPollIntervalMs();
|
|
13871
13909
|
const seenTaskIds = /* @__PURE__ */ new Set();
|
|
13872
13910
|
const seenMessageIds = /* @__PURE__ */ new Set();
|
|
13873
13911
|
async function pollInbox() {
|
|
13874
13912
|
try {
|
|
13875
|
-
|
|
13876
|
-
|
|
13877
|
-
|
|
13878
|
-
|
|
13879
|
-
|
|
13880
|
-
|
|
13881
|
-
|
|
13882
|
-
|
|
13883
|
-
|
|
13884
|
-
|
|
13885
|
-
|
|
13886
|
-
|
|
13887
|
-
|
|
13888
|
-
|
|
13889
|
-
seenMessageIds.add(msg.id);
|
|
13890
|
-
await server.server.sendLoggingMessage({
|
|
13891
|
-
level: "info",
|
|
13892
|
-
data: `\u{1F4AC} New ${msg.type} message from ${msg.fromAgent} in thread ${msg.threadId} \u2014 call get_unread_messages to read`
|
|
13893
|
-
});
|
|
13894
|
-
}
|
|
13913
|
+
await runInboxPoll({
|
|
13914
|
+
getAgent: () => apiClient.getAgent(AGENT_ID),
|
|
13915
|
+
getTasks: () => apiClient.getTasks({
|
|
13916
|
+
repoId: REPO_ID,
|
|
13917
|
+
assignedTo: AGENT_ID,
|
|
13918
|
+
status: "assigned"
|
|
13919
|
+
}),
|
|
13920
|
+
getUnread: () => apiClient.getUnread(AGENT_ID, REPO_ID).then(
|
|
13921
|
+
(r) => r.data ?? []
|
|
13922
|
+
),
|
|
13923
|
+
seenTaskIds,
|
|
13924
|
+
seenMessageIds,
|
|
13925
|
+
notify: (data) => server.server.sendLoggingMessage({ level: "info", data })
|
|
13926
|
+
});
|
|
13895
13927
|
} catch (err) {
|
|
13896
13928
|
console.error("[relai-mcp] inbox poll failed:", err instanceof Error ? err.message : err);
|
|
13897
13929
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pitboss/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Connect your coding agents to a pitboss instance",
|
|
6
6
|
"keywords": [
|
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
"cli"
|
|
14
14
|
],
|
|
15
15
|
"license": "UNLICENSED",
|
|
16
|
-
"homepage": "https://github.com/
|
|
16
|
+
"homepage": "https://github.com/FunctionizeTeam/pitboss/tree/main/packages/cli",
|
|
17
17
|
"repository": {
|
|
18
18
|
"type": "git",
|
|
19
|
-
"url": "git+https://github.com/
|
|
19
|
+
"url": "git+https://github.com/FunctionizeTeam/pitboss.git",
|
|
20
20
|
"directory": "packages/cli"
|
|
21
21
|
},
|
|
22
22
|
"bugs": {
|
|
23
|
-
"url": "https://github.com/
|
|
23
|
+
"url": "https://github.com/FunctionizeTeam/pitboss/issues"
|
|
24
24
|
},
|
|
25
25
|
"publishConfig": {
|
|
26
26
|
"access": "public"
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@getrelai/claude-worker": "workspace:*",
|
|
46
46
|
"@getrelai/git": "workspace:*",
|
|
47
|
+
"@getrelai/types": "workspace:*",
|
|
47
48
|
"@inquirer/prompts": "^8.7.0",
|
|
48
49
|
"@types/node": "^22.0.0",
|
|
49
50
|
"chalk": "^5.3.0",
|