very-happy-cli 0.2.151 → 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-BjTA5gUB.mjs → AcpBackend-Bs3wwYbt.mjs} +2 -2
- package/dist/{AcpBackend-blE9IEod.cjs → AcpBackend-CogiKCng.cjs} +2 -2
- package/dist/{AcpSessionManager-D-9mrp40.mjs → AcpSessionManager-BT8PJMEm.mjs} +1 -1
- package/dist/{AcpSessionManager-bF8PYu_B.cjs → AcpSessionManager-BazkBuFo.cjs} +9 -9
- package/dist/agentGuidance-CqBn77AI.cjs +553 -0
- package/dist/agentGuidance-Daxn5Nvj.mjs +529 -0
- package/dist/{agentHome-CeFDNzvq.mjs → agentHome-CCYfs8Ec.mjs} +3 -3
- package/dist/{agentHome-D6MSfwnp.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-C-A1XF62.mjs → config-DbKpQpdL.mjs} +5 -5
- package/dist/{config-Cadw1sAk.cjs → config-DeJgSMh-.cjs} +5 -5
- package/dist/{index-CIyjJy8D.mjs → index-BauTv2jB.mjs} +712 -39
- package/dist/{index-DYQgcgL2.mjs → index-CN7OF2cU.mjs} +5 -5
- package/dist/{index-vyRqLjDv.cjs → index-CRaJ9k2p.cjs} +5 -5
- package/dist/{index-DEp6CmmS.mjs → index-DahaLbu4.mjs} +19 -19
- package/dist/{index-CqtfRzug.cjs → index-ZIW1jrYm.cjs} +750 -71
- package/dist/{index-BKGspSWk.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-Bf_adbl3.cjs → installTerminalHooks-BoDxpaBO.cjs} +5 -5
- package/dist/{installTerminalHooks-Ch_yTtEA.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-CMxEZ6-0.cjs → mcp-CCKCKL6l.cjs} +5 -5
- package/dist/{mcp-zu8n8TkR.mjs → mcp-D4dAo5BQ.mjs} +5 -5
- package/dist/{piRuntime-D3ny0mVg.cjs → piRuntime-Bn5ypbjH.cjs} +2 -2
- package/dist/{piRuntime-Cu6cXZ6N.mjs → piRuntime-CW-XA4rz.mjs} +2 -2
- package/dist/{piTerminal-kDSd_r9X.cjs → piTerminal-B7K75aXB.cjs} +6 -6
- package/dist/{piTerminal-M677UP7y.mjs → piTerminal-BLSXK1u2.mjs} +6 -6
- package/dist/{runGemini-DuwdVV1O.cjs → runGemini-Bgg1BzDK.cjs} +7 -7
- package/dist/{runGemini-DvJwQVGp.mjs → runGemini-D5N11LxO.mjs} +7 -7
- package/dist/{runOpenClaw-BsvtCE_H.cjs → runOpenClaw-B9qWHybc.cjs} +6 -6
- package/dist/{runOpenClaw-BG82bF18.mjs → runOpenClaw-CCpyKbrn.mjs} +6 -6
- package/dist/{send-o6WM-1G8.mjs → send-C44kcXz5.mjs} +5 -5
- package/dist/{send-aUkjhey7.cjs → send-wl9ei8yG.cjs} +5 -5
- package/dist/{sessions-DlU3foY_.mjs → sessions-DXJEcUg6.mjs} +5 -5
- package/dist/{sessions-B3vP18kz.cjs → sessions-Tad8elmy.cjs} +5 -5
- package/dist/{spawn-DxHIIqhi.mjs → spawn-BOlb5y1_.mjs} +6 -31
- package/dist/{spawn-DH6_p4-f.cjs → spawn-DA8WAFmm.cjs} +7 -32
- package/dist/{teams-DoAA_CcN.mjs → teams-Bs8lI887.mjs} +15 -72
- package/dist/{teams-BW8D1n3o.cjs → teams-qVLrFcdd.cjs} +13 -70
- package/dist/{todo-Bcc2XxGh.cjs → todo-mCMoGjJk.cjs} +4 -23
- package/dist/{todo-BhJbG5eq.mjs → todo-nzsqGZqp.mjs} +2 -21
- package/dist/{types-CPubg05Z.cjs → types-CN28qJl7.cjs} +31 -856
- package/dist/{types-BN3eiipi.mjs → types-xKrxbCRv.mjs} +204 -1017
- package/package.json +7 -7
- package/dist/agentGuidance-BXa9ikrZ.cjs +0 -267
- package/dist/agentGuidance-Dvo3cZMW.mjs +0 -251
- package/dist/limits-DS-bnYYp.mjs +0 -1165
- package/dist/limits-DwcY3kyi.cjs +0 -1236
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var AcpBackend = require('./AcpBackend-
|
|
4
|
-
var persistence = require('./limits-
|
|
5
|
-
var AcpSessionManager = require('./AcpSessionManager-
|
|
6
|
-
var index = require('./
|
|
7
|
-
var
|
|
8
|
-
var piRuntime = require('./piRuntime-D3ny0mVg.cjs');
|
|
3
|
+
var AcpBackend = require('./AcpBackend-CogiKCng.cjs');
|
|
4
|
+
var persistence = require('./limits-BNTWyUaZ.cjs');
|
|
5
|
+
var AcpSessionManager = require('./AcpSessionManager-BazkBuFo.cjs');
|
|
6
|
+
var index$1 = require('./index-ZIW1jrYm.cjs');
|
|
7
|
+
var piRuntime = require('./piRuntime-Bn5ypbjH.cjs');
|
|
9
8
|
var node_crypto = require('node:crypto');
|
|
10
9
|
var path = require('node:path');
|
|
10
|
+
var index = require('./types-CN28qJl7.cjs');
|
|
11
11
|
var projectPath = require('./projectPath-ChLhdOyt.cjs');
|
|
12
12
|
require('node:child_process');
|
|
13
13
|
require('@agentclientprotocol/sdk');
|
|
@@ -19,25 +19,23 @@ require('tweetnacl');
|
|
|
19
19
|
require('chalk');
|
|
20
20
|
require('node:util');
|
|
21
21
|
require('@paralleldrive/cuid2');
|
|
22
|
+
require('cross-spawn');
|
|
23
|
+
require('node:readline');
|
|
24
|
+
require('./agentGuidance-CqBn77AI.cjs');
|
|
25
|
+
require('fs/promises');
|
|
26
|
+
require('ink');
|
|
27
|
+
require('react');
|
|
28
|
+
require('@anthropic-ai/claude-agent-sdk');
|
|
22
29
|
require('axios');
|
|
23
30
|
require('node:events');
|
|
24
31
|
require('socket.io-client');
|
|
25
|
-
require('child_process');
|
|
26
|
-
require('util');
|
|
27
|
-
require('fs/promises');
|
|
28
|
-
require('crypto');
|
|
29
|
-
require('path');
|
|
30
|
-
require('cross-spawn');
|
|
31
32
|
require('os');
|
|
32
|
-
require('
|
|
33
|
+
require('child_process');
|
|
33
34
|
require('fs');
|
|
34
|
-
require('
|
|
35
|
+
require('path');
|
|
35
36
|
require('@anthropic-ai/sandbox-runtime');
|
|
36
37
|
require('expo-server-sdk');
|
|
37
|
-
require('
|
|
38
|
-
require('ink');
|
|
39
|
-
require('react');
|
|
40
|
-
require('@anthropic-ai/claude-agent-sdk');
|
|
38
|
+
require('crypto');
|
|
41
39
|
require('ps-list');
|
|
42
40
|
require('tmp');
|
|
43
41
|
require('open');
|
|
@@ -48,9 +46,11 @@ require('@modelcontextprotocol/sdk/server/mcp.js');
|
|
|
48
46
|
require('node:http');
|
|
49
47
|
require('@modelcontextprotocol/sdk/server/streamableHttp.js');
|
|
50
48
|
require('http');
|
|
49
|
+
require('util');
|
|
51
50
|
require('inquirer');
|
|
52
51
|
require('node:url');
|
|
53
52
|
require('node:readline/promises');
|
|
53
|
+
require('node-pty');
|
|
54
54
|
require('url');
|
|
55
55
|
|
|
56
56
|
function createAcpBackend(options) {
|
|
@@ -1164,7 +1164,7 @@ async function runAcp(opts) {
|
|
|
1164
1164
|
}
|
|
1165
1165
|
const prompt = todoDiscoverySent ? batch.message : `${batch.message}
|
|
1166
1166
|
|
|
1167
|
-
${
|
|
1167
|
+
${persistence.BUILTIN_TODO_DISCOVERY}`;
|
|
1168
1168
|
const attachments = batch.attachments ?? [];
|
|
1169
1169
|
const staged = attachments.length ? await index$1.stageClaudeAttachments(attachments, {
|
|
1170
1170
|
happyHomeDir: persistence.configuration.happyHomeDir,
|
package/dist/index.cjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
require('chalk');
|
|
4
|
-
require('./index-
|
|
5
|
-
require('./limits-
|
|
4
|
+
require('./index-ZIW1jrYm.cjs');
|
|
5
|
+
require('./limits-BNTWyUaZ.cjs');
|
|
6
6
|
require('zod');
|
|
7
|
-
require('./types-
|
|
7
|
+
require('./types-CN28qJl7.cjs');
|
|
8
8
|
require('node:child_process');
|
|
9
9
|
require('node:os');
|
|
10
10
|
require('node:crypto');
|
|
@@ -15,7 +15,7 @@ require('node:fs');
|
|
|
15
15
|
require('./projectPath-ChLhdOyt.cjs');
|
|
16
16
|
require('path');
|
|
17
17
|
require('url');
|
|
18
|
-
require('./agentGuidance-
|
|
18
|
+
require('./agentGuidance-CqBn77AI.cjs');
|
|
19
19
|
require('node:fs/promises');
|
|
20
20
|
require('fs/promises');
|
|
21
21
|
require('ink');
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import 'chalk';
|
|
2
|
-
import './index-
|
|
3
|
-
import './limits-
|
|
2
|
+
import './index-BauTv2jB.mjs';
|
|
3
|
+
import './limits-CKR_HJnO.mjs';
|
|
4
4
|
import 'zod';
|
|
5
|
-
import './types-
|
|
5
|
+
import './types-xKrxbCRv.mjs';
|
|
6
6
|
import 'node:child_process';
|
|
7
7
|
import 'node:os';
|
|
8
8
|
import 'node:crypto';
|
|
@@ -13,7 +13,7 @@ import 'node:fs';
|
|
|
13
13
|
import './projectPath-CSpqWdxp.mjs';
|
|
14
14
|
import 'path';
|
|
15
15
|
import 'url';
|
|
16
|
-
import './agentGuidance-
|
|
16
|
+
import './agentGuidance-Daxn5Nvj.mjs';
|
|
17
17
|
import 'node:fs/promises';
|
|
18
18
|
import 'fs/promises';
|
|
19
19
|
import 'ink';
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { unlink, rmdir, mkdir, lstat, readFile, writeFile, rename } from 'node:fs/promises';
|
|
3
|
+
import { join, resolve, dirname } from 'node:path';
|
|
4
|
+
import { y as TEAM_SKILL } from './index-BauTv2jB.mjs';
|
|
5
|
+
|
|
6
|
+
async function assertPrivateDestination(directory) {
|
|
7
|
+
for (let path = directory; ; path = dirname(path)) {
|
|
8
|
+
try {
|
|
9
|
+
const stat = await lstat(path);
|
|
10
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error("Refusing symlink or non-directory in managed skill destination: " + path);
|
|
11
|
+
} catch (error) {
|
|
12
|
+
if (error.code !== "ENOENT") throw error;
|
|
13
|
+
}
|
|
14
|
+
if (dirname(path) === path) break;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const digest = (text) => createHash("sha256").update(text).digest("hex");
|
|
18
|
+
async function read(path) {
|
|
19
|
+
try {
|
|
20
|
+
const stat = await lstat(path);
|
|
21
|
+
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error("Refusing non-regular managed file");
|
|
22
|
+
return await readFile(path, "utf8");
|
|
23
|
+
} catch (error) {
|
|
24
|
+
if (error.code === "ENOENT") return null;
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function publish(path, content) {
|
|
29
|
+
const temporary = `${path}.${randomUUID()}.new`;
|
|
30
|
+
try {
|
|
31
|
+
await writeFile(temporary, content, { flag: "wx", mode: 384 });
|
|
32
|
+
await rename(temporary, path);
|
|
33
|
+
} finally {
|
|
34
|
+
await unlink(temporary).catch(() => {
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function installManagedSkill(skill, options) {
|
|
39
|
+
if (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(skill.name)) throw new Error("Invalid managed skill name");
|
|
40
|
+
const directory = join(resolve(options.home), ".local", "share", "very-happy", "skills", skill.name);
|
|
41
|
+
await assertPrivateDestination(directory);
|
|
42
|
+
const file = join(directory, "SKILL.md");
|
|
43
|
+
const manifest = join(directory, ".very-happy-managed.json");
|
|
44
|
+
const current = await read(file);
|
|
45
|
+
const managed = await read(manifest);
|
|
46
|
+
const previous = managed ? JSON.parse(managed) : null;
|
|
47
|
+
if (previous && previous.owner !== "very-happy") throw new Error("Existing manifest is not owned by Very Happy");
|
|
48
|
+
if (current !== null && (!previous || previous.owner !== "very-happy" || previous.sha256 !== digest(current) && previous.pendingSha256 !== digest(current))) {
|
|
49
|
+
throw new Error("Existing skill is not owned by Very Happy or was edited; preserve it and resolve manually");
|
|
50
|
+
}
|
|
51
|
+
const action = options.uninstall ? current === null && managed === null ? "unchanged" : "remove" : current === skill.content && !previous?.pendingSha256 ? "unchanged" : "write";
|
|
52
|
+
if (options.apply && action !== "unchanged") {
|
|
53
|
+
await assertPrivateDestination(directory);
|
|
54
|
+
if (options.uninstall) {
|
|
55
|
+
if (current !== null) await unlink(file);
|
|
56
|
+
if (managed !== null) await unlink(manifest);
|
|
57
|
+
await rmdir(directory).catch(() => {
|
|
58
|
+
});
|
|
59
|
+
} else {
|
|
60
|
+
await mkdir(directory, { recursive: true });
|
|
61
|
+
const nextHash = digest(skill.content);
|
|
62
|
+
await publish(manifest, JSON.stringify({ owner: "very-happy", sha256: current === null ? null : digest(current), pendingSha256: nextHash, version: 1 }));
|
|
63
|
+
await publish(file, skill.content);
|
|
64
|
+
await publish(manifest, JSON.stringify({ owner: "very-happy", sha256: nextHash, version: 1 }));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return { host: options.host, skill: skill.name, path: file, action, applied: !!options.apply, discovery: "No host skill directories are modified. Read this absolute skill path in a managed session.", managedSessions: skill.managedSessions };
|
|
68
|
+
}
|
|
69
|
+
async function installTeamSkill(options) {
|
|
70
|
+
return installManagedSkill({ name: "very-happy-teams", content: TEAM_SKILL, managedSessions: "Team tools are injected by Very Happy. Standalone agents require a managed session connection." }, options);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const AUTOMATION_SKILL = `---
|
|
74
|
+
name: very-happy-automations
|
|
75
|
+
description: Create and operate Very Happy automations \u2014 cron / interval / one-shot / manual triggers that spawn an agent session, continue a sticky session, or run a script on a chosen machine, with tracked runs. Use when asked to schedule recurring work, react to external events with an agent session, or inspect why an automated run failed.
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
# Very Happy Automations
|
|
79
|
+
|
|
80
|
+
An automation is account-level: a trigger (cron with time zone, fixed interval, one time, or manual only), an action (spawn a session, continue a sticky session, send into a fixed session, or run a script), and one machine that executes it. Every execution is a run with a status (queued \u2192 claimed \u2192 running \u2192 done | failed | skipped | expired | cancelled), an optional session link, a summary and an attention flag.
|
|
81
|
+
|
|
82
|
+
## Surfaces
|
|
83
|
+
|
|
84
|
+
- Managed sessions (Claude, Codex, pi): the \`automation_*\` MCP tools \u2014 \`automation_list\`, \`automation_get\`, \`automation_create\`, \`automation_update\`, \`automation_pause\`, \`automation_resume\`, \`automation_delete\`, \`automation_run\`, \`automation_fire\`, \`automation_runs\`, \`automation_report\`, \`automation_ack\`.
|
|
85
|
+
- Terminal: \`very-happy auto --help\` (same operations; \`--json\` for scripts). Fire from any local process with \`very-happy auto fire <name> [--payload-json \u2026] [--dedupe-key \u2026] [--wait]\`.
|
|
86
|
+
- The machine defaults to the one you are running on. If the tools answer that automations are not enabled, the server gate is off; do not work around it.
|
|
87
|
+
|
|
88
|
+
## Creating one
|
|
89
|
+
|
|
90
|
+
Pick the trigger, then the action. \`spawn\` takes an agent (claude / codex / pi / gemini / openclaw), an absolute directory and a prompt; defaults match the web launcher (yolo for claude / codex / pi). \`script\` takes an argv array (no shell): quote nothing, pass arguments as separate items. Prompts and argv may use \`{{payload}}\`, \`{{payload.field.path}}\` (JSON payload), \`{{run.id}}\`, \`{{automation.name}}\` and \`{{now}}\`.
|
|
91
|
+
|
|
92
|
+
Use \`sticky: { key }\` on a spawn action when repeated events about the same subject should continue one conversation: the key is a template (for example \`{{payload.conversationId}}\`). A run whose rendered key matches a live session sends the prompt into that session; otherwise it spawns a new one and remembers it. A dead or archived session is replaced on the next run.
|
|
93
|
+
|
|
94
|
+
\`concurrency: skip\` (default) records a run as skipped while the previous one is still running; \`queue\` waits. \`maxRuntimeMs\` bounds a run (default 6h; scripts 30m).
|
|
95
|
+
|
|
96
|
+
## Event triggers
|
|
97
|
+
|
|
98
|
+
There is no inbound webhook. A private adapter (an IM bot, a watcher, a systemd unit) calls \`very-happy auto fire <name>\` with a payload and a dedupe key; the same key within 24h returns the original run instead of starting another. Prefer \`manual\` triggers for automations that only ever fire this way.
|
|
99
|
+
|
|
100
|
+
## Finishing a run
|
|
101
|
+
|
|
102
|
+
When you are the session an automation started (\`VH_AUTOMATION_RUN_ID\` is set) or continued, finish by calling \`automation_report\` with \`status: done\` and a short summary, or \`status: failed\` with the error. Without an explicit report the daemon marks the run done when your turn ends and uses your last message as the summary \u2014 explicit reports are exact, so prefer them for long or multi-turn work. Set \`needsAttention\` with a reason when a human must look (blocked, ambiguous input, partial result).
|
|
103
|
+
|
|
104
|
+
## Operating
|
|
105
|
+
|
|
106
|
+
Inspect \`automation_runs\` (or \`very-happy auto runs --attention\`) before changing anything. Runs stuck in queued mean the machine's daemon is offline; expired means the lease or the runtime bound lapsed. Acknowledge attention with \`automation_ack\` after handling it. Updates need the current \`version\` from \`automation_get\`; read again on a conflict. Do not create automations that spawn other automations, and never put credentials into prompts, payloads or script arguments \u2014 scripts inherit the daemon's environment.
|
|
107
|
+
`;
|
|
108
|
+
|
|
109
|
+
const AUTOMATION_SKILL_NAME = "very-happy-automations";
|
|
110
|
+
async function installAutomationSkill(options) {
|
|
111
|
+
return installManagedSkill({ name: AUTOMATION_SKILL_NAME, content: AUTOMATION_SKILL, managedSessions: "automation_* tools are injected into managed Claude, Codex and pi sessions; `very-happy auto` works from any terminal on a logged-in machine." }, options);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export { AUTOMATION_SKILL as A, installTeamSkill as a, installAutomationSkill as i };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var node_crypto = require('node:crypto');
|
|
4
|
+
var promises = require('node:fs/promises');
|
|
5
|
+
var path = require('node:path');
|
|
6
|
+
var index = require('./index-ZIW1jrYm.cjs');
|
|
7
|
+
|
|
8
|
+
async function assertPrivateDestination(directory) {
|
|
9
|
+
for (let path$1 = directory; ; path$1 = path.dirname(path$1)) {
|
|
10
|
+
try {
|
|
11
|
+
const stat = await promises.lstat(path$1);
|
|
12
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error("Refusing symlink or non-directory in managed skill destination: " + path$1);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
if (error.code !== "ENOENT") throw error;
|
|
15
|
+
}
|
|
16
|
+
if (path.dirname(path$1) === path$1) break;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const digest = (text) => node_crypto.createHash("sha256").update(text).digest("hex");
|
|
20
|
+
async function read(path) {
|
|
21
|
+
try {
|
|
22
|
+
const stat = await promises.lstat(path);
|
|
23
|
+
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error("Refusing non-regular managed file");
|
|
24
|
+
return await promises.readFile(path, "utf8");
|
|
25
|
+
} catch (error) {
|
|
26
|
+
if (error.code === "ENOENT") return null;
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function publish(path, content) {
|
|
31
|
+
const temporary = `${path}.${node_crypto.randomUUID()}.new`;
|
|
32
|
+
try {
|
|
33
|
+
await promises.writeFile(temporary, content, { flag: "wx", mode: 384 });
|
|
34
|
+
await promises.rename(temporary, path);
|
|
35
|
+
} finally {
|
|
36
|
+
await promises.unlink(temporary).catch(() => {
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function installManagedSkill(skill, options) {
|
|
41
|
+
if (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(skill.name)) throw new Error("Invalid managed skill name");
|
|
42
|
+
const directory = path.join(path.resolve(options.home), ".local", "share", "very-happy", "skills", skill.name);
|
|
43
|
+
await assertPrivateDestination(directory);
|
|
44
|
+
const file = path.join(directory, "SKILL.md");
|
|
45
|
+
const manifest = path.join(directory, ".very-happy-managed.json");
|
|
46
|
+
const current = await read(file);
|
|
47
|
+
const managed = await read(manifest);
|
|
48
|
+
const previous = managed ? JSON.parse(managed) : null;
|
|
49
|
+
if (previous && previous.owner !== "very-happy") throw new Error("Existing manifest is not owned by Very Happy");
|
|
50
|
+
if (current !== null && (!previous || previous.owner !== "very-happy" || previous.sha256 !== digest(current) && previous.pendingSha256 !== digest(current))) {
|
|
51
|
+
throw new Error("Existing skill is not owned by Very Happy or was edited; preserve it and resolve manually");
|
|
52
|
+
}
|
|
53
|
+
const action = options.uninstall ? current === null && managed === null ? "unchanged" : "remove" : current === skill.content && !previous?.pendingSha256 ? "unchanged" : "write";
|
|
54
|
+
if (options.apply && action !== "unchanged") {
|
|
55
|
+
await assertPrivateDestination(directory);
|
|
56
|
+
if (options.uninstall) {
|
|
57
|
+
if (current !== null) await promises.unlink(file);
|
|
58
|
+
if (managed !== null) await promises.unlink(manifest);
|
|
59
|
+
await promises.rmdir(directory).catch(() => {
|
|
60
|
+
});
|
|
61
|
+
} else {
|
|
62
|
+
await promises.mkdir(directory, { recursive: true });
|
|
63
|
+
const nextHash = digest(skill.content);
|
|
64
|
+
await publish(manifest, JSON.stringify({ owner: "very-happy", sha256: current === null ? null : digest(current), pendingSha256: nextHash, version: 1 }));
|
|
65
|
+
await publish(file, skill.content);
|
|
66
|
+
await publish(manifest, JSON.stringify({ owner: "very-happy", sha256: nextHash, version: 1 }));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return { host: options.host, skill: skill.name, path: file, action, applied: !!options.apply, discovery: "No host skill directories are modified. Read this absolute skill path in a managed session.", managedSessions: skill.managedSessions };
|
|
70
|
+
}
|
|
71
|
+
async function installTeamSkill(options) {
|
|
72
|
+
return installManagedSkill({ name: "very-happy-teams", content: index.TEAM_SKILL, managedSessions: "Team tools are injected by Very Happy. Standalone agents require a managed session connection." }, options);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const AUTOMATION_SKILL = `---
|
|
76
|
+
name: very-happy-automations
|
|
77
|
+
description: Create and operate Very Happy automations \u2014 cron / interval / one-shot / manual triggers that spawn an agent session, continue a sticky session, or run a script on a chosen machine, with tracked runs. Use when asked to schedule recurring work, react to external events with an agent session, or inspect why an automated run failed.
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
# Very Happy Automations
|
|
81
|
+
|
|
82
|
+
An automation is account-level: a trigger (cron with time zone, fixed interval, one time, or manual only), an action (spawn a session, continue a sticky session, send into a fixed session, or run a script), and one machine that executes it. Every execution is a run with a status (queued \u2192 claimed \u2192 running \u2192 done | failed | skipped | expired | cancelled), an optional session link, a summary and an attention flag.
|
|
83
|
+
|
|
84
|
+
## Surfaces
|
|
85
|
+
|
|
86
|
+
- Managed sessions (Claude, Codex, pi): the \`automation_*\` MCP tools \u2014 \`automation_list\`, \`automation_get\`, \`automation_create\`, \`automation_update\`, \`automation_pause\`, \`automation_resume\`, \`automation_delete\`, \`automation_run\`, \`automation_fire\`, \`automation_runs\`, \`automation_report\`, \`automation_ack\`.
|
|
87
|
+
- Terminal: \`very-happy auto --help\` (same operations; \`--json\` for scripts). Fire from any local process with \`very-happy auto fire <name> [--payload-json \u2026] [--dedupe-key \u2026] [--wait]\`.
|
|
88
|
+
- The machine defaults to the one you are running on. If the tools answer that automations are not enabled, the server gate is off; do not work around it.
|
|
89
|
+
|
|
90
|
+
## Creating one
|
|
91
|
+
|
|
92
|
+
Pick the trigger, then the action. \`spawn\` takes an agent (claude / codex / pi / gemini / openclaw), an absolute directory and a prompt; defaults match the web launcher (yolo for claude / codex / pi). \`script\` takes an argv array (no shell): quote nothing, pass arguments as separate items. Prompts and argv may use \`{{payload}}\`, \`{{payload.field.path}}\` (JSON payload), \`{{run.id}}\`, \`{{automation.name}}\` and \`{{now}}\`.
|
|
93
|
+
|
|
94
|
+
Use \`sticky: { key }\` on a spawn action when repeated events about the same subject should continue one conversation: the key is a template (for example \`{{payload.conversationId}}\`). A run whose rendered key matches a live session sends the prompt into that session; otherwise it spawns a new one and remembers it. A dead or archived session is replaced on the next run.
|
|
95
|
+
|
|
96
|
+
\`concurrency: skip\` (default) records a run as skipped while the previous one is still running; \`queue\` waits. \`maxRuntimeMs\` bounds a run (default 6h; scripts 30m).
|
|
97
|
+
|
|
98
|
+
## Event triggers
|
|
99
|
+
|
|
100
|
+
There is no inbound webhook. A private adapter (an IM bot, a watcher, a systemd unit) calls \`very-happy auto fire <name>\` with a payload and a dedupe key; the same key within 24h returns the original run instead of starting another. Prefer \`manual\` triggers for automations that only ever fire this way.
|
|
101
|
+
|
|
102
|
+
## Finishing a run
|
|
103
|
+
|
|
104
|
+
When you are the session an automation started (\`VH_AUTOMATION_RUN_ID\` is set) or continued, finish by calling \`automation_report\` with \`status: done\` and a short summary, or \`status: failed\` with the error. Without an explicit report the daemon marks the run done when your turn ends and uses your last message as the summary \u2014 explicit reports are exact, so prefer them for long or multi-turn work. Set \`needsAttention\` with a reason when a human must look (blocked, ambiguous input, partial result).
|
|
105
|
+
|
|
106
|
+
## Operating
|
|
107
|
+
|
|
108
|
+
Inspect \`automation_runs\` (or \`very-happy auto runs --attention\`) before changing anything. Runs stuck in queued mean the machine's daemon is offline; expired means the lease or the runtime bound lapsed. Acknowledge attention with \`automation_ack\` after handling it. Updates need the current \`version\` from \`automation_get\`; read again on a conflict. Do not create automations that spawn other automations, and never put credentials into prompts, payloads or script arguments \u2014 scripts inherit the daemon's environment.
|
|
109
|
+
`;
|
|
110
|
+
|
|
111
|
+
const AUTOMATION_SKILL_NAME = "very-happy-automations";
|
|
112
|
+
async function installAutomationSkill(options) {
|
|
113
|
+
return installManagedSkill({ name: AUTOMATION_SKILL_NAME, content: AUTOMATION_SKILL, managedSessions: "automation_* tools are injected into managed Claude, Codex and pi sessions; `very-happy auto` works from any terminal on a logged-in machine." }, options);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
exports.AUTOMATION_SKILL = AUTOMATION_SKILL;
|
|
117
|
+
exports.installAutomationSkill = installAutomationSkill;
|
|
118
|
+
exports.installTeamSkill = installTeamSkill;
|
|
@@ -6,20 +6,20 @@ var os = require('node:os');
|
|
|
6
6
|
var node_child_process = require('node:child_process');
|
|
7
7
|
var chalk = require('chalk');
|
|
8
8
|
var projectPath = require('./projectPath-ChLhdOyt.cjs');
|
|
9
|
-
var index = require('./index-
|
|
9
|
+
var index = require('./index-ZIW1jrYm.cjs');
|
|
10
10
|
require('path');
|
|
11
11
|
require('url');
|
|
12
12
|
require('node:crypto');
|
|
13
|
-
require('./types-
|
|
13
|
+
require('./types-CN28qJl7.cjs');
|
|
14
14
|
require('axios');
|
|
15
|
-
require('./limits-
|
|
15
|
+
require('./limits-BNTWyUaZ.cjs');
|
|
16
16
|
require('node:fs/promises');
|
|
17
17
|
require('zod');
|
|
18
18
|
require('tweetnacl');
|
|
19
19
|
require('node:util');
|
|
20
|
+
require('@paralleldrive/cuid2');
|
|
20
21
|
require('node:events');
|
|
21
22
|
require('socket.io-client');
|
|
22
|
-
require('@paralleldrive/cuid2');
|
|
23
23
|
require('child_process');
|
|
24
24
|
require('util');
|
|
25
25
|
require('fs/promises');
|
|
@@ -31,7 +31,7 @@ require('fs');
|
|
|
31
31
|
require('node:readline');
|
|
32
32
|
require('@anthropic-ai/sandbox-runtime');
|
|
33
33
|
require('expo-server-sdk');
|
|
34
|
-
require('./agentGuidance-
|
|
34
|
+
require('./agentGuidance-CqBn77AI.cjs');
|
|
35
35
|
require('ink');
|
|
36
36
|
require('react');
|
|
37
37
|
require('@anthropic-ai/claude-agent-sdk');
|
|
@@ -4,20 +4,20 @@ import { homedir } from 'node:os';
|
|
|
4
4
|
import { spawnSync } from 'node:child_process';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import { p as projectPath } from './projectPath-CSpqWdxp.mjs';
|
|
7
|
-
import {
|
|
7
|
+
import { j as tmuxSupportsSessionEnv } from './index-BauTv2jB.mjs';
|
|
8
8
|
import 'path';
|
|
9
9
|
import 'url';
|
|
10
10
|
import 'node:crypto';
|
|
11
|
-
import './types-
|
|
11
|
+
import './types-xKrxbCRv.mjs';
|
|
12
12
|
import 'axios';
|
|
13
|
-
import './limits-
|
|
13
|
+
import './limits-CKR_HJnO.mjs';
|
|
14
14
|
import 'node:fs/promises';
|
|
15
15
|
import 'zod';
|
|
16
16
|
import 'tweetnacl';
|
|
17
17
|
import 'node:util';
|
|
18
|
+
import '@paralleldrive/cuid2';
|
|
18
19
|
import 'node:events';
|
|
19
20
|
import 'socket.io-client';
|
|
20
|
-
import '@paralleldrive/cuid2';
|
|
21
21
|
import 'child_process';
|
|
22
22
|
import 'util';
|
|
23
23
|
import 'fs/promises';
|
|
@@ -29,7 +29,7 @@ import 'fs';
|
|
|
29
29
|
import 'node:readline';
|
|
30
30
|
import '@anthropic-ai/sandbox-runtime';
|
|
31
31
|
import 'expo-server-sdk';
|
|
32
|
-
import './agentGuidance-
|
|
32
|
+
import './agentGuidance-Daxn5Nvj.mjs';
|
|
33
33
|
import 'ink';
|
|
34
34
|
import 'react';
|
|
35
35
|
import '@anthropic-ai/claude-agent-sdk';
|
package/dist/lib.cjs
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var index = require('./types-
|
|
4
|
-
var persistence = require('./limits-
|
|
3
|
+
var index = require('./types-CN28qJl7.cjs');
|
|
4
|
+
var persistence = require('./limits-BNTWyUaZ.cjs');
|
|
5
5
|
require('axios');
|
|
6
6
|
require('node:events');
|
|
7
7
|
require('socket.io-client');
|
|
8
8
|
require('zod');
|
|
9
|
-
require('@paralleldrive/cuid2');
|
|
10
9
|
require('node:crypto');
|
|
11
10
|
require('child_process');
|
|
12
11
|
require('util');
|
|
@@ -17,6 +16,7 @@ require('cross-spawn');
|
|
|
17
16
|
require('./projectPath-ChLhdOyt.cjs');
|
|
18
17
|
require('url');
|
|
19
18
|
require('os');
|
|
19
|
+
require('@paralleldrive/cuid2');
|
|
20
20
|
require('node:fs/promises');
|
|
21
21
|
require('node:path');
|
|
22
22
|
require('node:os');
|
package/dist/lib.mjs
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema } from './types-
|
|
2
|
-
export { c as configuration, l as logger } from './limits-
|
|
1
|
+
export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema } from './types-xKrxbCRv.mjs';
|
|
2
|
+
export { c as configuration, l as logger } from './limits-CKR_HJnO.mjs';
|
|
3
3
|
import 'axios';
|
|
4
4
|
import 'node:events';
|
|
5
5
|
import 'socket.io-client';
|
|
6
6
|
import 'zod';
|
|
7
|
-
import '@paralleldrive/cuid2';
|
|
8
7
|
import 'node:crypto';
|
|
9
8
|
import 'child_process';
|
|
10
9
|
import 'util';
|
|
@@ -15,6 +14,7 @@ import 'cross-spawn';
|
|
|
15
14
|
import './projectPath-CSpqWdxp.mjs';
|
|
16
15
|
import 'url';
|
|
17
16
|
import 'os';
|
|
17
|
+
import '@paralleldrive/cuid2';
|
|
18
18
|
import 'node:fs/promises';
|
|
19
19
|
import 'node:path';
|
|
20
20
|
import 'node:os';
|