very-happy-cli 0.2.150 → 0.2.152

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 (57) hide show
  1. package/dist/{AcpBackend-C-dhGJgd.mjs → AcpBackend-Bs3wwYbt.mjs} +2 -2
  2. package/dist/{AcpBackend-CXawaWWx.cjs → AcpBackend-CogiKCng.cjs} +2 -2
  3. package/dist/{AcpSessionManager-DKOtSqJc.mjs → AcpSessionManager-BT8PJMEm.mjs} +1 -1
  4. package/dist/{AcpSessionManager-TrwEZR7y.cjs → AcpSessionManager-BazkBuFo.cjs} +9 -9
  5. package/dist/agentGuidance-CqBn77AI.cjs +553 -0
  6. package/dist/agentGuidance-Daxn5Nvj.mjs +529 -0
  7. package/dist/{agentHome-Dm-Wjs38.mjs → agentHome-CCYfs8Ec.mjs} +3 -3
  8. package/dist/{agentHome-rylNIkix.cjs → agentHome-CpJjrQZc.cjs} +3 -3
  9. package/dist/auto-DV5emt1-.mjs +466 -0
  10. package/dist/auto-DncmTDvw.cjs +472 -0
  11. package/dist/codex/happyMcpStdioBridge.cjs +4 -2
  12. package/dist/codex/happyMcpStdioBridge.mjs +4 -2
  13. package/dist/{config-Dhu6LU0h.mjs → config-DbKpQpdL.mjs} +5 -5
  14. package/dist/{config-CgPu2wtS.cjs → config-DeJgSMh-.cjs} +5 -5
  15. package/dist/{index-CpNYvMTJ.mjs → index-BauTv2jB.mjs} +900 -72
  16. package/dist/{index-BsYH1sjV.mjs → index-CN7OF2cU.mjs} +5 -5
  17. package/dist/{index-DvfCUuDK.cjs → index-CRaJ9k2p.cjs} +5 -5
  18. package/dist/{index-CzpooENj.mjs → index-DahaLbu4.mjs} +19 -19
  19. package/dist/{index-K6mUS0jF.cjs → index-ZIW1jrYm.cjs} +941 -104
  20. package/dist/{index-DEVkDRas.cjs → index-imt4mC41.cjs} +19 -19
  21. package/dist/index.cjs +4 -4
  22. package/dist/index.mjs +4 -4
  23. package/dist/install-C1K3nrxF.mjs +114 -0
  24. package/dist/install-CDR3qBhs.cjs +118 -0
  25. package/dist/{installTerminalHooks-NIbsQ1wi.cjs → installTerminalHooks-BoDxpaBO.cjs} +5 -5
  26. package/dist/{installTerminalHooks-BF_tmw9V.mjs → installTerminalHooks-VvmlO6Ue.mjs} +5 -5
  27. package/dist/lib.cjs +3 -3
  28. package/dist/lib.mjs +3 -3
  29. package/dist/limits-BNTWyUaZ.cjs +2175 -0
  30. package/dist/limits-CKR_HJnO.mjs +2080 -0
  31. package/dist/{mcp-_TVUSg8W.cjs → mcp-CCKCKL6l.cjs} +5 -5
  32. package/dist/{mcp-DzydgXpf.mjs → mcp-D4dAo5BQ.mjs} +5 -5
  33. package/dist/{piRuntime-CSAiJk0X.cjs → piRuntime-Bn5ypbjH.cjs} +2 -2
  34. package/dist/{piRuntime-ACOhil6t.mjs → piRuntime-CW-XA4rz.mjs} +2 -2
  35. package/dist/{piTerminal-DHxGB3Ic.cjs → piTerminal-B7K75aXB.cjs} +6 -6
  36. package/dist/{piTerminal-BlE4vbvZ.mjs → piTerminal-BLSXK1u2.mjs} +6 -6
  37. package/dist/{runGemini-BMYCU16c.cjs → runGemini-Bgg1BzDK.cjs} +7 -7
  38. package/dist/{runGemini-D5IV1MoF.mjs → runGemini-D5N11LxO.mjs} +7 -7
  39. package/dist/{runOpenClaw-Qs4eFtcS.cjs → runOpenClaw-B9qWHybc.cjs} +6 -6
  40. package/dist/{runOpenClaw-CWPXrKMJ.mjs → runOpenClaw-CCpyKbrn.mjs} +6 -6
  41. package/dist/{send-Dv0km8UI.mjs → send-C44kcXz5.mjs} +21 -7
  42. package/dist/{send-qXWGilm8.cjs → send-wl9ei8yG.cjs} +21 -7
  43. package/dist/{sessions-DoULfKui.mjs → sessions-DXJEcUg6.mjs} +57 -11
  44. package/dist/{sessions-CpIdDhH7.cjs → sessions-Tad8elmy.cjs} +57 -11
  45. package/dist/{spawn-DQuOmUP7.mjs → spawn-BOlb5y1_.mjs} +142 -28
  46. package/dist/{spawn-DqzG1bTH.cjs → spawn-DA8WAFmm.cjs} +142 -28
  47. package/dist/{teams-4mhNh5cj.mjs → teams-Bs8lI887.mjs} +15 -72
  48. package/dist/{teams-ByEgUzsH.cjs → teams-qVLrFcdd.cjs} +13 -70
  49. package/dist/{todo-D7SDXL0c.cjs → todo-mCMoGjJk.cjs} +4 -23
  50. package/dist/{todo-C9IAUf0K.mjs → todo-nzsqGZqp.mjs} +2 -21
  51. package/dist/{types-D2Gkpe0N.cjs → types-CN28qJl7.cjs} +38 -813
  52. package/dist/{types-BD542hb1.mjs → types-xKrxbCRv.mjs} +208 -978
  53. package/package.json +7 -7
  54. package/dist/agentGuidance-Dla-BXO8.cjs +0 -267
  55. package/dist/agentGuidance-DrfhtVP1.mjs +0 -251
  56. package/dist/limits-Dyma1REp.cjs +0 -1236
  57. package/dist/limits-FThmm7WR.mjs +0 -1165
