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.
- package/dist/{AcpBackend-C-dhGJgd.mjs → AcpBackend-Bs3wwYbt.mjs} +2 -2
- package/dist/{AcpBackend-CXawaWWx.cjs → AcpBackend-CogiKCng.cjs} +2 -2
- package/dist/{AcpSessionManager-DKOtSqJc.mjs → AcpSessionManager-BT8PJMEm.mjs} +1 -1
- package/dist/{AcpSessionManager-TrwEZR7y.cjs → AcpSessionManager-BazkBuFo.cjs} +9 -9
- package/dist/agentGuidance-CqBn77AI.cjs +553 -0
- package/dist/agentGuidance-Daxn5Nvj.mjs +529 -0
- package/dist/{agentHome-Dm-Wjs38.mjs → agentHome-CCYfs8Ec.mjs} +3 -3
- package/dist/{agentHome-rylNIkix.cjs → agentHome-CpJjrQZc.cjs} +3 -3
- package/dist/auto-DV5emt1-.mjs +466 -0
- package/dist/auto-DncmTDvw.cjs +472 -0
- package/dist/codex/happyMcpStdioBridge.cjs +4 -2
- package/dist/codex/happyMcpStdioBridge.mjs +4 -2
- package/dist/{config-Dhu6LU0h.mjs → config-DbKpQpdL.mjs} +5 -5
- package/dist/{config-CgPu2wtS.cjs → config-DeJgSMh-.cjs} +5 -5
- package/dist/{index-CpNYvMTJ.mjs → index-BauTv2jB.mjs} +900 -72
- package/dist/{index-BsYH1sjV.mjs → index-CN7OF2cU.mjs} +5 -5
- package/dist/{index-DvfCUuDK.cjs → index-CRaJ9k2p.cjs} +5 -5
- package/dist/{index-CzpooENj.mjs → index-DahaLbu4.mjs} +19 -19
- package/dist/{index-K6mUS0jF.cjs → index-ZIW1jrYm.cjs} +941 -104
- package/dist/{index-DEVkDRas.cjs → index-imt4mC41.cjs} +19 -19
- package/dist/index.cjs +4 -4
- package/dist/index.mjs +4 -4
- package/dist/install-C1K3nrxF.mjs +114 -0
- package/dist/install-CDR3qBhs.cjs +118 -0
- package/dist/{installTerminalHooks-NIbsQ1wi.cjs → installTerminalHooks-BoDxpaBO.cjs} +5 -5
- package/dist/{installTerminalHooks-BF_tmw9V.mjs → installTerminalHooks-VvmlO6Ue.mjs} +5 -5
- package/dist/lib.cjs +3 -3
- package/dist/lib.mjs +3 -3
- package/dist/limits-BNTWyUaZ.cjs +2175 -0
- package/dist/limits-CKR_HJnO.mjs +2080 -0
- package/dist/{mcp-_TVUSg8W.cjs → mcp-CCKCKL6l.cjs} +5 -5
- package/dist/{mcp-DzydgXpf.mjs → mcp-D4dAo5BQ.mjs} +5 -5
- package/dist/{piRuntime-CSAiJk0X.cjs → piRuntime-Bn5ypbjH.cjs} +2 -2
- package/dist/{piRuntime-ACOhil6t.mjs → piRuntime-CW-XA4rz.mjs} +2 -2
- package/dist/{piTerminal-DHxGB3Ic.cjs → piTerminal-B7K75aXB.cjs} +6 -6
- package/dist/{piTerminal-BlE4vbvZ.mjs → piTerminal-BLSXK1u2.mjs} +6 -6
- package/dist/{runGemini-BMYCU16c.cjs → runGemini-Bgg1BzDK.cjs} +7 -7
- package/dist/{runGemini-D5IV1MoF.mjs → runGemini-D5N11LxO.mjs} +7 -7
- package/dist/{runOpenClaw-Qs4eFtcS.cjs → runOpenClaw-B9qWHybc.cjs} +6 -6
- package/dist/{runOpenClaw-CWPXrKMJ.mjs → runOpenClaw-CCpyKbrn.mjs} +6 -6
- package/dist/{send-Dv0km8UI.mjs → send-C44kcXz5.mjs} +21 -7
- package/dist/{send-qXWGilm8.cjs → send-wl9ei8yG.cjs} +21 -7
- package/dist/{sessions-DoULfKui.mjs → sessions-DXJEcUg6.mjs} +57 -11
- package/dist/{sessions-CpIdDhH7.cjs → sessions-Tad8elmy.cjs} +57 -11
- package/dist/{spawn-DQuOmUP7.mjs → spawn-BOlb5y1_.mjs} +142 -28
- package/dist/{spawn-DqzG1bTH.cjs → spawn-DA8WAFmm.cjs} +142 -28
- package/dist/{teams-4mhNh5cj.mjs → teams-Bs8lI887.mjs} +15 -72
- package/dist/{teams-ByEgUzsH.cjs → teams-qVLrFcdd.cjs} +13 -70
- package/dist/{todo-D7SDXL0c.cjs → todo-mCMoGjJk.cjs} +4 -23
- package/dist/{todo-C9IAUf0K.mjs → todo-nzsqGZqp.mjs} +2 -21
- package/dist/{types-D2Gkpe0N.cjs → types-CN28qJl7.cjs} +38 -813
- package/dist/{types-BD542hb1.mjs → types-xKrxbCRv.mjs} +208 -978
- package/package.json +7 -7
- package/dist/agentGuidance-Dla-BXO8.cjs +0 -267
- package/dist/agentGuidance-DrfhtVP1.mjs +0 -251
- package/dist/limits-Dyma1REp.cjs +0 -1236
- 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-
|
|
7
|
-
var
|
|
8
|
-
var
|
|
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('
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
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
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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
|
|
209
|
-
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 (
|
|
233
|
-
payload.
|
|
343
|
+
if (agent !== void 0) {
|
|
344
|
+
payload.agent = agent;
|
|
345
|
+
}
|
|
346
|
+
if (forkSource) {
|
|
347
|
+
payload.forkedFrom = forkSource.parentSessionId;
|
|
234
348
|
}
|
|
235
|
-
if (
|
|
236
|
-
payload.
|
|
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 {
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
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 '
|
|
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
|
-
|
|
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-
|
|
8
|
-
var persistence = require('./limits-
|
|
9
|
-
var
|
|
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('
|
|
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
|
-
|
|
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-
|
|
4
|
-
var
|
|
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
|
|
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(
|
|
90
|
+
console.log(persistence.BUILTIN_TODO_SKILL);
|
|
110
91
|
return;
|
|
111
92
|
}
|
|
112
93
|
const client = await authenticatedBuiltinTodoClient();
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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) {
|