very-happy-cli 0.2.156 → 0.2.158

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 (55) hide show
  1. package/dist/{AcpBackend-BHe1_F1h.cjs → AcpBackend-DIDRig5G.cjs} +2 -2
  2. package/dist/{AcpBackend-C3nc1u-s.mjs → AcpBackend-vDVLHuYJ.mjs} +2 -2
  3. package/dist/{AcpSessionManager-BVxkQnNf.cjs → AcpSessionManager-C44U99CC.cjs} +1 -1
  4. package/dist/{AcpSessionManager-dN3k1LbI.mjs → AcpSessionManager-CmRjfhn3.mjs} +1 -1
  5. package/dist/{agentGuidance-C604n0Zq.mjs → agentGuidance-CrR9l62U.mjs} +562 -30
  6. package/dist/{agentGuidance-C-CmV03f.cjs → agentGuidance-DwOWbX55.cjs} +581 -29
  7. package/dist/{agentHome-D2XGsoqs.mjs → agentHome-CyLmGH5L.mjs} +2 -2
  8. package/dist/{agentHome-CmIJNV7f.cjs → agentHome-Dw8Jx_CJ.cjs} +2 -2
  9. package/dist/{auto-B1I6G_Nf.mjs → auto-BlMZPwOk.mjs} +19 -10
  10. package/dist/{auto-CMDq4oIu.cjs → auto-DZbNcTXx.cjs} +19 -10
  11. package/dist/codex/happyMcpStdioBridge.cjs +2 -2
  12. package/dist/codex/happyMcpStdioBridge.mjs +2 -2
  13. package/dist/{config-BkTu25bH.mjs → config-BtPArqRH.mjs} +4 -4
  14. package/dist/{config-BYHSRY2E.cjs → config-DG84bjwF.cjs} +4 -4
  15. package/dist/{index-BbtR-Qto.cjs → index-BG932eCh.cjs} +292 -47
  16. package/dist/{index-DAAreiRF.mjs → index-CVCt3kSt.mjs} +289 -44
  17. package/dist/{index-CB8QnjvS.cjs → index-D5ScEErM.cjs} +4 -4
  18. package/dist/{index-B_-7NpGZ.cjs → index-DPCsWlWK.cjs} +7 -7
  19. package/dist/{index-PYO8Mdz4.mjs → index-DgO0FHOx.mjs} +4 -4
  20. package/dist/{index-B5QbzEga.mjs → index-ZChGsuB7.mjs} +8 -8
  21. package/dist/index.cjs +4 -4
  22. package/dist/index.mjs +4 -4
  23. package/dist/{install-CNtXzwT_.mjs → install-DarS_Wco.mjs} +1 -1
  24. package/dist/{install-CO1ps6Wt.cjs → install-wKQGgXvN.cjs} +1 -1
  25. package/dist/{installTerminalHooks-CMpOV3RU.mjs → installTerminalHooks-CpNbRFPz.mjs} +4 -4
  26. package/dist/{installTerminalHooks-BOAnQ1Qe.cjs → installTerminalHooks-DcQfee_O.cjs} +4 -4
  27. package/dist/lib.cjs +2 -2
  28. package/dist/lib.d.cts +77 -1
  29. package/dist/lib.d.mts +77 -1
  30. package/dist/lib.mjs +2 -2
  31. package/dist/{limits-DmMdchTz.mjs → limits-D2uN090G.mjs} +163 -11
  32. package/dist/{limits-o29KXBgj.cjs → limits-kqFqhfsh.cjs} +170 -10
  33. package/dist/{mcp-C_uejotl.mjs → mcp-BN6bKIGb.mjs} +4 -4
  34. package/dist/{mcp-CwK1nI7Y.cjs → mcp-DGdeUUDJ.cjs} +4 -4
  35. package/dist/{piRuntime-Dj5WnqzJ.cjs → piRuntime-3qtbKujX.cjs} +2 -2
  36. package/dist/{piRuntime-Cu2-8nOT.mjs → piRuntime-bkw2NAZU.mjs} +2 -2
  37. package/dist/{piTerminal-D-BIbov9.cjs → piTerminal-BYxp3LMr.cjs} +6 -6
  38. package/dist/{piTerminal-DanZeBrO.mjs → piTerminal-DilKwM7B.mjs} +6 -6
  39. package/dist/{runGemini--QqK-Ap5.cjs → runGemini-28O2sqyI.cjs} +6 -6
  40. package/dist/{runGemini-CZ_lhMWY.mjs → runGemini-Cjii069p.mjs} +6 -6
  41. package/dist/{runOpenClaw-C0xLhTvk.cjs → runOpenClaw-CiMZad-H.cjs} +5 -5
  42. package/dist/{runOpenClaw-CxgLpm38.mjs → runOpenClaw-jaLr7KF1.mjs} +5 -5
  43. package/dist/{send-BaOkDgH0.mjs → send-C9mOtChi.mjs} +43 -31
  44. package/dist/{send-CTcGMVuG.cjs → send-D5ejHPXG.cjs} +43 -31
  45. package/dist/{sessions-OmvyJb0n.cjs → sessions-BNMPQoLE.cjs} +133 -49
  46. package/dist/{sessions-Bn304xF1.mjs → sessions-W6771g3X.mjs} +133 -49
  47. package/dist/{spawn-BKNabutV.mjs → spawn-D0QVhzRb.mjs} +4 -4
  48. package/dist/{spawn-CC6V2KiA.cjs → spawn-Ddtom1cb.cjs} +4 -4
  49. package/dist/{teams-XtrH3_h3.cjs → teams-BZ1tayg_.cjs} +5 -5
  50. package/dist/{teams-CLbS2_4Q.mjs → teams-CqSwtw2O.mjs} +5 -5
  51. package/dist/{todo-CEDvKNXK.cjs → todo-BYMg0fR0.cjs} +2 -2
  52. package/dist/{todo-BcPbNKt2.mjs → todo-BbusB1zM.mjs} +2 -2
  53. package/dist/{types-BEVzYSoV.mjs → types-DIBF4U0Y.mjs} +78 -5
  54. package/dist/{types-CphWjqHc.cjs → types-DnrPeN80.cjs} +78 -5
  55. package/package.json +7 -7
