very-happy-cli 0.2.96 → 0.2.97

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 (32) hide show
  1. package/dist/{AcpBackend-B5Eh8G3W.mjs → AcpBackend-CTUM4dvG.mjs} +1 -1
  2. package/dist/{AcpBackend-BaCt4_zS.cjs → AcpBackend-DrwdVSeJ.cjs} +1 -1
  3. package/dist/{AcpSessionManager-CvgmPlXB.cjs → AcpSessionManager-BieE187D.cjs} +1 -1
  4. package/dist/{AcpSessionManager-grr_aJEI.mjs → AcpSessionManager-BoBuHee3.mjs} +1 -1
  5. package/dist/{config-BjsUeQPA.cjs → config-COq6X7U7.cjs} +3 -3
  6. package/dist/{config-B69TZlJj.mjs → config-DCH33LOM.mjs} +3 -3
  7. package/dist/{index-uc-dtu-3.cjs → index-BD2fHJ8o.cjs} +526 -20
  8. package/dist/{index-BMmbxCTq.mjs → index-Bcgquogx.mjs} +527 -21
  9. package/dist/{index-C-uJXSm3.mjs → index-DkhMvofH.mjs} +6 -6
  10. package/dist/{index-DID1FV_n.cjs → index-Dn0iOBFd.cjs} +5 -5
  11. package/dist/index.cjs +3 -3
  12. package/dist/index.mjs +3 -3
  13. package/dist/{installTerminalHooks-CBHjEQ-a.mjs → installTerminalHooks-DqNtW6a2.mjs} +3 -3
  14. package/dist/{installTerminalHooks-CepXxAM_.cjs → installTerminalHooks-H3kpFEZS.cjs} +3 -3
  15. package/dist/lib.cjs +1 -1
  16. package/dist/lib.d.cts +67 -0
  17. package/dist/lib.d.mts +67 -0
  18. package/dist/lib.mjs +1 -1
  19. package/dist/{mcp-BdSHqi2d.mjs → mcp-BKAuzaLX.mjs} +3 -3
  20. package/dist/{mcp-CAtbtTXF.cjs → mcp-BwNc5eRs.cjs} +3 -3
  21. package/dist/{runGemini-cMZ3_CRf.mjs → runGemini-Brp0UmLx.mjs} +5 -5
  22. package/dist/{runGemini-qStPY5fH.cjs → runGemini-w35qiFBB.cjs} +5 -5
  23. package/dist/{runOpenClaw-BJ36wEbk.mjs → runOpenClaw-BvPRFx-L.mjs} +4 -4
  24. package/dist/{runOpenClaw-B8HR6GnX.cjs → runOpenClaw-CkHtTCpI.cjs} +4 -4
  25. package/dist/{send-TX13v8Xe.mjs → send-c-wEmh5U.mjs} +3 -3
  26. package/dist/{send-6eY55o3C.cjs → send-tyKG13xt.cjs} +3 -3
  27. package/dist/{spawn-vOZA0kOi.cjs → spawn-xdCsG2RT.cjs} +3 -3
  28. package/dist/{spawn-CfCE6JAv.mjs → spawn-yS6yY8Xi.mjs} +3 -3
  29. package/dist/{types-okYCRlgl.cjs → types-DoGaKVfK.cjs} +77 -10
  30. package/dist/{types-CLRl0ET3.mjs → types-xZOYW0_G.mjs} +76 -10
  31. package/package.json +7 -7
  32. package/scripts/shims/keychain-off/security +51 -0
@@ -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.96";
54
+ var version = "0.2.97";
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.96",
168
- "very-happy-tools-x64-darwin": "0.2.96",
169
- "very-happy-tools-arm64-linux": "0.2.96",
170
- "very-happy-tools-x64-linux": "0.2.96",
171
- "very-happy-tools-arm64-win32": "0.2.96",
172
- "very-happy-tools-x64-win32": "0.2.96"
167
+ "very-happy-tools-arm64-darwin": "0.2.97",
168
+ "very-happy-tools-x64-darwin": "0.2.97",
169
+ "very-happy-tools-arm64-linux": "0.2.97",
170
+ "very-happy-tools-x64-linux": "0.2.97",
171
+ "very-happy-tools-arm64-win32": "0.2.97",
172
+ "very-happy-tools-x64-win32": "0.2.97"
173
173
  };
