very-happy-cli 0.2.115 → 0.2.117
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-DEZTWZZu.mjs → AcpBackend-C4RMHt0I.mjs} +1 -1
- package/dist/{AcpBackend-BrZN8KM-.cjs → AcpBackend-CrbXvcgQ.cjs} +1 -1
- package/dist/{AcpSessionManager-DjulpKY0.cjs → AcpSessionManager-CjAVO4RX.cjs} +1 -1
- package/dist/{AcpSessionManager-BzP3eiOG.mjs → AcpSessionManager-Cnc8Jatl.mjs} +1 -1
- package/dist/{config-CMgRBQMS.mjs → config-CnA3JIUn.mjs} +2 -2
- package/dist/{config-BNAsx9Wg.cjs → config-O9quMfxg.cjs} +2 -2
- package/dist/{index-B2KfhNku.mjs → index-B0o3H6_x.mjs} +2 -2
- package/dist/{index-BHNEb4bc.mjs → index-BYZKuQoy.mjs} +590 -41
- package/dist/{index-tPXwlc_t.cjs → index-DcESl4cI.cjs} +601 -43
- package/dist/{index-CpGR7rIw.mjs → index-DiRN0Rv8.mjs} +78 -8
- package/dist/{index-B3UkbonP.cjs → index-DqmVH6R5.cjs} +2 -2
- package/dist/{index-ClR0jhjm.cjs → index-W-O4xR_O.cjs} +77 -7
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{installTerminalHooks-DD6QuHfT.mjs → installTerminalHooks-DeYqyjCB.mjs} +2 -2
- package/dist/{installTerminalHooks-CWfCF68p.cjs → installTerminalHooks-zfsWSotO.cjs} +2 -2
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.cts +66 -1
- package/dist/lib.d.mts +66 -1
- package/dist/lib.mjs +1 -1
- package/dist/{mcp-CiNrjYox.mjs → mcp-D_5aOK0I.mjs} +62 -17
- package/dist/{mcp-CqjiDRnG.cjs → mcp-nYqC4FYm.cjs} +62 -16
- package/dist/{runGemini-DdJqpywr.mjs → runGemini-CEURRmM4.mjs} +4 -4
- package/dist/{runGemini-UN5IznJp.cjs → runGemini-DYf3ntib.cjs} +4 -4
- package/dist/{runOpenClaw-CM5UrVBy.mjs → runOpenClaw-BozfKWng.mjs} +3 -3
- package/dist/{runOpenClaw-9hhfGllZ.cjs → runOpenClaw-CNKjAw-H.cjs} +3 -3
- package/dist/{send-CnVtXOj0.cjs → send-BZLhRZb-.cjs} +2 -2
- package/dist/{send-C6G2jsQp.mjs → send-CYznZGrp.mjs} +2 -2
- package/dist/sessions-DYPB3Peg.mjs +317 -0
- package/dist/sessions-YWt9YEN8.cjs +322 -0
- package/dist/{spawn-qb344Y5E.mjs → spawn-CsrHZd3y.mjs} +3 -4
- package/dist/{spawn-CfYddkS_.cjs → spawn-tSvo3stN.cjs} +6 -7
- package/dist/{types-CpnzfHHS.cjs → types-DD3AK6Nb.cjs} +189 -26
- package/dist/{types-B3qx8Ga1.mjs → types-jSfU_0o1.mjs} +187 -26
- package/package.json +7 -7
- package/dist/sessions-CGdcF05T.cjs +0 -204
- package/dist/sessions-CmabJzYS.mjs +0 -200
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
-
import { p as pushClipboardViaDaemon } from './index-
|
|
4
|
+
import { p as pushClipboardViaDaemon, k as setTerminalTitleViaDaemon, m as registerAssistantSessionTools } from './index-BYZKuQoy.mjs';
|
|
5
5
|
import { C as CLIPBOARD_TOOL_NAME, a as CLIPBOARD_TOOL_TITLE, b as CLIPBOARD_TOOL_DESCRIPTION, c as CLIPBOARD_MAX_BYTES } from './limits-CwFb5S2b.mjs';
|
|
6
|
-
import { l as logger } from './types-
|
|
7
|
-
import '
|
|
6
|
+
import { l as logger } from './types-jSfU_0o1.mjs';
|
|
7
|
+
import 'node:fs/promises';
|
|
8
8
|
import 'node:os';
|
|
9
9
|
import 'node:crypto';
|
|
10
|
-
import '
|
|
10
|
+
import 'axios';
|
|
11
|
+
import 'tweetnacl';
|
|
12
|
+
import 'socket.io-client';
|
|
13
|
+
import 'node:child_process';
|
|
14
|
+
import 'node:fs';
|
|
11
15
|
import 'node:path';
|
|
16
|
+
import 'chalk';
|
|
17
|
+
import 'cross-spawn';
|
|
12
18
|
import 'node:readline';
|
|
13
|
-
import 'node:fs';
|
|
14
19
|
import './agentGuidance-C2_pkgRI.mjs';
|
|
15
|
-
import 'node:fs/promises';
|
|
16
20
|
import 'fs/promises';
|
|
17
21
|
import 'ink';
|
|
18
22
|
import 'react';
|
|
19
23
|
import '@anthropic-ai/claude-agent-sdk';
|
|
20
|
-
import 'axios';
|
|
21
24
|
import 'node:events';
|
|
22
|
-
import 'socket.io-client';
|
|
23
|
-
import 'tweetnacl';
|
|
24
25
|
import 'os';
|
|
25
26
|
import 'child_process';
|
|
26
27
|
import '@paralleldrive/cuid2';
|
|
27
|
-
import 'node:child_process';
|
|
28
28
|
import 'fs';
|
|
29
29
|
import 'path';
|
|
30
30
|
import '@anthropic-ai/sandbox-runtime';
|
|
@@ -47,11 +47,19 @@ import 'node:util';
|
|
|
47
47
|
import 'url';
|
|
48
48
|
import 'node-pty';
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
});
|
|
50
|
+
const TERMINAL_TITLE_TOOL_NAME = "change_title";
|
|
51
|
+
function resolveMcpTerminalId(env) {
|
|
52
|
+
if (env.HAPPY_MCP_URL) return null;
|
|
53
|
+
const id = env.VH_TERMINAL_ID;
|
|
54
|
+
return id && /^[a-zA-Z0-9_-]{1,64}$/.test(id) ? id : null;
|
|
55
|
+
}
|
|
56
|
+
function resolveMcpToolSurface(env) {
|
|
57
|
+
if (env.HAPPY_SESSION_VARIANT !== "assistant") return "clipboard";
|
|
58
|
+
if (env.HAPPY_MANAGED === "1") return "clipboard";
|
|
59
|
+
return "assistant";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function registerMcpTools(server, surface, terminalId = null) {
|
|
55
63
|
server.registerTool(CLIPBOARD_TOOL_NAME, {
|
|
56
64
|
description: CLIPBOARD_TOOL_DESCRIPTION,
|
|
57
65
|
title: CLIPBOARD_TOOL_TITLE,
|
|
@@ -79,13 +87,50 @@ async function handleMcpCommand() {
|
|
|
79
87
|
isError: true
|
|
80
88
|
};
|
|
81
89
|
});
|
|
90
|
+
if (terminalId) {
|
|
91
|
+
server.registerTool(TERMINAL_TITLE_TOOL_NAME, {
|
|
92
|
+
description: "Change the title of the very-happy web terminal this agent is running in",
|
|
93
|
+
title: "Change Terminal Title",
|
|
94
|
+
inputSchema: {
|
|
95
|
+
title: z.string().min(1).max(200).describe("The new title for this terminal")
|
|
96
|
+
}
|
|
97
|
+
}, async (args) => {
|
|
98
|
+
logger.debug(`[MCP] change_title called for terminal ${terminalId}`);
|
|
99
|
+
const result = await setTerminalTitleViaDaemon(terminalId, args.title);
|
|
100
|
+
if (result.ok) {
|
|
101
|
+
return {
|
|
102
|
+
content: [{ type: "text", text: `Successfully changed terminal title to: "${args.title}"` }],
|
|
103
|
+
isError: false
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
content: [{
|
|
108
|
+
type: "text",
|
|
109
|
+
text: `Failed to change terminal title: ${result.error || "unknown error"}. The very-happy daemon must be running on this machine (start it with \`very-happy daemon start\`).`
|
|
110
|
+
}],
|
|
111
|
+
isError: true
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
if (surface === "assistant") {
|
|
116
|
+
registerAssistantSessionTools(server);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
async function handleMcpCommand() {
|
|
120
|
+
const server = new McpServer({
|
|
121
|
+
name: "very-happy",
|
|
122
|
+
version: "1.0.0"
|
|
123
|
+
});
|
|
124
|
+
const surface = resolveMcpToolSurface(process.env);
|
|
125
|
+
const terminalId = resolveMcpTerminalId(process.env);
|
|
126
|
+
registerMcpTools(server, surface, terminalId);
|
|
82
127
|
const transport = new StdioServerTransport();
|
|
83
128
|
await server.connect(transport);
|
|
84
|
-
logger.debug(
|
|
129
|
+
logger.debug(`[MCP] very-happy stdio MCP server started (surface=${surface}, terminal=${terminalId ?? "none"})`);
|
|
85
130
|
await new Promise((resolve) => {
|
|
86
131
|
process.stdin.on("close", resolve);
|
|
87
132
|
process.stdin.on("end", resolve);
|
|
88
133
|
});
|
|
89
134
|
}
|
|
90
135
|
|
|
91
|
-
export { handleMcpCommand };
|
|
136
|
+
export { handleMcpCommand, registerMcpTools };
|
|
@@ -3,30 +3,30 @@
|
|
|
3
3
|
var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
|
|
4
4
|
var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
5
5
|
var z = require('zod');
|
|
6
|
-
var index = require('./index-
|
|
6
|
+
var index = require('./index-DcESl4cI.cjs');
|
|
7
7
|
var limits = require('./limits-KdWKmwtH.cjs');
|
|
8
|
-
var persistence = require('./types-
|
|
9
|
-
require('
|
|
8
|
+
var persistence = require('./types-DD3AK6Nb.cjs');
|
|
9
|
+
require('node:fs/promises');
|
|
10
10
|
require('node:os');
|
|
11
11
|
require('node:crypto');
|
|
12
|
-
require('
|
|
12
|
+
require('axios');
|
|
13
|
+
require('tweetnacl');
|
|
14
|
+
require('socket.io-client');
|
|
15
|
+
require('node:child_process');
|
|
16
|
+
require('node:fs');
|
|
13
17
|
require('node:path');
|
|
18
|
+
require('chalk');
|
|
19
|
+
require('cross-spawn');
|
|
14
20
|
require('node:readline');
|
|
15
|
-
require('node:fs');
|
|
16
21
|
require('./agentGuidance-BifkGXdE.cjs');
|
|
17
|
-
require('node:fs/promises');
|
|
18
22
|
require('fs/promises');
|
|
19
23
|
require('ink');
|
|
20
24
|
require('react');
|
|
21
25
|
require('@anthropic-ai/claude-agent-sdk');
|
|
22
|
-
require('axios');
|
|
23
26
|
require('node:events');
|
|
24
|
-
require('socket.io-client');
|
|
25
|
-
require('tweetnacl');
|
|
26
27
|
require('os');
|
|
27
28
|
require('child_process');
|
|
28
29
|
require('@paralleldrive/cuid2');
|
|
29
|
-
require('node:child_process');
|
|
30
30
|
require('fs');
|
|
31
31
|
require('path');
|
|
32
32
|
require('@anthropic-ai/sandbox-runtime');
|
|
@@ -49,11 +49,19 @@ require('node:util');
|
|
|
49
49
|
require('url');
|
|
50
50
|
require('node-pty');
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
});
|
|
52
|
+
const TERMINAL_TITLE_TOOL_NAME = "change_title";
|
|
53
|
+
function resolveMcpTerminalId(env) {
|
|
54
|
+
if (env.HAPPY_MCP_URL) return null;
|
|
55
|
+
const id = env.VH_TERMINAL_ID;
|
|
56
|
+
return id && /^[a-zA-Z0-9_-]{1,64}$/.test(id) ? id : null;
|
|
57
|
+
}
|
|
58
|
+
function resolveMcpToolSurface(env) {
|
|
59
|
+
if (env.HAPPY_SESSION_VARIANT !== "assistant") return "clipboard";
|
|
60
|
+
if (env.HAPPY_MANAGED === "1") return "clipboard";
|
|
61
|
+
return "assistant";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function registerMcpTools(server, surface, terminalId = null) {
|
|
57
65
|
server.registerTool(limits.CLIPBOARD_TOOL_NAME, {
|
|
58
66
|
description: limits.CLIPBOARD_TOOL_DESCRIPTION,
|
|
59
67
|
title: limits.CLIPBOARD_TOOL_TITLE,
|
|
@@ -81,9 +89,46 @@ async function handleMcpCommand() {
|
|
|
81
89
|
isError: true
|
|
82
90
|
};
|
|
83
91
|
});
|
|
92
|
+
if (terminalId) {
|
|
93
|
+
server.registerTool(TERMINAL_TITLE_TOOL_NAME, {
|
|
94
|
+
description: "Change the title of the very-happy web terminal this agent is running in",
|
|
95
|
+
title: "Change Terminal Title",
|
|
96
|
+
inputSchema: {
|
|
97
|
+
title: z.z.string().min(1).max(200).describe("The new title for this terminal")
|
|
98
|
+
}
|
|
99
|
+
}, async (args) => {
|
|
100
|
+
persistence.logger.debug(`[MCP] change_title called for terminal ${terminalId}`);
|
|
101
|
+
const result = await index.setTerminalTitleViaDaemon(terminalId, args.title);
|
|
102
|
+
if (result.ok) {
|
|
103
|
+
return {
|
|
104
|
+
content: [{ type: "text", text: `Successfully changed terminal title to: "${args.title}"` }],
|
|
105
|
+
isError: false
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
content: [{
|
|
110
|
+
type: "text",
|
|
111
|
+
text: `Failed to change terminal title: ${result.error || "unknown error"}. The very-happy daemon must be running on this machine (start it with \`very-happy daemon start\`).`
|
|
112
|
+
}],
|
|
113
|
+
isError: true
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
if (surface === "assistant") {
|
|
118
|
+
index.registerAssistantSessionTools(server);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async function handleMcpCommand() {
|
|
122
|
+
const server = new mcp_js.McpServer({
|
|
123
|
+
name: "very-happy",
|
|
124
|
+
version: "1.0.0"
|
|
125
|
+
});
|
|
126
|
+
const surface = resolveMcpToolSurface(process.env);
|
|
127
|
+
const terminalId = resolveMcpTerminalId(process.env);
|
|
128
|
+
registerMcpTools(server, surface, terminalId);
|
|
84
129
|
const transport = new stdio_js.StdioServerTransport();
|
|
85
130
|
await server.connect(transport);
|
|
86
|
-
persistence.logger.debug(
|
|
131
|
+
persistence.logger.debug(`[MCP] very-happy stdio MCP server started (surface=${surface}, terminal=${terminalId ?? "none"})`);
|
|
87
132
|
await new Promise((resolve) => {
|
|
88
133
|
process.stdin.on("close", resolve);
|
|
89
134
|
process.stdin.on("end", resolve);
|
|
@@ -91,3 +136,4 @@ async function handleMcpCommand() {
|
|
|
91
136
|
}
|
|
92
137
|
|
|
93
138
|
exports.handleMcpCommand = handleMcpCommand;
|
|
139
|
+
exports.registerMcpTools = registerMcpTools;
|
|
@@ -2,10 +2,10 @@ import { useStdout, useInput, Box, Text, render } from 'ink';
|
|
|
2
2
|
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
import { l as logger, h as connectionState, A as ApiClient, j as readSettings, e as errorLogMetadata, k as encodeBase64, p as projectPath, d as contentLogMetadata } from './types-
|
|
6
|
-
import {
|
|
7
|
-
import { A as AcpBackend } from './AcpBackend-
|
|
8
|
-
import { readGeminiLocalConfig, determineGeminiModel, getGeminiModelSource, getInitialGeminiModel, saveGeminiModelToConfig } from './config-
|
|
5
|
+
import { l as logger, h as connectionState, A as ApiClient, j as readSettings, e as errorLogMetadata, k as encodeBase64, p as projectPath, d as contentLogMetadata } from './types-jSfU_0o1.mjs';
|
|
6
|
+
import { I as GEMINI_API_KEY_ENV, J as GOOGLE_API_KEY_ENV, G as GEMINI_MODEL_ENV, C as BasePermissionHandler, K as BaseReasoningProcessor, n as initialMachineMetadata, o as createSessionMetadata, u as setupOfflineReconnection, v as notifyDaemonSessionStarted, x as MessageQueue2, y as hashObject, L as MessageBuffer, N as CHANGE_TITLE_INSTRUCTION, B as registerKillSessionHandler, z as startHappyServer } from './index-BYZKuQoy.mjs';
|
|
7
|
+
import { A as AcpBackend } from './AcpBackend-C4RMHt0I.mjs';
|
|
8
|
+
import { readGeminiLocalConfig, determineGeminiModel, getGeminiModelSource, getInitialGeminiModel, saveGeminiModelToConfig } from './config-CnA3JIUn.mjs';
|
|
9
9
|
import 'axios';
|
|
10
10
|
import 'chalk';
|
|
11
11
|
import 'node:util';
|
|
@@ -4,10 +4,10 @@ var ink = require('ink');
|
|
|
4
4
|
var React = require('react');
|
|
5
5
|
var node_crypto = require('node:crypto');
|
|
6
6
|
var path = require('node:path');
|
|
7
|
-
var persistence = require('./types-
|
|
8
|
-
var index = require('./index-
|
|
9
|
-
var AcpBackend = require('./AcpBackend-
|
|
10
|
-
var config = require('./config-
|
|
7
|
+
var persistence = require('./types-DD3AK6Nb.cjs');
|
|
8
|
+
var index = require('./index-DcESl4cI.cjs');
|
|
9
|
+
var AcpBackend = require('./AcpBackend-CrbXvcgQ.cjs');
|
|
10
|
+
var config = require('./config-O9quMfxg.cjs');
|
|
11
11
|
require('axios');
|
|
12
12
|
require('chalk');
|
|
13
13
|
require('node:util');
|
|
@@ -3,9 +3,9 @@ import { execFileSync } from 'node:child_process';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import { existsSync, readFileSync } from 'node:fs';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
|
-
import { m as ensurePrivateDirectorySync, n as hardenPrivateFileSync, w as writePrivateFileSync, e as errorLogMetadata, d as contentLogMetadata, h as connectionState, A as ApiClient, j as readSettings, k as encodeBase64, l as logger, c as configuration } from './types-
|
|
7
|
-
import { A as AcpSessionManager } from './AcpSessionManager-
|
|
8
|
-
import {
|
|
6
|
+
import { m as ensurePrivateDirectorySync, n as hardenPrivateFileSync, w as writePrivateFileSync, e as errorLogMetadata, d as contentLogMetadata, h as connectionState, A as ApiClient, j as readSettings, k as encodeBase64, l as logger, c as configuration } from './types-jSfU_0o1.mjs';
|
|
7
|
+
import { A as AcpSessionManager } from './AcpSessionManager-Cnc8Jatl.mjs';
|
|
8
|
+
import { n as initialMachineMetadata, o as createSessionMetadata, u as setupOfflineReconnection, v as notifyDaemonSessionStarted, x as MessageQueue2, B as registerKillSessionHandler } from './index-BYZKuQoy.mjs';
|
|
9
9
|
import WebSocket from 'ws';
|
|
10
10
|
import * as ed from '@noble/ed25519';
|
|
11
11
|
import { sha512 } from '@noble/hashes/sha2.js';
|
|
@@ -5,9 +5,9 @@ var node_child_process = require('node:child_process');
|
|
|
5
5
|
var os = require('node:os');
|
|
6
6
|
var node_fs = require('node:fs');
|
|
7
7
|
var path = require('node:path');
|
|
8
|
-
var persistence = require('./types-
|
|
9
|
-
var AcpSessionManager = require('./AcpSessionManager-
|
|
10
|
-
var index = require('./index-
|
|
8
|
+
var persistence = require('./types-DD3AK6Nb.cjs');
|
|
9
|
+
var AcpSessionManager = require('./AcpSessionManager-CjAVO4RX.cjs');
|
|
10
|
+
var index = require('./index-DcESl4cI.cjs');
|
|
11
11
|
var WebSocket = require('ws');
|
|
12
12
|
var ed = require('@noble/ed25519');
|
|
13
13
|
var sha2_js = require('@noble/hashes/sha2.js');
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
var chalk = require('chalk');
|
|
4
4
|
var node_fs = require('node:fs');
|
|
5
5
|
var path = require('node:path');
|
|
6
|
-
var persistence = require('./types-
|
|
7
|
-
var index = require('./index-
|
|
6
|
+
var persistence = require('./types-DD3AK6Nb.cjs');
|
|
7
|
+
var index = require('./index-DcESl4cI.cjs');
|
|
8
8
|
require('axios');
|
|
9
9
|
require('node:util');
|
|
10
10
|
require('node:os');
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
|
-
import { r as readPersistedSessions, c as configuration } from './types-
|
|
5
|
-
import { d as sendUserMessage, a as sessionWebUrl } from './index-
|
|
4
|
+
import { r as readPersistedSessions, c as configuration } from './types-jSfU_0o1.mjs';
|
|
5
|
+
import { d as sendUserMessage, a as sessionWebUrl } from './index-BYZKuQoy.mjs';
|
|
6
6
|
import 'axios';
|
|
7
7
|
import 'node:util';
|
|
8
8
|
import 'node:os';
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { D as DEFAULT_RECENT_LIMIT, M as MAX_READ_LIMIT, l as listAccountSessions, e as listSessions, r as readSessionTranscript, f as stopSession, g as resolvePermissionRequest, h as archiveSession } from './index-BYZKuQoy.mjs';
|
|
3
|
+
import { i as isValidSessionId } from './types-jSfU_0o1.mjs';
|
|
4
|
+
import 'node:os';
|
|
5
|
+
import 'node:crypto';
|
|
6
|
+
import 'cross-spawn';
|
|
7
|
+
import 'node:path';
|
|
8
|
+
import 'node:readline';
|
|
9
|
+
import 'node:fs';
|
|
10
|
+
import './agentGuidance-C2_pkgRI.mjs';
|
|
11
|
+
import 'node:fs/promises';
|
|
12
|
+
import 'fs/promises';
|
|
13
|
+
import 'ink';
|
|
14
|
+
import 'react';
|
|
15
|
+
import '@anthropic-ai/claude-agent-sdk';
|
|
16
|
+
import 'axios';
|
|
17
|
+
import 'node:events';
|
|
18
|
+
import 'socket.io-client';
|
|
19
|
+
import 'tweetnacl';
|
|
20
|
+
import 'os';
|
|
21
|
+
import 'child_process';
|
|
22
|
+
import '@paralleldrive/cuid2';
|
|
23
|
+
import 'node:child_process';
|
|
24
|
+
import 'fs';
|
|
25
|
+
import 'path';
|
|
26
|
+
import '@anthropic-ai/sandbox-runtime';
|
|
27
|
+
import 'expo-server-sdk';
|
|
28
|
+
import 'crypto';
|
|
29
|
+
import 'ps-list';
|
|
30
|
+
import 'tmp';
|
|
31
|
+
import 'open';
|
|
32
|
+
import 'fastify';
|
|
33
|
+
import 'zod';
|
|
34
|
+
import 'fastify-type-provider-zod';
|
|
35
|
+
import 'node:module';
|
|
36
|
+
import '@modelcontextprotocol/sdk/server/mcp.js';
|
|
37
|
+
import 'node:http';
|
|
38
|
+
import '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
39
|
+
import './limits-CwFb5S2b.mjs';
|
|
40
|
+
import 'http';
|
|
41
|
+
import 'util';
|
|
42
|
+
import 'inquirer';
|
|
43
|
+
import 'node:url';
|
|
44
|
+
import 'node:readline/promises';
|
|
45
|
+
import 'node:util';
|
|
46
|
+
import 'url';
|
|
47
|
+
import 'node-pty';
|
|
48
|
+
|
|
49
|
+
const ACTIONS = ["list", "read", "stop", "archive", "approve", "deny"];
|
|
50
|
+
const ACTIONS_NEEDING_ID = /* @__PURE__ */ new Set(["read", "stop", "archive", "approve", "deny"]);
|
|
51
|
+
const ACTIONS_NEEDING_REQUEST_ID = /* @__PURE__ */ new Set(["approve", "deny"]);
|
|
52
|
+
const REQUEST_ID_RE = /^[a-zA-Z0-9_.:-]{1,128}$/;
|
|
53
|
+
function defaultOptions() {
|
|
54
|
+
return { action: "help", all: false, includeArchived: false, forSession: false, json: false };
|
|
55
|
+
}
|
|
56
|
+
function parseSessionsArgs(args) {
|
|
57
|
+
const options = defaultOptions();
|
|
58
|
+
let actionSeen = false;
|
|
59
|
+
const positionals = [];
|
|
60
|
+
for (let i = 0; i < args.length; i++) {
|
|
61
|
+
const arg = args[i];
|
|
62
|
+
if (arg === "--help" || arg === "-h") {
|
|
63
|
+
return defaultOptions();
|
|
64
|
+
} else if (arg === "--json") {
|
|
65
|
+
options.json = true;
|
|
66
|
+
} else if (arg === "--all") {
|
|
67
|
+
options.all = true;
|
|
68
|
+
} else if (arg === "--include-archived") {
|
|
69
|
+
options.includeArchived = true;
|
|
70
|
+
} else if (arg === "--for-session") {
|
|
71
|
+
options.forSession = true;
|
|
72
|
+
} else if (arg === "--reason") {
|
|
73
|
+
const value = args[++i];
|
|
74
|
+
if (value === void 0) throw new Error("--reason requires a value");
|
|
75
|
+
options.reason = value;
|
|
76
|
+
} else if (arg === "--tag") {
|
|
77
|
+
const value = args[++i];
|
|
78
|
+
if (value === void 0) throw new Error("--tag requires a value");
|
|
79
|
+
options.tag = value;
|
|
80
|
+
} else if (arg === "--limit") {
|
|
81
|
+
const value = args[++i];
|
|
82
|
+
if (value === void 0) throw new Error("--limit requires a value");
|
|
83
|
+
const parsed = Number(value);
|
|
84
|
+
if (!Number.isInteger(parsed) || parsed < 1) throw new Error("--limit must be a positive integer");
|
|
85
|
+
options.limit = parsed;
|
|
86
|
+
} else if (arg.startsWith("-")) {
|
|
87
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
88
|
+
} else if (!actionSeen) {
|
|
89
|
+
if (!ACTIONS.includes(arg)) {
|
|
90
|
+
throw new Error(`Unknown action: ${arg} (expected ${ACTIONS.join(", ")})`);
|
|
91
|
+
}
|
|
92
|
+
options.action = arg;
|
|
93
|
+
actionSeen = true;
|
|
94
|
+
} else {
|
|
95
|
+
positionals.push(arg);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!actionSeen) return defaultOptions();
|
|
99
|
+
const expectedPositionals = ACTIONS_NEEDING_REQUEST_ID.has(options.action) ? 2 : ACTIONS_NEEDING_ID.has(options.action) ? 1 : 0;
|
|
100
|
+
if (positionals.length > expectedPositionals) {
|
|
101
|
+
throw new Error(`Unexpected ${expectedPositionals > 0 ? "extra " : ""}argument: ${positionals[expectedPositionals]}`);
|
|
102
|
+
}
|
|
103
|
+
if (ACTIONS_NEEDING_ID.has(options.action)) {
|
|
104
|
+
const id = positionals[0];
|
|
105
|
+
if (id === void 0) throw new Error(`${options.action} requires a session id`);
|
|
106
|
+
if (!isValidSessionId(id)) throw new Error(`Invalid session id: ${id}`);
|
|
107
|
+
options.sessionId = id;
|
|
108
|
+
}
|
|
109
|
+
if (ACTIONS_NEEDING_REQUEST_ID.has(options.action)) {
|
|
110
|
+
const requestId = positionals[1];
|
|
111
|
+
if (requestId === void 0) throw new Error(`${options.action} requires a session id and a request id`);
|
|
112
|
+
if (!REQUEST_ID_RE.test(requestId)) throw new Error(`Invalid request id: ${requestId}`);
|
|
113
|
+
options.requestId = requestId;
|
|
114
|
+
}
|
|
115
|
+
if (options.action !== "list") {
|
|
116
|
+
if (options.tag !== void 0) throw new Error("--tag only applies to `sessions list`");
|
|
117
|
+
if (options.all) throw new Error("--all only applies to `sessions list`");
|
|
118
|
+
if (options.includeArchived) throw new Error("--include-archived only applies to `sessions list --all`");
|
|
119
|
+
} else if (options.includeArchived && !options.all) {
|
|
120
|
+
throw new Error("--include-archived only applies to `sessions list --all`");
|
|
121
|
+
}
|
|
122
|
+
if (options.forSession && options.action !== "approve") throw new Error("--for-session only applies to `sessions approve`");
|
|
123
|
+
if (options.reason !== void 0 && options.action !== "deny") throw new Error("--reason only applies to `sessions deny`");
|
|
124
|
+
return options;
|
|
125
|
+
}
|
|
126
|
+
const SESSIONS_HELP = `
|
|
127
|
+
${chalk.bold("very-happy sessions")} - Inspect and control sessions on this machine (for automation)
|
|
128
|
+
|
|
129
|
+
${chalk.bold("Usage:")}
|
|
130
|
+
very-happy sessions list [--all [--include-archived]] [--tag <name>] [--limit <n>] [--json]
|
|
131
|
+
very-happy sessions read <id> [--limit <n>] [--json]
|
|
132
|
+
very-happy sessions stop <id> [--json]
|
|
133
|
+
very-happy sessions archive <id> [--json]
|
|
134
|
+
very-happy sessions approve <id> <requestId> [--for-session] [--json]
|
|
135
|
+
very-happy sessions deny <id> <requestId> [--reason <text>] [--json]
|
|
136
|
+
|
|
137
|
+
${chalk.bold("Actions:")}
|
|
138
|
+
list Running sessions plus recently seen ones, newest first.
|
|
139
|
+
With --all: every session on the account (newest 150, server
|
|
140
|
+
REST), attention first. Each row says whether this machine could
|
|
141
|
+
decrypt it (\`decryptable\`); rows from other machines show only
|
|
142
|
+
id / active / archived / timestamps / url.
|
|
143
|
+
read The tail of a session as a role-tagged transcript.
|
|
144
|
+
stop SIGTERM the session's process via the local daemon.
|
|
145
|
+
archive Mark the session inactive server-side (it stays resumable).
|
|
146
|
+
approve Answer a pending permission request \`requestId\` with approve \u2014
|
|
147
|
+
the same RPC the web permission card sends. --for-session makes
|
|
148
|
+
it \`approved_for_session\` (the card's "allow for this session").
|
|
149
|
+
deny Answer it with deny (optional --reason is shown to the agent).
|
|
150
|
+
|
|
151
|
+
${chalk.bold("Options:")}
|
|
152
|
+
--all list only: account-wide over REST instead of this
|
|
153
|
+
machine's daemon + local keys.
|
|
154
|
+
--include-archived list --all only: also show server-archived rows.
|
|
155
|
+
--tag <name> list only: keep sessions carrying this origin tag (see
|
|
156
|
+
\`spawn --spawned-by\`). With --all only decryptable rows
|
|
157
|
+
can match.
|
|
158
|
+
--limit <n> list: how many NOT-running / not-attention sessions to
|
|
159
|
+
include (default ${DEFAULT_RECENT_LIMIT}; running and attention rows are
|
|
160
|
+
never cut). read: how many messages (default 20, max ${MAX_READ_LIMIT}).
|
|
161
|
+
--for-session approve only: approved_for_session instead of approved.
|
|
162
|
+
--reason <text> deny only: reason forwarded to the wrapper.
|
|
163
|
+
--json Machine-readable output.
|
|
164
|
+
-h, --help Show this help
|
|
165
|
+
|
|
166
|
+
${chalk.bold("Scope:")}
|
|
167
|
+
read / approve / deny need the session key from ~/.happy/sessions.json,
|
|
168
|
+
which exists for sessions this machine's daemon spawned and is pruned after
|
|
169
|
+
14 days (the message payloads and the permission RPC are encrypted with it).
|
|
170
|
+
\`list --all\` sees every session on the account but can only decrypt those
|
|
171
|
+
same ones; the rest come back with decryptable=false. Reading, listing in
|
|
172
|
+
full and answering another machine's session from here needs the CLI to hold
|
|
173
|
+
the account content key \u2014 a credentials change, not a flag.
|
|
174
|
+
|
|
175
|
+
${chalk.bold("Exit codes:")}
|
|
176
|
+
0 success (approve/deny: the wrapper acknowledged the verdict; check
|
|
177
|
+
\`settled\` in --json to see whether the request has left the pending set)
|
|
178
|
+
1 bad arguments, unknown session, no local key, request not pending,
|
|
179
|
+
session not online, or the operation failed (including \`stop\` on a
|
|
180
|
+
session the daemon is not running)
|
|
181
|
+
`;
|
|
182
|
+
function formatWait(ms) {
|
|
183
|
+
const minutes = Math.floor(ms / 6e4);
|
|
184
|
+
if (minutes < 1) return `${Math.floor(ms / 1e3)}s`;
|
|
185
|
+
if (minutes < 60) return `${minutes}m`;
|
|
186
|
+
return `${Math.floor(minutes / 60)}h${minutes % 60}m`;
|
|
187
|
+
}
|
|
188
|
+
function formatAccountSummaryLine(summary) {
|
|
189
|
+
const state = summary.attention ? "[attention]" : summary.live ? "[running here]" : summary.active ? "[active elsewhere]" : summary.archived ? "[archived]" : "[idle]";
|
|
190
|
+
const parts = [summary.id, state];
|
|
191
|
+
if (!summary.decryptable) {
|
|
192
|
+
parts.push("(not decryptable from this machine)");
|
|
193
|
+
} else {
|
|
194
|
+
if (summary.title) parts.push(`title="${summary.title}"`);
|
|
195
|
+
if (summary.flavor) parts.push(`agent=${summary.flavor}`);
|
|
196
|
+
if (summary.tags?.length) parts.push(`tags=${summary.tags.join(",")}`);
|
|
197
|
+
if (summary.variant) parts.push(`variant=${summary.variant}`);
|
|
198
|
+
if (summary.machineId) parts.push(`machine=${summary.machineId}`);
|
|
199
|
+
if (summary.cwd) parts.push(`cwd=${summary.cwd}`);
|
|
200
|
+
for (const request of summary.pending ?? []) {
|
|
201
|
+
parts.push(`pending=${request.id}:${request.tool}${request.waitingMs !== void 0 ? `(${formatWait(request.waitingMs)})` : ""}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
parts.push(summary.url);
|
|
205
|
+
return parts.join(" ");
|
|
206
|
+
}
|
|
207
|
+
function permissionFailureRecord(sessionId, requestId, error) {
|
|
208
|
+
return { sessionId, requestId, error: error instanceof Error ? error.message : String(error) };
|
|
209
|
+
}
|
|
210
|
+
function formatSummaryLine(summary) {
|
|
211
|
+
const parts = [summary.id, summary.live ? "[running]" : "[not running]"];
|
|
212
|
+
if (summary.title) parts.push(`title="${summary.title}"`);
|
|
213
|
+
if (summary.flavor) parts.push(`agent=${summary.flavor}`);
|
|
214
|
+
if (summary.tags?.length) parts.push(`tags=${summary.tags.join(",")}`);
|
|
215
|
+
if (summary.variant) parts.push(`variant=${summary.variant}`);
|
|
216
|
+
if (summary.cwd) parts.push(`cwd=${summary.cwd}`);
|
|
217
|
+
if (summary.pid !== void 0) parts.push(`pid=${summary.pid}`);
|
|
218
|
+
parts.push(summary.url);
|
|
219
|
+
return parts.join(" ");
|
|
220
|
+
}
|
|
221
|
+
async function handleSessionsCommand(args) {
|
|
222
|
+
let options;
|
|
223
|
+
try {
|
|
224
|
+
options = parseSessionsArgs(args);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
console.error(chalk.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
227
|
+
console.error(`Run ${chalk.cyan("very-happy sessions --help")} for usage.`);
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
if (options.action === "help") {
|
|
231
|
+
console.log(SESSIONS_HELP);
|
|
232
|
+
process.exit(0);
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
if (options.action === "list" && options.all) {
|
|
236
|
+
const sessions = await listAccountSessions({ tag: options.tag, recentLimit: options.limit, includeArchived: options.includeArchived });
|
|
237
|
+
if (options.json) {
|
|
238
|
+
console.log(JSON.stringify({ sessions, scope: "account" }));
|
|
239
|
+
} else if (sessions.length === 0) {
|
|
240
|
+
console.log(options.tag ? `No decryptable sessions tagged "${options.tag}" on this account.` : "No sessions found on this account.");
|
|
241
|
+
} else {
|
|
242
|
+
for (const summary of sessions) console.log(formatAccountSummaryLine(summary));
|
|
243
|
+
const foreign = sessions.filter((summary) => !summary.decryptable).length;
|
|
244
|
+
if (foreign > 0) {
|
|
245
|
+
console.log(chalk.dim(`${foreign} session(s) belong to another machine and cannot be decrypted here (no local key).`));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
process.exit(0);
|
|
249
|
+
}
|
|
250
|
+
if (options.action === "list") {
|
|
251
|
+
const sessions = await listSessions({ tag: options.tag, recentLimit: options.limit });
|
|
252
|
+
if (options.json) {
|
|
253
|
+
console.log(JSON.stringify({ sessions }));
|
|
254
|
+
} else if (sessions.length === 0) {
|
|
255
|
+
console.log(options.tag ? `No sessions tagged "${options.tag}" on this machine.` : "No sessions found on this machine.");
|
|
256
|
+
} else {
|
|
257
|
+
for (const summary of sessions) console.log(formatSummaryLine(summary));
|
|
258
|
+
}
|
|
259
|
+
process.exit(0);
|
|
260
|
+
}
|
|
261
|
+
const sessionId = options.sessionId;
|
|
262
|
+
if (options.action === "read") {
|
|
263
|
+
const result = await readSessionTranscript(sessionId, options.limit ?? 20);
|
|
264
|
+
if (options.json) {
|
|
265
|
+
console.log(JSON.stringify(result));
|
|
266
|
+
} else {
|
|
267
|
+
console.log(formatSummaryLine(result.summary));
|
|
268
|
+
console.log(`--- last ${result.messageCount} message(s) ---`);
|
|
269
|
+
console.log(result.transcript.length > 0 ? result.transcript : "(no readable conversation content in this range)");
|
|
270
|
+
}
|
|
271
|
+
process.exit(0);
|
|
272
|
+
}
|
|
273
|
+
if (options.action === "stop") {
|
|
274
|
+
const stopped = await stopSession(sessionId);
|
|
275
|
+
if (!stopped) {
|
|
276
|
+
if (options.json) console.log(JSON.stringify({ sessionId, stopped: false }));
|
|
277
|
+
console.error(chalk.red("Error:"), `Session ${sessionId} is not among the daemon's running sessions.`);
|
|
278
|
+
process.exit(1);
|
|
279
|
+
}
|
|
280
|
+
if (options.json) console.log(JSON.stringify({ sessionId, stopped: true }));
|
|
281
|
+
else console.log(`Stopped ${sessionId}`);
|
|
282
|
+
process.exit(0);
|
|
283
|
+
}
|
|
284
|
+
if (options.action === "approve" || options.action === "deny") {
|
|
285
|
+
const requestId = options.requestId;
|
|
286
|
+
const verdict = options.action === "approve" ? { kind: "approve", forSession: options.forSession } : { kind: "deny", reason: options.reason };
|
|
287
|
+
let result;
|
|
288
|
+
try {
|
|
289
|
+
result = await resolvePermissionRequest(sessionId, requestId, verdict);
|
|
290
|
+
} catch (error) {
|
|
291
|
+
if (options.json) console.log(JSON.stringify(permissionFailureRecord(sessionId, requestId, error)));
|
|
292
|
+
throw error;
|
|
293
|
+
}
|
|
294
|
+
if (result.outcome.status !== "acknowledged") {
|
|
295
|
+
if (options.json) console.log(JSON.stringify(result));
|
|
296
|
+
console.error(chalk.red("Error:"), `${options.action} of ${requestId} on ${sessionId} was not delivered (${result.outcome.status}): ${result.outcome.message}`);
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
if (options.json) {
|
|
300
|
+
console.log(JSON.stringify(result));
|
|
301
|
+
} else {
|
|
302
|
+
const verb = options.action === "approve" ? "Approved" : "Denied";
|
|
303
|
+
console.log(`${verb} ${requestId} on ${sessionId} (${result.payload.decision})${result.settled ? "" : " \u2014 wrapper acknowledged, but the request was still listed as pending when we stopped waiting"}`);
|
|
304
|
+
}
|
|
305
|
+
process.exit(0);
|
|
306
|
+
}
|
|
307
|
+
await archiveSession(sessionId);
|
|
308
|
+
if (options.json) console.log(JSON.stringify({ sessionId, archived: true }));
|
|
309
|
+
else console.log(`Archived ${sessionId}`);
|
|
310
|
+
process.exit(0);
|
|
311
|
+
} catch (error) {
|
|
312
|
+
console.error(chalk.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export { SESSIONS_HELP, handleSessionsCommand, parseSessionsArgs, permissionFailureRecord };
|