@@ -4,12 +4,12 @@ var ink = require('ink');
4
4
  var React = require('react');
5
5
  var node_crypto = require('node:crypto');
6
6
  var path = require('node:path');
7
- var index$1 = require('./types-CphWjqHc.cjs');
8
- var persistence = require('./limits-o29KXBgj.cjs');
9
- var index = require('./index-BbtR-Qto.cjs');
7
+ var index$1 = require('./types-DnrPeN80.cjs');
8
+ var persistence = require('./limits-kqFqhfsh.cjs');
9
+ var index = require('./index-BG932eCh.cjs');
10
10
  var terminalTitleSuggest = require('./terminalTitleSuggest-Dji8I0Jy.cjs');
11
- var AcpBackend = require('./AcpBackend-BHe1_F1h.cjs');
12
- var config = require('./config-BYHSRY2E.cjs');
11
+ var AcpBackend = require('./AcpBackend-DIDRig5G.cjs');
12
+ var config = require('./config-DG84bjwF.cjs');
13
13
  require('axios');
14
14
  require('node:events');
15
15
  require('socket.io-client');
@@ -34,7 +34,7 @@ require('@anthropic-ai/sandbox-runtime');
34
34
  require('expo-server-sdk');
35
35
  require('chalk');
36
36
  require('node:util');
37
- require('./agentGuidance-C-CmV03f.cjs');
37
+ require('./agentGuidance-DwOWbX55.cjs');
38
38
  require('@anthropic-ai/claude-agent-sdk');
39
39
  require('ps-list');
40
40
  require('tmp');
@@ -2,12 +2,12 @@ import { useStdout, useInput, Box, Text, render } from 'ink';
2
2
  import React, { useState, useRef, useEffect, useCallback } from 'react';
3
3
  import { randomUUID } from 'node:crypto';
