huddle-cli 0.1.0 → 0.1.3
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/CHANGELOG.md +69 -0
- package/QUICKSTART.md +135 -0
- package/README.md +26 -4
- package/dist/adapters/amp.js +1 -1
- package/dist/adapters/antigravity.js +2 -0
- package/dist/adapters/claude.js +19 -3
- package/dist/adapters/codex.js +9 -1
- package/dist/adapters/cursor.js +12 -5
- package/dist/adapters/custom.js +4 -2
- package/dist/adapters/droid.js +1 -0
- package/dist/adapters/goose.js +2 -0
- package/dist/adapters/index.js +44 -18
- package/dist/adapters/kiro.js +1 -1
- package/dist/adapters/opencode.js +1 -1
- package/dist/app/live.js +142 -0
- package/dist/app/page.js +701 -0
- package/dist/app/server.js +262 -0
- package/dist/args.js +18 -0
- package/dist/attach/connect.js +7 -4
- package/dist/attach/guard.js +21 -2
- package/dist/attach/live.js +5 -1
- package/dist/attach/plug.js +21 -3
- package/dist/attach/screen.js +150 -0
- package/dist/attach/session.js +7 -2
- package/dist/cli.js +134 -42
- package/dist/mcp/context-tools.js +79 -0
- package/dist/mcp/server.js +57 -11
- package/dist/orchestrator/ask.js +13 -10
- package/dist/orchestrator/brain.js +16 -4
- package/dist/orchestrator/budget.js +283 -0
- package/dist/orchestrator/busy.js +106 -0
- package/dist/orchestrator/council.js +434 -87
- package/dist/orchestrator/goal.js +196 -0
- package/dist/orchestrator/handoff.js +2 -2
- package/dist/orchestrator/launcher.js +31 -13
- package/dist/orchestrator/learn.js +101 -0
- package/dist/orchestrator/planner.js +54 -9
- package/dist/orchestrator/qa.js +71 -15
- package/dist/orchestrator/radar.js +719 -0
- package/dist/orchestrator/scheduler.js +575 -50
- package/dist/orchestrator/takeover.js +155 -0
- package/dist/platform.js +377 -0
- package/dist/room/context.js +1285 -0
- package/dist/room/contract.js +132 -0
- package/dist/room/db.js +79 -16
- package/dist/room/followup.js +233 -0
- package/dist/room/identity.js +2 -2
- package/dist/room/lessons.js +275 -0
- package/dist/room/live.js +75 -20
- package/dist/room/mail.js +225 -0
- package/dist/room/progress.js +40 -1
- package/dist/room/server.js +220 -39
- package/dist/room/tunnel.js +6 -5
- package/dist/room/user.js +54 -0
- package/dist/sandbox/sync.js +4 -4
- package/dist/sandbox/worktree.js +145 -14
- package/dist/ui/app.js +963 -159
- package/dist/ui/brief.js +494 -0
- package/dist/ui/catalog.js +62 -24
- package/dist/ui/dashboard.js +269 -55
- package/dist/ui/doctor.js +15 -11
- package/dist/ui/export.js +1194 -0
- package/dist/ui/lessons.js +81 -0
- package/dist/ui/narrator.js +56 -0
- package/dist/ui/paste.js +515 -0
- package/dist/ui/remote.js +11 -5
- package/dist/ui/setup.js +152 -0
- package/dist/ui/story.js +716 -94
- package/dist/ui/talk.js +176 -0
- package/dist/ui/term.js +182 -16
- package/dist/ui/transcript.js +434 -0
- package/dist/ui/watch.js +149 -48
- package/package.json +6 -3
package/dist/app/live.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// The bridge between a run and the dashboard (`huddle app`), which may be
|
|
2
|
+
// another process: the run can start in a terminal and be watched, answered
|
|
3
|
+
// and stopped from the browser, or start from the browser itself.
|
|
4
|
+
//
|
|
5
|
+
// .huddle/live.json the run's live state, rewritten every second: phase,
|
|
6
|
+
// narrator, each agent's task / % / tokens, the
|
|
7
|
+
// council so far, a question waiting on you
|
|
8
|
+
// .huddle/inbox/ what the dashboard sends the run: your answer to a
|
|
9
|
+
// question, a line for the agents, stop. One JSON file
|
|
10
|
+
// each; the run takes them within a second
|
|
11
|
+
// .huddle/app.json the dashboard is open (pid, url): a run with no
|
|
12
|
+
// terminal still asks you its questions there
|
|
13
|
+
//
|
|
14
|
+
// Files, not a socket: the council runs before the Room exists, and either
|
|
15
|
+
// side may start first. Everything here is local to the repo's .huddle/.
|
|
16
|
+
import fs from "node:fs";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
const dir = (root) => path.join(root, ".huddle");
|
|
19
|
+
export const livePath = (root) => path.join(dir(root), "live.json");
|
|
20
|
+
export const inboxDir = (root) => path.join(dir(root), "inbox");
|
|
21
|
+
export const appFile = (root) => path.join(dir(root), "app.json");
|
|
22
|
+
/** Is `pid` a process on this machine? */
|
|
23
|
+
export function alive(pid) {
|
|
24
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
25
|
+
return false;
|
|
26
|
+
try {
|
|
27
|
+
process.kill(pid, 0);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
return e?.code === "EPERM"; // exists, owned by someone else
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Write atomically: a reader never sees half a file. */
|
|
35
|
+
function writeJson(file, value) {
|
|
36
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
37
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
38
|
+
fs.writeFileSync(tmp, JSON.stringify(value));
|
|
39
|
+
fs.renameSync(tmp, file);
|
|
40
|
+
}
|
|
41
|
+
function readJson(file) {
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function writeLive(root, s) {
|
|
50
|
+
try {
|
|
51
|
+
writeJson(livePath(root), s);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
/* a read-only or vanished .huddle: the dashboard just won't see this run */
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** The run in progress, or null: none, finished, or its process is gone (a crash, a kill -9). */
|
|
58
|
+
export function readLive(root) {
|
|
59
|
+
const s = readJson(livePath(root));
|
|
60
|
+
if (!s || s.done || !alive(s.pid))
|
|
61
|
+
return null;
|
|
62
|
+
return s;
|
|
63
|
+
}
|
|
64
|
+
let seq = 0;
|
|
65
|
+
/** The dashboard's side: queue a command for the run. */
|
|
66
|
+
export function postCommand(root, c) {
|
|
67
|
+
const d = inboxDir(root);
|
|
68
|
+
fs.mkdirSync(d, { recursive: true });
|
|
69
|
+
// Sorted by name = oldest first: two commands in one millisecond keep their order (seen on Linux).
|
|
70
|
+
const name = `${String(Date.now()).padStart(15, "0")}-${String(seq++).padStart(6, "0")}-${process.pid}.json`;
|
|
71
|
+
writeJson(path.join(d, name), c);
|
|
72
|
+
}
|
|
73
|
+
/** The run's side: every queued command, oldest first, each taken once. */
|
|
74
|
+
export function takeCommands(root) {
|
|
75
|
+
const d = inboxDir(root);
|
|
76
|
+
let names = [];
|
|
77
|
+
try {
|
|
78
|
+
names = fs.readdirSync(d).filter((n) => n.endsWith(".json")).sort();
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
const out = [];
|
|
84
|
+
for (const n of names) {
|
|
85
|
+
const f = path.join(d, n);
|
|
86
|
+
const c = readJson(f);
|
|
87
|
+
try {
|
|
88
|
+
fs.unlinkSync(f);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
continue; // another taker got it
|
|
92
|
+
}
|
|
93
|
+
if (c && typeof c === "object" && ["answer", "say", "stop"].includes(c.type))
|
|
94
|
+
out.push(c);
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
/** Commands left from an earlier run must not steer this one. */
|
|
99
|
+
export function clearCommands(root) {
|
|
100
|
+
try {
|
|
101
|
+
for (const n of fs.readdirSync(inboxDir(root)))
|
|
102
|
+
fs.rmSync(path.join(inboxDir(root), n), { force: true });
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
/* no inbox yet */
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export function writeAppInfo(root, info) {
|
|
109
|
+
writeJson(appFile(root), info);
|
|
110
|
+
}
|
|
111
|
+
export function clearAppInfo(root) {
|
|
112
|
+
const cur = readJson(appFile(root));
|
|
113
|
+
if (cur?.pid === process.pid)
|
|
114
|
+
fs.rmSync(appFile(root), { force: true });
|
|
115
|
+
}
|
|
116
|
+
/** The dashboard that's open on this repo, if any. */
|
|
117
|
+
export function openApp(root) {
|
|
118
|
+
const a = readJson(appFile(root));
|
|
119
|
+
return a && alive(a.pid) ? a : null;
|
|
120
|
+
}
|
|
121
|
+
/** The run's WHAT YOU GOT write-up, kept for the dashboard: .huddle/reports/<session>.md */
|
|
122
|
+
const reportFile = (root, session) => path.join(dir(root), "reports", `${session.replace(/[^\w-]/g, "_")}.md`);
|
|
123
|
+
export function saveReport(root, session, text) {
|
|
124
|
+
try {
|
|
125
|
+
fs.mkdirSync(path.dirname(reportFile(root, session)), { recursive: true });
|
|
126
|
+
fs.writeFileSync(reportFile(root, session), text);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
/* the terminal had it; the dashboard just won't */
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
export function readReport(root, session) {
|
|
133
|
+
if (!session)
|
|
134
|
+
return null;
|
|
135
|
+
try {
|
|
136
|
+
return fs.readFileSync(reportFile(root, session), "utf8");
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=live.js.map
|