very-happy-cli 0.2.102 → 0.2.103
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-C1nF06ZR.mjs → AcpBackend-C8CgzRI_.mjs} +1 -1
- package/dist/{AcpBackend-CpKkdwIL.cjs → AcpBackend-Zt10QM8S.cjs} +1 -1
- package/dist/{AcpSessionManager-BDad8lK3.mjs → AcpSessionManager-BAinHcHh.mjs} +1 -1
- package/dist/{AcpSessionManager-DjWIwGTR.cjs → AcpSessionManager-D_iGrEbD.cjs} +1 -1
- package/dist/{config-BlTfR0KA.mjs → config-11py0X-q.mjs} +2 -2
- package/dist/{config-DSF29tTS.cjs → config-CG4aqDiu.cjs} +2 -2
- package/dist/{index-DjYlt3fw.mjs → index-B9SnF1mG.mjs} +2 -2
- package/dist/{index-yLIv8JPD.cjs → index-B_soRPsA.cjs} +4 -4
- package/dist/{index-CPC-a5lH.mjs → index-BjDfS9DT.mjs} +5 -5
- package/dist/{index-D_2eCeep.cjs → index-CimI2p1M.cjs} +26 -20
- package/dist/{index-CF3rNFlZ.mjs → index-Dnma0J5k.mjs} +22 -16
- package/dist/{index-C8HuYyZq.cjs → index-VvPyRTet.cjs} +2 -2
- package/dist/index.cjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{installTerminalHooks-BVMhXRZO.cjs → installTerminalHooks-BP9uu1Xn.cjs} +2 -2
- package/dist/{installTerminalHooks-DAR3YEo3.mjs → installTerminalHooks-UL5Zfif6.mjs} +2 -2
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.cts +17 -0
- package/dist/lib.d.mts +17 -0
- package/dist/lib.mjs +1 -1
- package/dist/{mcp--I-Uz_wH.mjs → mcp-CxepBrGm.mjs} +2 -2
- package/dist/{mcp-LCZtdhF0.cjs → mcp-mX6g7PpH.cjs} +2 -2
- package/dist/{runGemini-Crna_6uM.cjs → runGemini-BPAUC7TY.cjs} +4 -4
- package/dist/{runGemini-cLR7rfj6.mjs → runGemini-Cxa0Tj_7.mjs} +4 -4
- package/dist/{runOpenClaw-C1u9xEH7.mjs → runOpenClaw-DMAn3-Hq.mjs} +3 -3
- package/dist/{runOpenClaw-CWjOCdWg.cjs → runOpenClaw-QJ8YzPvo.cjs} +3 -3
- package/dist/{send-CufsYMXq.cjs → send-CwRV7cbV.cjs} +2 -2
- package/dist/{send-BtQ9qf_z.mjs → send-D6NLQaYt.mjs} +2 -2
- package/dist/{spawn-rdb0uM9R.mjs → spawn-DIW1I72G.mjs} +2 -2
- package/dist/{spawn-DPllkoUN.cjs → spawn-Db22DkVT.cjs} +2 -2
- package/dist/{types-B3Dnzv_q.mjs → types-D9ORBxOD.mjs} +326 -105
- package/dist/{types-7-mfp-j5.cjs → types-DKO60j29.cjs} +328 -105
- package/package.json +7 -7
|
@@ -51,7 +51,7 @@ var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
|
|
|
51
51
|
var pty__namespace = /*#__PURE__*/_interopNamespaceDefault(pty);
|
|
52
52
|
|
|
53
53
|
var name = "very-happy-cli";
|
|
54
|
-
var version = "0.2.
|
|
54
|
+
var version = "0.2.103";
|
|
55
55
|
var description = "Remote multi-agent workspace CLI with deep Claude integration, native terminal streaming, and self-hosting.";
|
|
56
56
|
var author = "Kirill Dubovitskiy";
|
|
57
57
|
var contributors = [
|
|
@@ -164,12 +164,12 @@ var dependencies = {
|
|
|
164
164
|
zod: "^4.0.0"
|
|
165
165
|
};
|
|
166
166
|
var optionalDependencies = {
|
|
167
|
-
"very-happy-tools-arm64-darwin": "0.2.
|
|
168
|
-
"very-happy-tools-x64-darwin": "0.2.
|
|
169
|
-
"very-happy-tools-arm64-linux": "0.2.
|
|
170
|
-
"very-happy-tools-x64-linux": "0.2.
|
|
171
|
-
"very-happy-tools-arm64-win32": "0.2.
|
|
172
|
-
"very-happy-tools-x64-win32": "0.2.
|
|
167
|
+
"very-happy-tools-arm64-darwin": "0.2.103",
|
|
168
|
+
"very-happy-tools-x64-darwin": "0.2.103",
|
|
169
|
+
"very-happy-tools-arm64-linux": "0.2.103",
|
|
170
|
+
"very-happy-tools-x64-linux": "0.2.103",
|
|
171
|
+
"very-happy-tools-arm64-win32": "0.2.103",
|
|
172
|
+
"very-happy-tools-x64-win32": "0.2.103"
|
|
173
173
|
};
|
|
174
174
|
var devDependencies = {
|
|
175
175
|
"@slopus/happy-wire": "workspace:*",
|
|
@@ -1127,6 +1127,15 @@ z.z.object({
|
|
|
1127
1127
|
/** B-282: `kill-terminal` honours `alsoAttached`. */
|
|
1128
1128
|
killAttached: z.z.boolean().optional()
|
|
1129
1129
|
}).optional(),
|
|
1130
|
+
/**
|
|
1131
|
+
* B-290 capability flag: this daemon answers `claude-list-history` (import a
|
|
1132
|
+
* Claude Code conversation that was never started through very-happy).
|
|
1133
|
+
* Same trust rule (`detectedAt >= startedAt`).
|
|
1134
|
+
*/
|
|
1135
|
+
claudeHistory: z.z.object({
|
|
1136
|
+
rpcAvailable: z.z.boolean(),
|
|
1137
|
+
detectedAt: z.z.number()
|
|
1138
|
+
}).optional(),
|
|
1130
1139
|
/** Relay-owned CLI compatibility/update policy last checked by this daemon. */
|
|
1131
1140
|
cliUpdate: CliUpdateStateSchema.optional(),
|
|
1132
1141
|
/**
|
|
@@ -1585,7 +1594,7 @@ class RpcHandlerManager {
|
|
|
1585
1594
|
}
|
|
1586
1595
|
}
|
|
1587
1596
|
|
|
1588
|
-
const __dirname$1 = path$1.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-
|
|
1597
|
+
const __dirname$1 = path$1.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-DKO60j29.cjs', document.baseURI).href))));
|
|
1589
1598
|
function projectPath() {
|
|
1590
1599
|
const path = path$1.resolve(__dirname$1, "..");
|
|
1591
1600
|
return path;
|
|
@@ -2116,102 +2125,66 @@ function registerCommonHandlers(rpcHandlerManager, workingDirectory) {
|
|
|
2116
2125
|
}
|
|
2117
2126
|
|
|
2118
2127
|
const PRICING = {
|
|
2119
|
-
// --- Claude
|
|
2120
|
-
"claude-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
},
|
|
2126
|
-
"claude-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
},
|
|
2132
|
-
"claude-4-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
},
|
|
2138
|
-
"claude-4.5-sonnet": {
|
|
2139
|
-
input: 3,
|
|
2140
|
-
output: 15,
|
|
2141
|
-
cache_write: 3.75,
|
|
2142
|
-
cache_read: 0.3
|
|
2143
|
-
},
|
|
2144
|
-
"claude-4-sonnet": {
|
|
2145
|
-
input: 3,
|
|
2146
|
-
output: 15,
|
|
2147
|
-
cache_write: 3.75,
|
|
2148
|
-
cache_read: 0.3
|
|
2149
|
-
},
|
|
2150
|
-
"claude-4.5-haiku": {
|
|
2151
|
-
input: 1,
|
|
2152
|
-
output: 5,
|
|
2153
|
-
cache_write: 1.25,
|
|
2154
|
-
cache_read: 0.1
|
|
2155
|
-
},
|
|
2128
|
+
// --- Claude Fable / Mythos (same tier, same price) ---
|
|
2129
|
+
"claude-fable-5-1": { input: 10, output: 50, cache_write: 12.5, cache_read: 0.25 },
|
|
2130
|
+
"claude-fable-5": { input: 10, output: 50, cache_write: 12.5, cache_read: 1 },
|
|
2131
|
+
"claude-mythos-5-1": { input: 10, output: 50, cache_write: 12.5, cache_read: 0.25 },
|
|
2132
|
+
"claude-mythos-5": { input: 10, output: 50, cache_write: 12.5, cache_read: 1 },
|
|
2133
|
+
// --- Claude 5 ---
|
|
2134
|
+
"claude-opus-5": { input: 5, output: 25, cache_write: 6.25, cache_read: 0.5 },
|
|
2135
|
+
"claude-sonnet-5": { input: 2, output: 10, cache_write: 2.5, cache_read: 0.2 },
|
|
2136
|
+
// --- Claude 4.x ---
|
|
2137
|
+
"claude-opus-4-8": { input: 5, output: 25, cache_write: 6.25, cache_read: 0.5 },
|
|
2138
|
+
"claude-opus-4-7": { input: 5, output: 25, cache_write: 6.25, cache_read: 0.5 },
|
|
2139
|
+
"claude-opus-4-6": { input: 5, output: 25, cache_write: 6.25, cache_read: 0.5 },
|
|
2140
|
+
"claude-opus-4-5": { input: 5, output: 25, cache_write: 6.25, cache_read: 0.5 },
|
|
2141
|
+
"claude-opus-4-1": { input: 15, output: 75, cache_write: 18.75, cache_read: 1.5 },
|
|
2142
|
+
"claude-opus-4": { input: 15, output: 75, cache_write: 18.75, cache_read: 1.5 },
|
|
2143
|
+
"claude-sonnet-4-6": { input: 3, output: 15, cache_write: 3.75, cache_read: 0.3 },
|
|
2144
|
+
"claude-sonnet-4-5": { input: 3, output: 15, cache_write: 3.75, cache_read: 0.3 },
|
|
2145
|
+
"claude-sonnet-4": { input: 3, output: 15, cache_write: 3.75, cache_read: 0.3 },
|
|
2146
|
+
"claude-haiku-4-5": { input: 1, output: 5, cache_write: 1.25, cache_read: 0.1 },
|
|
2156
2147
|
// --- Legacy / Claude 3 ---
|
|
2157
|
-
"claude-3-opus-20240229": {
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
},
|
|
2163
|
-
"claude-3-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
"claude-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
cache_read: 0.3
|
|
2174
|
-
},
|
|
2175
|
-
// New Sonnet 3.5 updated model
|
|
2176
|
-
"claude-3-5-sonnet-20241022": {
|
|
2177
|
-
input: 3,
|
|
2178
|
-
output: 15,
|
|
2179
|
-
cache_write: 3.75,
|
|
2180
|
-
cache_read: 0.3
|
|
2181
|
-
},
|
|
2182
|
-
"claude-3-haiku-20240307": {
|
|
2183
|
-
input: 0.25,
|
|
2184
|
-
output: 1.25,
|
|
2185
|
-
cache_write: 0.3125,
|
|
2186
|
-
cache_read: 0.025
|
|
2187
|
-
},
|
|
2188
|
-
"claude-3-5-haiku-20241022": {
|
|
2189
|
-
input: 0.8,
|
|
2190
|
-
output: 4,
|
|
2191
|
-
cache_write: 1,
|
|
2192
|
-
// Approx based on 1.25x rule usually or custom
|
|
2193
|
-
cache_read: 0.08
|
|
2194
|
-
}
|
|
2148
|
+
"claude-3-opus-20240229": { input: 15, output: 75, cache_write: 18.75, cache_read: 1.5 },
|
|
2149
|
+
"claude-3-sonnet-20240229": { input: 3, output: 15, cache_write: 3.75, cache_read: 0.3 },
|
|
2150
|
+
"claude-3-5-sonnet-20240620": { input: 3, output: 15, cache_write: 3.75, cache_read: 0.3 },
|
|
2151
|
+
"claude-3-5-sonnet-20241022": { input: 3, output: 15, cache_write: 3.75, cache_read: 0.3 },
|
|
2152
|
+
"claude-3-7-sonnet-20250219": { input: 3, output: 15, cache_write: 3.75, cache_read: 0.3 },
|
|
2153
|
+
"claude-3-haiku-20240307": { input: 0.25, output: 1.25, cache_write: 0.3125, cache_read: 0.025 },
|
|
2154
|
+
"claude-3-5-haiku-20241022": { input: 0.8, output: 4, cache_write: 1, cache_read: 0.08 }
|
|
2155
|
+
};
|
|
2156
|
+
const DEFAULT_MODEL = "claude-opus-5";
|
|
2157
|
+
const FAMILY_DEFAULTS = {
|
|
2158
|
+
fable: "claude-fable-5-1",
|
|
2159
|
+
// Project Glasswing counterpart of Fable: same tier, same published rates.
|
|
2160
|
+
mythos: "claude-mythos-5-1",
|
|
2161
|
+
opus: "claude-opus-5",
|
|
2162
|
+
sonnet: "claude-sonnet-5",
|
|
2163
|
+
haiku: "claude-haiku-4-5"
|
|
2195
2164
|
};
|
|
2196
|
-
|
|
2165
|
+
function resolvePricingKey(modelId) {
|
|
2166
|
+
if (!modelId) return DEFAULT_MODEL;
|
|
2167
|
+
const raw = modelId.trim().toLowerCase();
|
|
2168
|
+
if (raw in PRICING) return raw;
|
|
2169
|
+
const id = raw.replace(/\[1m\]$/, "");
|
|
2170
|
+
if (id in PRICING) return id;
|
|
2171
|
+
const family = ["fable", "mythos", "opus", "sonnet", "haiku"].find((f) => id.includes(f));
|
|
2172
|
+
if (!family) return DEFAULT_MODEL;
|
|
2173
|
+
const version = id.match(/(?:^|[^0-9])(\d)(?:[-._](\d))?(?:[^0-9]|$)/);
|
|
2174
|
+
const major = version ? version[1] : null;
|
|
2175
|
+
const minor = version && version[2] ? version[2] : null;
|
|
2176
|
+
const key = major ? `claude-${family}-${major}${minor ? `-${minor}` : ""}` : null;
|
|
2177
|
+
if (key && key in PRICING) return key;
|
|
2178
|
+
if (major === "3" && family !== "fable" && family !== "mythos") {
|
|
2179
|
+
if (family === "opus") return "claude-3-opus-20240229";
|
|
2180
|
+
if (family === "haiku") return minor === "5" ? "claude-3-5-haiku-20241022" : "claude-3-haiku-20240307";
|
|
2181
|
+
if (minor === "7") return "claude-3-7-sonnet-20250219";
|
|
2182
|
+
return minor === "5" ? "claude-3-5-sonnet-20241022" : "claude-3-sonnet-20240229";
|
|
2183
|
+
}
|
|
2184
|
+
return FAMILY_DEFAULTS[family];
|
|
2185
|
+
}
|
|
2197
2186
|
function calculateCost(usage, modelId) {
|
|
2198
|
-
|
|
2199
|
-
if (!pricing) {
|
|
2200
|
-
if (modelId?.includes("opus")) {
|
|
2201
|
-
if (modelId.includes("4.5")) pricing = PRICING["claude-4.5-opus"];
|
|
2202
|
-
else if (modelId.includes("4.1")) pricing = PRICING["claude-4.1-opus"];
|
|
2203
|
-
else if (modelId.includes("4")) pricing = PRICING["claude-4-opus"];
|
|
2204
|
-
else pricing = PRICING["claude-3-opus-20240229"];
|
|
2205
|
-
} else if (modelId?.includes("sonnet")) {
|
|
2206
|
-
if (modelId.includes("4.5")) pricing = PRICING["claude-4.5-sonnet"];
|
|
2207
|
-
else if (modelId.includes("4")) pricing = PRICING["claude-4-sonnet"];
|
|
2208
|
-
else pricing = PRICING["claude-3-5-sonnet-20241022"];
|
|
2209
|
-
} else if (modelId?.includes("haiku")) {
|
|
2210
|
-
if (modelId.includes("4.5")) pricing = PRICING["claude-4.5-haiku"];
|
|
2211
|
-
else if (modelId.includes("3.5")) pricing = PRICING["claude-3-5-haiku-20241022"];
|
|
2212
|
-
else pricing = PRICING["claude-3-haiku-20240307"];
|
|
2213
|
-
} else pricing = PRICING[DEFAULT_MODEL];
|
|
2214
|
-
}
|
|
2187
|
+
const pricing = PRICING[resolvePricingKey(modelId)];
|
|
2215
2188
|
const inputCost = usage.input_tokens / 1e6 * pricing.input;
|
|
2216
2189
|
const outputCost = usage.output_tokens / 1e6 * pricing.output;
|
|
2217
2190
|
const cacheWriteCost = (usage.cache_creation_input_tokens || 0) / 1e6 * pricing.cache_write;
|
|
@@ -4056,6 +4029,7 @@ function summarizeSpawnSessionForLog(value) {
|
|
|
4056
4029
|
environmentVariableNames: Object.keys(environmentVariables).sort(),
|
|
4057
4030
|
resumesClaude: typeof input.resumeClaudeSessionId === "string" && input.resumeClaudeSessionId.length > 0,
|
|
4058
4031
|
resumesCodex: typeof input.resumeCodexThreadId === "string" && input.resumeCodexThreadId.length > 0,
|
|
4032
|
+
importsClaude: typeof input.importedFromClaudeSessionId === "string" && input.importedFromClaudeSessionId.length > 0,
|
|
4059
4033
|
forceNew: input.forceNew === true,
|
|
4060
4034
|
...typeof input.permissionMode === "string" ? { permissionMode: input.permissionMode } : {}
|
|
4061
4035
|
};
|
|
@@ -5005,10 +4979,15 @@ function scrubTmuxClientEnv(env) {
|
|
|
5005
4979
|
return env;
|
|
5006
4980
|
}
|
|
5007
4981
|
|
|
4982
|
+
function getClaudeConfigDir() {
|
|
4983
|
+
return process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), ".claude");
|
|
4984
|
+
}
|
|
4985
|
+
function getClaudeProjectsRoot() {
|
|
4986
|
+
return path.join(getClaudeConfigDir(), "projects");
|
|
4987
|
+
}
|
|
5008
4988
|
function getProjectPath(workingDirectory) {
|
|
5009
4989
|
const projectId = path.resolve(workingDirectory).replace(/[^a-zA-Z0-9-]/g, "-");
|
|
5010
|
-
|
|
5011
|
-
return path.join(claudeConfigDir, "projects", projectId);
|
|
4990
|
+
return path.join(getClaudeProjectsRoot(), projectId);
|
|
5012
4991
|
}
|
|
5013
4992
|
|
|
5014
4993
|
var xtermHeadless = {};
|
|
@@ -12591,6 +12570,182 @@ function detectResumeSupport(happyHomeDir = configuration.happyHomeDir) {
|
|
|
12591
12570
|
};
|
|
12592
12571
|
}
|
|
12593
12572
|
|
|
12573
|
+
const TITLE_PROMPT_PREFIX = "Summarize the SPECIFIC topic/request of this message into a 3-6 word title";
|
|
12574
|
+
const BOARD_ANALYZER_PROMPT_PREFIX = "You are a status analyzer for a task board of coding-agent sessions.";
|
|
12575
|
+
const VERY_HAPPY_ONE_SHOT_PROMPT_PREFIXES = [
|
|
12576
|
+
TITLE_PROMPT_PREFIX,
|
|
12577
|
+
BOARD_ANALYZER_PROMPT_PREFIX
|
|
12578
|
+
];
|
|
12579
|
+
function isVeryHappyOneShotPrompt(firstPrompt) {
|
|
12580
|
+
const text = firstPrompt.trimStart();
|
|
12581
|
+
return VERY_HAPPY_ONE_SHOT_PROMPT_PREFIXES.some((prefix) => text.startsWith(prefix));
|
|
12582
|
+
}
|
|
12583
|
+
|
|
12584
|
+
const UUID_RE$1 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
12585
|
+
async function listClaudeProjectDirs(projectsRoot) {
|
|
12586
|
+
let names;
|
|
12587
|
+
try {
|
|
12588
|
+
names = await promises.readdir(projectsRoot);
|
|
12589
|
+
} catch (error) {
|
|
12590
|
+
if (error.code === "ENOENT") return [];
|
|
12591
|
+
throw error;
|
|
12592
|
+
}
|
|
12593
|
+
const dirs = [];
|
|
12594
|
+
for (const name of names) {
|
|
12595
|
+
const full = path.join(projectsRoot, name);
|
|
12596
|
+
try {
|
|
12597
|
+
if ((await promises.stat(full)).isDirectory()) dirs.push(full);
|
|
12598
|
+
} catch {
|
|
12599
|
+
}
|
|
12600
|
+
}
|
|
12601
|
+
return dirs;
|
|
12602
|
+
}
|
|
12603
|
+
async function collectCandidates(projectDirs, exclude) {
|
|
12604
|
+
const out = [];
|
|
12605
|
+
for (const dir of projectDirs) {
|
|
12606
|
+
let names;
|
|
12607
|
+
try {
|
|
12608
|
+
names = await promises.readdir(dir);
|
|
12609
|
+
} catch {
|
|
12610
|
+
continue;
|
|
12611
|
+
}
|
|
12612
|
+
for (const name of names) {
|
|
12613
|
+
if (!name.endsWith(".jsonl")) continue;
|
|
12614
|
+
const id = name.slice(0, -".jsonl".length);
|
|
12615
|
+
if (!UUID_RE$1.test(id) || exclude.has(id.toLowerCase())) continue;
|
|
12616
|
+
const path$1 = path.join(dir, name);
|
|
12617
|
+
try {
|
|
12618
|
+
const s = await promises.stat(path$1);
|
|
12619
|
+
if (!s.isFile() || s.size === 0) continue;
|
|
12620
|
+
out.push({ path: path$1, id, updatedAt: s.mtimeMs, sizeBytes: s.size });
|
|
12621
|
+
} catch {
|
|
12622
|
+
}
|
|
12623
|
+
}
|
|
12624
|
+
}
|
|
12625
|
+
out.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
12626
|
+
const seen = /* @__PURE__ */ new Set();
|
|
12627
|
+
return out.filter((c) => {
|
|
12628
|
+
const key = c.id.toLowerCase();
|
|
12629
|
+
if (seen.has(key)) return false;
|
|
12630
|
+
seen.add(key);
|
|
12631
|
+
return true;
|
|
12632
|
+
});
|
|
12633
|
+
}
|
|
12634
|
+
async function readHead(path, bytes) {
|
|
12635
|
+
const fh = await promises.open(path, "r");
|
|
12636
|
+
try {
|
|
12637
|
+
const buffer = Buffer.alloc(bytes);
|
|
12638
|
+
const { bytesRead } = await fh.read(buffer, 0, bytes, 0);
|
|
12639
|
+
return buffer.subarray(0, bytesRead).toString("utf-8");
|
|
12640
|
+
} finally {
|
|
12641
|
+
await fh.close();
|
|
12642
|
+
}
|
|
12643
|
+
}
|
|
12644
|
+
function oneLine(text, max) {
|
|
12645
|
+
const collapsed = text.replace(/\s+/g, " ").trim();
|
|
12646
|
+
return collapsed.length > max ? `${collapsed.slice(0, max - 1)}\u2026` : collapsed;
|
|
12647
|
+
}
|
|
12648
|
+
function parseTimestamp(raw) {
|
|
12649
|
+
if (typeof raw === "number" && Number.isFinite(raw)) return raw;
|
|
12650
|
+
if (typeof raw === "string") {
|
|
12651
|
+
const t = Date.parse(raw);
|
|
12652
|
+
if (Number.isFinite(t)) return t;
|
|
12653
|
+
}
|
|
12654
|
+
return null;
|
|
12655
|
+
}
|
|
12656
|
+
function parseClaudeHistoryHead(head, promptChars) {
|
|
12657
|
+
let cwd;
|
|
12658
|
+
let firstPrompt;
|
|
12659
|
+
let summary;
|
|
12660
|
+
let startedAt = null;
|
|
12661
|
+
let entrypoint;
|
|
12662
|
+
let gitBranch;
|
|
12663
|
+
let version;
|
|
12664
|
+
const lines = head.split("\n");
|
|
12665
|
+
for (const line of lines) {
|
|
12666
|
+
if (line.length === 0) continue;
|
|
12667
|
+
let parsed;
|
|
12668
|
+
try {
|
|
12669
|
+
parsed = JSON.parse(line);
|
|
12670
|
+
} catch {
|
|
12671
|
+
continue;
|
|
12672
|
+
}
|
|
12673
|
+
if (!parsed || typeof parsed !== "object") continue;
|
|
12674
|
+
if (parsed.type === "summary" && typeof parsed.summary === "string" && !summary) {
|
|
12675
|
+
const s = oneLine(parsed.summary, promptChars);
|
|
12676
|
+
if (s) summary = s;
|
|
12677
|
+
continue;
|
|
12678
|
+
}
|
|
12679
|
+
if (typeof parsed.cwd === "string" && parsed.cwd && !cwd) cwd = parsed.cwd;
|
|
12680
|
+
if (typeof parsed.entrypoint === "string" && parsed.entrypoint && !entrypoint) entrypoint = parsed.entrypoint;
|
|
12681
|
+
if (typeof parsed.gitBranch === "string" && parsed.gitBranch && !gitBranch) gitBranch = parsed.gitBranch;
|
|
12682
|
+
if (typeof parsed.version === "string" && parsed.version && !version) version = parsed.version;
|
|
12683
|
+
if (startedAt === null) startedAt = parseTimestamp(parsed.timestamp);
|
|
12684
|
+
if (!firstPrompt && parsed.type === "user" && !parsed.isSidechain) {
|
|
12685
|
+
const content = parsed.message?.content;
|
|
12686
|
+
let text = null;
|
|
12687
|
+
if (typeof content === "string") {
|
|
12688
|
+
text = content;
|
|
12689
|
+
} else if (Array.isArray(content)) {
|
|
12690
|
+
const block = content.find((b) => b && b.type === "text" && typeof b.text === "string");
|
|
12691
|
+
if (block) text = block.text;
|
|
12692
|
+
}
|
|
12693
|
+
if (text) {
|
|
12694
|
+
if (isVeryHappyOneShotPrompt(text)) return null;
|
|
12695
|
+
const cleaned = oneLine(stripHarnessTags(text), promptChars);
|
|
12696
|
+
if (cleaned) firstPrompt = cleaned;
|
|
12697
|
+
}
|
|
12698
|
+
}
|
|
12699
|
+
if (cwd && firstPrompt && summary !== void 0 && startedAt !== null) break;
|
|
12700
|
+
}
|
|
12701
|
+
if (!cwd) return null;
|
|
12702
|
+
if (!firstPrompt && !summary) return null;
|
|
12703
|
+
return {
|
|
12704
|
+
cwd,
|
|
12705
|
+
firstPrompt: firstPrompt ?? summary ?? "",
|
|
12706
|
+
...summary ? { summary } : {},
|
|
12707
|
+
startedAt: startedAt ?? 0,
|
|
12708
|
+
...entrypoint ? { entrypoint } : {},
|
|
12709
|
+
...gitBranch ? { gitBranch } : {},
|
|
12710
|
+
...version ? { version } : {}
|
|
12711
|
+
};
|
|
12712
|
+
}
|
|
12713
|
+
function stripHarnessTags(text) {
|
|
12714
|
+
return text.replace(/<(system-reminder|task-notification|command-message|command-name|command-args|local-command-stdout|local-command-caveat)>[\s\S]*?<\/\1>/g, " ").replace(/<\/?(system-reminder|task-notification)>/g, " ");
|
|
12715
|
+
}
|
|
12716
|
+
async function listClaudeSessionHistory(options) {
|
|
12717
|
+
const limit = Math.max(1, Math.min(options.limit ?? 60, 500));
|
|
12718
|
+
const headBytes = Math.max(4096, options.headBytes ?? 64 * 1024);
|
|
12719
|
+
const promptChars = Math.max(20, options.promptChars ?? 200);
|
|
12720
|
+
const exclude = new Set(Array.from(options.exclude ?? [], (id) => id.toLowerCase()));
|
|
12721
|
+
const candidates = await collectCandidates(options.projectDirs, exclude);
|
|
12722
|
+
const entries = [];
|
|
12723
|
+
let scanned = 0;
|
|
12724
|
+
let truncated = false;
|
|
12725
|
+
for (const candidate of candidates) {
|
|
12726
|
+
if (entries.length >= limit) {
|
|
12727
|
+
truncated = true;
|
|
12728
|
+
break;
|
|
12729
|
+
}
|
|
12730
|
+
scanned += 1;
|
|
12731
|
+
let head;
|
|
12732
|
+
try {
|
|
12733
|
+
head = await readHead(candidate.path, headBytes);
|
|
12734
|
+
} catch {
|
|
12735
|
+
continue;
|
|
12736
|
+
}
|
|
12737
|
+
const parsed = parseClaudeHistoryHead(head, promptChars);
|
|
12738
|
+
if (!parsed) continue;
|
|
12739
|
+
entries.push({
|
|
12740
|
+
claudeSessionId: candidate.id.toLowerCase(),
|
|
12741
|
+
updatedAt: Math.round(candidate.updatedAt),
|
|
12742
|
+
sizeBytes: candidate.sizeBytes,
|
|
12743
|
+
...parsed
|
|
12744
|
+
});
|
|
12745
|
+
}
|
|
12746
|
+
return { entries, truncated, scanned };
|
|
12747
|
+
}
|
|
12748
|
+
|
|
12594
12749
|
class ForkTruncateUuidNotFoundError extends Error {
|
|
12595
12750
|
constructor(cutAfterUuid, sourcePath) {
|
|
12596
12751
|
super(`Truncation UUID ${cutAfterUuid} not found in ${sourcePath}`);
|
|
@@ -12615,6 +12770,16 @@ function isUserPrompt(parsed) {
|
|
|
12615
12770
|
const content = parsed.message?.content;
|
|
12616
12771
|
return typeof content === "string";
|
|
12617
12772
|
}
|
|
12773
|
+
async function discardForkedSession(projectDir, claudeSessionId) {
|
|
12774
|
+
try {
|
|
12775
|
+
await promises.unlink(jsonlPath(projectDir, claudeSessionId));
|
|
12776
|
+
logger.debug(`[CLAUDE FORK] Discarded unused fork ${claudeSessionId}`);
|
|
12777
|
+
} catch (error) {
|
|
12778
|
+
if (error.code !== "ENOENT") {
|
|
12779
|
+
logger.debug(`[CLAUDE FORK] Failed to discard fork ${claudeSessionId}:`, error);
|
|
12780
|
+
}
|
|
12781
|
+
}
|
|
12782
|
+
}
|
|
12618
12783
|
async function forkSession(projectDir, sourceClaudeSessionId) {
|
|
12619
12784
|
const newId = node_crypto.randomUUID();
|
|
12620
12785
|
const src = jsonlPath(projectDir, sourceClaudeSessionId);
|
|
@@ -14211,12 +14376,12 @@ class ApiMachineClient {
|
|
|
14211
14376
|
this.stopSessionHandler = stopSession;
|
|
14212
14377
|
this.listTrackedSessionIds = listTrackedSessionIds ?? null;
|
|
14213
14378
|
this.rpcHandlerManager.registerHandler("spawn-happy-session", async (params) => {
|
|
14214
|
-
const { directory, sessionId, machineId, approvedNewDirectoryCreation, agent, environmentVariables, token, resumeClaudeSessionId, resumeCodexThreadId, parentSessionId, forkedFromMessageId, variant, forceNew, permissionMode } = params || {};
|
|
14379
|
+
const { directory, sessionId, machineId, approvedNewDirectoryCreation, agent, environmentVariables, token, resumeClaudeSessionId, resumeCodexThreadId, parentSessionId, forkedFromMessageId, importedFromClaudeSessionId, variant, forceNew, permissionMode } = params || {};
|
|
14215
14380
|
logger.debug("[API MACHINE] Spawning session:", summarizeSpawnSessionForLog(params));
|
|
14216
14381
|
if (!directory && variant !== "assistant") {
|
|
14217
14382
|
throw new Error("Directory is required");
|
|
14218
14383
|
}
|
|
14219
|
-
const result = await spawnSession({ directory: directory || "", sessionId, machineId, approvedNewDirectoryCreation, agent, environmentVariables, token, resumeClaudeSessionId, resumeCodexThreadId, parentSessionId, forkedFromMessageId, variant: variant === "assistant" ? "assistant" : void 0, forceNew: forceNew === true, permissionMode: typeof permissionMode === "string" ? permissionMode : void 0 });
|
|
14384
|
+
const result = await spawnSession({ directory: directory || "", sessionId, machineId, approvedNewDirectoryCreation, agent, environmentVariables, token, resumeClaudeSessionId, resumeCodexThreadId, parentSessionId, forkedFromMessageId, importedFromClaudeSessionId: typeof importedFromClaudeSessionId === "string" ? importedFromClaudeSessionId : void 0, variant: variant === "assistant" ? "assistant" : void 0, forceNew: forceNew === true, permissionMode: typeof permissionMode === "string" ? permissionMode : void 0 });
|
|
14220
14385
|
switch (result.type) {
|
|
14221
14386
|
case "success":
|
|
14222
14387
|
logger.debug(`[API MACHINE] Spawned session ${result.sessionId}`);
|
|
@@ -14228,6 +14393,46 @@ class ApiMachineClient {
|
|
|
14228
14393
|
throw new Error(result.errorMessage);
|
|
14229
14394
|
}
|
|
14230
14395
|
});
|
|
14396
|
+
this.rpcHandlerManager.registerHandler("claude-import-session", async (params) => {
|
|
14397
|
+
const { directory, claudeSessionId, approvedNewDirectoryCreation, permissionMode } = params || {};
|
|
14398
|
+
if (typeof directory !== "string" || directory.length === 0) {
|
|
14399
|
+
throw new Error("directory is required");
|
|
14400
|
+
}
|
|
14401
|
+
if (typeof claudeSessionId !== "string" || !UUID_RE.test(claudeSessionId)) {
|
|
14402
|
+
throw new Error("claudeSessionId must be a valid UUID");
|
|
14403
|
+
}
|
|
14404
|
+
const projectDir = getProjectPath(directory);
|
|
14405
|
+
let newClaudeSessionId;
|
|
14406
|
+
try {
|
|
14407
|
+
newClaudeSessionId = await forkSession(projectDir, claudeSessionId);
|
|
14408
|
+
} catch (error) {
|
|
14409
|
+
if (error instanceof ForkSourceMissingError) {
|
|
14410
|
+
throw new Error("Claude session file not found on this machine");
|
|
14411
|
+
}
|
|
14412
|
+
throw error;
|
|
14413
|
+
}
|
|
14414
|
+
let result;
|
|
14415
|
+
try {
|
|
14416
|
+
result = await spawnSession({
|
|
14417
|
+
directory,
|
|
14418
|
+
machineId: this.machine.id,
|
|
14419
|
+
approvedNewDirectoryCreation: approvedNewDirectoryCreation === true,
|
|
14420
|
+
agent: "claude",
|
|
14421
|
+
resumeClaudeSessionId: newClaudeSessionId,
|
|
14422
|
+
importedFromClaudeSessionId: claudeSessionId,
|
|
14423
|
+
permissionMode: typeof permissionMode === "string" ? permissionMode : void 0
|
|
14424
|
+
});
|
|
14425
|
+
} catch (error) {
|
|
14426
|
+
await discardForkedSession(projectDir, newClaudeSessionId);
|
|
14427
|
+
throw error;
|
|
14428
|
+
}
|
|
14429
|
+
if (result.type !== "success") {
|
|
14430
|
+
await discardForkedSession(projectDir, newClaudeSessionId);
|
|
14431
|
+
return result;
|
|
14432
|
+
}
|
|
14433
|
+
logger.debug(`[API MACHINE] Imported Claude conversation ${claudeSessionId} as session ${result.sessionId}`);
|
|
14434
|
+
return { type: "success", sessionId: result.sessionId, newClaudeSessionId };
|
|
14435
|
+
});
|
|
14231
14436
|
this.syncResumeSessionRpcRegistration();
|
|
14232
14437
|
this.syncRestartSessionRpcRegistration();
|
|
14233
14438
|
this.rpcHandlerManager.registerHandler("open-terminal", async (params) => {
|
|
@@ -14419,6 +14624,20 @@ class ApiMachineClient {
|
|
|
14419
14624
|
throw error;
|
|
14420
14625
|
}
|
|
14421
14626
|
});
|
|
14627
|
+
this.rpcHandlerManager.registerHandler("claude-list-history", async (params) => {
|
|
14628
|
+
const { directory, limit, exclude } = params || {};
|
|
14629
|
+
if (directory !== void 0 && (typeof directory !== "string" || directory.length === 0)) {
|
|
14630
|
+
throw new Error("directory must be a non-empty string when provided");
|
|
14631
|
+
}
|
|
14632
|
+
const projectDirs = typeof directory === "string" ? [getProjectPath(directory)] : await listClaudeProjectDirs(getClaudeProjectsRoot());
|
|
14633
|
+
const excludeIds = Array.isArray(exclude) ? exclude.filter((id) => typeof id === "string" && UUID_RE.test(id)) : [];
|
|
14634
|
+
const result = await listClaudeSessionHistory({
|
|
14635
|
+
projectDirs,
|
|
14636
|
+
limit: typeof limit === "number" && Number.isFinite(limit) ? Math.max(1, Math.min(Math.floor(limit), 200)) : 60,
|
|
14637
|
+
exclude: excludeIds
|
|
14638
|
+
});
|
|
14639
|
+
return { type: "success", ...result };
|
|
14640
|
+
});
|
|
14422
14641
|
this.rpcHandlerManager.registerHandler("codex-fork-thread", async (params) => {
|
|
14423
14642
|
const directory = requireNonEmptyString(params?.directory, "directory");
|
|
14424
14643
|
const codexThreadId = requireNonEmptyString(params?.codexThreadId, "codexThreadId");
|
|
@@ -14716,6 +14935,8 @@ class ApiMachineClient {
|
|
|
14716
14935
|
terminalRestore: { rpcAvailable: true, detectedAt: now },
|
|
14717
14936
|
// B-273 capability flag (same restamp discipline).
|
|
14718
14937
|
tmuxSessions: { rpcAvailable: true, detectedAt: now, killAttached: true },
|
|
14938
|
+
// B-290 capability flag: `claude-list-history` (same restamp discipline).
|
|
14939
|
+
claudeHistory: { rpcAvailable: true, detectedAt: now },
|
|
14719
14940
|
// B-084: closed records survive daemon restarts (persisted
|
|
14720
14941
|
// in closed-terminals.json), so the connect snapshot ships
|
|
14721
14942
|
// them too — not just the incremental pushes.
|
|
@@ -16060,6 +16281,7 @@ const RawJSONLinesSchema = z.z.discriminatedUnion("type", [
|
|
|
16060
16281
|
exports.ApiClient = ApiClient;
|
|
16061
16282
|
exports.ApiSessionClient = ApiSessionClient;
|
|
16062
16283
|
exports.AsyncLock = AsyncLock;
|
|
16284
|
+
exports.BOARD_ANALYZER_PROMPT_PREFIX = BOARD_ANALYZER_PROMPT_PREFIX;
|
|
16063
16285
|
exports.CodexAppServerClient = CodexAppServerClient;
|
|
16064
16286
|
exports.DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS = DEFAULT_CLI_UPDATE_CHECK_INTERVAL_MS;
|
|
16065
16287
|
exports.InvalidateSync = InvalidateSync;
|
|
@@ -16067,6 +16289,7 @@ exports.MAX_CHAT_ATTACHMENT_SOURCE_BYTES = MAX_CHAT_ATTACHMENT_SOURCE_BYTES;
|
|
|
16067
16289
|
exports.MIRROR_BACKFILL_LINES_DEFAULT = MIRROR_BACKFILL_LINES_DEFAULT;
|
|
16068
16290
|
exports.RawJSONLinesSchema = RawJSONLinesSchema;
|
|
16069
16291
|
exports.SandboxConfigSchema = SandboxConfigSchema;
|
|
16292
|
+
exports.TITLE_PROMPT_PREFIX = TITLE_PROMPT_PREFIX;
|
|
16070
16293
|
exports.acquireDaemonLock = acquireDaemonLock;
|
|
16071
16294
|
exports.api = api;
|
|
16072
16295
|
exports.clearCredentials = clearCredentials;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "very-happy-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.103",
|
|
4
4
|
"description": "Remote multi-agent workspace CLI with deep Claude integration, native terminal streaming, and self-hosting.",
|
|
5
5
|
"author": "Kirill Dubovitskiy",
|
|
6
6
|
"contributors": [
|
|
@@ -113,12 +113,12 @@
|
|
|
113
113
|
"zod": "^4.0.0"
|
|
114
114
|
},
|
|
115
115
|
"optionalDependencies": {
|
|
116
|
-
"very-happy-tools-arm64-darwin": "0.2.
|
|
117
|
-
"very-happy-tools-x64-darwin": "0.2.
|
|
118
|
-
"very-happy-tools-arm64-linux": "0.2.
|
|
119
|
-
"very-happy-tools-x64-linux": "0.2.
|
|
120
|
-
"very-happy-tools-arm64-win32": "0.2.
|
|
121
|
-
"very-happy-tools-x64-win32": "0.2.
|
|
116
|
+
"very-happy-tools-arm64-darwin": "0.2.103",
|
|
117
|
+
"very-happy-tools-x64-darwin": "0.2.103",
|
|
118
|
+
"very-happy-tools-arm64-linux": "0.2.103",
|
|
119
|
+
"very-happy-tools-x64-linux": "0.2.103",
|
|
120
|
+
"very-happy-tools-arm64-win32": "0.2.103",
|
|
121
|
+
"very-happy-tools-x64-win32": "0.2.103"
|
|
122
122
|
},
|
|
123
123
|
"devDependencies": {
|
|
124
124
|
"@slopus/happy-wire": "workspace:*",
|