4
4
  import { join } from 'node:path';
5
- import { h as connectionState, A as ApiClient, i as errorLogMetadata, j as contentLogMetadata } from './types-BEVzYSoV.mjs';
6
- import { l as logger, r as readSettings, v as notifyDaemonSessionStarted, w as encodeBase64 } from './limits-DmMdchTz.mjs';
7
- import { I as GEMINI_API_KEY_ENV, J as GOOGLE_API_KEY_ENV, G as GEMINI_MODEL_ENV, B as BasePermissionHandler, K as BaseReasoningProcessor, h as initialMachineMetadata, j as createSessionMetadata, k as setupOfflineReconnection, M as MessageQueue2, m as hashObject, L as MessageBuffer, N as CHANGE_TITLE_INSTRUCTION, v as registerKillSessionHandler, n as startHappyServer } from './index-DAAreiRF.mjs';
5
+ import { h as connectionState, A as ApiClient, i as errorLogMetadata, j as contentLogMetadata } from './types-DIBF4U0Y.mjs';
6
+ import { l as logger, r as readSettings, v as notifyDaemonSessionStarted, w as encodeBase64 } from './limits-D2uN090G.mjs';
7
+ import { I as GEMINI_API_KEY_ENV, J as GOOGLE_API_KEY_ENV, G as GEMINI_MODEL_ENV, B as BasePermissionHandler, K as BaseReasoningProcessor, h as initialMachineMetadata, j as createSessionMetadata, k as setupOfflineReconnection, M as MessageQueue2, m as hashObject, L as MessageBuffer, N as CHANGE_TITLE_INSTRUCTION, v as registerKillSessionHandler, n as startHappyServer } from './index-CVCt3kSt.mjs';
8
8
  import { p as projectPath } from './terminalTitleSuggest-DKgX3osW.mjs';
9
- import { A as AcpBackend } from './AcpBackend-C3nc1u-s.mjs';
10
- import { readGeminiLocalConfig, determineGeminiModel, getGeminiModelSource, getInitialGeminiModel, saveGeminiModelToConfig } from './config-BkTu25bH.mjs';
9
+ import { A as AcpBackend } from './AcpBackend-vDVLHuYJ.mjs';
10
+ import { readGeminiLocalConfig, determineGeminiModel, getGeminiModelSource, getInitialGeminiModel, saveGeminiModelToConfig } from './config-BtPArqRH.mjs';
11
11
  import 'axios';
12
12
  import 'node:events';
13
13
  import 'socket.io-client';
@@ -32,7 +32,7 @@ import '@anthropic-ai/sandbox-runtime';
32
32
  import 'expo-server-sdk';
33
33
  import 'chalk';
34
34
  import 'node:util';
35
- import './agentGuidance-C604n0Zq.mjs';
35
+ import './agentGuidance-CrR9l62U.mjs';
36
36
  import '@anthropic-ai/claude-agent-sdk';
37
37
  import 'ps-list';
38
38
  import 'tmp';
@@ -5,10 +5,10 @@ var node_child_process = require('node:child_process');
5
5
  var os = require('node:os');
6
6
  var node_fs = require('node:fs');
7
7
  var path = require('node:path');
8
- var index = require('./types-CphWjqHc.cjs');
9
- var AcpSessionManager = require('./AcpSessionManager-BVxkQnNf.cjs');
10
- var persistence = require('./limits-o29KXBgj.cjs');
11
- var index$1 = require('./index-BbtR-Qto.cjs');
8
+ var index = require('./types-DnrPeN80.cjs');
9
+ var AcpSessionManager = require('./AcpSessionManager-C44U99CC.cjs');
10
+ var persistence = require('./limits-kqFqhfsh.cjs');
11
+ var index$1 = require('./index-BG932eCh.cjs');
12
12
  var WebSocket = require('ws');
13
13
  var ed = require('@noble/ed25519');
14
14
  var sha2_js = require('@noble/hashes/sha2.js');
@@ -35,7 +35,7 @@ require('@anthropic-ai/sandbox-runtime');
35
35
  require('expo-server-sdk');
