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
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { resolve } from 'node:path';
|
|
5
|
+
import { A as AUTOMATION_NAME_PATTERN, i as isAutomationRunTerminal, c as configuration } from './limits-CKR_HJnO.mjs';
|
|
6
|
+
import { d as authenticatedAutomationsClient, e as automationRunIdFromEnv, i as isAutomationsUnavailable, A as AUTOMATIONS_UNAVAILABLE_MESSAGE } from './agentGuidance-Daxn5Nvj.mjs';
|
|
7
|
+
import { p as parseDuration, e as sessionWebUrl, t as triggerFromFlags, g as describeTrigger, h as describeAction, S as SPAWN_AGENTS, i as sanitizeSpawnPermissionMode, A as ALLOWED_SPAWN_PERMISSION_MODES } from './index-BauTv2jB.mjs';
|
|
8
|
+
import { A as AUTOMATION_SKILL, i as installAutomationSkill } from './install-C1K3nrxF.mjs';
|
|
9
|
+
import { parseEnvAssignment } from './spawn-BOlb5y1_.mjs';
|
|
10
|
+
import { c as delay } from './types-xKrxbCRv.mjs';
|
|
11
|
+
import 'node:fs/promises';
|
|
12
|
+
import 'zod';
|
|
13
|
+
import 'node:crypto';
|
|
14
|
+
import 'tweetnacl';
|
|
15
|
+
import 'node:util';
|
|
16
|
+
import '@paralleldrive/cuid2';
|
|
17
|
+
import 'cross-spawn';
|
|
18
|
+
import 'node:readline';
|
|
19
|
+
import './projectPath-CSpqWdxp.mjs';
|
|
20
|
+
import 'path';
|
|
21
|
+
import 'url';
|
|
22
|
+
import 'fs/promises';
|
|
23
|
+
import 'ink';
|
|
24
|
+
import 'react';
|
|
25
|
+
import '@anthropic-ai/claude-agent-sdk';
|
|
26
|
+
import 'axios';
|
|
27
|
+
import 'node:events';
|
|
28
|
+
import 'socket.io-client';
|
|
29
|
+
import 'os';
|
|
30
|
+
import 'child_process';
|
|
31
|
+
import 'node:child_process';
|
|
32
|
+
import 'fs';
|
|
33
|
+
import '@anthropic-ai/sandbox-runtime';
|
|
34
|
+
import 'expo-server-sdk';
|
|
35
|
+
import 'crypto';
|
|
36
|
+
import 'ps-list';
|
|
37
|
+
import 'tmp';
|
|
38
|
+
import 'open';
|
|
39
|
+
import 'fastify';
|
|
40
|
+
import 'fastify-type-provider-zod';
|
|
41
|
+
import 'node:module';
|
|
42
|
+
import '@modelcontextprotocol/sdk/server/mcp.js';
|
|
43
|
+
import 'node:http';
|
|
44
|
+
import '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
45
|
+
import 'http';
|
|
46
|
+
import 'util';
|
|
47
|
+
import 'inquirer';
|
|
48
|
+
import 'node:url';
|
|
49
|
+
import 'node:readline/promises';
|
|
50
|
+
import 'node-pty';
|
|
51
|
+
|
|
52
|
+
const AUTO_ACTIONS = ["list", "show", "create", "edit", "pause", "resume", "rm", "run", "fire", "runs", "cancel", "ack", "report", "skill", "install", "uninstall", "help"];
|
|
53
|
+
const VALUE_FLAGS = ["--name", "--rename", "--description", "--machine", "--cron", "--tz", "--every", "--at", "--spawn-dir", "--prompt", "--prompt-file", "--agent", "--model", "--permission-mode", "--sticky-key", "--send-session", "--cwd", "--env", "--timeout", "--concurrency", "--max-runtime", "--payload", "--payload-json", "--payload-file", "--dedupe-key", "--status", "--limit", "--run", "--summary", "--error", "--attention", "--host", "--home"];
|
|
54
|
+
const BOOL_FLAGS = ["--manual", "--script", "--worktree", "--paused", "--wait", "--json", "--apply", "--all-machines", "--attention", "--help", "-h"];
|
|
55
|
+
function parseAutoArgs(args) {
|
|
56
|
+
const command = { action: "help", flags: {}, env: [], bools: /* @__PURE__ */ new Set(), argv: [] };
|
|
57
|
+
if (args.length === 0) return command;
|
|
58
|
+
const separator = args.indexOf("--");
|
|
59
|
+
const head = separator === -1 ? args : args.slice(0, separator);
|
|
60
|
+
command.argv = separator === -1 ? [] : args.slice(separator + 1);
|
|
61
|
+
const action = head[0];
|
|
62
|
+
if (action === "--help" || action === "-h") return command;
|
|
63
|
+
if (!AUTO_ACTIONS.includes(action)) throw new Error(`Unknown auto command: ${action} (see auto --help)`);
|
|
64
|
+
command.action = action;
|
|
65
|
+
for (let i = 1; i < head.length; i++) {
|
|
66
|
+
const arg = head[i];
|
|
67
|
+
if (BOOL_FLAGS.includes(arg) && !(arg === "--attention" && command.action === "report")) {
|
|
68
|
+
command.bools.add(arg);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (arg.startsWith("--")) {
|
|
72
|
+
const eq = arg.indexOf("=");
|
|
73
|
+
const flag = eq === -1 ? arg : arg.slice(0, eq);
|
|
74
|
+
if (!VALUE_FLAGS.includes(flag)) throw new Error(`Unknown option: ${arg}`);
|
|
75
|
+
const value = eq === -1 ? head[++i] : arg.slice(eq + 1);
|
|
76
|
+
if (value === void 0) throw new Error(`${flag} requires a value`);
|
|
77
|
+
if (flag === "--env") {
|
|
78
|
+
command.env.push(value);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (command.flags[flag] !== void 0) throw new Error(`Duplicate option: ${flag}`);
|
|
82
|
+
command.flags[flag] = value;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (command.target !== void 0) throw new Error(`Unexpected argument: ${arg}`);
|
|
86
|
+
command.target = arg;
|
|
87
|
+
}
|
|
88
|
+
if (command.bools.has("--help") || command.bools.has("-h")) command.action = "help";
|
|
89
|
+
if (command.argv.length > 0 && !command.bools.has("--script")) throw new Error("Arguments after -- need --script");
|
|
90
|
+
return command;
|
|
91
|
+
}
|
|
92
|
+
function readPrompt(flags) {
|
|
93
|
+
if (flags["--prompt"] !== void 0 && flags["--prompt-file"] !== void 0) throw new Error("--prompt and --prompt-file are mutually exclusive");
|
|
94
|
+
if (flags["--prompt-file"] !== void 0) return readFileSync(resolve(flags["--prompt-file"]), "utf8");
|
|
95
|
+
return flags["--prompt"];
|
|
96
|
+
}
|
|
97
|
+
function readPayload(flags) {
|
|
98
|
+
const given = ["--payload", "--payload-json", "--payload-file"].filter((flag) => flags[flag] !== void 0);
|
|
99
|
+
if (given.length > 1) throw new Error("Use only one of --payload, --payload-json, --payload-file");
|
|
100
|
+
if (flags["--payload-json"] !== void 0) {
|
|
101
|
+
try {
|
|
102
|
+
JSON.parse(flags["--payload-json"]);
|
|
103
|
+
} catch {
|
|
104
|
+
throw new Error("--payload-json is not valid JSON");
|
|
105
|
+
}
|
|
106
|
+
return flags["--payload-json"];
|
|
107
|
+
}
|
|
108
|
+
if (flags["--payload-file"] !== void 0) return readFileSync(resolve(flags["--payload-file"]), "utf8");
|
|
109
|
+
return flags["--payload"];
|
|
110
|
+
}
|
|
111
|
+
function actionFromCommand(command, current) {
|
|
112
|
+
const { flags, bools } = command;
|
|
113
|
+
const kinds = [flags["--spawn-dir"] !== void 0, flags["--send-session"] !== void 0, bools.has("--script")].filter(Boolean).length;
|
|
114
|
+
if (kinds > 1) throw new Error("Use only one of --spawn-dir, --send-session, --script");
|
|
115
|
+
const prompt = readPrompt(flags);
|
|
116
|
+
const spawnOnly = ["--agent", "--model", "--permission-mode", "--sticky-key"].filter((flag) => flags[flag] !== void 0).concat(bools.has("--worktree") ? ["--worktree"] : []);
|
|
117
|
+
const scriptOnly = ["--cwd", "--timeout"].filter((flag) => flags[flag] !== void 0).concat(command.env.length ? ["--env"] : []);
|
|
118
|
+
if (kinds === 0) {
|
|
119
|
+
if (!current) {
|
|
120
|
+
if (prompt !== void 0 || spawnOnly.length || scriptOnly.length) throw new Error("Give an action: --spawn-dir <dir>, --send-session <id>, or --script -- <argv\u2026>");
|
|
121
|
+
return void 0;
|
|
122
|
+
}
|
|
123
|
+
if (prompt === void 0 && spawnOnly.length === 0 && scriptOnly.length === 0) return void 0;
|
|
124
|
+
if (current.kind === "script") {
|
|
125
|
+
if (prompt !== void 0 || spawnOnly.length) throw new Error(`${current.kind} actions do not take ${[...prompt !== void 0 ? ["--prompt"] : [], ...spawnOnly].join(", ")}`);
|
|
126
|
+
return { ...current, ...flags["--cwd"] !== void 0 ? { cwd: flags["--cwd"] } : {}, ...flags["--timeout"] !== void 0 ? { timeoutMs: parseDuration(flags["--timeout"]) } : {}, ...command.env.length ? { env: Object.fromEntries(command.env.map(parseEnvAssignment)) } : {} };
|
|
127
|
+
}
|
|
128
|
+
if (scriptOnly.length) throw new Error(`${current.kind} actions do not take ${scriptOnly.join(", ")}`);
|
|
129
|
+
if (current.kind === "send") {
|
|
130
|
+
if (spawnOnly.length) throw new Error(`send actions do not take ${spawnOnly.join(", ")}`);
|
|
131
|
+
return { ...current, ...prompt !== void 0 ? { prompt } : {} };
|
|
132
|
+
}
|
|
133
|
+
return { ...current, ...prompt !== void 0 ? { prompt } : {}, ...spawnDetails(command) };
|
|
134
|
+
}
|
|
135
|
+
if (bools.has("--script")) {
|
|
136
|
+
if (command.argv.length === 0) throw new Error("--script needs the command after --, e.g. --script -- /usr/bin/env python3 job.py");
|
|
137
|
+
if (prompt !== void 0 || spawnOnly.length) throw new Error(`script actions do not take ${[...prompt !== void 0 ? ["--prompt"] : [], ...spawnOnly].join(", ")}`);
|
|
138
|
+
return { kind: "script", command: command.argv, ...flags["--cwd"] !== void 0 ? { cwd: flags["--cwd"] } : {}, ...flags["--timeout"] !== void 0 ? { timeoutMs: parseDuration(flags["--timeout"]) } : {}, ...command.env.length ? { env: Object.fromEntries(command.env.map(parseEnvAssignment)) } : {} };
|
|
139
|
+
}
|
|
140
|
+
if (scriptOnly.length) throw new Error(`${flags["--send-session"] !== void 0 ? "send" : "spawn"} actions do not take ${scriptOnly.join(", ")}`);
|
|
141
|
+
if (prompt === void 0) throw new Error("--prompt or --prompt-file is required");
|
|
142
|
+
if (prompt.trim().length === 0) throw new Error("Prompt is empty");
|
|
143
|
+
if (flags["--send-session"] !== void 0) {
|
|
144
|
+
if (spawnOnly.length) throw new Error(`send actions do not take ${spawnOnly.join(", ")}`);
|
|
145
|
+
return { kind: "send", sessionId: flags["--send-session"], prompt };
|
|
146
|
+
}
|
|
147
|
+
return { kind: "spawn", agent: flags["--agent"] ?? "claude", directory: resolve(flags["--spawn-dir"]), prompt, ...spawnDetails(command) };
|
|
148
|
+
}
|
|
149
|
+
function spawnDetails(command) {
|
|
150
|
+
const { flags, bools } = command;
|
|
151
|
+
if (flags["--agent"] !== void 0 && !SPAWN_AGENTS.includes(flags["--agent"])) throw new Error(`--agent must be one of: ${SPAWN_AGENTS.join(", ")}`);
|
|
152
|
+
if (flags["--permission-mode"] !== void 0 && sanitizeSpawnPermissionMode(flags["--permission-mode"]) === null) throw new Error(`--permission-mode must be one of: ${ALLOWED_SPAWN_PERMISSION_MODES.join(", ")}`);
|
|
153
|
+
return {
|
|
154
|
+
...flags["--agent"] !== void 0 ? { agent: flags["--agent"] } : {},
|
|
155
|
+
...flags["--model"] !== void 0 ? { model: flags["--model"] } : {},
|
|
156
|
+
...flags["--permission-mode"] !== void 0 ? { permissionMode: flags["--permission-mode"] } : {},
|
|
157
|
+
...bools.has("--worktree") ? { worktree: true } : {},
|
|
158
|
+
...flags["--sticky-key"] !== void 0 ? { sticky: { key: flags["--sticky-key"] } } : {}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const AUTO_HELP = `${chalk.bold("very-happy auto")} - account-level automations: cron / interval / one-shot / manual triggers that spawn a session, continue a sticky session, or run a script on a machine
|
|
162
|
+
|
|
163
|
+
${chalk.bold("Usage:")}
|
|
164
|
+
very-happy auto list [--all-machines] [--json]
|
|
165
|
+
very-happy auto show <name> [--json]
|
|
166
|
+
very-happy auto create --name <name> <trigger> <action> [options]
|
|
167
|
+
very-happy auto edit <name> [<trigger>] [<action changes>] [--rename <new>] [options]
|
|
168
|
+
very-happy auto pause|resume|rm <name>
|
|
169
|
+
very-happy auto run <name> [--payload\u2026 ] [--wait [--timeout <dur>]]
|
|
170
|
+
very-happy auto fire <name> [--payload\u2026] [--dedupe-key <key>] [--wait [--timeout <dur>]]
|
|
171
|
+
very-happy auto runs [--name <name>] [--status <s>] [--attention] [--limit <n>] [--json]
|
|
172
|
+
very-happy auto runs <runId> [--json]
|
|
173
|
+
very-happy auto cancel <runId> | ack <runId>
|
|
174
|
+
very-happy auto report [--run <id>] --status done|failed [--summary <text>] [--error <text>] [--attention <reason>]
|
|
175
|
+
very-happy auto skill | install|uninstall --host claude|codex|pi [--home <path>] [--apply]
|
|
176
|
+
|
|
177
|
+
${chalk.bold("Trigger (one):")}
|
|
178
|
+
--cron '<5 fields>' --tz <IANA zone> e.g. --cron '0 9 * * 1-5' --tz Asia/Singapore
|
|
179
|
+
--every <dur> e.g. 5m, 1h30m (minimum 1m)
|
|
180
|
+
--at <ISO timestamp> one time
|
|
181
|
+
--manual only \`run\` / \`fire\`
|
|
182
|
+
|
|
183
|
+
${chalk.bold("Action (one):")}
|
|
184
|
+
--spawn-dir <dir> --prompt <text>|--prompt-file <file>
|
|
185
|
+
[--agent claude|codex|pi|gemini|openclaw] [--model <id>] [--permission-mode <m>]
|
|
186
|
+
[--worktree] [--sticky-key <template>]
|
|
187
|
+
--send-session <id> --prompt <text>|--prompt-file <file>
|
|
188
|
+
--script -- <argv\u2026> [--cwd <dir>] [--env K=V]... [--timeout <dur>]
|
|
189
|
+
|
|
190
|
+
${chalk.bold("Options:")}
|
|
191
|
+
--machine <id>|this Executing machine (default: this machine)
|
|
192
|
+
--description <text>
|
|
193
|
+
--concurrency skip|queue skip (default) records a run as skipped while one is active
|
|
194
|
+
--max-runtime <dur> Bound per run (default 6h; scripts 30m)
|
|
195
|
+
--paused Create paused
|
|
196
|
+
--payload <text> | --payload-json <json> | --payload-file <file>
|
|
197
|
+
--wait run/fire: wait for the run to finish (--timeout, default 10m)
|
|
198
|
+
Inside a continued (sticky) session pass --run <id> from the latest prompt header;
|
|
199
|
+
$VH_AUTOMATION_RUN_ID is the run that started the session.
|
|
200
|
+
--json Machine-readable output
|
|
201
|
+
|
|
202
|
+
${chalk.bold("Templates")} in prompts, argv and sticky keys: {{payload}}, {{payload.a.b}}, {{run.id}}, {{automation.name}}, {{now}}.
|
|
203
|
+
Inside an automation-started session \`report\` defaults --run to $VH_AUTOMATION_RUN_ID.
|
|
204
|
+
|
|
205
|
+
${chalk.bold("Exit codes:")} 0 ok \xB7 1 error \xB7 2 --wait timed out \xB7 3 run ended (or was created) failed/expired/cancelled/skipped`;
|
|
206
|
+
function fmtTime(value) {
|
|
207
|
+
return typeof value === "number" ? new Date(value).toISOString().replace("T", " ").slice(0, 19) : "-";
|
|
208
|
+
}
|
|
209
|
+
function printAutomation(a) {
|
|
210
|
+
console.log(`${chalk.bold(a.name)} ${a.status === "paused" ? chalk.yellow("paused") : chalk.green("active")} v${a.version} ${chalk.dim(a.id)}`);
|
|
211
|
+
if (a.description) console.log(` ${a.description}`);
|
|
212
|
+
console.log(` trigger: ${describeTrigger(a.trigger)} next: ${fmtTime(a.nextRunAt)}`);
|
|
213
|
+
console.log(` action: ${describeAction(a.action)}`);
|
|
214
|
+
console.log(` machine: ${a.machineId} concurrency: ${a.concurrency} max runtime: ${Math.round(a.maxRuntimeMs / 6e4)}m`);
|
|
215
|
+
console.log(` last run: ${fmtTime(a.lastRunAt)} ${a.lastRunStatus ?? ""}`);
|
|
216
|
+
}
|
|
217
|
+
function runLine(run) {
|
|
218
|
+
const status = run.status === "done" ? chalk.green(run.status) : ["failed", "expired"].includes(run.status) ? chalk.red(run.status) : run.status === "running" ? chalk.cyan(run.status) : run.status;
|
|
219
|
+
return `${fmtTime(run.createdAt)} ${status.padEnd(9)} ${run.automationName.padEnd(20)} ${run.source.padEnd(8)} ${run.id}${run.needsAttention ? chalk.yellow(` ! ${run.attentionReason ?? "attention"}`) : ""}`;
|
|
220
|
+
}
|
|
221
|
+
function printRun(run) {
|
|
222
|
+
console.log(runLine(run));
|
|
223
|
+
if (run.sessionId) console.log(` session: ${sessionWebUrl(run.sessionId)}`);
|
|
224
|
+
if (run.stickyKey) console.log(` sticky: ${run.stickyKey}`);
|
|
225
|
+
if (run.error) console.log(` error: ${run.error}`);
|
|
226
|
+
if (run.summary) console.log(` summary: ${run.summary.split("\n").join("\n ")}`);
|
|
227
|
+
if (run.exitCode !== null && run.exitCode !== void 0) console.log(` exit: ${run.exitCode}`);
|
|
228
|
+
}
|
|
229
|
+
async function waitForRun(client, runId, timeoutMs) {
|
|
230
|
+
const deadline = Date.now() + timeoutMs;
|
|
231
|
+
while (true) {
|
|
232
|
+
const run = await client.getRun(runId);
|
|
233
|
+
if (isAutomationRunTerminal(run.status)) return run;
|
|
234
|
+
if (Date.now() + 3e3 > deadline) return null;
|
|
235
|
+
await delay(3e3);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
async function handleAutoCommand(args) {
|
|
239
|
+
let command;
|
|
240
|
+
try {
|
|
241
|
+
command = parseAutoArgs(args);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.error(chalk.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
244
|
+
console.error(`Run ${chalk.cyan("very-happy auto --help")} for usage.`);
|
|
245
|
+
process.exitCode = 1;
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const json = command.bools.has("--json");
|
|
249
|
+
if (command.action === "help") {
|
|
250
|
+
console.log(AUTO_HELP);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (command.action === "skill") {
|
|
254
|
+
console.log(AUTOMATION_SKILL);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (command.action === "install" || command.action === "uninstall") {
|
|
258
|
+
const host = command.flags["--host"];
|
|
259
|
+
if (!host || !["claude", "codex", "pi"].includes(host)) throw new Error("--host must be claude, codex, or pi");
|
|
260
|
+
console.log(JSON.stringify(await installAutomationSkill({ host, home: command.flags["--home"] ?? homedir(), apply: command.bools.has("--apply"), uninstall: command.action === "uninstall" })));
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const needsName = ["show", "edit", "pause", "resume", "rm", "run", "fire"];
|
|
264
|
+
const needsRunId = ["cancel", "ack"];
|
|
265
|
+
if (needsName.includes(command.action) && !command.target) throw new Error(`${command.action} needs an automation name`);
|
|
266
|
+
if (needsRunId.includes(command.action) && !command.target) throw new Error(`${command.action} needs a run id`);
|
|
267
|
+
if (command.action === "create" && !command.flags["--name"]) throw new Error("create needs --name <name>");
|
|
268
|
+
for (const name of [command.flags["--name"], command.flags["--rename"], needsName.includes(command.action) ? command.target : void 0]) {
|
|
269
|
+
if (name !== void 0 && !AUTOMATION_NAME_PATTERN.test(name)) throw new Error(`Invalid automation name "${name}": use [a-z0-9][a-z0-9-_.]{0,63}`);
|
|
270
|
+
}
|
|
271
|
+
const { client, machineId } = await authenticatedAutomationsClient();
|
|
272
|
+
const machine = (requested) => {
|
|
273
|
+
if (requested && requested !== "this") return requested;
|
|
274
|
+
if (!machineId) throw new Error("This machine is not registered with Very Happy; pass --machine <id>");
|
|
275
|
+
return machineId;
|
|
276
|
+
};
|
|
277
|
+
try {
|
|
278
|
+
switch (command.action) {
|
|
279
|
+
case "list": {
|
|
280
|
+
const automations = await client.list(command.bools.has("--all-machines") ? void 0 : machine(command.flags["--machine"]));
|
|
281
|
+
if (json) {
|
|
282
|
+
console.log(JSON.stringify({ serverUrl: configuration.serverUrl, automations }));
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (automations.length === 0) {
|
|
286
|
+
console.log("No automations" + (command.bools.has("--all-machines") ? "." : " on this machine (try --all-machines)."));
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
for (const a of automations) console.log(`${(a.status === "paused" ? chalk.yellow("paused") : chalk.green("active")).padEnd(6)} ${a.name.padEnd(24)} ${describeTrigger(a.trigger).padEnd(32)} next ${fmtTime(a.nextRunAt)} ${describeAction(a.action)}`);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
case "show": {
|
|
293
|
+
const automation = await client.getByName(command.target);
|
|
294
|
+
const runs = await client.runs({ automationId: automation.id, limit: 5 });
|
|
295
|
+
if (json) {
|
|
296
|
+
console.log(JSON.stringify({ automation, runs }));
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
printAutomation(automation);
|
|
300
|
+
if (runs.length) {
|
|
301
|
+
console.log(" recent runs:");
|
|
302
|
+
for (const run of runs) console.log(` ${runLine(run)}`);
|
|
303
|
+
}
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
case "create": {
|
|
307
|
+
const trigger = triggerFromFlags({ cron: command.flags["--cron"], tz: command.flags["--tz"], every: command.flags["--every"], at: command.flags["--at"], manual: command.bools.has("--manual") });
|
|
308
|
+
if (!trigger) throw new Error("Give a trigger: --cron <expr> --tz <zone>, --every <dur>, --at <iso>, or --manual");
|
|
309
|
+
const action = actionFromCommand(command);
|
|
310
|
+
if (!action) throw new Error("Give an action: --spawn-dir <dir>, --send-session <id>, or --script -- <argv\u2026>");
|
|
311
|
+
const input = {
|
|
312
|
+
name: command.flags["--name"],
|
|
313
|
+
machineId: machine(command.flags["--machine"]),
|
|
314
|
+
trigger,
|
|
315
|
+
action,
|
|
316
|
+
...command.flags["--description"] !== void 0 ? { description: command.flags["--description"] } : {},
|
|
317
|
+
...command.flags["--concurrency"] !== void 0 ? { concurrency: concurrency(command.flags["--concurrency"]) } : {},
|
|
318
|
+
...command.flags["--max-runtime"] !== void 0 ? { maxRuntimeMs: parseDuration(command.flags["--max-runtime"]) } : {},
|
|
319
|
+
...command.bools.has("--paused") ? { status: "paused" } : {}
|
|
320
|
+
};
|
|
321
|
+
const automation = await client.create(input);
|
|
322
|
+
if (json) {
|
|
323
|
+
console.log(JSON.stringify({ automation }));
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
console.log(chalk.green("Created."));
|
|
327
|
+
printAutomation(automation);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
case "edit": {
|
|
331
|
+
const current = await client.getByName(command.target);
|
|
332
|
+
const trigger = triggerFromFlags({ cron: command.flags["--cron"], tz: command.flags["--tz"], every: command.flags["--every"], at: command.flags["--at"], manual: command.bools.has("--manual") });
|
|
333
|
+
const action = actionFromCommand(command, current.action);
|
|
334
|
+
const input = {
|
|
335
|
+
version: current.version,
|
|
336
|
+
...command.flags["--rename"] !== void 0 ? { name: command.flags["--rename"] } : {},
|
|
337
|
+
...command.flags["--description"] !== void 0 ? { description: command.flags["--description"] } : {},
|
|
338
|
+
...command.flags["--machine"] !== void 0 ? { machineId: machine(command.flags["--machine"]) } : {},
|
|
339
|
+
...trigger ? { trigger } : {},
|
|
340
|
+
...action ? { action } : {},
|
|
341
|
+
...command.flags["--concurrency"] !== void 0 ? { concurrency: concurrency(command.flags["--concurrency"]) } : {},
|
|
342
|
+
...command.flags["--max-runtime"] !== void 0 ? { maxRuntimeMs: parseDuration(command.flags["--max-runtime"]) } : {}
|
|
343
|
+
};
|
|
344
|
+
if (Object.keys(input).length === 1) throw new Error("Nothing to change");
|
|
345
|
+
const automation = await client.update(current.id, input);
|
|
346
|
+
if (json) {
|
|
347
|
+
console.log(JSON.stringify({ automation }));
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
console.log(chalk.green("Updated."));
|
|
351
|
+
printAutomation(automation);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
case "pause":
|
|
355
|
+
case "resume": {
|
|
356
|
+
const current = await client.getByName(command.target);
|
|
357
|
+
const automation = command.action === "pause" ? await client.pause(current.id) : await client.resume(current.id);
|
|
358
|
+
if (json) {
|
|
359
|
+
console.log(JSON.stringify({ automation }));
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
console.log(`${automation.name}: ${automation.status}${automation.nextRunAt ? `, next ${fmtTime(automation.nextRunAt)}` : ""}`);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
case "rm": {
|
|
366
|
+
const current = await client.getByName(command.target);
|
|
367
|
+
await client.remove(current.id);
|
|
368
|
+
if (json) {
|
|
369
|
+
console.log(JSON.stringify({ deleted: true, id: current.id, name: current.name }));
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
console.log(`Deleted ${current.name} and its runs.`);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
case "run":
|
|
376
|
+
case "fire": {
|
|
377
|
+
const payload = readPayload(command.flags);
|
|
378
|
+
let run;
|
|
379
|
+
let deduplicated = false;
|
|
380
|
+
if (command.action === "fire") {
|
|
381
|
+
const fired = await client.fire(command.target, { ...payload !== void 0 ? { payload } : {}, ...command.flags["--dedupe-key"] !== void 0 ? { dedupeKey: command.flags["--dedupe-key"] } : {} });
|
|
382
|
+
run = fired.run;
|
|
383
|
+
deduplicated = fired.deduplicated;
|
|
384
|
+
} else {
|
|
385
|
+
if (command.flags["--dedupe-key"] !== void 0) throw new Error("--dedupe-key only applies to fire");
|
|
386
|
+
run = await client.run((await client.getByName(command.target)).id, payload !== void 0 ? { payload } : {});
|
|
387
|
+
}
|
|
388
|
+
if (!json) {
|
|
389
|
+
console.log(`${deduplicated ? "Existing run" : "Run"} ${run.id}: ${run.status}`);
|
|
390
|
+
}
|
|
391
|
+
if (command.bools.has("--wait") && !isAutomationRunTerminal(run.status)) {
|
|
392
|
+
const timeoutMs = command.flags["--timeout"] !== void 0 ? parseDuration(command.flags["--timeout"]) : 6e5;
|
|
393
|
+
const final = await waitForRun(client, run.id, timeoutMs);
|
|
394
|
+
if (!final) {
|
|
395
|
+
if (json) console.log(JSON.stringify({ run, deduplicated, timedOut: true }));
|
|
396
|
+
else console.error(chalk.yellow(`Run ${run.id} did not finish within ${Math.round(timeoutMs / 1e3)}s (it may still complete).`));
|
|
397
|
+
process.exitCode = 2;
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
run = final;
|
|
401
|
+
}
|
|
402
|
+
if (json) {
|
|
403
|
+
console.log(JSON.stringify({ run, deduplicated }));
|
|
404
|
+
} else if (command.bools.has("--wait") || isAutomationRunTerminal(run.status)) printRun(run);
|
|
405
|
+
else if (run.sessionId) console.log(` session: ${sessionWebUrl(run.sessionId)}`);
|
|
406
|
+
if (isAutomationRunTerminal(run.status) && run.status !== "done") process.exitCode = 3;
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
case "runs": {
|
|
410
|
+
if (command.target) {
|
|
411
|
+
const run = await client.getRun(command.target);
|
|
412
|
+
if (json) console.log(JSON.stringify({ run }));
|
|
413
|
+
else printRun(run);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
const limit = command.flags["--limit"] !== void 0 ? Number(command.flags["--limit"]) : void 0;
|
|
417
|
+
if (limit !== void 0 && !(Number.isInteger(limit) && limit > 0)) throw new Error("--limit must be a positive integer");
|
|
418
|
+
const runs = await client.runs({ name: command.flags["--name"], status: command.flags["--status"], attention: command.bools.has("--attention"), limit });
|
|
419
|
+
if (json) {
|
|
420
|
+
console.log(JSON.stringify({ runs }));
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (runs.length === 0) {
|
|
424
|
+
console.log("No runs.");
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
for (const run of runs) console.log(runLine(run));
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
case "cancel":
|
|
431
|
+
case "ack": {
|
|
432
|
+
const run = command.action === "cancel" ? await client.cancel(command.target) : await client.ack(command.target);
|
|
433
|
+
if (json) console.log(JSON.stringify({ run }));
|
|
434
|
+
else printRun(run);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
case "report": {
|
|
438
|
+
const runId = command.flags["--run"] ?? automationRunIdFromEnv();
|
|
439
|
+
if (!runId) throw new Error("--run <id> is required outside an automation-started session (VH_AUTOMATION_RUN_ID is not set)");
|
|
440
|
+
const status = command.flags["--status"];
|
|
441
|
+
if (status !== "done" && status !== "failed") throw new Error("--status must be done or failed");
|
|
442
|
+
const report = {
|
|
443
|
+
status,
|
|
444
|
+
...command.flags["--summary"] !== void 0 ? { summary: command.flags["--summary"] } : {},
|
|
445
|
+
...command.flags["--error"] !== void 0 ? { error: command.flags["--error"] } : {},
|
|
446
|
+
...command.flags["--attention"] !== void 0 ? { needsAttention: true, attentionReason: command.flags["--attention"] } : {}
|
|
447
|
+
};
|
|
448
|
+
const run = await client.report(runId, report);
|
|
449
|
+
if (json) console.log(JSON.stringify({ run }));
|
|
450
|
+
else printRun(run);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
default:
|
|
454
|
+
throw new Error(`Unhandled auto command: ${command.action}`);
|
|
455
|
+
}
|
|
456
|
+
} catch (error) {
|
|
457
|
+
if (isAutomationsUnavailable(error)) throw new Error(AUTOMATIONS_UNAVAILABLE_MESSAGE);
|
|
458
|
+
throw error;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
function concurrency(value) {
|
|
462
|
+
if (value !== "skip" && value !== "queue") throw new Error("--concurrency must be skip or queue");
|
|
463
|
+
return value;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export { AUTO_ACTIONS, AUTO_HELP, actionFromCommand, handleAutoCommand, parseAutoArgs };
|