174
174
  var devDependencies = {
175
175
  "@slopus/happy-wire": "workspace:*",
@@ -1002,6 +1002,22 @@ z.z.object({
1002
1002
  detectedAt: z.z.number()
1003
1003
  }).optional()
1004
1004
  });
1005
+ const ClaudeAuthStateSchema = z.z.object({
1006
+ probeVersion: z.z.number(),
1007
+ daemonPid: z.z.number(),
1008
+ status: z.z.union([z.z.enum(["ok", "not-logged-in", "unknown", "error", "claude-missing"]), z.z.string()]),
1009
+ authMethod: z.z.string().optional(),
1010
+ subscriptionType: z.z.string().optional(),
1011
+ diagnosis: z.z.string().optional(),
1012
+ detail: z.z.string().optional(),
1013
+ repairable: z.z.string().optional(),
1014
+ context: z.z.object({
1015
+ platform: z.z.string(),
1016
+ lineage: z.z.string(),
1017
+ credentialStore: z.z.string()
1018
+ }),
1019
+ checkedAt: z.z.number()
1020
+ });
1005
1021
  const CliUpdateStateSchema = z.z.object({
1006
1022
  currentVersion: z.z.string(),
1007
1023
  recommendedVersion: z.z.string().nullable(),
@@ -1096,7 +1112,15 @@ z.z.object({
1096
1112
  detectedAt: z.z.number()
1097
1113
  }).optional(),
1098
1114
  /** Relay-owned CLI compatibility/update policy last checked by this daemon. */
1099
- cliUpdate: CliUpdateStateSchema.optional()
1115
+ cliUpdate: CliUpdateStateSchema.optional(),
1116
+ /**
1117
+ * B-276: result of the daemon-context Claude auth preflight (spec
1118
+ * 2026-09-claude-auth-preflight). Trust rule for consumers:
1119
+ * `claudeAuth.daemonPid === daemonState.pid` — daemonState survives daemon
1120
+ * restarts (the server returns the existing row and connect spreads it
1121
+ * forward), so a downgraded daemon carries this field along unchanged.
1122
+ */
1123
+ claudeAuth: ClaudeAuthStateSchema.optional()
1100
1124
  });