@@ -3,14 +3,13 @@
3
3
  var chalk = require('chalk');
4
4
  var node_fs = require('node:fs');
5
5
  var path = require('node:path');
6
- var index$1 = require('./types-D2Gkpe0N.cjs');
7
- var index = require('./index-K6mUS0jF.cjs');
8
- var persistence = require('./limits-Dyma1REp.cjs');
6
+ var index$1 = require('./types-CN28qJl7.cjs');
7
+ var persistence = require('./limits-BNTWyUaZ.cjs');
8
+ var index = require('./index-ZIW1jrYm.cjs');
9
9
  require('axios');
10
10
  require('node:events');
11
11
  require('socket.io-client');
12
12
  require('zod');
13
- require('@paralleldrive/cuid2');
14
13
  require('node:crypto');
15
14
  require('child_process');
16
15
  require('util');
@@ -21,6 +20,7 @@ require('cross-spawn');
21
20
  require('./projectPath-ChLhdOyt.cjs');
22
21
  require('url');
23
22
  require('os');
23
+ require('@paralleldrive/cuid2');
24
24
  require('node:fs/promises');
25
25
  require('node:os');
26
26
  require('node:child_process');
@@ -30,7 +30,8 @@ require('tweetnacl');
30
30
  require('node:readline');
31
31
  require('@anthropic-ai/sandbox-runtime');
32
32
  require('expo-server-sdk');
33
- require('./agentGuidance-Dla-BXO8.cjs');
33
+ require('node:util');
34
+ require('./agentGuidance-CqBn77AI.cjs');
34
35
  require('ink');
35
36
  require('react');
36
37
  require('@anthropic-ai/claude-agent-sdk');
@@ -40,7 +41,6 @@ require('open');
40
41
  require('fastify');
41
42
  require('fastify-type-provider-zod');
42
43
  require('node:module');
43
- require('node:util');
44
44
  require('@modelcontextprotocol/sdk/server/mcp.js');
45
45
  require('node:http');
46
46
  require('@modelcontextprotocol/sdk/server/streamableHttp.js');
@@ -49,6 +49,46 @@ require('inquirer');
49
49
  require('node:url');
50
50
  require('node:readline/promises');
51
51
 
