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.
Files changed (37) hide show
  1. package/dist/{AcpBackend-DEZTWZZu.mjs → AcpBackend-C4RMHt0I.mjs} +1 -1
  2. package/dist/{AcpBackend-BrZN8KM-.cjs → AcpBackend-CrbXvcgQ.cjs} +1 -1
  3. package/dist/{AcpSessionManager-DjulpKY0.cjs → AcpSessionManager-CjAVO4RX.cjs} +1 -1
  4. package/dist/{AcpSessionManager-BzP3eiOG.mjs → AcpSessionManager-Cnc8Jatl.mjs} +1 -1
  5. package/dist/{config-CMgRBQMS.mjs → config-CnA3JIUn.mjs} +2 -2
  6. package/dist/{config-BNAsx9Wg.cjs → config-O9quMfxg.cjs} +2 -2
  7. package/dist/{index-B2KfhNku.mjs → index-B0o3H6_x.mjs} +2 -2
  8. package/dist/{index-BHNEb4bc.mjs → index-BYZKuQoy.mjs} +590 -41
  9. package/dist/{index-tPXwlc_t.cjs → index-DcESl4cI.cjs} +601 -43
  10. package/dist/{index-CpGR7rIw.mjs → index-DiRN0Rv8.mjs} +78 -8
  11. package/dist/{index-B3UkbonP.cjs → index-DqmVH6R5.cjs} +2 -2
  12. package/dist/{index-ClR0jhjm.cjs → index-W-O4xR_O.cjs} +77 -7
  13. package/dist/index.cjs +2 -2
  14. package/dist/index.mjs +2 -2
  15. package/dist/{installTerminalHooks-DD6QuHfT.mjs → installTerminalHooks-DeYqyjCB.mjs} +2 -2
  16. package/dist/{installTerminalHooks-CWfCF68p.cjs → installTerminalHooks-zfsWSotO.cjs} +2 -2
  17. package/dist/lib.cjs +1 -1
  18. package/dist/lib.d.cts +66 -1
  19. package/dist/lib.d.mts +66 -1
  20. package/dist/lib.mjs +1 -1
  21. package/dist/{mcp-CiNrjYox.mjs → mcp-D_5aOK0I.mjs} +62 -17
  22. package/dist/{mcp-CqjiDRnG.cjs → mcp-nYqC4FYm.cjs} +62 -16
  23. package/dist/{runGemini-DdJqpywr.mjs → runGemini-CEURRmM4.mjs} +4 -4
  24. package/dist/{runGemini-UN5IznJp.cjs → runGemini-DYf3ntib.cjs} +4 -4
  25. package/dist/{runOpenClaw-CM5UrVBy.mjs → runOpenClaw-BozfKWng.mjs} +3 -3
  26. package/dist/{runOpenClaw-9hhfGllZ.cjs → runOpenClaw-CNKjAw-H.cjs} +3 -3
  27. package/dist/{send-CnVtXOj0.cjs → send-BZLhRZb-.cjs} +2 -2
  28. package/dist/{send-C6G2jsQp.mjs → send-CYznZGrp.mjs} +2 -2
  29. package/dist/sessions-DYPB3Peg.mjs +317 -0
  30. package/dist/sessions-YWt9YEN8.cjs +322 -0
  31. package/dist/{spawn-qb344Y5E.mjs → spawn-CsrHZd3y.mjs} +3 -4
  32. package/dist/{spawn-CfYddkS_.cjs → spawn-tSvo3stN.cjs} +6 -7
  33. package/dist/{types-CpnzfHHS.cjs → types-DD3AK6Nb.cjs} +189 -26
  34. package/dist/{types-B3qx8Ga1.mjs → types-jSfU_0o1.mjs} +187 -26
  35. package/package.json +7 -7
  36. package/dist/sessions-CGdcF05T.cjs +0 -204
  37. 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.115";
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.115",
146
- "very-happy-tools-x64-darwin": "0.2.115",
147
- "very-happy-tools-arm64-linux": "0.2.115",
148
- "very-happy-tools-x64-linux": "0.2.115",
149
- "very-happy-tools-arm64-win32": "0.2.115",
150
- "very-happy-tools-x64-win32": "0.2.115"
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
- const chunk = created.ingest(data);
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
- proc.onData((data) => {
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
- return { claude, codex, gemini, openclaw, detectedAt: Date.now() };
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 = (name) => {
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
- return { claude, codex, gemini, openclaw, detectedAt: Date.now() };
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 { mirrorLineKey as $, ApiClient as A, credentialRelayProblem as B, deriveLocalCliUpdateSummary as C, updateSettings as D, encodeBase64Url as E, decodeBase64 as F, writeCredentialsLegacy as G, writeCredentialsDataKey as H, InvalidateSync as I, readCredentialsForConfiguredRelay as J, encrypt as K, isClaudeSessionId as L, MAX_CHAT_ATTACHMENT_SOURCE_BYTES as M, getLocalHappyAgentCredentialPath as N, readLocalHappyAgentCredentials as O, decryptWithDataKey as P, decryptLegacy as Q, RawJSONLinesSchema as R, STREAM_DELTA_MAX_CHARS as S, extractCompleteLines as T, decideBackfill as U, parseTerminalHookPayload as V, tmuxArgs as W, scrubTmuxClientEnv as X, decideMirrorBinding as Y, MIRROR_BACKFILL_LINES_DEFAULT as Z, persistSession as _, ApiSessionClient as a, mirrorLocalId as a0, detectResumeSupport as a1, detectCLIAvailability as a2, acquireDaemonLock as a3, summarizeSpawnSessionForLog as a4, deletePersistedSessions as a5, shellescape as a6, decrypt as a7, writeDaemonState as a8, writeSettings as a9, fetchCliUpdateState as aa, resolveCliUpdateCheckInterval as ab, DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS as ac, releaseDaemonLock as ad, listVhTerminals as ae, isValidTerminalId as af, readVhTerminal as ag, sendToVhTerminal as ah, TITLE_PROMPT_PREFIX as ai, BOARD_ANALYZER_PROMPT_PREFIX as aj, startOfflineReconnection as ak, clearCredentials as al, clearMachineId as am, SandboxConfigSchema as an, CodexAppServerClient as ao, getLatestDaemonLog as ap, persistence as aq, api as ar, 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, readCredentials as z };
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.115",
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.115",
117
- "very-happy-tools-x64-darwin": "0.2.115",
118
- "very-happy-tools-arm64-linux": "0.2.115",
119
- "very-happy-tools-x64-linux": "0.2.115",
120
- "very-happy-tools-arm64-win32": "0.2.115",
121
- "very-happy-tools-x64-win32": "0.2.115"
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;