very-happy-cli 0.2.116 → 0.2.118

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-CfKPvoTU.mjs → AcpBackend-Bk9b823B.mjs} +116 -12
  2. package/dist/{AcpBackend-CauAQpC1.cjs → AcpBackend-p_KNJkHc.cjs} +116 -12
  3. package/dist/{AcpSessionManager-CA6xeET_.cjs → AcpSessionManager-B9FTbpGQ.cjs} +15 -2
  4. package/dist/{AcpSessionManager-BQ6pBRW4.mjs → AcpSessionManager-CDvqVHKf.mjs} +15 -2
  5. package/dist/{config-BpR_CwZu.mjs → config-CIpgmGLR.mjs} +2 -2
  6. package/dist/{config-BoteFn-v.cjs → config-CkUn1dI4.cjs} +2 -2
  7. package/dist/{index-C45xN8s0.mjs → index-B6cpyG_G.mjs} +78 -8
  8. package/dist/{index-D7azxfcs.mjs → index-BUVQf9PI.mjs} +2 -2
  9. package/dist/{index-4jiUeiUz.cjs → index-BXOXRblP.cjs} +513 -47
  10. package/dist/{index-DWmhmji9.cjs → index-BzgtwTxC.cjs} +77 -7
  11. package/dist/{index-lX1UliOs.cjs → index-CxjX8DGz.cjs} +2 -2
  12. package/dist/{index-Dq-eFp89.mjs → index-DIXjhnV1.mjs} +502 -45
  13. package/dist/index.cjs +2 -2
  14. package/dist/index.mjs +2 -2
  15. package/dist/{installTerminalHooks-k3TrBHD0.mjs → installTerminalHooks-BAa6LaQQ.mjs} +2 -2
  16. package/dist/{installTerminalHooks-DnaFkrTF.cjs → installTerminalHooks-lXQa6bQU.cjs} +2 -2
  17. package/dist/lib.cjs +1 -1
  18. package/dist/lib.d.cts +43 -1
  19. package/dist/lib.d.mts +43 -1
  20. package/dist/lib.mjs +1 -1
  21. package/dist/{mcp-DSpEpM_2.cjs → mcp-DMD7FxBI.cjs} +62 -16
  22. package/dist/{mcp-DRePTOap.mjs → mcp-Dwc299mU.mjs} +62 -17
  23. package/dist/{runGemini-_9-a7duU.mjs → runGemini-BaxGXE9w.mjs} +4 -4
  24. package/dist/{runGemini-IWHyBq0A.cjs → runGemini-Bj4Egq7j.cjs} +4 -4
  25. package/dist/{runOpenClaw-0myYIz1U.cjs → runOpenClaw-BJhSvXYy.cjs} +3 -3
  26. package/dist/{runOpenClaw-Boq6LQ9a.mjs → runOpenClaw-BwJhbIKU.mjs} +3 -3
  27. package/dist/{send-CwWvFfay.cjs → send-BWGUkWhi.cjs} +2 -2
  28. package/dist/{send-Cyo6mgMl.mjs → send-DX13svrS.mjs} +2 -2
  29. package/dist/sessions-CGhpBNqn.cjs +322 -0
  30. package/dist/sessions-L7O3ypda.mjs +317 -0
  31. package/dist/{spawn-DQ6FTUK9.cjs → spawn-CbY233Eg.cjs} +6 -7
  32. package/dist/{spawn-CU0DMauA.mjs → spawn-DPDWZdoW.mjs} +3 -4
  33. package/dist/{types-Ds0SiQ1D.mjs → types-BtuOM9lw.mjs} +52 -19
  34. package/dist/{types-C5kEpkcZ.cjs → types-ClvvHves.cjs} +54 -19
  35. package/package.json +7 -7
  36. package/dist/sessions-CbzqYiAy.cjs +0 -204
  37. package/dist/sessions-lvfTH_ql.mjs +0 -200
@@ -3,8 +3,8 @@
3
3
  var chalk = require('chalk');