36
36
  require('chalk');
37
37
  require('node:util');
38
- require('./agentGuidance-C-CmV03f.cjs');
38
+ require('./agentGuidance-DwOWbX55.cjs');
39
39
  require('ink');
40
40
  require('react');
41
41
  require('@anthropic-ai/claude-agent-sdk');
@@ -3,10 +3,10 @@ import { execFileSync } from 'node:child_process';
3
3
  import os from 'node:os';
4
4
  import { existsSync, readFileSync } from 'node:fs';
5
5
  import { join } from 'node:path';
6
- import { i as errorLogMetadata, j as contentLogMetadata, h as connectionState, A as ApiClient } from './types-BEVzYSoV.mjs';
7
- import { A as AcpSessionManager } from './AcpSessionManager-dN3k1LbI.mjs';
8
- import { z as ensurePrivateDirectorySync, D as hardenPrivateFileSync, E as writePrivateFileSync, r as readSettings, v as notifyDaemonSessionStarted, w as encodeBase64, l as logger, c as configuration } from './limits-DmMdchTz.mjs';
9
- import { h as initialMachineMetadata, j as createSessionMetadata, k as setupOfflineReconnection, M as MessageQueue2, v as registerKillSessionHandler } from './index-DAAreiRF.mjs';
6
+ import { i as errorLogMetadata, j as contentLogMetadata, h as connectionState, A as ApiClient } from './types-DIBF4U0Y.mjs';
7
+ import { A as AcpSessionManager } from './AcpSessionManager-CmRjfhn3.mjs';
8
+ import { z as ensurePrivateDirectorySync, D as hardenPrivateFileSync, E as writePrivateFileSync, r as readSettings, v as notifyDaemonSessionStarted, w as encodeBase64, l as logger, c as configuration } from './limits-D2uN090G.mjs';
9
+ import { h as initialMachineMetadata, j as createSessionMetadata, k as setupOfflineReconnection, M as MessageQueue2, v as registerKillSessionHandler } from './index-CVCt3kSt.mjs';
10
10
  import WebSocket from 'ws';
11
11
  import * as ed from '@noble/ed25519';
12
12
  import { sha512 } from '@noble/hashes/sha2.js';
@@ -33,7 +33,7 @@ import '@anthropic-ai/sandbox-runtime';
33
33
  import 'expo-server-sdk';
34
34
  import 'chalk';
35
35
  import 'node:util';
36
- import './agentGuidance-C604n0Zq.mjs';
36
+ import './agentGuidance-CrR9l62U.mjs';
37
37
  import 'ink';
38
38
  import 'react';
39
39
  import '@anthropic-ai/claude-agent-sdk';
@@ -1,8 +1,8 @@
1
1
  import chalk from 'chalk';
