very-happy-cli 0.2.115 → 0.2.117
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/dist/{AcpBackend-DEZTWZZu.mjs → AcpBackend-C4RMHt0I.mjs} +1 -1
- package/dist/{AcpBackend-BrZN8KM-.cjs → AcpBackend-CrbXvcgQ.cjs} +1 -1
- package/dist/{AcpSessionManager-DjulpKY0.cjs → AcpSessionManager-CjAVO4RX.cjs} +1 -1
- package/dist/{AcpSessionManager-BzP3eiOG.mjs → AcpSessionManager-Cnc8Jatl.mjs} +1 -1
- package/dist/{config-CMgRBQMS.mjs → config-CnA3JIUn.mjs} +2 -2
- package/dist/{config-BNAsx9Wg.cjs → config-O9quMfxg.cjs} +2 -2
- package/dist/{index-B2KfhNku.mjs → index-B0o3H6_x.mjs} +2 -2
- package/dist/{index-BHNEb4bc.mjs → index-BYZKuQoy.mjs} +590 -41
- package/dist/{index-tPXwlc_t.cjs → index-DcESl4cI.cjs} +601 -43
- package/dist/{index-CpGR7rIw.mjs → index-DiRN0Rv8.mjs} +78 -8
- package/dist/{index-B3UkbonP.cjs → index-DqmVH6R5.cjs} +2 -2
- package/dist/{index-ClR0jhjm.cjs → index-W-O4xR_O.cjs} +77 -7
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{installTerminalHooks-DD6QuHfT.mjs → installTerminalHooks-DeYqyjCB.mjs} +2 -2
- package/dist/{installTerminalHooks-CWfCF68p.cjs → installTerminalHooks-zfsWSotO.cjs} +2 -2
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.cts +66 -1
- package/dist/lib.d.mts +66 -1
- package/dist/lib.mjs +1 -1
- package/dist/{mcp-CiNrjYox.mjs → mcp-D_5aOK0I.mjs} +62 -17
- package/dist/{mcp-CqjiDRnG.cjs → mcp-nYqC4FYm.cjs} +62 -16
- package/dist/{runGemini-DdJqpywr.mjs → runGemini-CEURRmM4.mjs} +4 -4
- package/dist/{runGemini-UN5IznJp.cjs → runGemini-DYf3ntib.cjs} +4 -4
- package/dist/{runOpenClaw-CM5UrVBy.mjs → runOpenClaw-BozfKWng.mjs} +3 -3
- package/dist/{runOpenClaw-9hhfGllZ.cjs → runOpenClaw-CNKjAw-H.cjs} +3 -3
- package/dist/{send-CnVtXOj0.cjs → send-BZLhRZb-.cjs} +2 -2
- package/dist/{send-C6G2jsQp.mjs → send-CYznZGrp.mjs} +2 -2
- package/dist/sessions-DYPB3Peg.mjs +317 -0
- package/dist/sessions-YWt9YEN8.cjs +322 -0
- package/dist/{spawn-qb344Y5E.mjs → spawn-CsrHZd3y.mjs} +3 -4
- package/dist/{spawn-CfYddkS_.cjs → spawn-tSvo3stN.cjs} +6 -7
- package/dist/{types-CpnzfHHS.cjs → types-DD3AK6Nb.cjs} +189 -26
- package/dist/{types-B3qx8Ga1.mjs → types-jSfU_0o1.mjs} +187 -26
- package/package.json +7 -7
- package/dist/sessions-CGdcF05T.cjs +0 -204
- package/dist/sessions-CmabJzYS.mjs +0 -200
|
@@ -29,7 +29,7 @@ import { SandboxManager } from '@anthropic-ai/sandbox-runtime';
|
|
|
29
29
|
import { Expo } from 'expo-server-sdk';
|
|
30
30
|
|
|
31
31
|
var name = "very-happy-cli";
|
|
32
|
-
var version = "0.2.
|
|
32
|
+
var version = "0.2.117";
|
|
33
33
|
var description = "Remote multi-agent workspace CLI with deep Claude integration, native terminal streaming, and self-hosting.";
|
|
34
34
|
var author = "Kirill Dubovitskiy";
|
|
35
35
|
var contributors = [
|
|
@@ -142,12 +142,12 @@ var dependencies = {
|
|
|
142
142
|
zod: "^4.0.0"
|
|
143
143
|
};
|
|
144
144
|
var optionalDependencies = {
|
|
145
|
-
"very-happy-tools-arm64-darwin": "0.2.
|
|
146
|
-
"very-happy-tools-x64-darwin": "0.2.
|
|
147
|
-
"very-happy-tools-arm64-linux": "0.2.
|
|
148
|
-
"very-happy-tools-x64-linux": "0.2.
|
|
149
|
-
"very-happy-tools-arm64-win32": "0.2.
|
|
150
|
-
"very-happy-tools-x64-win32": "0.2.
|
|
145
|
+
"very-happy-tools-arm64-darwin": "0.2.117",
|
|
146
|
+
"very-happy-tools-x64-darwin": "0.2.117",
|
|
147
|
+
"very-happy-tools-arm64-linux": "0.2.117",
|
|
148
|
+
"very-happy-tools-x64-linux": "0.2.117",
|
|
149
|
+
"very-happy-tools-arm64-win32": "0.2.117",
|
|
150
|
+
"very-happy-tools-x64-win32": "0.2.117"
|
|
151
151
|
};
|
|
152
152
|
var devDependencies = {
|
|
153
153
|
"@slopus/happy-wire": "workspace:*",
|
|
@@ -1017,6 +1017,8 @@ z$1.object({
|
|
|
1017
1017
|
codex: z$1.boolean(),
|
|
1018
1018
|
gemini: z$1.boolean(),
|
|
1019
1019
|
openclaw: z$1.boolean(),
|
|
1020
|
+
pi: z$1.boolean().optional(),
|
|
1021
|
+
// absent on daemons that predate the pi runner
|
|
1020
1022
|
detectedAt: z$1.number()
|
|
1021
1023
|
}).optional(),
|
|
1022
1024
|
resumeSupport: z$1.object({
|
|
@@ -1054,7 +1056,14 @@ const CliUpdateStateSchema = z$1.object({
|
|
|
1054
1056
|
* serving the old code. Present only while that is true. Optional and additive:
|
|
1055
1057
|
* old web builds ignore it, old daemons never send it (iron rule 4).
|
|
1056
1058
|
*/
|
|
1057
|
-
handoverHold: z$1.object({ reason: z$1.string(), at: z$1.number() }).nullable().optional()
|
|
1059
|
+
handoverHold: z$1.object({ reason: z$1.string(), at: z$1.number() }).nullable().optional(),
|
|
1060
|
+
/** B-327: the last thing the machine's auto-update did, or why it did nothing. */
|
|
1061
|
+
autoUpdate: z$1.object({
|
|
1062
|
+
state: z$1.union([z$1.enum(["installing", "installed", "failed", "skipped"]), z$1.string()]),
|
|
1063
|
+
version: z$1.string().nullable(),
|
|
1064
|
+
detail: z$1.string().optional(),
|
|
1065
|
+
at: z$1.number()
|
|
1066
|
+
}).nullable().optional()
|
|
1058
1067
|
});
|
|
1059
1068
|
const WebTerminalListItemSchema = z$1.object({
|
|
1060
1069
|
id: z$1.string(),
|
|
@@ -9834,6 +9843,49 @@ class SnapshotStore {
|
|
|
9834
9843
|
}
|
|
9835
9844
|
}
|
|
9836
9845
|
|
|
9846
|
+
const OUTPUT_COALESCE_MS = 16;
|
|
9847
|
+
const OUTPUT_COALESCE_MAX_BYTES = 64 * 1024;
|
|
9848
|
+
function createOutputCoalescer({ maxDelayMs, maxBytes } = {
|
|
9849
|
+
maxDelayMs: OUTPUT_COALESCE_MS,
|
|
9850
|
+
maxBytes: OUTPUT_COALESCE_MAX_BYTES
|
|
9851
|
+
}) {
|
|
9852
|
+
let buffered = [];
|
|
9853
|
+
let bufferedBytes = 0;
|
|
9854
|
+
let firstBufferedAt = 0;
|
|
9855
|
+
let lastEmitAt = Number.NEGATIVE_INFINITY;
|
|
9856
|
+
const drain = (now) => {
|
|
9857
|
+
if (bufferedBytes === 0) return null;
|
|
9858
|
+
const out = buffered.length === 1 ? buffered[0] : Buffer.concat(buffered, bufferedBytes);
|
|
9859
|
+
buffered = [];
|
|
9860
|
+
bufferedBytes = 0;
|
|
9861
|
+
firstBufferedAt = 0;
|
|
9862
|
+
lastEmitAt = now;
|
|
9863
|
+
return out;
|
|
9864
|
+
};
|
|
9865
|
+
return {
|
|
9866
|
+
push(data, now) {
|
|
9867
|
+
if (data.length === 0) return null;
|
|
9868
|
+
if (bufferedBytes === 0 && now - lastEmitAt >= maxDelayMs) {
|
|
9869
|
+
lastEmitAt = now;
|
|
9870
|
+
return data;
|
|
9871
|
+
}
|
|
9872
|
+
if (bufferedBytes === 0) firstBufferedAt = now;
|
|
9873
|
+
buffered.push(data);
|
|
9874
|
+
bufferedBytes += data.length;
|
|
9875
|
+
return bufferedBytes >= maxBytes ? drain(now) : null;
|
|
9876
|
+
},
|
|
9877
|
+
flush(now) {
|
|
9878
|
+
return drain(now);
|
|
9879
|
+
},
|
|
9880
|
+
dueAt() {
|
|
9881
|
+
return bufferedBytes === 0 ? null : firstBufferedAt + maxDelayMs;
|
|
9882
|
+
},
|
|
9883
|
+
pendingBytes() {
|
|
9884
|
+
return bufferedBytes;
|
|
9885
|
+
}
|
|
9886
|
+
};
|
|
9887
|
+
}
|
|
9888
|
+
|
|
9837
9889
|
const MAX_SEND_KEYS_PAYLOAD_BYTES = 1e3;
|
|
9838
9890
|
function tmuxSingleQuote(text) {
|
|
9839
9891
|
return `'${text.split("'").join("'\\''")}'`;
|
|
@@ -10744,6 +10796,18 @@ class TerminalSession {
|
|
|
10744
10796
|
lastOutputAt;
|
|
10745
10797
|
cols;
|
|
10746
10798
|
rows;
|
|
10799
|
+
/**
|
|
10800
|
+
* B-334: `%output` arrives as one message per producer write — pi's startup
|
|
10801
|
+
* frame measured 1029 chunks of a median 9 bytes. Merging them before they
|
|
10802
|
+
* are given a seq is what keeps the wire (and the web's write chain) from
|
|
10803
|
+
* carrying a thousand frames for 12 KB. See outputCoalescer.ts.
|
|
10804
|
+
*/
|
|
10805
|
+
coalescer = createOutputCoalescer();
|
|
10806
|
+
flushTimer = null;
|
|
10807
|
+
/** Set by the manager right after construction: how a merged chunk reaches
|
|
10808
|
+
* the subscribers. Left undefined ⇒ the session buffers and drops nothing,
|
|
10809
|
+
* it just has nobody to tell. */
|
|
10810
|
+
onOutputChunk;
|
|
10747
10811
|
constructor(id, transport, tmuxSession, cols, rows) {
|
|
10748
10812
|
this.id = id;
|
|
10749
10813
|
this.transport = transport;
|
|
@@ -10790,6 +10854,61 @@ class TerminalSession {
|
|
|
10790
10854
|
}
|
|
10791
10855
|
return chunk;
|
|
10792
10856
|
}
|
|
10857
|
+
/**
|
|
10858
|
+
* The live-output entry point (B-334). Every `%output` / pty chunk goes
|
|
10859
|
+
* through here instead of straight to `ingest`: the coalescer decides
|
|
10860
|
+
* whether this byte run is emitted now (the common interactive case — an
|
|
10861
|
+
* echo after an idle window pays zero extra latency) or merged into the
|
|
10862
|
+
* frame currently forming. Merging is byte-exact concatenation, so one seq
|
|
10863
|
+
* covering the merged run is indistinguishable from tmux having handed us
|
|
10864
|
+
* one bigger read — the ring/replay/gap protocol is untouched.
|
|
10865
|
+
*/
|
|
10866
|
+
pushOutput(data) {
|
|
10867
|
+
const ready = this.coalescer.push(data, Date.now());
|
|
10868
|
+
if (ready) {
|
|
10869
|
+
this.clearFlushTimer();
|
|
10870
|
+
this.emitOutput(ready);
|
|
10871
|
+
}
|
|
10872
|
+
this.armFlushTimer();
|
|
10873
|
+
}
|
|
10874
|
+
/**
|
|
10875
|
+
* Emit whatever is buffered right now. MUST be called before anything that
|
|
10876
|
+
* reads or changes the screen's identity, because buffered bytes have not
|
|
10877
|
+
* been ingested yet and therefore own no seq:
|
|
10878
|
+
* - the capture anchor (`runCaptureBatch`) — pre-anchor output is already
|
|
10879
|
+
* inside the capture, so it has to own a seq <= `seqAtAnchor` or the
|
|
10880
|
+
* client applies it a second time on top of the restore;
|
|
10881
|
+
* - `resizeHeadless` — those bytes were produced at the OLD size;
|
|
10882
|
+
* - transport exit — otherwise the last frame before a shell dies is lost.
|
|
10883
|
+
*/
|
|
10884
|
+
flushOutput() {
|
|
10885
|
+
this.clearFlushTimer();
|
|
10886
|
+
const ready = this.coalescer.flush(Date.now());
|
|
10887
|
+
if (ready) this.emitOutput(ready);
|
|
10888
|
+
}
|
|
10889
|
+
emitOutput(data) {
|
|
10890
|
+
const chunk = this.ingest(data);
|
|
10891
|
+
this.onOutputChunk?.(chunk);
|
|
10892
|
+
}
|
|
10893
|
+
armFlushTimer() {
|
|
10894
|
+
const due = this.coalescer.dueAt();
|
|
10895
|
+
if (due === null) {
|
|
10896
|
+
this.clearFlushTimer();
|
|
10897
|
+
return;
|
|
10898
|
+
}
|
|
10899
|
+
if (this.flushTimer) return;
|
|
10900
|
+
const timer = setTimeout(() => {
|
|
10901
|
+
this.flushTimer = null;
|
|
10902
|
+
this.flushOutput();
|
|
10903
|
+
}, Math.max(0, due - Date.now()));
|
|
10904
|
+
timer.unref?.();
|
|
10905
|
+
this.flushTimer = timer;
|
|
10906
|
+
}
|
|
10907
|
+
clearFlushTimer() {
|
|
10908
|
+
if (!this.flushTimer) return;
|
|
10909
|
+
clearTimeout(this.flushTimer);
|
|
10910
|
+
this.flushTimer = null;
|
|
10911
|
+
}
|
|
10793
10912
|
/**
|
|
10794
10913
|
* Write a RESTORE payload (the open capture) into the authoritative screen
|
|
10795
10914
|
* WITHOUT giving it a seq: it is not a live chunk, so it must not enter the
|
|
@@ -10809,6 +10928,7 @@ class TerminalSession {
|
|
|
10809
10928
|
/** Keep the authoritative screen's dimensions in lockstep with the terminal
|
|
10810
10929
|
* so a later snapshot serialize() reflects the real geometry. */
|
|
10811
10930
|
resizeHeadless(cols, rows) {
|
|
10931
|
+
this.flushOutput();
|
|
10812
10932
|
this.cols = cols;
|
|
10813
10933
|
this.rows = rows;
|
|
10814
10934
|
try {
|
|
@@ -10888,6 +11008,7 @@ class TerminalSession {
|
|
|
10888
11008
|
} catch {
|
|
10889
11009
|
}
|
|
10890
11010
|
}
|
|
11011
|
+
this.clearFlushTimer();
|
|
10891
11012
|
try {
|
|
10892
11013
|
this.headless.dispose();
|
|
10893
11014
|
} catch {
|
|
@@ -11739,14 +11860,16 @@ class WebTerminalManager {
|
|
|
11739
11860
|
client: void 0
|
|
11740
11861
|
// replaced below
|
|
11741
11862
|
}, tmuxSession, cols, rows);
|
|
11863
|
+
created.onOutputChunk = (chunk) => {
|
|
11864
|
+
this.emit("terminal-output", { terminalId: id, data: chunk.data, seq: chunk.seq });
|
|
11865
|
+
this.noteActivity();
|
|
11866
|
+
};
|
|
11742
11867
|
const client = new ControlClient(tmuxSession, env, {
|
|
11743
11868
|
onOutput: (pane, data) => {
|
|
11744
11869
|
if (this.terminals.get(id) !== created) return;
|
|
11745
11870
|
if (!created.ingesting) return;
|
|
11746
11871
|
if (created.paneId && pane !== created.paneId) return;
|
|
11747
|
-
|
|
11748
|
-
this.emit("terminal-output", { terminalId: id, data: chunk.data, seq: chunk.seq });
|
|
11749
|
-
this.noteActivity();
|
|
11872
|
+
created.pushOutput(data);
|
|
11750
11873
|
},
|
|
11751
11874
|
onNotification: (name, args2) => {
|
|
11752
11875
|
if (this.terminals.get(id) !== created) return;
|
|
@@ -11768,6 +11891,7 @@ class WebTerminalManager {
|
|
|
11768
11891
|
},
|
|
11769
11892
|
onExit: (code) => {
|
|
11770
11893
|
if (this.terminals.get(id) !== created) return;
|
|
11894
|
+
created.flushOutput();
|
|
11771
11895
|
this.terminals.delete(id);
|
|
11772
11896
|
this.snapshots.drop(id);
|
|
11773
11897
|
created.dispose();
|
|
@@ -11788,14 +11912,17 @@ class WebTerminalManager {
|
|
|
11788
11912
|
}
|
|
11789
11913
|
}
|
|
11790
11914
|
const created = new TerminalSession(id, { kind: "pty", pty: proc }, void 0, cols, rows);
|
|
11791
|
-
|
|
11792
|
-
if (this.terminals.get(id) !== created) return;
|
|
11793
|
-
const chunk = created.ingest(Buffer.from(data, "utf8"));
|
|
11915
|
+
created.onOutputChunk = (chunk) => {
|
|
11794
11916
|
this.emit("terminal-output", { terminalId: id, data: chunk.data, seq: chunk.seq });
|
|
11795
11917
|
this.noteActivity();
|
|
11918
|
+
};
|
|
11919
|
+
proc.onData((data) => {
|
|
11920
|
+
if (this.terminals.get(id) !== created) return;
|
|
11921
|
+
created.pushOutput(Buffer.from(data, "utf8"));
|
|
11796
11922
|
});
|
|
11797
11923
|
proc.onExit(({ exitCode }) => {
|
|
11798
11924
|
if (this.terminals.get(id) !== created) return;
|
|
11925
|
+
created.flushOutput();
|
|
11799
11926
|
this.terminals.delete(id);
|
|
11800
11927
|
this.snapshots.drop(id);
|
|
11801
11928
|
created.dispose();
|
|
@@ -11939,6 +12066,7 @@ class WebTerminalManager {
|
|
|
11939
12066
|
command: c.command,
|
|
11940
12067
|
label: c.key,
|
|
11941
12068
|
onBlock: c.key === "anchor" ? () => {
|
|
12069
|
+
session.flushOutput();
|
|
11942
12070
|
seqAtAnchor = session.seq;
|
|
11943
12071
|
if (fresh) session.ingesting = true;
|
|
11944
12072
|
} : void 0
|
|
@@ -12605,6 +12733,19 @@ function commandExists(command) {
|
|
|
12605
12733
|
return false;
|
|
12606
12734
|
}
|
|
12607
12735
|
}
|
|
12736
|
+
function commandExistsWindows(name) {
|
|
12737
|
+
try {
|
|
12738
|
+
execSync(`powershell -NoProfile -Command "Get-Command ${name} -ErrorAction SilentlyContinue"`, { stdio: "ignore", windowsHide: true });
|
|
12739
|
+
return true;
|
|
12740
|
+
} catch {
|
|
12741
|
+
return false;
|
|
12742
|
+
}
|
|
12743
|
+
}
|
|
12744
|
+
const SAFE_COMMAND_NAME = /^[A-Za-z0-9._-]+$/;
|
|
12745
|
+
function commandOnPath(name) {
|
|
12746
|
+
if (!SAFE_COMMAND_NAME.test(name)) return false;
|
|
12747
|
+
return os.platform() === "win32" ? commandExistsWindows(name) : commandExists(name);
|
|
12748
|
+
}
|
|
12608
12749
|
function detectPosix() {
|
|
12609
12750
|
const claude = commandExists("claude");
|
|
12610
12751
|
const codex = commandExists("codex");
|
|
@@ -12613,17 +12754,11 @@ function detectPosix() {
|
|
|
12613
12754
|
const openclawConfig = existsSync$1(join$1(os.homedir(), ".openclaw", "openclaw.json"));
|
|
12614
12755
|
const openclawEnv = !!process.env.OPENCLAW_GATEWAY_URL;
|
|
12615
12756
|
const openclaw = openclawCommand || openclawConfig || openclawEnv;
|
|
12616
|
-
|
|
12757
|
+
const pi = commandExists("pi") && commandExists("pi-acp");
|
|
12758
|
+
return { claude, codex, gemini, openclaw, pi, detectedAt: Date.now() };
|
|
12617
12759
|
}
|
|
12618
12760
|
function detectWindows() {
|
|
12619
|
-
const checkCommand =
|
|
12620
|
-
try {
|
|
12621
|
-
execSync(`powershell -NoProfile -Command "Get-Command ${name} -ErrorAction SilentlyContinue"`, { stdio: "ignore", windowsHide: true });
|
|
12622
|
-
return true;
|
|
12623
|
-
} catch {
|
|
12624
|
-
return false;
|
|
12625
|
-
}
|
|
12626
|
-
};
|
|
12761
|
+
const checkCommand = commandExistsWindows;
|
|
12627
12762
|
const claude = checkCommand("claude");
|
|
12628
12763
|
const codex = checkCommand("codex");
|
|
12629
12764
|
const gemini = checkCommand("gemini");
|
|
@@ -12631,7 +12766,8 @@ function detectWindows() {
|
|
|
12631
12766
|
const openclawConfig = existsSync$1(join$1(process.env.USERPROFILE || os.homedir(), ".openclaw", "openclaw.json"));
|
|
12632
12767
|
const openclawEnv = !!process.env.OPENCLAW_GATEWAY_URL;
|
|
12633
12768
|
const openclaw = openclawCommand || openclawConfig || openclawEnv;
|
|
12634
|
-
|
|
12769
|
+
const pi = checkCommand("pi") && checkCommand("pi-acp");
|
|
12770
|
+
return { claude, codex, gemini, openclaw, pi, detectedAt: Date.now() };
|
|
12635
12771
|
}
|
|
12636
12772
|
|
|
12637
12773
|
const AgentCredentialsSchema = z$1.object({
|
|
@@ -14409,6 +14545,15 @@ class ApiMachineClient {
|
|
|
14409
14545
|
// recover account material or impersonate a Web client. (Declared before
|
|
14410
14546
|
// webTerminal so the emit closure can read it.)
|
|
14411
14547
|
encTerminals = /* @__PURE__ */ new Set();
|
|
14548
|
+
/** B-327: an auto-update swaps the process that owns these terminals, so it
|
|
14549
|
+
* waits until there are none. */
|
|
14550
|
+
hasLiveTerminals() {
|
|
14551
|
+
try {
|
|
14552
|
+
return this.webTerminal.buildTerminalList().length > 0;
|
|
14553
|
+
} catch {
|
|
14554
|
+
return true;
|
|
14555
|
+
}
|
|
14556
|
+
}
|
|
14412
14557
|
webTerminal = new WebTerminalManager((event, payload) => {
|
|
14413
14558
|
let out = payload;
|
|
14414
14559
|
if (event === "terminal-output" && this.encTerminals.has(payload.terminalId)) {
|
|
@@ -14431,6 +14576,22 @@ class ApiMachineClient {
|
|
|
14431
14576
|
event: n.event
|
|
14432
14577
|
});
|
|
14433
14578
|
});
|
|
14579
|
+
/**
|
|
14580
|
+
* The CLI availability last advertised in machine metadata (re-probed by
|
|
14581
|
+
* the keep-alive). Null until the first keep-alive tick; the daemon falls
|
|
14582
|
+
* back to its startup probe then.
|
|
14583
|
+
*/
|
|
14584
|
+
getCLIAvailability() {
|
|
14585
|
+
return this.lastKnownCLIAvailability;
|
|
14586
|
+
}
|
|
14587
|
+
/**
|
|
14588
|
+
* Title a web terminal from inside it (`very-happy mcp` change_title →
|
|
14589
|
+
* daemon /terminal-title → here). Same tmux truthfulness as the
|
|
14590
|
+
* `set-terminal-title` RPC.
|
|
14591
|
+
*/
|
|
14592
|
+
setTerminalTitle(terminalId, title, ifAbsent) {
|
|
14593
|
+
return this.webTerminal.setTitle(terminalId, title, ifAbsent);
|
|
14594
|
+
}
|
|
14434
14595
|
/**
|
|
14435
14596
|
* Push text to the clipboard of every web client the user has open
|
|
14436
14597
|
* (terminal-path claude → `very-happy mcp` → daemon /clipboard → here).
|
|
@@ -15239,7 +15400,7 @@ class ApiMachineClient {
|
|
|
15239
15400
|
const prev = this.lastKnownCLIAvailability;
|
|
15240
15401
|
const newResumeSupport = detectResumeSupport();
|
|
15241
15402
|
const prevResume = this.lastKnownResumeSupport;
|
|
15242
|
-
const cliAvailabilityChanged = !prev || prev.claude !== newAvailability.claude || prev.codex !== newAvailability.codex || prev.gemini !== newAvailability.gemini || prev.openclaw !== newAvailability.openclaw;
|
|
15403
|
+
const cliAvailabilityChanged = !prev || prev.claude !== newAvailability.claude || prev.codex !== newAvailability.codex || prev.gemini !== newAvailability.gemini || prev.openclaw !== newAvailability.openclaw || prev.pi !== newAvailability.pi;
|
|
15243
15404
|
const resumeSupportChanged = !prevResume || prevResume.rpcAvailable !== newResumeSupport.rpcAvailable || prevResume.happyAgentAuthenticated !== newResumeSupport.happyAgentAuthenticated;
|
|
15244
15405
|
if (cliAvailabilityChanged || resumeSupportChanged) {
|
|
15245
15406
|
this.lastKnownCLIAvailability = newAvailability;
|
|
@@ -16462,4 +16623,4 @@ const RawJSONLinesSchema = z$1.discriminatedUnion("type", [
|
|
|
16462
16623
|
}).passthrough()
|
|
16463
16624
|
]);
|
|
16464
16625
|
|
|
16465
|
-
export {
|
|
16626
|
+
export { MIRROR_BACKFILL_LINES_DEFAULT as $, ApiClient as A, readCredentials as B, credentialRelayProblem as C, deriveLocalCliUpdateSummary as D, updateSettings as E, encodeBase64Url as F, decodeBase64 as G, writeCredentialsLegacy as H, InvalidateSync as I, writeCredentialsDataKey as J, readCredentialsForConfiguredRelay as K, encrypt as L, MAX_CHAT_ATTACHMENT_SOURCE_BYTES as M, isClaudeSessionId as N, getLocalHappyAgentCredentialPath as O, readLocalHappyAgentCredentials as P, decryptWithDataKey as Q, RawJSONLinesSchema as R, STREAM_DELTA_MAX_CHARS as S, decryptLegacy as T, compareExactVersions as U, extractCompleteLines as V, decideBackfill as W, parseTerminalHookPayload as X, tmuxArgs as Y, scrubTmuxClientEnv as Z, decideMirrorBinding as _, ApiSessionClient as a, persistSession as a0, mirrorLineKey as a1, mirrorLocalId as a2, detectCLIAvailability as a3, detectResumeSupport as a4, acquireDaemonLock as a5, summarizeSpawnSessionForLog as a6, deletePersistedSessions as a7, shellescape as a8, decrypt as a9, writeDaemonState as aa, writeSettings as ab, fetchCliUpdateState as ac, resolveCliUpdateCheckInterval as ad, DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS as ae, releaseDaemonLock as af, listVhTerminals as ag, isValidTerminalId as ah, readVhTerminal as ai, sendToVhTerminal as aj, TITLE_PROMPT_PREFIX as ak, BOARD_ANALYZER_PROMPT_PREFIX as al, startOfflineReconnection as am, clearCredentials as an, clearMachineId as ao, SandboxConfigSchema as ap, CodexAppServerClient as aq, getLatestDaemonLog as ar, persistence as as, api as at, createEnvelope as b, configuration as c, contentLogMetadata as d, errorLogMetadata as e, packageJson as f, delay as g, connectionState as h, isValidSessionId as i, readSettings as j, encodeBase64 as k, logger as l, ensurePrivateDirectorySync as m, hardenPrivateFileSync as n, readDaemonState as o, projectPath as p, clearDaemonState as q, readPersistedSessions as r, getProjectPath as s, initializeSandbox as t, wrapCommand as u, AsyncLock as v, writePrivateFileSync as w, ensurePrivateDirectory as x, hardenPrivateFile as y, commandOnPath as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "very-happy-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.117",
|
|
4
4
|
"description": "Remote multi-agent workspace CLI with deep Claude integration, native terminal streaming, and self-hosting.",
|
|
5
5
|
"author": "Kirill Dubovitskiy",
|
|
6
6
|
"contributors": [
|
|
@@ -113,12 +113,12 @@
|
|
|
113
113
|
"zod": "^4.0.0"
|
|
114
114
|
},
|
|
115
115
|
"optionalDependencies": {
|
|
116
|
-
"very-happy-tools-arm64-darwin": "0.2.
|
|
117
|
-
"very-happy-tools-x64-darwin": "0.2.
|
|
118
|
-
"very-happy-tools-arm64-linux": "0.2.
|
|
119
|
-
"very-happy-tools-x64-linux": "0.2.
|
|
120
|
-
"very-happy-tools-arm64-win32": "0.2.
|
|
121
|
-
"very-happy-tools-x64-win32": "0.2.
|
|
116
|
+
"very-happy-tools-arm64-darwin": "0.2.117",
|
|
117
|
+
"very-happy-tools-x64-darwin": "0.2.117",
|
|
118
|
+
"very-happy-tools-arm64-linux": "0.2.117",
|
|
119
|
+
"very-happy-tools-x64-linux": "0.2.117",
|
|
120
|
+
"very-happy-tools-arm64-win32": "0.2.117",
|
|
121
|
+
"very-happy-tools-x64-win32": "0.2.117"
|
|
122
122
|
},
|
|
123
123
|
"devDependencies": {
|
|
124
124
|
"@slopus/happy-wire": "workspace:*",
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var chalk = require('chalk');
|
|
4
|
-
var index = require('./index-tPXwlc_t.cjs');
|
|
5
|
-
var persistence = require('./types-CpnzfHHS.cjs');
|
|
6
|
-
require('node:os');
|
|
7
|
-
require('node:crypto');
|
|
8
|
-
require('cross-spawn');
|
|
9
|
-
require('node:path');
|
|
10
|
-
require('node:readline');
|
|
11
|
-
require('node:fs');
|
|
12
|
-
require('./agentGuidance-BifkGXdE.cjs');
|
|
13
|
-
require('node:fs/promises');
|
|
14
|
-
require('fs/promises');
|
|
15
|
-
require('ink');
|
|
16
|
-
require('react');
|
|
17
|
-
require('@anthropic-ai/claude-agent-sdk');
|
|
18
|
-
require('axios');
|
|
19
|
-
require('node:events');
|
|
20
|
-
require('socket.io-client');
|
|
21
|
-
require('tweetnacl');
|
|
22
|
-
require('os');
|
|
23
|
-
require('child_process');
|
|
24
|
-
require('@paralleldrive/cuid2');
|
|
25
|
-
require('node:child_process');
|
|
26
|
-
require('fs');
|
|
27
|
-
require('path');
|
|
28
|
-
require('@anthropic-ai/sandbox-runtime');
|
|
29
|
-
require('expo-server-sdk');
|
|
30
|
-
require('crypto');
|
|
31
|
-
require('ps-list');
|
|
32
|
-
require('tmp');
|
|
33
|
-
require('open');
|
|
34
|
-
require('fastify');
|
|
35
|
-
require('zod');
|
|
36
|
-
require('fastify-type-provider-zod');
|
|
37
|
-
require('node:module');
|
|
38
|
-
require('@modelcontextprotocol/sdk/server/mcp.js');
|
|
39
|
-
require('node:http');
|
|
40
|
-
require('@modelcontextprotocol/sdk/server/streamableHttp.js');
|
|
41
|
-
require('./limits-KdWKmwtH.cjs');
|
|
42
|
-
require('http');
|
|
43
|
-
require('util');
|
|
44
|
-
require('inquirer');
|
|
45
|
-
require('node:url');
|
|
46
|
-
require('node:readline/promises');
|
|
47
|
-
require('node:util');
|
|
48
|
-
require('url');
|
|
49
|
-
require('node-pty');
|
|
50
|
-
|
|
51
|
-
const ACTIONS_NEEDING_ID = /* @__PURE__ */ new Set(["read", "stop", "archive"]);
|
|
52
|
-
function parseSessionsArgs(args) {
|
|
53
|
-
const options = { action: "help", json: false };
|
|
54
|
-
let actionSeen = false;
|
|
55
|
-
const positionals = [];
|
|
56
|
-
for (let i = 0; i < args.length; i++) {
|
|
57
|
-
const arg = args[i];
|
|
58
|
-
if (arg === "--help" || arg === "-h") {
|
|
59
|
-
return { action: "help", json: false };
|
|
60
|
-
} else if (arg === "--json") {
|
|
61
|
-
options.json = true;
|
|
62
|
-
} else if (arg === "--tag") {
|
|
63
|
-
const value = args[++i];
|
|
64
|
-
if (value === void 0) throw new Error("--tag requires a value");
|
|
65
|
-
options.tag = value;
|
|
66
|
-
} else if (arg === "--limit") {
|
|
67
|
-
const value = args[++i];
|
|
68
|
-
if (value === void 0) throw new Error("--limit requires a value");
|
|
69
|
-
const parsed = Number(value);
|
|
70
|
-
if (!Number.isInteger(parsed) || parsed < 1) throw new Error("--limit must be a positive integer");
|
|
71
|
-
options.limit = parsed;
|
|
72
|
-
} else if (arg.startsWith("-")) {
|
|
73
|
-
throw new Error(`Unknown argument: ${arg}`);
|
|
74
|
-
} else if (!actionSeen) {
|
|
75
|
-
if (arg !== "list" && arg !== "read" && arg !== "stop" && arg !== "archive") {
|
|
76
|
-
throw new Error(`Unknown action: ${arg} (expected list, read, stop or archive)`);
|
|
77
|
-
}
|
|
78
|
-
options.action = arg;
|
|
79
|
-
actionSeen = true;
|
|
80
|
-
} else {
|
|
81
|
-
positionals.push(arg);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
if (!actionSeen) return { action: "help", json: false };
|
|
85
|
-
if (positionals.length > 1) throw new Error(`Unexpected extra argument: ${positionals[1]}`);
|
|
86
|
-
if (ACTIONS_NEEDING_ID.has(options.action)) {
|
|
87
|
-
const id = positionals[0];
|
|
88
|
-
if (id === void 0) throw new Error(`${options.action} requires a session id`);
|
|
89
|
-
if (!persistence.isValidSessionId(id)) throw new Error(`Invalid session id: ${id}`);
|
|
90
|
-
options.sessionId = id;
|
|
91
|
-
} else if (positionals.length > 0) {
|
|
92
|
-
throw new Error(`Unexpected argument: ${positionals[0]}`);
|
|
93
|
-
}
|
|
94
|
-
if (options.action !== "list" && options.tag !== void 0) {
|
|
95
|
-
throw new Error("--tag only applies to `sessions list`");
|
|
96
|
-
}
|
|
97
|
-
return options;
|
|
98
|
-
}
|
|
99
|
-
const SESSIONS_HELP = `
|
|
100
|
-
${chalk.bold("very-happy sessions")} - Inspect and control sessions on this machine (for automation)
|
|
101
|
-
|
|
102
|
-
${chalk.bold("Usage:")}
|
|
103
|
-
very-happy sessions list [--tag <name>] [--limit <n>] [--json]
|
|
104
|
-
very-happy sessions read <id> [--limit <n>] [--json]
|
|
105
|
-
very-happy sessions stop <id> [--json]
|
|
106
|
-
very-happy sessions archive <id> [--json]
|
|
107
|
-
|
|
108
|
-
${chalk.bold("Actions:")}
|
|
109
|
-
list Running sessions plus recently seen ones, newest first.
|
|
110
|
-
read The tail of a session as a role-tagged transcript.
|
|
111
|
-
stop SIGTERM the session's process via the local daemon.
|
|
112
|
-
archive Mark the session inactive server-side (it stays resumable).
|
|
113
|
-
|
|
114
|
-
${chalk.bold("Options:")}
|
|
115
|
-
--tag <name> list only: keep sessions carrying this origin tag (see
|
|
116
|
-
\`spawn --spawned-by\`).
|
|
117
|
-
--limit <n> list: how many NOT-running sessions to include (default ${index.DEFAULT_RECENT_LIMIT};
|
|
118
|
-
running ones are never cut). read: how many messages
|
|
119
|
-
(default 20, max ${index.MAX_READ_LIMIT}).
|
|
120
|
-
--json Machine-readable output.
|
|
121
|
-
-h, --help Show this help
|
|
122
|
-
|
|
123
|
-
${chalk.bold("Scope:")}
|
|
124
|
-
This machine only. Reads need the session key from ~/.happy/sessions.json,
|
|
125
|
-
which exists for sessions this machine's daemon spawned and is pruned after
|
|
126
|
-
14 days. A session on another machine cannot be read or stopped from here.
|
|
127
|
-
|
|
128
|
-
${chalk.bold("Exit codes:")}
|
|
129
|
-
0 success
|
|
130
|
-
1 bad arguments, unknown session, no local key, or the operation failed
|
|
131
|
-
(including \`stop\` on a session the daemon is not running)
|
|
132
|
-
`;
|
|
133
|
-
function formatSummaryLine(summary) {
|
|
134
|
-
const parts = [summary.id, summary.live ? "[running]" : "[not running]"];
|
|
135
|
-
if (summary.title) parts.push(`title="${summary.title}"`);
|
|
136
|
-
if (summary.flavor) parts.push(`agent=${summary.flavor}`);
|
|
137
|
-
if (summary.tags?.length) parts.push(`tags=${summary.tags.join(",")}`);
|
|
138
|
-
if (summary.variant) parts.push(`variant=${summary.variant}`);
|
|
139
|
-
if (summary.cwd) parts.push(`cwd=${summary.cwd}`);
|
|
140
|
-
if (summary.pid !== void 0) parts.push(`pid=${summary.pid}`);
|
|
141
|
-
parts.push(summary.url);
|
|
142
|
-
return parts.join(" ");
|
|
143
|
-
}
|
|
144
|
-
async function handleSessionsCommand(args) {
|
|
145
|
-
let options;
|
|
146
|
-
try {
|
|
147
|
-
options = parseSessionsArgs(args);
|
|
148
|
-
} catch (error) {
|
|
149
|
-
console.error(chalk.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
150
|
-
console.error(`Run ${chalk.cyan("very-happy sessions --help")} for usage.`);
|
|
151
|
-
process.exit(1);
|
|
152
|
-
}
|
|
153
|
-
if (options.action === "help") {
|
|
154
|
-
console.log(SESSIONS_HELP);
|
|
155
|
-
process.exit(0);
|
|
156
|
-
}
|
|
157
|
-
try {
|
|
158
|
-
if (options.action === "list") {
|
|
159
|
-
const sessions = await index.listSessions({ tag: options.tag, recentLimit: options.limit });
|
|
160
|
-
if (options.json) {
|
|
161
|
-
console.log(JSON.stringify({ sessions }));
|
|
162
|
-
} else if (sessions.length === 0) {
|
|
163
|
-
console.log(options.tag ? `No sessions tagged "${options.tag}" on this machine.` : "No sessions found on this machine.");
|
|
164
|
-
} else {
|
|
165
|
-
for (const summary of sessions) console.log(formatSummaryLine(summary));
|
|
166
|
-
}
|
|
167
|
-
process.exit(0);
|
|
168
|
-
}
|
|
169
|
-
const sessionId = options.sessionId;
|
|
170
|
-
if (options.action === "read") {
|
|
171
|
-
const result = await index.readSessionTranscript(sessionId, options.limit ?? 20);
|
|
172
|
-
if (options.json) {
|
|
173
|
-
console.log(JSON.stringify(result));
|
|
174
|
-
} else {
|
|
175
|
-
console.log(formatSummaryLine(result.summary));
|
|
176
|
-
console.log(`--- last ${result.messageCount} message(s) ---`);
|
|
177
|
-
console.log(result.transcript.length > 0 ? result.transcript : "(no readable conversation content in this range)");
|
|
178
|
-
}
|
|
179
|
-
process.exit(0);
|
|
180
|
-
}
|
|
181
|
-
if (options.action === "stop") {
|
|
182
|
-
const stopped = await index.stopSession(sessionId);
|
|
183
|
-
if (!stopped) {
|
|
184
|
-
if (options.json) console.log(JSON.stringify({ sessionId, stopped: false }));
|
|
185
|
-
console.error(chalk.red("Error:"), `Session ${sessionId} is not among the daemon's running sessions.`);
|
|
186
|
-
process.exit(1);
|
|
187
|
-
}
|
|
188
|
-
if (options.json) console.log(JSON.stringify({ sessionId, stopped: true }));
|
|
189
|
-
else console.log(`Stopped ${sessionId}`);
|
|
190
|
-
process.exit(0);
|
|
191
|
-
}
|
|
192
|
-
await index.archiveSession(sessionId);
|
|
193
|
-
if (options.json) console.log(JSON.stringify({ sessionId, archived: true }));
|
|
194
|
-
else console.log(`Archived ${sessionId}`);
|
|
195
|
-
process.exit(0);
|
|
196
|
-
} catch (error) {
|
|
197
|
-
console.error(chalk.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
198
|
-
process.exit(1);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
exports.SESSIONS_HELP = SESSIONS_HELP;
|
|
203
|
-
exports.handleSessionsCommand = handleSessionsCommand;
|
|
204
|
-
exports.parseSessionsArgs = parseSessionsArgs;
|