52
+ function nonEmpty(value) {
53
+ return typeof value === "string" && value.trim().length > 0;
54
+ }
55
+ function resolveForkSource(sessionId, persisted) {
56
+ if (!persisted) {
57
+ throw new Error(`Session ${sessionId} has no local record \u2014 only sessions spawned by this machine's daemon (in the last 14 days) can be forked from here.`);
58
+ }
59
+ const metadata = persisted.metadata;
60
+ const directory = metadata?.path;
61
+ if (!nonEmpty(directory)) throw new Error(`Session ${sessionId} has no working directory in its metadata.`);
62
+ const permissionMode = nonEmpty(metadata.permissionMode) ? metadata.permissionMode : void 0;
63
+ if (metadata.flavor === "codex") {
64
+ if (!nonEmpty(metadata.codexThreadId)) throw new Error(`Codex session ${sessionId} has no thread id yet (it never ran a turn) \u2014 nothing to fork.`);
65
+ return { agent: "codex", directory, codexThreadId: metadata.codexThreadId, permissionMode, parentSessionId: sessionId };
66
+ }
67
+ if (metadata.flavor && metadata.flavor !== "claude") {
68
+ throw new Error(`Session ${sessionId} runs ${metadata.flavor}; only claude and codex sessions can be forked.`);
69
+ }
70
+ if (!nonEmpty(metadata.claudeSessionId)) throw new Error(`Session ${sessionId} has no Claude conversation id yet (it never ran a turn) \u2014 nothing to fork.`);
71
+ return { agent: "claude", directory, claudeSessionId: metadata.claudeSessionId, permissionMode, parentSessionId: sessionId };
72
+ }
73
+ async function forkProviderConversation(source) {
74
+ if (source.agent === "claude") {
75
+ try {
76
+ return { resumeClaudeSessionId: await index$1.forkSession(index$1.getProjectPath(source.directory), source.claudeSessionId) };
77
+ } catch (error) {
78
+ if (error instanceof index$1.ForkSourceMissingError) throw new Error("Claude session file not found on this machine");
79
+ throw error;
80
+ }
81
+ }
82
+ const client = new index$1.CodexAppServerClient();
83
+ await client.connect();
84
+ try {
85
+ const result = await index$1.forkCodexThread(client, { threadId: source.codexThreadId, cwd: source.directory });
86
+ return { resumeCodexThreadId: result.newCodexThreadId };
87
+ } finally {
88
+ await client.disconnect();
89
+ }
90
+ }
91
+
52
92
  function parseEnvAssignment(raw) {
53
93
  const eq = raw.indexOf("=");
54
94
  if (eq <= 0) throw new Error(`--env expects KEY=VALUE, got: ${raw}`);
@@ -98,6 +138,15 @@ function parseSpawnArgs(args) {
98
138
  if (value === void 0) throw new Error("--env requires a value");
99
139
  const [key, val] = parseEnvAssignment(value);
100
140
  options.env = { ...options.env ?? {}, [key]: val };
141
+ } else if (arg === "--model" || arg === "-m") {
142
+ const value = args[++i];
143
+ if (value === void 0 || value.trim().length === 0) throw new Error("--model requires a value");
144
+ options.model = value;
145
+ } else if (arg === "--fork") {
146
+ const value = args[++i];
147
+ if (value === void 0) throw new Error("--fork requires a session id");
148
+ if (!/^[a-zA-Z0-9_-]{1,128}$/.test(value)) throw new Error(`Invalid session id for --fork: ${value}`);
149
+ options.fork = value;
101
150
  } else if (arg === "--json") {
102
151
  options.json = true;
103
152
  } else if (arg === "--help" || arg === "-h") {
@@ -118,7 +167,9 @@ ${chalk.bold("very-happy spawn")} - Spawn a remote session via the local daemon
118
167
  ${chalk.bold("Usage:")}
119
168
  very-happy spawn --dir <path> [--prompt <text> | --prompt-file <file>]
120
169
  [--spawned-by <name>] [--permission-mode <mode>]
121
- [--agent <name>] [--env KEY=VALUE]... [--json]
170
+ [--agent <name>] [--model <id>] [--env KEY=VALUE]... [--json]
171
+ very-happy spawn --fork <sessionId> [--prompt \u2026] [--permission-mode <mode>]
172
+ [--model <id>] [--spawned-by <name>] [--json]
122
173
 
123
174
  ${chalk.bold("Options:")}
124
175
  --dir, -d <path> Working directory for the new session (required)
@@ -129,22 +180,34 @@ ${chalk.bold("Options:")}
129
180
  the web list and is searchable as #<name>.
130
181
  --permission-mode <m> Permission mode for the new session: default,
131
182
  acceptEdits, plan, yolo, bypassPermissions.
132
- WITHOUT this the session runs in 'default' and stops
133
- at the first un-allowlisted tool waiting for someone
134
- to approve \u2014 if nothing is watching, it just hangs.
135
- Unattended dispatchers want bypassPermissions.
183
+ Default = the web launcher's: yolo for claude /
184
+ codex / pi, 'default' for gemini / openclaw. A fork
185
+ keeps its source's mode. 'default' stops at the first
186
+ un-allowlisted tool until someone approves it (see
187
+ \`sessions approve\`).
136
188
  --agent <name> Backend to run: claude (default), codex, gemini,
137
189
  openclaw, pi (needs pi-acp on the daemon's PATH).
190
+ --model, -m <id> Model for the session, sent with the first message
191
+ (needs --prompt). Default = the web launcher's:
192
+ claude-opus-5-5 for claude (the machine default when
193
+ its Claude Code is too old for it); other agents use
194
+ their own default. 'default' = the machine default.
195
+ --fork <sessionId> Fork an existing session of THIS machine (same as the
196
+ web's fork): copies its conversation and continues
197
+ it in a new session, in the same directory and with
198
+ the same agent (so --dir / --agent are not needed).
138
199
  --env KEY=VALUE Extra environment for the session process. Repeatable.
139
200
  \${VAR} is expanded against the daemon's environment;
140
201
  an unresolved reference fails the spawn.
141
- --json Machine-readable output: {"sessionId", "url"}
202
+ --json Machine-readable output: {"sessionId", "url",
203
+ "permissionMode", \u2026}
142
204
  -h, --help Show this help
143
205
 
144
206
  ${chalk.bold("Behavior:")}
145
207
  Requires the Very Happy daemon to be running on this machine (same semantics
146
208
  as spawning from the web: an offline machine cannot spawn). Without
147
- --prompt / --prompt-file the session is spawned idle.
209
+ --prompt / --prompt-file the session is spawned idle. To collect the reply:
210
+ \`very-happy sessions read <id> --wait --answer\`.
148
211
 
149
212
  ${chalk.bold("Exit codes:")}
150
213
  0 success
@@ -152,9 +215,11 @@ ${chalk.bold("Exit codes:")}
152
215
  2 session spawned but first message failed (session URL still printed)
153
216
  `);
154
217
  }
155
- async function sendFirstMessage(sessionId, text) {
218
+ async function sendFirstMessage(sessionId, text, metaFor) {
156
219
  const persisted = await index.waitForSessionKey(sessionId, 15e3);
157
- await index.sendUserMessage(sessionId, persisted, text, "cli-spawn");
220
+ const meta = metaFor(persisted.metadata?.capabilities ?? null);
221
+ await index.sendUserMessage(sessionId, persisted, text, "cli-spawn", meta);
222
+ return meta;
158
223
  }
159
224
  async function handleSpawnCommand(args) {
160
225
  let options;
@@ -169,12 +234,32 @@ async function handleSpawnCommand(args) {
169
234
  printHelp();
170
235
  process.exit(0);
171
236
  }
172
- if (!options.dir) {
173
- console.error(chalk.red("Error:"), "--dir is required");
237
+ let forkSource = null;
238
+ if (options.fork !== void 0) {
239
+ try {
240
+ const persisted = persistence.readPersistedSessions()[options.fork];
241
+ const current = persisted ? await index.readSessionMetadata(options.fork, persisted) : null;
242
+ forkSource = resolveForkSource(options.fork, persisted && current ? { ...persisted, metadata: { ...persisted.metadata, ...current } } : persisted);
243
+ } catch (error) {
244
+ console.error(chalk.red("Error:"), error instanceof Error ? error.message : String(error));
245
+ process.exit(1);
246
+ }
247
+ if (options.dir !== void 0 && path.resolve(options.dir) !== forkSource.directory) {
248
+ console.error(chalk.red("Error:"), `--dir ${path.resolve(options.dir)} differs from the forked session's directory ${forkSource.directory}; a fork always continues in its source directory.`);
249
+ process.exit(1);
250
+ }
251
+ if (options.agent !== void 0 && options.agent !== forkSource.agent) {
252
+ console.error(chalk.red("Error:"), `--agent ${options.agent} differs from the forked session's agent ${forkSource.agent}.`);
253
+ process.exit(1);
254
+ }
255
+ } else if (!options.dir) {
256
+ console.error(chalk.red("Error:"), "--dir is required (or --fork <sessionId>)");
174
257
  console.error(`Run ${chalk.cyan("very-happy spawn --help")} for usage.`);
175
258
  process.exit(1);
176
259
  }
177
- const directory = path.resolve(options.dir);
260
+ const directory = forkSource ? forkSource.directory : path.resolve(options.dir);
261
+ const agent = forkSource ? forkSource.agent : options.agent;
262
+ const permissionMode = options.permissionMode ?? forkSource?.permissionMode ?? index.resolveSpawnPermissionMode(agent, void 0);
178
263
  let prompt = options.prompt;
179
264
  if (options.promptFile !== void 0) {
180
265
  try {
@@ -188,6 +273,10 @@ async function handleSpawnCommand(args) {
188
273
  console.error(chalk.red("Error:"), "Prompt is empty");
189
274
  process.exit(1);
190
275
  }
276
+ if (options.model !== void 0 && prompt === void 0) {
277
+ console.error(chalk.red("Error:"), "--model needs --prompt / --prompt-file (the model is sent with the first message); for an idle session pass it later with `very-happy send --model`.");
278
+ process.exit(1);
279
+ }
191
280
  try {
192
281
  if (!node_fs.statSync(directory).isDirectory()) {
193
282
  console.error(chalk.red("Error:"), `Not a directory: ${directory}`);
@@ -202,12 +291,23 @@ async function handleSpawnCommand(args) {
202
291
  console.error(`Start it with ${chalk.cyan("very-happy daemon start")} and retry.`);
203
292
  process.exit(1);
204
293
  }
205
- persistence.logger.debug(`[SPAWN CMD] Spawning session in ${directory}`);
294
+ let resumeIds = {};
295
+ if (forkSource) {
296
+ try {
297
+ resumeIds = await forkProviderConversation(forkSource);
298
+ } catch (error) {
299
+ console.error(chalk.red("Error:"), `Failed to fork session ${forkSource.parentSessionId}: ${error instanceof Error ? error.message : String(error)}`);
300
+ process.exit(1);
301
+ }
302
+ }
303
+ persistence.logger.debug(`[SPAWN CMD] Spawning session in ${directory}${forkSource ? ` (fork of ${forkSource.parentSessionId})` : ""}`);
206
304
  const result = await index$1.spawnDaemonSession(directory, void 0, {
207
305
  spawnedBy: options.spawnedBy,
208
- permissionMode: options.permissionMode,
209
- agent: options.agent,
210
- environmentVariables: options.env
306
+ permissionMode,
307
+ agent,
308
+ environmentVariables: options.env,
309
+ ...resumeIds,
310
+ ...forkSource ? { parentSessionId: forkSource.parentSessionId } : {}
211
311
  });
212
312
  if (result?.error || !result?.success || !result?.sessionId) {
213
313
  const message = result?.error || "Daemon returned no session ID";
@@ -216,24 +316,38 @@ async function handleSpawnCommand(args) {
216
316
  }
217
317
  const sessionId = result.sessionId;
218
318
  const url = index.sessionWebUrl(sessionId);
319
+ if (forkSource && result.resumed !== true) {
320
+ await index$1.stopDaemonSession(sessionId).catch(() => false);
321
+ console.error(chalk.red("Error:"), "The running daemon is too old to fork from the CLI (it spawned a fresh session, now stopped). Restart the daemon on this CLI version and retry.");
322
+ process.exit(1);
323
+ }
219
324
  let promptError = null;
325
+ let sentMeta = null;
220
326
  if (prompt !== void 0) {
221
327
  try {
222
- await sendFirstMessage(sessionId, prompt);
328
+ sentMeta = await sendFirstMessage(sessionId, prompt, (capabilities) => index.resolveFirstMessageMeta({
329
+ agent,
330
+ permissionMode,
331
+ explicitModel: options.model,
332
+ capabilities
333
+ }));
223
334
  } catch (error) {
224
335
  promptError = error instanceof Error ? error.message : String(error);
225
336
  }
226
337
  }
227
338
  if (options.json) {
228
- const payload = { sessionId, url };
339
+ const payload = { sessionId, url, permissionMode };
229
340
  if (options.spawnedBy !== void 0) {
230
341
  payload.spawnedBy = options.spawnedBy;
231
342
  }
232
- if (options.permissionMode !== void 0) {
233
- payload.permissionMode = options.permissionMode;
343
+ if (agent !== void 0) {
344
+ payload.agent = agent;
345
+ }
346
+ if (forkSource) {
347
+ payload.forkedFrom = forkSource.parentSessionId;
234
348
  }
235
- if (options.agent !== void 0) {
236
- payload.agent = options.agent;
349
+ if (sentMeta && sentMeta.model !== void 0) {
350
+ payload.model = sentMeta.model;
237
351
  }
238
352
  if (prompt !== void 0) {
239
353
  payload.promptDelivered = promptError === null;
@@ -1,20 +1,21 @@
1
1
  import { homedir } from 'node:os';
2
- import { unlink, rmdir, mkdir, lstat, readFile, writeFile, rename } from 'node:fs/promises';
2
+ import { readFile } from 'node:fs/promises';
3
3
  import { z } from 'zod';
4
- import { createHash, randomUUID } from 'node:crypto';
5
- import { d as teamRequest, e as createTeamsClient } from './agentGuidance-DrfhtVP1.mjs';
6
- import { e as readCredentialsForConfiguredRelay } from './limits-FThmm7WR.mjs';
7
- import { join, resolve, dirname } from 'node:path';
8
- import { J as TEAM_SKILL } from './index-CpNYvMTJ.mjs';
4
+ import { randomUUID } from 'node:crypto';
5
+ import { g as teamRequest, h as createTeamsClient } from './agentGuidance-Daxn5Nvj.mjs';
6
+ import { f as readCredentialsForConfiguredRelay } from './limits-CKR_HJnO.mjs';
7
+ import { a as installTeamSkill, i as installAutomationSkill } from './install-C1K3nrxF.mjs';
9
8
  import 'node:fs';
9
+ import 'node:path';
10
10
  import 'tweetnacl';
11
11
  import 'chalk';
12
12
  import 'node:util';
13
- import './types-BD542hb1.mjs';
13
+ import '@paralleldrive/cuid2';
14
+ import './index-BauTv2jB.mjs';
15
+ import './types-xKrxbCRv.mjs';
14
16
  import 'axios';
15
17
  import 'node:events';
16
18
  import 'socket.io-client';
17
- import '@paralleldrive/cuid2';
18
19
  import 'child_process';
19
20
  import 'util';
20
21
  import 'fs/promises';
@@ -98,69 +99,6 @@ async function setTeamPermissionMode(teamId, mode, requestId) {
98
99
  return safe;
99
100
  }
100
101
 
101
- async function assertPrivateDestination(directory) {
102
- for (let path = directory; ; path = dirname(path)) {
103
- try {
104
- const stat = await lstat(path);
105
- if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error("Refusing symlink or non-directory in managed skill destination: " + path);
106
- } catch (error) {
107
- if (error.code !== "ENOENT") throw error;
108
- }
109
- if (dirname(path) === path) break;
110
- }
111
- }
112
- const digest = (text) => createHash("sha256").update(text).digest("hex");
113
- async function read(path) {
114
- try {
115
- const stat = await lstat(path);
116
- if (!stat.isFile() || stat.isSymbolicLink()) throw new Error("Refusing non-regular managed file");
117
- return await readFile(path, "utf8");
118
- } catch (error) {
119
- if (error.code === "ENOENT") return null;
120
- throw error;
121
- }
122
- }
123
- async function publish(path, content) {
124
- const temporary = `${path}.${randomUUID()}.new`;
125
- try {
126
- await writeFile(temporary, content, { flag: "wx", mode: 384 });
127
- await rename(temporary, path);
128
- } finally {
129
- await unlink(temporary).catch(() => {
130
- });
131
- }
132
- }
133
- async function installTeamSkill(options) {
134
- const directory = join(resolve(options.home), ".local", "share", "very-happy", "skills", "very-happy-teams");
135
- await assertPrivateDestination(directory);
136
- const file = join(directory, "SKILL.md");
137
- const manifest = join(directory, ".very-happy-managed.json");
138
- const current = await read(file);
139
- const managed = await read(manifest);
140
- const previous = managed ? JSON.parse(managed) : null;
141
- if (previous && previous.owner !== "very-happy") throw new Error("Existing manifest is not owned by Very Happy");
142
- if (current !== null && (!previous || previous.owner !== "very-happy" || previous.sha256 !== digest(current) && previous.pendingSha256 !== digest(current))) {
143
- throw new Error("Existing skill is not owned by Very Happy or was edited; preserve it and resolve manually");
144
- }
145
- const action = options.uninstall ? current === null && managed === null ? "unchanged" : "remove" : current === TEAM_SKILL && !previous?.pendingSha256 ? "unchanged" : "write";
146
- if (options.apply && action !== "unchanged") {
147
- await assertPrivateDestination(directory);
148
- if (options.uninstall) {
149
- if (current !== null) await unlink(file);
150
- if (managed !== null) await unlink(manifest);
151
- await rmdir(directory).catch(() => {
152
- });
153
- } else {
154
- await mkdir(directory, { recursive: true });
155
- const nextHash = digest(TEAM_SKILL);
156
- await publish(manifest, JSON.stringify({ owner: "very-happy", sha256: current === null ? null : digest(current), pendingSha256: nextHash, version: 1 }));
157
- await publish(file, TEAM_SKILL);
158
- await publish(manifest, JSON.stringify({ owner: "very-happy", sha256: nextHash, version: 1 }));
159
- }
160
- }
161
- return { host: options.host, path: file, action, applied: !!options.apply, discovery: "No host skill directories are modified. Read this absolute skill path in a managed session.", managedSessions: "Team tools are injected by Very Happy. Standalone agents require a managed session connection." };
162
- }
163
-
164
102
  async function handleTeamsCommand(args) {
165
103
  const command = args[0];
166
104
  const value = (flag) => {
@@ -174,7 +112,12 @@ async function handleTeamsCommand(args) {
174
112
  if (command === "install" || command === "uninstall") {
175
113
  const host = value("--host");
176
114
  if (!host || !["claude", "codex", "pi"].includes(host)) throw new Error("--host must be claude, codex, or pi");
177
- console.log(JSON.stringify(await installTeamSkill({ host, home: value("--home") ?? homedir(), apply: args.includes("--apply"), uninstall: command === "uninstall" })));
115
+ const options = { host, home: value("--home") ?? homedir(), apply: args.includes("--apply"), uninstall: command === "uninstall" };
116
+ await installTeamSkill({ ...options, apply: false });
117
+ await installAutomationSkill({ ...options, apply: false });
118
+ const teams = await installTeamSkill(options);
119
+ const automations = await installAutomationSkill(options);
120
+ console.log(JSON.stringify({ ...teams, also: [automations] }));
178
121
  return;
179
122
  }
180
123
  if (command === "doctor") {
@@ -4,19 +4,20 @@ var os = require('node:os');
4
4
  var promises = require('node:fs/promises');
5
5
  var z = require('zod');
6
6
  var node_crypto = require('node:crypto');
7
- var agentGuidance = require('./agentGuidance-Dla-BXO8.cjs');
8
- var persistence = require('./limits-Dyma1REp.cjs');
9
- var path = require('node:path');
10
- var index = require('./index-K6mUS0jF.cjs');
7
+ var agentGuidance = require('./agentGuidance-CqBn77AI.cjs');
8
+ var persistence = require('./limits-BNTWyUaZ.cjs');
9
+ var install = require('./install-CDR3qBhs.cjs');
11
10
  require('node:fs');
11
+ require('node:path');
12
12
  require('tweetnacl');
13
13
  require('chalk');
14
14
  require('node:util');
15
- require('./types-D2Gkpe0N.cjs');
15
+ require('@paralleldrive/cuid2');
16
+ require('./index-ZIW1jrYm.cjs');
17
+ require('./types-CN28qJl7.cjs');
16
18
  require('axios');
17
19
  require('node:events');
18
20
  require('socket.io-client');
19
- require('@paralleldrive/cuid2');
20
21
  require('child_process');
21
22
  require('util');
22
23
  require('fs/promises');
@@ -100,69 +101,6 @@ async function setTeamPermissionMode(teamId, mode, requestId) {
100
101
  return safe;
101
102
  }
102
103
 
103
- async function assertPrivateDestination(directory) {
104
- for (let path$1 = directory; ; path$1 = path.dirname(path$1)) {
105
- try {
106
- const stat = await promises.lstat(path$1);
107
- if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error("Refusing symlink or non-directory in managed skill destination: " + path$1);
108
- } catch (error) {
109
- if (error.code !== "ENOENT") throw error;
110
- }
111
- if (path.dirname(path$1) === path$1) break;
112
- }
113
- }
114
- const digest = (text) => node_crypto.createHash("sha256").update(text).digest("hex");
115
- async function read(path) {
116
- try {
117
- const stat = await promises.lstat(path);
118
- if (!stat.isFile() || stat.isSymbolicLink()) throw new Error("Refusing non-regular managed file");
119
- return await promises.readFile(path, "utf8");
120
- } catch (error) {
121
- if (error.code === "ENOENT") return null;
122
- throw error;
123
- }
124
- }
125
- async function publish(path, content) {
126
- const temporary = `${path}.${node_crypto.randomUUID()}.new`;
127
- try {
128
- await promises.writeFile(temporary, content, { flag: "wx", mode: 384 });
129
- await promises.rename(temporary, path);
130
- } finally {
131
- await promises.unlink(temporary).catch(() => {
132
- });
133
- }
134
- }
135
- async function installTeamSkill(options) {
136
- const directory = path.join(path.resolve(options.home), ".local", "share", "very-happy", "skills", "very-happy-teams");
137
- await assertPrivateDestination(directory);
138
- const file = path.join(directory, "SKILL.md");
139
- const manifest = path.join(directory, ".very-happy-managed.json");
140
- const current = await read(file);
141
- const managed = await read(manifest);
142
- const previous = managed ? JSON.parse(managed) : null;
143
- if (previous && previous.owner !== "very-happy") throw new Error("Existing manifest is not owned by Very Happy");
144
- if (current !== null && (!previous || previous.owner !== "very-happy" || previous.sha256 !== digest(current) && previous.pendingSha256 !== digest(current))) {
145
- throw new Error("Existing skill is not owned by Very Happy or was edited; preserve it and resolve manually");
146
- }
147
- const action = options.uninstall ? current === null && managed === null ? "unchanged" : "remove" : current === index.TEAM_SKILL && !previous?.pendingSha256 ? "unchanged" : "write";
148
- if (options.apply && action !== "unchanged") {
149
- await assertPrivateDestination(directory);
150
- if (options.uninstall) {
151
- if (current !== null) await promises.unlink(file);
152
- if (managed !== null) await promises.unlink(manifest);
153
- await promises.rmdir(directory).catch(() => {
154
- });
155
- } else {
156
- await promises.mkdir(directory, { recursive: true });
157
- const nextHash = digest(index.TEAM_SKILL);
158
- await publish(manifest, JSON.stringify({ owner: "very-happy", sha256: current === null ? null : digest(current), pendingSha256: nextHash, version: 1 }));
159
- await publish(file, index.TEAM_SKILL);
160
- await publish(manifest, JSON.stringify({ owner: "very-happy", sha256: nextHash, version: 1 }));
161
- }
162
- }
163
- return { host: options.host, path: file, action, applied: !!options.apply, discovery: "No host skill directories are modified. Read this absolute skill path in a managed session.", managedSessions: "Team tools are injected by Very Happy. Standalone agents require a managed session connection." };
164
- }
165
-
166
104
  async function handleTeamsCommand(args) {
167
105
  const command = args[0];
168
106
  const value = (flag) => {
@@ -176,7 +114,12 @@ async function handleTeamsCommand(args) {
176
114
  if (command === "install" || command === "uninstall") {
177
115
  const host = value("--host");
178
116
  if (!host || !["claude", "codex", "pi"].includes(host)) throw new Error("--host must be claude, codex, or pi");
179
- console.log(JSON.stringify(await installTeamSkill({ host, home: value("--home") ?? os.homedir(), apply: args.includes("--apply"), uninstall: command === "uninstall" })));
117
+ const options = { host, home: value("--home") ?? os.homedir(), apply: args.includes("--apply"), uninstall: command === "uninstall" };
118
+ await install.installTeamSkill({ ...options, apply: false });
119
+ await install.installAutomationSkill({ ...options, apply: false });
120
+ const teams = await install.installTeamSkill(options);
121
+ const automations = await install.installAutomationSkill(options);
122
+ console.log(JSON.stringify({ ...teams, also: [automations] }));
180
123
  return;
181
124
  }
182
125
  if (command === "doctor") {
@@ -1,8 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var persistence = require('./limits-Dyma1REp.cjs');
4
- var index = require('./types-D2Gkpe0N.cjs');
5
- var agentGuidance = require('./agentGuidance-Dla-BXO8.cjs');
3
+ var persistence = require('./limits-BNTWyUaZ.cjs');
4
+ var agentGuidance = require('./agentGuidance-CqBn77AI.cjs');
6
5
  require('node:fs/promises');
7
6
  require('node:fs');
8
7
  require('node:os');
@@ -12,25 +11,7 @@ require('node:crypto');
12
11
  require('tweetnacl');
13
12
  require('chalk');
14
13
  require('node:util');
15
- require('axios');
16
- require('node:events');
17
- require('socket.io-client');
18
14
  require('@paralleldrive/cuid2');
19
- require('child_process');
20
- require('util');
21
- require('fs/promises');
22
- require('crypto');
23
- require('path');
24
- require('cross-spawn');
25
- require('./projectPath-ChLhdOyt.cjs');
26
- require('url');
27
- require('os');
28
- require('node:child_process');
29
- require('node-pty');
30
- require('fs');
31
- require('node:readline');
32
- require('@anthropic-ai/sandbox-runtime');
33
- require('expo-server-sdk');
34
15
 
35
16
  async function authenticatedBuiltinTodoClient() {
36
17
  if (process.env.VH_TEAM_SCOPE_FILE !== void 0) {
@@ -42,7 +23,7 @@ async function authenticatedBuiltinTodoClient() {
42
23
  }
43
24
  const credentials = await persistence.readCredentialsForConfiguredRelay();
44
25
  if (!credentials) throw new Error("Sign in to Very Happy with `very-happy auth login` before accessing Todos.");
45
- return index.createBuiltinTodoClient(credentials, { serverUrl: persistence.configuration.serverUrl, clientId: "very-happy-cli-todo" });
26
+ return persistence.createBuiltinTodoClient(credentials, { serverUrl: persistence.configuration.serverUrl, clientId: "very-happy-cli-todo" });
46
27
  }
47
28
 
48
29
  const TODO_HELP = `very-happy todo \u2014 account-owned built-in Todos (JSON output)
@@ -106,7 +87,7 @@ async function handleTodoCommand(args) {
106
87
  return;
107
88
  }
108
89
  if (command.action === "skill") {
109
- console.log(index.BUILTIN_TODO_SKILL);
90
+ console.log(persistence.BUILTIN_TODO_SKILL);
110
91
  return;
111
92
  }
112
93
  const client = await authenticatedBuiltinTodoClient();
@@ -1,6 +1,5 @@
1
- import { e as readCredentialsForConfiguredRelay, c as configuration } from './limits-FThmm7WR.mjs';
2
- import { h as createBuiltinTodoClient, B as BUILTIN_TODO_SKILL } from './types-BD542hb1.mjs';
3
- import { c as readTeamScope, t as teamScopePath } from './agentGuidance-DrfhtVP1.mjs';
1
+ import { f as readCredentialsForConfiguredRelay, g as createBuiltinTodoClient, c as configuration, B as BUILTIN_TODO_SKILL } from './limits-CKR_HJnO.mjs';
2
+ import { f as readTeamScope, t as teamScopePath } from './agentGuidance-Daxn5Nvj.mjs';
4
3
  import 'node:fs/promises';
5
4
  import 'node:fs';
6
5
  import 'node:os';
@@ -10,25 +9,7 @@ import 'node:crypto';
10
9
  import 'tweetnacl';
11
10
  import 'chalk';
12
11
  import 'node:util';
13
- import 'axios';
14
- import 'node:events';
15
- import 'socket.io-client';
16
12
  import '@paralleldrive/cuid2';
17
- import 'child_process';
18
- import 'util';
19
- import 'fs/promises';
20
- import 'crypto';
21
- import 'path';
22
- import 'cross-spawn';
23
- import './projectPath-CSpqWdxp.mjs';
24
- import 'url';
25
- import 'os';
26
- import 'node:child_process';
27
- import 'node-pty';
28
- import 'fs';
29
- import 'node:readline';
30
- import '@anthropic-ai/sandbox-runtime';
31
- import 'expo-server-sdk';
32
13
 
33
14
  async function authenticatedBuiltinTodoClient() {
34
15
  if (process.env.VH_TEAM_SCOPE_FILE !== void 0) {