2
2
  import { readFileSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
- import { d as readPersistedSessions, c as configuration } from './limits-DmMdchTz.mjs';
5
- import { n as deliverToSession, o as sessionWebUrl } from './agentGuidance-C604n0Zq.mjs';
4
+ import { d as readPersistedSessions } from './limits-D2uN090G.mjs';
5
+ import { u as deliverToSession, v as sendRemoteMessage, x as sessionWebUrl } from './agentGuidance-CrR9l62U.mjs';
6
6
  import 'zod';
7
7
  import '@paralleldrive/cuid2';
8
8
  import 'node:fs/promises';
@@ -14,6 +14,7 @@ import 'axios';
14
14
  import 'socket.io-client';
15
15
 
16
16
  const EXIT_SESSION_NOT_LIVE = 3;
17
+ const MACHINE_ID_RE = /^[a-zA-Z0-9_-]{1,128}$/;
17
18
  function parseSendArgs(args) {
18
19
  const options = { resume: false, json: false, help: false };
19
20
  for (let i = 0; i < args.length; i++) {
@@ -36,6 +37,10 @@ function parseSendArgs(args) {
36
37
  options.model = value;
37
38
  } else if (arg === "--resume") {
38
39
  options.resume = true;
40
+ } else if (arg === "--machine") {
41
+ const value = args[++i];
42
+ if (value === void 0 || !MACHINE_ID_RE.test(value)) throw new Error("--machine requires a machine id");
43
+ options.machine = value;
39
44
  } else if (arg === "--json") {
40
45
  options.json = true;
41
46
  } else if (arg === "--help" || arg === "-h") {
@@ -55,7 +60,7 @@ ${chalk.bold("very-happy send")} - Send a message into an existing session (for
55
60
 
56
61
  ${chalk.bold("Usage:")}
57
62
  very-happy send --session <id> (--prompt <text> | --prompt-file <file>)
58
- [--model <id>] [--resume] [--json]
63
+ [--model <id>] [--resume] [--machine <id>] [--json]
59
64
 
60
65
  ${chalk.bold("Options:")}
61
66
  --session, -s <id> Target session id (required)
@@ -65,24 +70,30 @@ ${chalk.bold("Options:")}
65
70
  ('default' = the machine default). Without it the
66
71
  session keeps whatever model it is on.
67
72
  --resume If the session is archived or offline, bring it back
68
- on THIS machine first (same as the web's Restore),
73
+ on its machine first (same as the web's Restore),
69
74
  wait until it is live, then send.
75
+ --machine <id> The machine the session runs on. Skips the lookup
76
+ across the account's machines and always goes
77
+ through that machine's daemon.
70
78
  --json Machine-readable output (see below)
71
79
  -h, --help Show this help
72
80
 
73
81
  ${chalk.bold("Behavior:")}
74
- The session must have been spawned by THIS machine's daemon (its
75
- encryption key must be in ${configuration.sessionsFile}); sessions from
76
- other machines or from daemons too old to persist keys cannot be reached.
82
+ A session spawned by THIS machine's daemon is sent to directly (its key is
83
+ in ~/.happy/sessions.json). Any other session of the account is sent
84
+ through the daemon of the machine that spawned it: that machine must be
85
+ online and on CLI \u2265 0.2.157; the CLI here never sees its key. Without
86
+ --machine the online machines are asked in turn which one holds it.
77
87
 
78
- Before sending, the session is classified from the local daemon and the
79
- server: live (a wrapper is attached, here or on another machine), archived,
80
- offline (wrapper exited / presence timed out) or not_found. The server
81
- stores messages for any session, so only "live" means someone will read it;
82
- anything else is refused with exit 3 unless --resume is given.
88
+ Before sending, the session is classified from its daemon and the server:
89
+ live (a wrapper is attached), archived, offline (wrapper exited / presence
90
+ timed out) or not_found. The server stores messages for any session, so
91
+ only "live" means someone will read it; anything else is refused with exit
92
+ 3 unless --resume is given (a remote session is resumed on ITS machine).
83
93
 
84
94
  ${chalk.bold("JSON output:")}
85
- delivered {"sessionId","url","delivered":true,"status":"live","resumed":false|true}
95
+ delivered {"sessionId","url","delivered":true,"status":"live","resumed":false|true,
96
+ "machine":{"id","host"}} (machine only when proxied)
86
97
  refused {"sessionId","url","delivered":false,"status":"archived"|"offline"|"not_found",
87
98
  "error":"...","resume":{"ok":false,"error":"..."}} (resume only with --resume)
88
99
  dropped {"delivered":false,"stored":true,...} the POST succeeded but the wrapper
@@ -90,7 +101,8 @@ ${chalk.bold("JSON output:")}
90
101
 
91
102
  ${chalk.bold("Exit codes:")}
92
103
  0 message delivered to a live wrapper
93
- 1 bad arguments, unknown session, or transport failure
104
+ 1 bad arguments, unknown session (no machine of the account holds it, or
105
+ its machine is offline), or transport failure
94
106
  3 session not live (archived / offline / not found) or resume failed
95
107
  `);
96
108
  }
@@ -142,24 +154,23 @@ async function handleSendCommand(args) {
142
154
  if (prompt.trim().length === 0) {
143
155
  fail(options, sessionId, "Prompt is empty");
144
156
  }
145
- const persisted = readPersistedSessions()[sessionId];
146
- if (!persisted) {
147
- fail(
148
- options,
149
- sessionId,
150
- `Session ${sessionId} has no encryption key in ${configuration.sessionsFile}. It was not spawned by this machine's daemon, or the daemon is too old to persist session keys.`
151
- );
152
- }
157
+ const persisted = options.machine ? void 0 : readPersistedSessions()[sessionId];
158
+ const model = options.model !== void 0 ? { model: options.model === "default" ? null : options.model } : {};
153
159
  let result;
160
+ let machine;
154
161
  try {
155
- result = await deliverToSession(sessionId, persisted, prompt, "cli-send", {
156
- resume: options.resume,
157
- ...options.model !== void 0 ? { model: options.model === "default" ? null : options.model } : {}
158
- });
162
+ if (persisted) {
163
+ result = await deliverToSession(sessionId, persisted, prompt, "cli-send", { resume: options.resume, ...model });
164
+ } else {
165
+ const remote = await sendRemoteMessage(sessionId, prompt, { resume: options.resume, ...model, ...options.machine ? { machineId: options.machine } : {} });
166
+ result = remote;
167
+ machine = remote.machine;
168
+ }
159
169
  } catch (error) {
160
170
  fail(options, sessionId, `Failed to send message: ${error instanceof Error ? error.message : String(error)}`);
161
171
  }
162
172
  const url = sessionWebUrl(sessionId);
173
+ const where = machine ? ` on ${machine.host}` : "";
163
174
  if (!result.delivered) {
164
175
  if (options.json) {
165
176
  console.log(JSON.stringify({
@@ -170,20 +181,21 @@ async function handleSendCommand(args) {
170
181
  resumed: result.resumed,
171
182
  stored: result.stored,
172
183
  error: result.error,
173
- ...result.resume ? { resume: result.resume } : {}
184
+ ...result.resume ? { resume: result.resume } : {},
185
+ ...machine ? { machine } : {}
174
186
  }));
175
187
  } else {
176
- console.error(chalk.red("Not delivered:"), result.error ?? `session is ${result.status}`);
188
+ console.error(chalk.red("Not delivered:"), result.error ?? `session${where} is ${result.status}`);
177
189
  if (result.stored) console.error(chalk.yellow("The message is stored server-side; nothing is reading it."));
178
190
  }
179
191
  process.exit(EXIT_SESSION_NOT_LIVE);
180
192
  }
181
193
  if (options.json) {
182
- console.log(JSON.stringify({ sessionId, url, delivered: true, status: "live", resumed: result.resumed }));
194
+ console.log(JSON.stringify({ sessionId, url, delivered: true, status: "live", resumed: result.resumed, ...machine ? { machine } : {} }));
183
195
  } else {
184
- console.log(`${chalk.bold("Session:")} ${sessionId}`);
196
+ console.log(`${chalk.bold("Session:")} ${sessionId}${machine ? chalk.dim(` (machine ${machine.host})`) : ""}`);
185
197
  console.log(`${chalk.bold("URL:")} ${url}`);
186
- if (result.resumed) console.log(chalk.green("Session resumed on this machine."));
198
+ if (result.resumed) console.log(chalk.green(`Session resumed on ${machine ? machine.host : "this machine"}.`));
187
199
  console.log(chalk.green("Message sent."));
188
200
  }
189
201
  process.exit(0);
@@ -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 persistence = require('./limits-o29KXBgj.cjs');
7
- var agentGuidance = require('./agentGuidance-C-CmV03f.cjs');
6
+ var persistence = require('./limits-kqFqhfsh.cjs');
7
+ var agentGuidance = require('./agentGuidance-DwOWbX55.cjs');
8
8
  require('zod');
9
9
  require('@paralleldrive/cuid2');
10
10
  require('node:fs/promises');
@@ -16,6 +16,7 @@ require('axios');
16
16
  require('socket.io-client');
17
17
 
18
18
  const EXIT_SESSION_NOT_LIVE = 3;
19
+ const MACHINE_ID_RE = /^[a-zA-Z0-9_-]{1,128}$/;
19
20
  function parseSendArgs(args) {
20
21
  const options = { resume: false, json: false, help: false };
21
22
  for (let i = 0; i < args.length; i++) {
@@ -38,6 +39,10 @@ function parseSendArgs(args) {
38
39
  options.model = value;
39
40
  } else if (arg === "--resume") {
40
41
  options.resume = true;
42
+ } else if (arg === "--machine") {
43
+ const value = args[++i];
44
+ if (value === void 0 || !MACHINE_ID_RE.test(value)) throw new Error("--machine requires a machine id");
45
+ options.machine = value;
41
46
  } else if (arg === "--json") {
42
47
  options.json = true;
43
48
  } else if (arg === "--help" || arg === "-h") {
@@ -57,7 +62,7 @@ ${chalk.bold("very-happy send")} - Send a message into an existing session (for
57
62
 
58
63
  ${chalk.bold("Usage:")}
59
64
  very-happy send --session <id> (--prompt <text> | --prompt-file <file>)
60
- [--model <id>] [--resume] [--json]
65
+ [--model <id>] [--resume] [--machine <id>] [--json]
61
66
 
62
67
  ${chalk.bold("Options:")}
63
68
  --session, -s <id> Target session id (required)
@@ -67,24 +72,30 @@ ${chalk.bold("Options:")}
67
72
  ('default' = the machine default). Without it the
68
73
  session keeps whatever model it is on.
69
74
  --resume If the session is archived or offline, bring it back
70
- on THIS machine first (same as the web's Restore),
75
+ on its machine first (same as the web's Restore),
71
76
  wait until it is live, then send.
77
+ --machine <id> The machine the session runs on. Skips the lookup
78
+ across the account's machines and always goes
79
+ through that machine's daemon.
72
80
  --json Machine-readable output (see below)
73
81
  -h, --help Show this help
74
82
 
75
83
  ${chalk.bold("Behavior:")}
76
- The session must have been spawned by THIS machine's daemon (its
77
- encryption key must be in ${persistence.configuration.sessionsFile}); sessions from
78
- other machines or from daemons too old to persist keys cannot be reached.
84
+ A session spawned by THIS machine's daemon is sent to directly (its key is
85
+ in ~/.happy/sessions.json). Any other session of the account is sent
86
+ through the daemon of the machine that spawned it: that machine must be
87
+ online and on CLI \u2265 0.2.157; the CLI here never sees its key. Without
88
+ --machine the online machines are asked in turn which one holds it.
79
89
 
80
- Before sending, the session is classified from the local daemon and the
81
- server: live (a wrapper is attached, here or on another machine), archived,
82
- offline (wrapper exited / presence timed out) or not_found. The server
83
- stores messages for any session, so only "live" means someone will read it;
84
- anything else is refused with exit 3 unless --resume is given.
90
+ Before sending, the session is classified from its daemon and the server:
91
+ live (a wrapper is attached), archived, offline (wrapper exited / presence
92
+ timed out) or not_found. The server stores messages for any session, so
93
+ only "live" means someone will read it; anything else is refused with exit
94
+ 3 unless --resume is given (a remote session is resumed on ITS machine).
85
95
 
86
96
  ${chalk.bold("JSON output:")}
87
- delivered {"sessionId","url","delivered":true,"status":"live","resumed":false|true}
97
+ delivered {"sessionId","url","delivered":true,"status":"live","resumed":false|true,
98
+ "machine":{"id","host"}} (machine only when proxied)
88
99
  refused {"sessionId","url","delivered":false,"status":"archived"|"offline"|"not_found",
89
100
  "error":"...","resume":{"ok":false,"error":"..."}} (resume only with --resume)
90
101
  dropped {"delivered":false,"stored":true,...} the POST succeeded but the wrapper
@@ -92,7 +103,8 @@ ${chalk.bold("JSON output:")}
92
103
 
93
104
  ${chalk.bold("Exit codes:")}
94
105
  0 message delivered to a live wrapper
95
- 1 bad arguments, unknown session, or transport failure
106
+ 1 bad arguments, unknown session (no machine of the account holds it, or
107
+ its machine is offline), or transport failure
96
108
  3 session not live (archived / offline / not found) or resume failed
97
109
  `);
98
110
  }
@@ -144,24 +156,23 @@ async function handleSendCommand(args) {
144
156
  if (prompt.trim().length === 0) {
145
157
  fail(options, sessionId, "Prompt is empty");
146
158
  }
147
- const persisted = persistence.readPersistedSessions()[sessionId];
148
- if (!persisted) {
149
- fail(
150
- options,
151
- sessionId,
152
- `Session ${sessionId} has no encryption key in ${persistence.configuration.sessionsFile}. It was not spawned by this machine's daemon, or the daemon is too old to persist session keys.`
153
- );
154
- }
159
+ const persisted = options.machine ? void 0 : persistence.readPersistedSessions()[sessionId];
160
+ const model = options.model !== void 0 ? { model: options.model === "default" ? null : options.model } : {};
155
161
  let result;
162
+ let machine;
156
163
  try {
157
- result = await agentGuidance.deliverToSession(sessionId, persisted, prompt, "cli-send", {
158
- resume: options.resume,
159
- ...options.model !== void 0 ? { model: options.model === "default" ? null : options.model } : {}
160
- });
164
+ if (persisted) {
165
+ result = await agentGuidance.deliverToSession(sessionId, persisted, prompt, "cli-send", { resume: options.resume, ...model });
166
+ } else {
167
+ const remote = await agentGuidance.sendRemoteMessage(sessionId, prompt, { resume: options.resume, ...model, ...options.machine ? { machineId: options.machine } : {} });
168
+ result = remote;
169
+ machine = remote.machine;
170
+ }
161
171
  } catch (error) {
162
172
  fail(options, sessionId, `Failed to send message: ${error instanceof Error ? error.message : String(error)}`);
163
173
  }
164
174
  const url = agentGuidance.sessionWebUrl(sessionId);
175
+ const where = machine ? ` on ${machine.host}` : "";
165
176
  if (!result.delivered) {
166
177
  if (options.json) {
167
178
  console.log(JSON.stringify({
@@ -172,20 +183,21 @@ async function handleSendCommand(args) {
172
183
  resumed: result.resumed,
173
184
  stored: result.stored,
174
185
  error: result.error,
175
- ...result.resume ? { resume: result.resume } : {}
186
+ ...result.resume ? { resume: result.resume } : {},
187
+ ...machine ? { machine } : {}
176
188
  }));
177
189
  } else {
178
- console.error(chalk.red("Not delivered:"), result.error ?? `session is ${result.status}`);
190
+ console.error(chalk.red("Not delivered:"), result.error ?? `session${where} is ${result.status}`);
179
191
  if (result.stored) console.error(chalk.yellow("The message is stored server-side; nothing is reading it."));
180
192
  }
181
193
  process.exit(EXIT_SESSION_NOT_LIVE);
182
194
  }
183
195
  if (options.json) {
184
- console.log(JSON.stringify({ sessionId, url, delivered: true, status: "live", resumed: result.resumed }));
196
+ console.log(JSON.stringify({ sessionId, url, delivered: true, status: "live", resumed: result.resumed, ...machine ? { machine } : {} }));
185
197
  } else {
186
- console.log(`${chalk.bold("Session:")} ${sessionId}`);
198
+ console.log(`${chalk.bold("Session:")} ${sessionId}${machine ? chalk.dim(` (machine ${machine.host})`) : ""}`);
187
199
  console.log(`${chalk.bold("URL:")} ${url}`);
188
- if (result.resumed) console.log(chalk.green("Session resumed on this machine."));
200
+ if (result.resumed) console.log(chalk.green(`Session resumed on ${machine ? machine.host : "this machine"}.`));
189
201
  console.log(chalk.green("Message sent."));
190
202
  }
191
203
  process.exit(0);