4
4
  var node_fs = require('node:fs');
5
5
  var path = require('node:path');
6
- var index = require('./index-4jiUeiUz.cjs');
7
- var persistence = require('./types-C5kEpkcZ.cjs');
6
+ var index = require('./index-BXOXRblP.cjs');
7
+ var persistence = require('./types-ClvvHves.cjs');
8
8
  require('node:os');
9
9
  require('node:crypto');
10
10
  require('cross-spawn');
@@ -48,7 +48,6 @@ require('node:util');
48
48
  require('url');
49
49
  require('node-pty');
50
50
 
51
- const SPAWN_AGENTS = ["claude", "codex", "gemini", "openclaw"];
52
51
  function parseEnvAssignment(raw) {
53
52
  const eq = raw.indexOf("=");
54
53
  if (eq <= 0) throw new Error(`--env expects KEY=VALUE, got: ${raw}`);
@@ -89,8 +88,8 @@ function parseSpawnArgs(args) {
89
88
  } else if (arg === "--agent") {
90
89
  const value = args[++i];
91
90
  if (value === void 0) throw new Error("--agent requires a value");
92
- if (!SPAWN_AGENTS.includes(value)) {
93
- throw new Error(`--agent must be one of: ${SPAWN_AGENTS.join(", ")}`);
91
+ if (!index.SPAWN_AGENTS.includes(value)) {
92
+ throw new Error(`--agent must be one of: ${index.SPAWN_AGENTS.join(", ")}`);
94
93
  }
95
94
  options.agent = value;
96
95
  } else if (arg === "--env") {
@@ -134,7 +133,7 @@ ${chalk.bold("Options:")}
134
133
  to approve \u2014 if nothing is watching, it just hangs.
135
134
  Unattended dispatchers want bypassPermissions.
136
135
  --agent <name> Backend to run: claude (default), codex, gemini,
137
- openclaw.
136
+ openclaw, pi (needs pi-acp on the daemon's PATH).
138
137
  --env KEY=VALUE Extra environment for the session process. Repeatable.
139
138
  \${VAR} is expanded against the daemon's environment;
140
139
  an unresolved reference fails the spawn.
@@ -257,8 +256,8 @@ async function handleSpawnCommand(args) {
257
256
  process.exit(0);
258
257
  }
259
258
 
259
+ exports.SPAWN_AGENTS = index.SPAWN_AGENTS;
260
260
  exports.sessionWebUrl = index.sessionWebUrl;
261
- exports.SPAWN_AGENTS = SPAWN_AGENTS;
262
261
  exports.handleSpawnCommand = handleSpawnCommand;
263
262
  exports.parseEnvAssignment = parseEnvAssignment;
264
263
  exports.parseSpawnArgs = parseSpawnArgs;
@@ -1,8 +1,8 @@
1
1
  import chalk from 'chalk';
2
2
  import { readFileSync, statSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
- import { c as checkIfDaemonRunningAndCleanupStaleState, s as spawnDaemonSession, a as sessionWebUrl, i as isValidSpawnOrigin, b as sanitizeSpawnPermissionMode, A as ALLOWED_SPAWN_PERMISSION_MODES, w as waitForSessionKey, d as sendUserMessage } from './index-Dq-eFp89.mjs';
5
- import { l as logger } from './types-Ds0SiQ1D.mjs';
4
+ import { c as checkIfDaemonRunningAndCleanupStaleState, s as spawnDaemonSession, a as sessionWebUrl, i as isValidSpawnOrigin, b as sanitizeSpawnPermissionMode, A as ALLOWED_SPAWN_PERMISSION_MODES, S as SPAWN_AGENTS, w as waitForSessionKey, d as sendUserMessage } from './index-DIXjhnV1.mjs';
5
+ import { l as logger } from './types-BtuOM9lw.mjs';
6
6
  import 'node:os';
7
7
  import 'node:crypto';
8
8
  import 'cross-spawn';
@@ -46,7 +46,6 @@ import 'node:util';
46
46
  import 'url';
47
47
  import 'node-pty';
48
48
 
49
- const SPAWN_AGENTS = ["claude", "codex", "gemini", "openclaw"];
50
49
  function parseEnvAssignment(raw) {
51
50
  const eq = raw.indexOf("=");
52
51
  if (eq <= 0) throw new Error(`--env expects KEY=VALUE, got: ${raw}`);
@@ -132,7 +131,7 @@ ${chalk.bold("Options:")}
132
131
  to approve \u2014 if nothing is watching, it just hangs.
133
132
  Unattended dispatchers want bypassPermissions.
134
133
  --agent <name> Backend to run: claude (default), codex, gemini,
135
- openclaw.
134
+ openclaw, pi (needs pi-acp on the daemon's PATH).
136
135
  --env KEY=VALUE Extra environment for the session process. Repeatable.
137
136
  \${VAR} is expanded against the daemon's environment;
138
137
  an unresolved reference fails the spawn.
@@ -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.116";
32
+ var version = "0.2.118";
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.116",
146
- "very-happy-tools-x64-darwin": "0.2.116",
147
- "very-happy-tools-arm64-linux": "0.2.116",
148
- "very-happy-tools-x64-linux": "0.2.116",
149
- "very-happy-tools-arm64-win32": "0.2.116",
150
- "very-happy-tools-x64-win32": "0.2.116"
145
+ "very-happy-tools-arm64-darwin": "0.2.118",
146
+ "very-happy-tools-x64-darwin": "0.2.118",
147
+ "very-happy-tools-arm64-linux": "0.2.118",
148
+ "very-happy-tools-x64-linux": "0.2.118",
149
+ "very-happy-tools-arm64-win32": "0.2.118",
150
+ "very-happy-tools-x64-win32": "0.2.118"
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({
@@ -1048,6 +1050,9 @@ const CliUpdateStateSchema = z$1.object({
1048
1050
  recommendedVersion: z$1.string().nullable(),
1049
1051
  minimumVersion: z$1.string().nullable(),
1050
1052
  status: z$1.enum(["current", "available", "required"]),
1053
+ /** B-351: the version this machine may install by itself. Pinned separately
1054
+ * from `recommendedVersion`; null means no unattended install. */
1055
+ autoUpdateVersion: z$1.string().nullable().optional(),
1051
1056
  checkedAt: z$1.number(),
1052
1057
  /**
1053
1058
  * B-321: a new bundle is on disk but refused to run, so the daemon is still
@@ -4212,6 +4217,7 @@ function deriveCliUpdateState(currentVersion, policy) {
4212
4217
  if (!parseExactVersion(currentVersion)) return null;
4213
4218
  const recommended = policy.recommendedVersion && parseExactVersion(policy.recommendedVersion) ? policy.recommendedVersion.replace(/^v/, "") : null;
4214
4219
  const minimum = policy.minimumVersion && parseExactVersion(policy.minimumVersion) ? policy.minimumVersion.replace(/^v/, "") : null;
4220
+ const autoUpdate = policy.autoUpdateVersion && parseExactVersion(policy.autoUpdateVersion) ? policy.autoUpdateVersion.replace(/^v/, "") : null;
4215
4221
  if (!recommended && !minimum) return null;
4216
4222
  const belowMinimum = minimum ? compareExactVersions(currentVersion, minimum) === -1 : false;
4217
4223
  const belowRecommended = recommended ? compareExactVersions(currentVersion, recommended) === -1 : false;
@@ -4220,6 +4226,7 @@ function deriveCliUpdateState(currentVersion, policy) {
4220
4226
  recommendedVersion: recommended ? parseExactVersion(recommended).exact : null,
4221
4227
  minimumVersion: minimum ? parseExactVersion(minimum).exact : null,
4222
4228
  status: belowMinimum ? "required" : belowRecommended ? "available" : "current",
4229
+ autoUpdateVersion: autoUpdate ? parseExactVersion(autoUpdate).exact : null,
4223
4230
  checkedAt: policy.checkedAt
4224
4231
  };
4225
4232
  }
@@ -4255,9 +4262,11 @@ async function fetchCliUpdateState(serverUrl, currentVersion, fetcher = fetch, t
4255
4262
  if (!["configured", "registry", "unavailable"].includes(String(raw.source))) return null;
4256
4263
  if (raw.recommendedVersion !== null && raw.recommendedVersion !== void 0 && typeof raw.recommendedVersion !== "string") return null;
4257
4264
  if (raw.minimumVersion !== null && raw.minimumVersion !== void 0 && typeof raw.minimumVersion !== "string") return null;
4265
+ if (raw.autoUpdateVersion !== null && raw.autoUpdateVersion !== void 0 && typeof raw.autoUpdateVersion !== "string") return null;
4258
4266
  return deriveCliUpdateState(currentVersion, {
4259
4267
  recommendedVersion: raw.recommendedVersion ?? null,
4260
4268
  minimumVersion: raw.minimumVersion ?? null,
4269
+ autoUpdateVersion: raw.autoUpdateVersion ?? null,
4261
4270
  checkedAt: raw.checkedAt,
4262
4271
  source: raw.source
4263
4272
  });
@@ -12731,6 +12740,19 @@ function commandExists(command) {
12731
12740
  return false;
12732
12741
  }
12733
12742
  }
12743
+ function commandExistsWindows(name) {
12744
+ try {
12745
+ execSync(`powershell -NoProfile -Command "Get-Command ${name} -ErrorAction SilentlyContinue"`, { stdio: "ignore", windowsHide: true });
12746
+ return true;
12747
+ } catch {
12748
+ return false;
12749
+ }
12750
+ }
12751
+ const SAFE_COMMAND_NAME = /^[A-Za-z0-9._-]+$/;
12752
+ function commandOnPath(name) {
12753
+ if (!SAFE_COMMAND_NAME.test(name)) return false;
12754
+ return os.platform() === "win32" ? commandExistsWindows(name) : commandExists(name);
12755
+ }
12734
12756
  function detectPosix() {
12735
12757
  const claude = commandExists("claude");
12736
12758
  const codex = commandExists("codex");
@@ -12739,17 +12761,11 @@ function detectPosix() {
12739
12761
  const openclawConfig = existsSync$1(join$1(os.homedir(), ".openclaw", "openclaw.json"));
12740
12762
  const openclawEnv = !!process.env.OPENCLAW_GATEWAY_URL;
12741
12763
  const openclaw = openclawCommand || openclawConfig || openclawEnv;
12742
- return { claude, codex, gemini, openclaw, detectedAt: Date.now() };
12764
+ const pi = commandExists("pi") && commandExists("pi-acp");
12765
+ return { claude, codex, gemini, openclaw, pi, detectedAt: Date.now() };
12743
12766
  }
12744
12767
  function detectWindows() {
12745
- const checkCommand = (name) => {
12746
- try {
12747
- execSync(`powershell -NoProfile -Command "Get-Command ${name} -ErrorAction SilentlyContinue"`, { stdio: "ignore", windowsHide: true });
12748
- return true;
12749
- } catch {
12750
- return false;
12751
- }
12752
- };
12768
+ const checkCommand = commandExistsWindows;
12753
12769
  const claude = checkCommand("claude");
12754
12770
  const codex = checkCommand("codex");
12755
12771
  const gemini = checkCommand("gemini");
@@ -12757,7 +12773,8 @@ function detectWindows() {
12757
12773
  const openclawConfig = existsSync$1(join$1(process.env.USERPROFILE || os.homedir(), ".openclaw", "openclaw.json"));
12758
12774
  const openclawEnv = !!process.env.OPENCLAW_GATEWAY_URL;
12759
12775
  const openclaw = openclawCommand || openclawConfig || openclawEnv;
12760
- return { claude, codex, gemini, openclaw, detectedAt: Date.now() };
12776
+ const pi = checkCommand("pi") && checkCommand("pi-acp");
12777
+ return { claude, codex, gemini, openclaw, pi, detectedAt: Date.now() };
12761
12778
  }
12762
12779
 
12763
12780
  const AgentCredentialsSchema = z$1.object({
@@ -14566,6 +14583,22 @@ class ApiMachineClient {
14566
14583
  event: n.event
14567
14584
  });
14568
14585
  });
14586
+ /**
14587
+ * The CLI availability last advertised in machine metadata (re-probed by
14588
+ * the keep-alive). Null until the first keep-alive tick; the daemon falls
14589
+ * back to its startup probe then.
14590
+ */
14591
+ getCLIAvailability() {
14592
+ return this.lastKnownCLIAvailability;
14593
+ }
14594
+ /**
14595
+ * Title a web terminal from inside it (`very-happy mcp` change_title →
14596
+ * daemon /terminal-title → here). Same tmux truthfulness as the
14597
+ * `set-terminal-title` RPC.
14598
+ */
14599
+ setTerminalTitle(terminalId, title, ifAbsent) {
14600
+ return this.webTerminal.setTitle(terminalId, title, ifAbsent);
14601
+ }
14569
14602
  /**
14570
14603
  * Push text to the clipboard of every web client the user has open
14571
14604
  * (terminal-path claude → `very-happy mcp` → daemon /clipboard → here).
@@ -15374,7 +15407,7 @@ class ApiMachineClient {
15374
15407
  const prev = this.lastKnownCLIAvailability;
15375
15408
  const newResumeSupport = detectResumeSupport();
15376
15409
  const prevResume = this.lastKnownResumeSupport;
15377
- const cliAvailabilityChanged = !prev || prev.claude !== newAvailability.claude || prev.codex !== newAvailability.codex || prev.gemini !== newAvailability.gemini || prev.openclaw !== newAvailability.openclaw;
15410
+ const cliAvailabilityChanged = !prev || prev.claude !== newAvailability.claude || prev.codex !== newAvailability.codex || prev.gemini !== newAvailability.gemini || prev.openclaw !== newAvailability.openclaw || prev.pi !== newAvailability.pi;
15378
15411
  const resumeSupportChanged = !prevResume || prevResume.rpcAvailable !== newResumeSupport.rpcAvailable || prevResume.happyAgentAuthenticated !== newResumeSupport.happyAgentAuthenticated;
15379
15412
  if (cliAvailabilityChanged || resumeSupportChanged) {
15380
15413
  this.lastKnownCLIAvailability = newAvailability;
@@ -16597,4 +16630,4 @@ const RawJSONLinesSchema = z$1.discriminatedUnion("type", [
16597
16630
  }).passthrough()
16598
16631
  ]);
16599
16632
 
16600
- 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 };
16633
+ 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, connectionState as d, readSettings as e, encodeBase64 as f, contentLogMetadata as g, errorLogMetadata as h, isValidSessionId as i, packageJson as j, delay 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 };
@@ -51,7 +51,7 @@ var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
51
51
  var pty__namespace = /*#__PURE__*/_interopNamespaceDefault(pty);
52
52
 
53
53
  var name = "very-happy-cli";
54
- var version = "0.2.116";
54
+ var version = "0.2.118";
55
55
  var description = "Remote multi-agent workspace CLI with deep Claude integration, native terminal streaming, and self-hosting.";
56
56
  var author = "Kirill Dubovitskiy";
57
57
  var contributors = [
@@ -164,12 +164,12 @@ var dependencies = {
164
164
  zod: "^4.0.0"
165
165
  };
166
166
  var optionalDependencies = {
167
- "very-happy-tools-arm64-darwin": "0.2.116",
168
- "very-happy-tools-x64-darwin": "0.2.116",
169
- "very-happy-tools-arm64-linux": "0.2.116",
170
- "very-happy-tools-x64-linux": "0.2.116",
171
- "very-happy-tools-arm64-win32": "0.2.116",
172
- "very-happy-tools-x64-win32": "0.2.116"
167
+ "very-happy-tools-arm64-darwin": "0.2.118",
168
+ "very-happy-tools-x64-darwin": "0.2.118",
169
+ "very-happy-tools-arm64-linux": "0.2.118",
170
+ "very-happy-tools-x64-linux": "0.2.118",
171
+ "very-happy-tools-arm64-win32": "0.2.118",
172
+ "very-happy-tools-x64-win32": "0.2.118"
173
173
  };
174
174
  var devDependencies = {
175
175
  "@slopus/happy-wire": "workspace:*",
@@ -1039,6 +1039,8 @@ z.z.object({
1039
1039
  codex: z.z.boolean(),
1040
1040
  gemini: z.z.boolean(),
1041
1041
  openclaw: z.z.boolean(),
1042
+ pi: z.z.boolean().optional(),
1043
+ // absent on daemons that predate the pi runner
1042
1044
  detectedAt: z.z.number()
1043
1045
  }).optional(),
1044
1046
  resumeSupport: z.z.object({
@@ -1070,6 +1072,9 @@ const CliUpdateStateSchema = z.z.object({
1070
1072
  recommendedVersion: z.z.string().nullable(),
1071
1073
  minimumVersion: z.z.string().nullable(),
1072
1074
  status: z.z.enum(["current", "available", "required"]),
1075
+ /** B-351: the version this machine may install by itself. Pinned separately
1076
+ * from `recommendedVersion`; null means no unattended install. */
1077
+ autoUpdateVersion: z.z.string().nullable().optional(),
1073
1078
  checkedAt: z.z.number(),
1074
1079
  /**
1075
1080
  * B-321: a new bundle is on disk but refused to run, so the daemon is still
@@ -1680,7 +1685,7 @@ class RpcHandlerManager {
1680
1685
  }
1681
1686
  }
1682
1687
 
1683
- const __dirname$1 = path$1.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-C5kEpkcZ.cjs', document.baseURI).href))));
1688
+ const __dirname$1 = path$1.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-ClvvHves.cjs', document.baseURI).href))));
1684
1689
  function projectPath() {
1685
1690
  const path = path$1.resolve(__dirname$1, "..");
1686
1691
  return path;
@@ -4234,6 +4239,7 @@ function deriveCliUpdateState(currentVersion, policy) {
4234
4239
  if (!parseExactVersion(currentVersion)) return null;
4235
4240
  const recommended = policy.recommendedVersion && parseExactVersion(policy.recommendedVersion) ? policy.recommendedVersion.replace(/^v/, "") : null;
4236
4241
  const minimum = policy.minimumVersion && parseExactVersion(policy.minimumVersion) ? policy.minimumVersion.replace(/^v/, "") : null;
4242
+ const autoUpdate = policy.autoUpdateVersion && parseExactVersion(policy.autoUpdateVersion) ? policy.autoUpdateVersion.replace(/^v/, "") : null;
4237
4243
  if (!recommended && !minimum) return null;
4238
4244
  const belowMinimum = minimum ? compareExactVersions(currentVersion, minimum) === -1 : false;
4239
4245
  const belowRecommended = recommended ? compareExactVersions(currentVersion, recommended) === -1 : false;
@@ -4242,6 +4248,7 @@ function deriveCliUpdateState(currentVersion, policy) {
4242
4248
  recommendedVersion: recommended ? parseExactVersion(recommended).exact : null,
4243
4249
  minimumVersion: minimum ? parseExactVersion(minimum).exact : null,
4244
4250
  status: belowMinimum ? "required" : belowRecommended ? "available" : "current",
4251
+ autoUpdateVersion: autoUpdate ? parseExactVersion(autoUpdate).exact : null,
4245
4252
  checkedAt: policy.checkedAt
4246
4253
  };
4247
4254
  }
@@ -4277,9 +4284,11 @@ async function fetchCliUpdateState(serverUrl, currentVersion, fetcher = fetch, t
4277
4284
  if (!["configured", "registry", "unavailable"].includes(String(raw.source))) return null;
4278
4285
  if (raw.recommendedVersion !== null && raw.recommendedVersion !== void 0 && typeof raw.recommendedVersion !== "string") return null;
4279
4286
  if (raw.minimumVersion !== null && raw.minimumVersion !== void 0 && typeof raw.minimumVersion !== "string") return null;
4287
+ if (raw.autoUpdateVersion !== null && raw.autoUpdateVersion !== void 0 && typeof raw.autoUpdateVersion !== "string") return null;
4280
4288
  return deriveCliUpdateState(currentVersion, {
4281
4289
  recommendedVersion: raw.recommendedVersion ?? null,
4282
4290
  minimumVersion: raw.minimumVersion ?? null,
4291
+ autoUpdateVersion: raw.autoUpdateVersion ?? null,
4283
4292
  checkedAt: raw.checkedAt,
4284
4293
  source: raw.source
4285
4294
  });
@@ -12753,6 +12762,19 @@ function commandExists(command) {
12753
12762
  return false;
12754
12763
  }
12755
12764
  }
12765
+ function commandExistsWindows(name) {
12766
+ try {
12767
+ child_process.execSync(`powershell -NoProfile -Command "Get-Command ${name} -ErrorAction SilentlyContinue"`, { stdio: "ignore", windowsHide: true });
12768
+ return true;
12769
+ } catch {
12770
+ return false;
12771
+ }
12772
+ }
12773
+ const SAFE_COMMAND_NAME = /^[A-Za-z0-9._-]+$/;
12774
+ function commandOnPath(name) {
12775
+ if (!SAFE_COMMAND_NAME.test(name)) return false;
12776
+ return os$1.platform() === "win32" ? commandExistsWindows(name) : commandExists(name);
12777
+ }
12756
12778
  function detectPosix() {
12757
12779
  const claude = commandExists("claude");
12758
12780
  const codex = commandExists("codex");
@@ -12761,17 +12783,11 @@ function detectPosix() {
12761
12783
  const openclawConfig = fs$1.existsSync(path$1.join(os$1.homedir(), ".openclaw", "openclaw.json"));
12762
12784
  const openclawEnv = !!process.env.OPENCLAW_GATEWAY_URL;
12763
12785
  const openclaw = openclawCommand || openclawConfig || openclawEnv;
12764
- return { claude, codex, gemini, openclaw, detectedAt: Date.now() };
12786
+ const pi = commandExists("pi") && commandExists("pi-acp");
12787
+ return { claude, codex, gemini, openclaw, pi, detectedAt: Date.now() };
12765
12788
  }
12766
12789
  function detectWindows() {
12767
- const checkCommand = (name) => {
12768
- try {
12769
- child_process.execSync(`powershell -NoProfile -Command "Get-Command ${name} -ErrorAction SilentlyContinue"`, { stdio: "ignore", windowsHide: true });
12770
- return true;
12771
- } catch {
12772
- return false;
12773
- }
12774
- };
12790
+ const checkCommand = commandExistsWindows;
12775
12791
  const claude = checkCommand("claude");
12776
12792
  const codex = checkCommand("codex");
12777
12793
  const gemini = checkCommand("gemini");
@@ -12779,7 +12795,8 @@ function detectWindows() {
12779
12795
  const openclawConfig = fs$1.existsSync(path$1.join(process.env.USERPROFILE || os$1.homedir(), ".openclaw", "openclaw.json"));
12780
12796
  const openclawEnv = !!process.env.OPENCLAW_GATEWAY_URL;
12781
12797
  const openclaw = openclawCommand || openclawConfig || openclawEnv;
12782
- return { claude, codex, gemini, openclaw, detectedAt: Date.now() };
12798
+ const pi = checkCommand("pi") && checkCommand("pi-acp");
12799
+ return { claude, codex, gemini, openclaw, pi, detectedAt: Date.now() };
12783
12800
  }
12784
12801
 
12785
12802
  const AgentCredentialsSchema = z.z.object({
@@ -14588,6 +14605,22 @@ class ApiMachineClient {
14588
14605
  event: n.event
14589
14606
  });
14590
14607
  });
14608
+ /**
14609
+ * The CLI availability last advertised in machine metadata (re-probed by
14610
+ * the keep-alive). Null until the first keep-alive tick; the daemon falls
14611
+ * back to its startup probe then.
14612
+ */
14613
+ getCLIAvailability() {
14614
+ return this.lastKnownCLIAvailability;
14615
+ }
14616
+ /**
14617
+ * Title a web terminal from inside it (`very-happy mcp` change_title →
14618
+ * daemon /terminal-title → here). Same tmux truthfulness as the
14619
+ * `set-terminal-title` RPC.
14620
+ */
14621
+ setTerminalTitle(terminalId, title, ifAbsent) {
14622
+ return this.webTerminal.setTitle(terminalId, title, ifAbsent);
14623
+ }
14591
14624
  /**
14592
14625
  * Push text to the clipboard of every web client the user has open
14593
14626
  * (terminal-path claude → `very-happy mcp` → daemon /clipboard → here).
@@ -15396,7 +15429,7 @@ class ApiMachineClient {
15396
15429
  const prev = this.lastKnownCLIAvailability;
15397
15430
  const newResumeSupport = detectResumeSupport();
15398
15431
  const prevResume = this.lastKnownResumeSupport;
15399
- const cliAvailabilityChanged = !prev || prev.claude !== newAvailability.claude || prev.codex !== newAvailability.codex || prev.gemini !== newAvailability.gemini || prev.openclaw !== newAvailability.openclaw;
15432
+ const cliAvailabilityChanged = !prev || prev.claude !== newAvailability.claude || prev.codex !== newAvailability.codex || prev.gemini !== newAvailability.gemini || prev.openclaw !== newAvailability.openclaw || prev.pi !== newAvailability.pi;
15400
15433
  const resumeSupportChanged = !prevResume || prevResume.rpcAvailable !== newResumeSupport.rpcAvailable || prevResume.happyAgentAuthenticated !== newResumeSupport.happyAgentAuthenticated;
15401
15434
  if (cliAvailabilityChanged || resumeSupportChanged) {
15402
15435
  this.lastKnownCLIAvailability = newAvailability;
@@ -16637,6 +16670,8 @@ exports.api = api;
16637
16670
  exports.clearCredentials = clearCredentials;
16638
16671
  exports.clearDaemonState = clearDaemonState;
16639
16672
  exports.clearMachineId = clearMachineId;
16673
+ exports.commandOnPath = commandOnPath;
16674
+ exports.compareExactVersions = compareExactVersions;
16640
16675
  exports.configuration = configuration;
16641
16676
  exports.connectionState = connectionState;
16642
16677
  exports.contentLogMetadata = contentLogMetadata;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "very-happy-cli",
3
- "version": "0.2.116",
3
+ "version": "0.2.118",
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.116",
117
- "very-happy-tools-x64-darwin": "0.2.116",
118
- "very-happy-tools-arm64-linux": "0.2.116",
119
- "very-happy-tools-x64-linux": "0.2.116",
120
- "very-happy-tools-arm64-win32": "0.2.116",
121
- "very-happy-tools-x64-win32": "0.2.116"
116
+ "very-happy-tools-arm64-darwin": "0.2.118",
117
+ "very-happy-tools-x64-darwin": "0.2.118",
118
+ "very-happy-tools-arm64-linux": "0.2.118",
119
+ "very-happy-tools-x64-linux": "0.2.118",
120
+ "very-happy-tools-arm64-win32": "0.2.118",
121
+ "very-happy-tools-x64-win32": "0.2.118"
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-4jiUeiUz.cjs');
5
- var persistence = require('./types-C5kEpkcZ.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;