1101
1125
  const MessageMetaSchema = z.z.object({
1102
1126
  sentFrom: z.z.string().optional(),
@@ -1545,7 +1569,7 @@ class RpcHandlerManager {
1545
1569
  }
1546
1570
  }
1547
1571
 
1548
- 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-okYCRlgl.cjs', document.baseURI).href))));
1572
+ 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-DoGaKVfK.cjs', document.baseURI).href))));
1549
1573
  function projectPath() {
1550
1574
  const path = path$1.resolve(__dirname$1, "..");
1551
1575
  return path;
@@ -13781,6 +13805,7 @@ class ApiMachineClient {
13781
13805
  lastKnownResumeSupport = null;
13782
13806
  cliUpdateState = null;
13783
13807
  cliUpdatePushChain = Promise.resolve();
13808
+ claudeAuthState = null;
13784
13809
  rpcHandlerManager;
13785
13810
  resumeSessionHandler = null;
13786
13811
  restartSessionHandler = null;
@@ -13847,6 +13872,44 @@ class ApiMachineClient {
13847
13872
  if (!this.socket?.connected) return;
13848
13873
  this.cliUpdatePushChain = this.cliUpdatePushChain.then(() => this.updateDaemonState((current) => withCurrentCliUpdateState(current, this.cliUpdateState))).catch((error) => logger.debug("[API MACHINE] Failed to publish CLI update policy:", error));
13849
13874
  }
13875
+ /**
13876
+ * B-276: publish the daemon-context Claude auth preflight into daemonState
13877
+ * (same serialized push chain as the CLI update policy). Resolves true when
13878
+ * the server acknowledged the write with our value in it, false otherwise
13879
+ * (rate-limit `result:'error'` is swallowed by updateDaemonState, so the
13880
+ * caller checks the echoed state and re-sends on the next probe).
13881
+ */
13882
+ setClaudeAuthState(state) {
13883
+ this.claudeAuthState = state;
13884
+ if (!this.socket?.connected) return Promise.resolve(false);
13885
+ const attempt = this.cliUpdatePushChain.then(() => this.updateDaemonState((current) => ({
13886
+ ...current ?? {},
13887
+ status: current?.status ?? "running",
13888
+ claudeAuth: state
13889
+ }))).then(() => this.machine.daemonState?.claudeAuth?.checkedAt === state.checkedAt && this.machine.daemonState?.claudeAuth?.daemonPid === state.daemonPid).catch((error) => {
13890
+ logger.debug("[API MACHINE] Failed to publish Claude auth state:", error);
13891
+ return false;
13892
+ });
13893
+ this.cliUpdatePushChain = attempt.then(() => void 0);
13894
+ return attempt;
13895
+ }
13896
+ /** B-276 machine RPCs. Responses always carry `claudeAuth` so the web can refresh in place. */
13897
+ setClaudeAuthHandlers(handlers) {
13898
+ this.rpcHandlerManager.registerHandler("claude-auth-probe", async () => {
13899
+ const claudeAuth = await handlers.probe();
13900
+ return { claudeAuth };
13901
+ });
13902
+ this.rpcHandlerManager.registerHandler("claude-auth-repair", async (params) => {
13903
+ const action = typeof params?.action === "string" ? params.action : "";
13904
+ return handlers.repair(action);
13905
+ });
13906
+ this.rpcHandlerManager.registerHandler("claude-auth-set-store", async (params) => {
13907
+ const store = params?.store === "file" ? "file" : params?.store === "auto" ? "auto" : null;
13908
+ if (!store) return { error: "invalid-store", claudeAuth: this.claudeAuthState };
13909
+ const claudeAuth = await handlers.setStore(store);
13910
+ return { claudeAuth };
13911
+ });
13912
+ }
13850
13913
  setRPCHandlers({
13851
13914
  spawnSession,
13852
13915
  resumeSession,
@@ -14361,7 +14424,10 @@ class ApiMachineClient {
14361
14424
  // B-084: closed records survive daemon restarts (persisted
14362
14425
  // in closed-terminals.json), so the connect snapshot ships
14363
14426
  // them too — not just the incremental pushes.
14364
- closedTerminals: this.webTerminal.getClosedTerminals()
14427
+ closedTerminals: this.webTerminal.getClosedTerminals(),
14428
+ // B-276: re-ship the last preflight so a reconnect never
14429
+ // leaves a stale-pid value visible longer than needed.
14430
+ ...this.claudeAuthState ? { claudeAuth: this.claudeAuthState } : {}
14365
14431
  };
14366
14432
  });
14367
14433
  this.webTerminal.startListTracking((terminals) => this.pushTerminalList(terminals));
@@ -15773,3 +15839,4 @@ exports.writeCredentialsDataKey = writeCredentialsDataKey;
15773
15839
  exports.writeCredentialsLegacy = writeCredentialsLegacy;
15774
15840
  exports.writeDaemonState = writeDaemonState;
15775
15841
  exports.writePrivateFileSync = writePrivateFileSync;
15842
+ exports.writeSettings = writeSettings;
@@ -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.96";
32
+ var version = "0.2.97";
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.96",
146
- "very-happy-tools-x64-darwin": "0.2.96",
147
- "very-happy-tools-arm64-linux": "0.2.96",
148
- "very-happy-tools-x64-linux": "0.2.96",
149
- "very-happy-tools-arm64-win32": "0.2.96",
150
- "very-happy-tools-x64-win32": "0.2.96"
145
+ "very-happy-tools-arm64-darwin": "0.2.97",
146
+ "very-happy-tools-x64-darwin": "0.2.97",
147
+ "very-happy-tools-arm64-linux": "0.2.97",
148
+ "very-happy-tools-x64-linux": "0.2.97",
149
+ "very-happy-tools-arm64-win32": "0.2.97",
150
+ "very-happy-tools-x64-win32": "0.2.97"
151
151
  };
152
152
  var devDependencies = {
153
153
  "@slopus/happy-wire": "workspace:*",
@@ -980,6 +980,22 @@ z$1.object({
980
980
  detectedAt: z$1.number()
981
981
  }).optional()
982
982
  });
983
+ const ClaudeAuthStateSchema = z$1.object({
984
+ probeVersion: z$1.number(),
985
+ daemonPid: z$1.number(),
986
+ status: z$1.union([z$1.enum(["ok", "not-logged-in", "unknown", "error", "claude-missing"]), z$1.string()]),
987
+ authMethod: z$1.string().optional(),
988
+ subscriptionType: z$1.string().optional(),
989
+ diagnosis: z$1.string().optional(),
990
+ detail: z$1.string().optional(),
991
+ repairable: z$1.string().optional(),
992
+ context: z$1.object({
993
+ platform: z$1.string(),
994
+ lineage: z$1.string(),
995
+ credentialStore: z$1.string()
996
+ }),
997
+ checkedAt: z$1.number()
998
+ });
983
999
  const CliUpdateStateSchema = z$1.object({
984
1000
  currentVersion: z$1.string(),
985
1001
  recommendedVersion: z$1.string().nullable(),
@@ -1074,7 +1090,15 @@ z$1.object({
1074
1090
  detectedAt: z$1.number()
1075
1091
  }).optional(),
1076
1092
  /** Relay-owned CLI compatibility/update policy last checked by this daemon. */
1077
- cliUpdate: CliUpdateStateSchema.optional()
1093
+ cliUpdate: CliUpdateStateSchema.optional(),
1094
+ /**
1095
+ * B-276: result of the daemon-context Claude auth preflight (spec
1096
+ * 2026-09-claude-auth-preflight). Trust rule for consumers:
1097
+ * `claudeAuth.daemonPid === daemonState.pid` — daemonState survives daemon
1098
+ * restarts (the server returns the existing row and connect spreads it
1099
+ * forward), so a downgraded daemon carries this field along unchanged.
1100
+ */
1101
+ claudeAuth: ClaudeAuthStateSchema.optional()
1078
1102
  });
1079
1103
  const MessageMetaSchema = z$1.object({
1080
1104
  sentFrom: z$1.string().optional(),
@@ -13759,6 +13783,7 @@ class ApiMachineClient {
13759
13783
  lastKnownResumeSupport = null;
13760
13784
  cliUpdateState = null;
13761
13785
  cliUpdatePushChain = Promise.resolve();
13786
+ claudeAuthState = null;
13762
13787
  rpcHandlerManager;
13763
13788
  resumeSessionHandler = null;
13764
13789
  restartSessionHandler = null;
@@ -13825,6 +13850,44 @@ class ApiMachineClient {
13825
13850
  if (!this.socket?.connected) return;
13826
13851
  this.cliUpdatePushChain = this.cliUpdatePushChain.then(() => this.updateDaemonState((current) => withCurrentCliUpdateState(current, this.cliUpdateState))).catch((error) => logger.debug("[API MACHINE] Failed to publish CLI update policy:", error));
13827
13852
  }
13853
+ /**
13854
+ * B-276: publish the daemon-context Claude auth preflight into daemonState
13855
+ * (same serialized push chain as the CLI update policy). Resolves true when
13856
+ * the server acknowledged the write with our value in it, false otherwise
13857
+ * (rate-limit `result:'error'` is swallowed by updateDaemonState, so the
13858
+ * caller checks the echoed state and re-sends on the next probe).
13859
+ */
13860
+ setClaudeAuthState(state) {
13861
+ this.claudeAuthState = state;
13862
+ if (!this.socket?.connected) return Promise.resolve(false);
13863
+ const attempt = this.cliUpdatePushChain.then(() => this.updateDaemonState((current) => ({
13864
+ ...current ?? {},
13865
+ status: current?.status ?? "running",
13866
+ claudeAuth: state
13867
+ }))).then(() => this.machine.daemonState?.claudeAuth?.checkedAt === state.checkedAt && this.machine.daemonState?.claudeAuth?.daemonPid === state.daemonPid).catch((error) => {
13868
+ logger.debug("[API MACHINE] Failed to publish Claude auth state:", error);
13869
+ return false;
13870
+ });
13871
+ this.cliUpdatePushChain = attempt.then(() => void 0);
13872
+ return attempt;
13873
+ }
13874
+ /** B-276 machine RPCs. Responses always carry `claudeAuth` so the web can refresh in place. */
13875
+ setClaudeAuthHandlers(handlers) {
13876
+ this.rpcHandlerManager.registerHandler("claude-auth-probe", async () => {
13877
+ const claudeAuth = await handlers.probe();
13878
+ return { claudeAuth };
13879
+ });
13880
+ this.rpcHandlerManager.registerHandler("claude-auth-repair", async (params) => {
13881
+ const action = typeof params?.action === "string" ? params.action : "";
13882
+ return handlers.repair(action);
13883
+ });
13884
+ this.rpcHandlerManager.registerHandler("claude-auth-set-store", async (params) => {
13885
+ const store = params?.store === "file" ? "file" : params?.store === "auto" ? "auto" : null;
13886
+ if (!store) return { error: "invalid-store", claudeAuth: this.claudeAuthState };
13887
+ const claudeAuth = await handlers.setStore(store);
13888
+ return { claudeAuth };
13889
+ });
13890
+ }
13828
13891
  setRPCHandlers({
13829
13892
  spawnSession,
13830
13893
  resumeSession,
@@ -14339,7 +14402,10 @@ class ApiMachineClient {
14339
14402
  // B-084: closed records survive daemon restarts (persisted
14340
14403
  // in closed-terminals.json), so the connect snapshot ships
14341
14404
  // them too — not just the incremental pushes.
14342
- closedTerminals: this.webTerminal.getClosedTerminals()
14405
+ closedTerminals: this.webTerminal.getClosedTerminals(),
14406
+ // B-276: re-ship the last preflight so a reconnect never
14407
+ // leaves a stale-pid value visible longer than needed.
14408
+ ...this.claudeAuthState ? { claudeAuth: this.claudeAuthState } : {}
14343
14409
  };
14344
14410
  });
14345
14411
  this.webTerminal.startListTracking((terminals) => this.pushTerminalList(terminals));
@@ -15674,4 +15740,4 @@ const RawJSONLinesSchema = z$1.discriminatedUnion("type", [
15674
15740
  }).passthrough()
15675
15741
  ]);
15676
15742
 
15677
- export { detectResumeSupport as $, ApiClient as A, deriveLocalCliUpdateSummary as B, updateSettings as C, encodeBase64Url as D, decodeBase64 as E, writeCredentialsLegacy as F, writeCredentialsDataKey as G, readCredentialsForConfiguredRelay as H, InvalidateSync as I, encrypt as J, isClaudeSessionId as K, getLocalHappyAgentCredentialPath as L, MAX_CHAT_ATTACHMENT_SOURCE_BYTES as M, readLocalHappyAgentCredentials as N, decryptWithDataKey as O, decryptLegacy as P, extractCompleteLines as Q, RawJSONLinesSchema as R, decideBackfill as S, parseTerminalHookPayload as T, tmuxArgs as U, scrubTmuxClientEnv as V, decideMirrorBinding as W, MIRROR_BACKFILL_LINES_DEFAULT as X, persistSession as Y, mirrorLineKey as Z, mirrorLocalId as _, ApiSessionClient as a, detectCLIAvailability as a0, acquireDaemonLock as a1, summarizeSpawnSessionForLog as a2, deletePersistedSessions as a3, decrypt as a4, writeDaemonState as a5, fetchCliUpdateState as a6, resolveCliUpdateCheckInterval as a7, DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS as a8, releaseDaemonLock as a9, isValidSessionId as aa, listVhTerminals as ab, isValidTerminalId as ac, readVhTerminal as ad, sendToVhTerminal as ae, startOfflineReconnection as af, clearCredentials as ag, clearMachineId as ah, SandboxConfigSchema as ai, CodexAppServerClient as aj, getLatestDaemonLog as ak, persistence as al, api as am, createEnvelope as b, configuration as c, contentLogMetadata as d, errorLogMetadata as e, packageJson as f, delay as g, connectionState as h, readSettings as i, encodeBase64 as j, ensurePrivateDirectorySync as k, logger as l, hardenPrivateFileSync as m, readDaemonState as n, clearDaemonState as o, projectPath as p, getProjectPath as q, readPersistedSessions as r, initializeSandbox as s, wrapCommand as t, AsyncLock as u, ensurePrivateDirectory as v, writePrivateFileSync as w, hardenPrivateFile as x, readCredentials as y, credentialRelayProblem as z };
15743
+ export { detectResumeSupport as $, ApiClient as A, deriveLocalCliUpdateSummary as B, updateSettings as C, encodeBase64Url as D, decodeBase64 as E, writeCredentialsLegacy as F, writeCredentialsDataKey as G, readCredentialsForConfiguredRelay as H, InvalidateSync as I, encrypt as J, isClaudeSessionId as K, getLocalHappyAgentCredentialPath as L, MAX_CHAT_ATTACHMENT_SOURCE_BYTES as M, readLocalHappyAgentCredentials as N, decryptWithDataKey as O, decryptLegacy as P, extractCompleteLines as Q, RawJSONLinesSchema as R, decideBackfill as S, parseTerminalHookPayload as T, tmuxArgs as U, scrubTmuxClientEnv as V, decideMirrorBinding as W, MIRROR_BACKFILL_LINES_DEFAULT as X, persistSession as Y, mirrorLineKey as Z, mirrorLocalId as _, ApiSessionClient as a, detectCLIAvailability as a0, acquireDaemonLock as a1, summarizeSpawnSessionForLog as a2, deletePersistedSessions as a3, decrypt as a4, writeDaemonState as a5, writeSettings as a6, fetchCliUpdateState as a7, resolveCliUpdateCheckInterval as a8, DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS as a9, releaseDaemonLock as aa, isValidSessionId as ab, listVhTerminals as ac, isValidTerminalId as ad, readVhTerminal as ae, sendToVhTerminal as af, startOfflineReconnection as ag, clearCredentials as ah, clearMachineId as ai, SandboxConfigSchema as aj, CodexAppServerClient as ak, getLatestDaemonLog as al, persistence as am, api as an, createEnvelope as b, configuration as c, contentLogMetadata as d, errorLogMetadata as e, packageJson as f, delay as g, connectionState as h, readSettings as i, encodeBase64 as j, ensurePrivateDirectorySync as k, logger as l, hardenPrivateFileSync as m, readDaemonState as n, clearDaemonState as o, projectPath as p, getProjectPath as q, readPersistedSessions as r, initializeSandbox as s, wrapCommand as t, AsyncLock as u, ensurePrivateDirectory as v, writePrivateFileSync as w, hardenPrivateFile as x, readCredentials as y, credentialRelayProblem as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "very-happy-cli",
3
- "version": "0.2.96",
3
+ "version": "0.2.97",
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.96",
117
- "very-happy-tools-x64-darwin": "0.2.96",
118
- "very-happy-tools-arm64-linux": "0.2.96",
119
- "very-happy-tools-x64-linux": "0.2.96",
120
- "very-happy-tools-arm64-win32": "0.2.96",
121
- "very-happy-tools-x64-win32": "0.2.96"
116
+ "very-happy-tools-arm64-darwin": "0.2.97",
117
+ "very-happy-tools-x64-darwin": "0.2.97",
118
+ "very-happy-tools-arm64-linux": "0.2.97",
119
+ "very-happy-tools-x64-linux": "0.2.97",
120
+ "very-happy-tools-arm64-win32": "0.2.97",
121
+ "very-happy-tools-x64-win32": "0.2.97"
122
122
  },
123
123
  "devDependencies": {
124
124
  "@slopus/happy-wire": "workspace:*",
@@ -0,0 +1,51 @@
1
+ #!/bin/bash
2
+ # very-happy keychain-off shim for Claude Code (B-276, spec 2026-09-claude-auth-preflight D8).
3
+ #
4
+ # Placed FIRST on PATH only for the Claude Code processes very-happy spawns when
5
+ # the machine-local setting `claudeCredentialStore` is `file`. Claude Code reads
6
+ # and writes its OAuth credentials through the bare `security` CLI; an exit code
7
+ # of 36 (errSecInteractionNotAllowed) on its own keychain item makes it fall
8
+ # back to ~/.claude/.credentials.json for reads AND writes without deleting the
9
+ # file — exactly what an ssh/tmux shell sees. Everything that is not Claude
10
+ # Code's own item is passed through to the real binary untouched.
11
+ #
12
+ # Exit code MUST be 36 or 44: anything else is treated by Claude Code as a
13
+ # transient READ_FAILED (cached credentials), not as "use the file".
14
+ REAL="${HAPPY_SECURITY_BIN:-/usr/bin/security}"
15
+
16
+ is_claude_item() {
17
+ # $@ = tokens of one security command line; true when -s <value> starts with "Claude Code"
18
+ local prev="" tok
19
+ for tok in "$@"; do
20
+ if [ "$prev" = "-s" ]; then
21
+ tok="${tok%\"}"; tok="${tok#\"}"; tok="${tok%\'}"; tok="${tok#\'}"
22
+ case "$tok" in "Claude Code"*) return 0 ;; esac
23
+ return 1
24
+ fi
25
+ prev="$tok"
26
+ done
27
+ return 1
28
+ }
29
+
30
+ case "${1:-}" in
31
+ show-keychain-info)
32
+ exit 36 ;;
33
+ find-generic-password|add-generic-password|delete-generic-password)
34
+ if is_claude_item "$@"; then exit 36; fi
35
+ exec "$REAL" "$@" ;;
36
+ -i)
37
+ # Interactive form: Claude Code's normal write path feeds
38
+ # `add-generic-password -U -a <user> -s "<service>" -X <hex>` on stdin.
39
+ input="$(cat)"
40
+ first_line="$(printf '%s\n' "$input" | sed -n '1p')"
41
+ case "$first_line" in
42
+ find-generic-password*|add-generic-password*|delete-generic-password*)
43
+ if [[ "$first_line" =~ (^|[[:space:]])-s[[:space:]]+(\"([^\"]*)\"|\'([^\']*)\'|([^[:space:]]+)) ]]; then
44
+ svc="${BASH_REMATCH[3]}${BASH_REMATCH[4]}${BASH_REMATCH[5]}"
45
+ case "$svc" in "Claude Code"*) exit 36 ;; esac
46
+ fi ;;
47
+ esac
48
+ printf '%s\n' "$input" | exec "$REAL" -i ;;
49
+ *)
50
+ exec "$REAL" "$@" ;;
51
+ esac