@zhigang1992/happy-cli 0.12.1 → 0.12.4
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/{index-DsHtmQqP.mjs → index-BERBU6rR.mjs} +201 -23
- package/dist/{index-BOBrKhX5.cjs → index-CHEjP0zg.cjs} +215 -37
- package/dist/index.cjs +6 -6
- package/dist/index.mjs +6 -6
- package/dist/lib.cjs +1 -2
- package/dist/lib.d.cts +118 -4
- package/dist/lib.d.mts +118 -4
- package/dist/lib.mjs +1 -2
- package/dist/{list-hET5tyMc.mjs → list-BvtUKVTq.mjs} +1 -1
- package/dist/{list-BW6QBLa1.cjs → list-DOsBjFRJ.cjs} +1 -1
- package/dist/{prompt-Dz7G8yGx.mjs → prompt-BbMNN7fl.mjs} +1 -2
- package/dist/{prompt-DXkgjktW.cjs → prompt-Dh_trad0.cjs} +1 -2
- package/dist/{runCodex-CLGYMNs2.mjs → runCodex-CYkmZphO.mjs} +20 -4
- package/dist/{runCodex-CylcX5Ug.cjs → runCodex-DUqqO-m8.cjs} +20 -4
- package/dist/{types-CGco5Y-r.mjs → types-Cw6y7GyQ.mjs} +156 -20
- package/dist/{types-BsjUgWOx.cjs → types-Q-euvEmG.cjs} +154 -18
- package/package.json +2 -4
- package/scripts/download-tool.cjs +187 -0
- package/scripts/ripgrep_launcher.cjs +53 -2
- package/scripts/tools-config.cjs +119 -0
- package/tools/archives/difftastic-LICENSE +0 -21
- package/tools/archives/difftastic-arm64-darwin.tar.gz +0 -0
- package/tools/archives/difftastic-arm64-linux.tar.gz +0 -0
- package/tools/archives/difftastic-x64-darwin.tar.gz +0 -0
- package/tools/archives/difftastic-x64-linux.tar.gz +0 -0
- package/tools/archives/difftastic-x64-win32.tar.gz +0 -0
- package/tools/archives/ripgrep-LICENSE +0 -3
- package/tools/archives/ripgrep-arm64-darwin.tar.gz +0 -0
- package/tools/archives/ripgrep-arm64-linux.tar.gz +0 -0
- package/tools/archives/ripgrep-x64-darwin.tar.gz +0 -0
- package/tools/archives/ripgrep-x64-linux.tar.gz +0 -0
- package/tools/archives/ripgrep-x64-win32.tar.gz +0 -0
- package/tools/licenses/difftastic-LICENSE +0 -21
- package/tools/licenses/ripgrep-LICENSE +0 -3
- package/tools/unpacked/difft +0 -0
- package/tools/unpacked/rg +0 -0
- package/tools/unpacked/ripgrep.node +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
1
|
+
import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import axios from 'axios';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
-
import { appendFileSync } from 'fs';
|
|
3
|
+
import { appendFileSync, existsSync as existsSync$1 } from 'fs';
|
|
4
4
|
import { existsSync, mkdirSync, constants, readFileSync, unlinkSync, writeFileSync, readdirSync, statSync } from 'node:fs';
|
|
5
5
|
import { homedir } from 'node:os';
|
|
6
6
|
import { join, basename } from 'node:path';
|
|
@@ -11,17 +11,16 @@ import { createHash, createDecipheriv, createCipheriv, randomBytes, randomUUID }
|
|
|
11
11
|
import tweetnacl from 'tweetnacl';
|
|
12
12
|
import { EventEmitter } from 'node:events';
|
|
13
13
|
import { io } from 'socket.io-client';
|
|
14
|
-
import { spawn, exec } from 'child_process';
|
|
14
|
+
import { spawn, spawnSync, exec } from 'child_process';
|
|
15
15
|
import { promisify } from 'util';
|
|
16
16
|
import { readFile as readFile$1, stat as stat$1, writeFile as writeFile$1, readdir } from 'fs/promises';
|
|
17
17
|
import { createHash as createHash$1 } from 'crypto';
|
|
18
18
|
import { dirname, resolve, join as join$1 } from 'path';
|
|
19
19
|
import { fileURLToPath } from 'url';
|
|
20
|
-
import { platform } from 'os';
|
|
21
20
|
import { Expo } from 'expo-server-sdk';
|
|
22
21
|
|
|
23
22
|
var name = "@zhigang1992/happy-cli";
|
|
24
|
-
var version = "0.12.
|
|
23
|
+
var version = "0.12.4";
|
|
25
24
|
var description = "Mobile and Web client for Claude Code and Codex";
|
|
26
25
|
var author = "Kirill Dubovitskiy";
|
|
27
26
|
var license = "MIT";
|
|
@@ -72,7 +71,6 @@ var files = [
|
|
|
72
71
|
"dist",
|
|
73
72
|
"bin",
|
|
74
73
|
"scripts",
|
|
75
|
-
"tools",
|
|
76
74
|
"package.json"
|
|
77
75
|
];
|
|
78
76
|
var scripts = {
|
|
@@ -85,8 +83,7 @@ var scripts = {
|
|
|
85
83
|
"dev:local-server": "yarn build && tsx --env-file .env.dev-local-server src/index.ts",
|
|
86
84
|
"dev:integration-test-env": "yarn build && tsx --env-file .env.integration-test src/index.ts",
|
|
87
85
|
prepublishOnly: "yarn build && yarn test",
|
|
88
|
-
release: "release-it"
|
|
89
|
-
postinstall: "node scripts/unpack-tools.cjs"
|
|
86
|
+
release: "release-it"
|
|
90
87
|
};
|
|
91
88
|
var dependencies = {
|
|
92
89
|
"@anthropic-ai/claude-code": "2.0.55",
|
|
@@ -314,6 +311,31 @@ function decryptWithDataKey(bundle, dataKey) {
|
|
|
314
311
|
return null;
|
|
315
312
|
}
|
|
316
313
|
}
|
|
314
|
+
function decryptBlobWithDataKey(bundle, dataKey) {
|
|
315
|
+
if (bundle.length < 1) {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
if (bundle[0] !== 0) {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
if (bundle.length < 12 + 16 + 1) {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
const nonce = bundle.slice(1, 13);
|
|
325
|
+
const authTag = bundle.slice(bundle.length - 16);
|
|
326
|
+
const ciphertext = bundle.slice(13, bundle.length - 16);
|
|
327
|
+
try {
|
|
328
|
+
const decipher = createDecipheriv("aes-256-gcm", dataKey, nonce);
|
|
329
|
+
decipher.setAuthTag(authTag);
|
|
330
|
+
const decrypted = Buffer.concat([
|
|
331
|
+
decipher.update(ciphertext),
|
|
332
|
+
decipher.final()
|
|
333
|
+
]);
|
|
334
|
+
return new Uint8Array(decrypted);
|
|
335
|
+
} catch (error) {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
317
339
|
function encrypt(key, variant, data) {
|
|
318
340
|
if (variant === "legacy") {
|
|
319
341
|
return encryptLegacy(data, key);
|
|
@@ -855,12 +877,27 @@ z$1.object({
|
|
|
855
877
|
agentStateVersion: z$1.number()
|
|
856
878
|
})
|
|
857
879
|
});
|
|
880
|
+
const TextContentSchema = z$1.object({
|
|
881
|
+
type: z$1.literal("text"),
|
|
882
|
+
text: z$1.string()
|
|
883
|
+
});
|
|
884
|
+
const ImageRefContentSchema = z$1.object({
|
|
885
|
+
type: z$1.literal("image_ref"),
|
|
886
|
+
blobId: z$1.string(),
|
|
887
|
+
mimeType: z$1.enum(["image/jpeg", "image/png", "image/gif", "image/webp"]),
|
|
888
|
+
width: z$1.number().optional(),
|
|
889
|
+
height: z$1.number().optional()
|
|
890
|
+
});
|
|
891
|
+
const ContentBlockSchema = z$1.union([TextContentSchema, ImageRefContentSchema]);
|
|
892
|
+
const UserMessageContentSchema = z$1.union([
|
|
893
|
+
TextContentSchema,
|
|
894
|
+
// Legacy: single text content
|
|
895
|
+
z$1.array(ContentBlockSchema)
|
|
896
|
+
// New: array of content blocks
|
|
897
|
+
]);
|
|
858
898
|
const UserMessageSchema = z$1.object({
|
|
859
899
|
role: z$1.literal("user"),
|
|
860
|
-
content:
|
|
861
|
-
type: z$1.literal("text"),
|
|
862
|
-
text: z$1.string()
|
|
863
|
-
}),
|
|
900
|
+
content: UserMessageContentSchema,
|
|
864
901
|
localKey: z$1.string().optional(),
|
|
865
902
|
// Mobile messages include this
|
|
866
903
|
meta: MessageMetaSchema.optional()
|
|
@@ -972,12 +1009,29 @@ class RpcHandlerManager {
|
|
|
972
1009
|
encryptionVariant;
|
|
973
1010
|
logger;
|
|
974
1011
|
socket = null;
|
|
1012
|
+
sessionContext = null;
|
|
975
1013
|
constructor(config) {
|
|
976
1014
|
this.scopePrefix = config.scopePrefix;
|
|
977
1015
|
this.encryptionKey = config.encryptionKey;
|
|
978
1016
|
this.encryptionVariant = config.encryptionVariant;
|
|
979
1017
|
this.logger = config.logger || ((msg, data) => logger.debug(msg, data));
|
|
980
1018
|
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Set the session context (path and environment)
|
|
1021
|
+
* This should be called after direnv environment is loaded
|
|
1022
|
+
* @param context - The session context with path and environment
|
|
1023
|
+
*/
|
|
1024
|
+
setSessionContext(context) {
|
|
1025
|
+
this.sessionContext = context;
|
|
1026
|
+
this.logger("[RPC] Session context set", { path: context.path, envVarCount: Object.keys(context.env).length });
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Get the current session context
|
|
1030
|
+
* @returns The session context or null if not set
|
|
1031
|
+
*/
|
|
1032
|
+
getSessionContext() {
|
|
1033
|
+
return this.sessionContext;
|
|
1034
|
+
}
|
|
981
1035
|
/**
|
|
982
1036
|
* Register an RPC handler for a specific method
|
|
983
1037
|
* @param method - The method name (without prefix)
|
|
@@ -1089,14 +1143,35 @@ function run$1(args, options) {
|
|
|
1089
1143
|
});
|
|
1090
1144
|
}
|
|
1091
1145
|
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1146
|
+
const toolsConfig = require(join$1(projectPath(), "scripts", "tools-config.cjs"));
|
|
1147
|
+
require(join$1(projectPath(), "scripts", "download-tool.cjs"));
|
|
1148
|
+
let cachedBinaryPath = null;
|
|
1149
|
+
function getBinaryPathSync() {
|
|
1150
|
+
if (cachedBinaryPath && existsSync$1(cachedBinaryPath)) {
|
|
1151
|
+
return cachedBinaryPath;
|
|
1152
|
+
}
|
|
1153
|
+
const expectedPath = toolsConfig.getToolPath("difftastic");
|
|
1154
|
+
if (existsSync$1(expectedPath)) {
|
|
1155
|
+
cachedBinaryPath = expectedPath;
|
|
1156
|
+
return expectedPath;
|
|
1157
|
+
}
|
|
1158
|
+
const result = spawnSync(process.execPath, [
|
|
1159
|
+
join$1(projectPath(), "scripts", "download-tool.cjs"),
|
|
1160
|
+
"difftastic"
|
|
1161
|
+
], {
|
|
1162
|
+
encoding: "utf-8",
|
|
1163
|
+
stdio: ["pipe", "pipe", "inherit"]
|
|
1164
|
+
// Show download progress on stderr
|
|
1165
|
+
});
|
|
1166
|
+
if (result.status !== 0) {
|
|
1167
|
+
throw new Error(`Failed to download difftastic: ${result.stderr || "unknown error"}`);
|
|
1168
|
+
}
|
|
1169
|
+
cachedBinaryPath = result.stdout.trim();
|
|
1170
|
+
return cachedBinaryPath;
|
|
1096
1171
|
}
|
|
1097
1172
|
function run(args, options) {
|
|
1098
|
-
const binaryPath =
|
|
1099
|
-
return new Promise((
|
|
1173
|
+
const binaryPath = getBinaryPathSync();
|
|
1174
|
+
return new Promise((resolve, reject) => {
|
|
1100
1175
|
const child = spawn(binaryPath, args, {
|
|
1101
1176
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1102
1177
|
cwd: options?.cwd,
|
|
@@ -1115,7 +1190,7 @@ function run(args, options) {
|
|
|
1115
1190
|
stderr += data.toString();
|
|
1116
1191
|
});
|
|
1117
1192
|
child.on("close", (code) => {
|
|
1118
|
-
|
|
1193
|
+
resolve({
|
|
1119
1194
|
exitCode: code || 0,
|
|
1120
1195
|
stdout,
|
|
1121
1196
|
stderr
|
|
@@ -1132,10 +1207,13 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1132
1207
|
rpcHandlerManager.registerHandler("bash", async (data) => {
|
|
1133
1208
|
logger.debug("Shell command request:", data.command);
|
|
1134
1209
|
try {
|
|
1210
|
+
const sessionContext = rpcHandlerManager.getSessionContext();
|
|
1135
1211
|
const options = {
|
|
1136
1212
|
cwd: data.cwd,
|
|
1137
|
-
timeout: data.timeout || 3e4
|
|
1213
|
+
timeout: data.timeout || 3e4,
|
|
1138
1214
|
// Default 30 seconds timeout
|
|
1215
|
+
// Use session environment if available, otherwise inherit process.env
|
|
1216
|
+
env: sessionContext?.env ?? process.env
|
|
1139
1217
|
};
|
|
1140
1218
|
const { stdout, stderr } = await execAsync(data.command, options);
|
|
1141
1219
|
return {
|
|
@@ -1665,6 +1743,64 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1665
1743
|
logger.debug("[API] socket.close() called");
|
|
1666
1744
|
this.socket.close();
|
|
1667
1745
|
}
|
|
1746
|
+
/**
|
|
1747
|
+
* Download and decrypt a blob from the server
|
|
1748
|
+
* @param blobId - The blob ID to download
|
|
1749
|
+
* @returns The decrypted binary data and metadata, or null if download/decryption fails
|
|
1750
|
+
*/
|
|
1751
|
+
async downloadBlob(blobId) {
|
|
1752
|
+
try {
|
|
1753
|
+
const response = await axios.get(
|
|
1754
|
+
`${configuration.serverUrl}/v1/sessions/${this.sessionId}/blobs/${blobId}`,
|
|
1755
|
+
{
|
|
1756
|
+
headers: {
|
|
1757
|
+
"Authorization": `Bearer ${this.token}`
|
|
1758
|
+
},
|
|
1759
|
+
responseType: "arraybuffer"
|
|
1760
|
+
}
|
|
1761
|
+
);
|
|
1762
|
+
const encryptedData = new Uint8Array(response.data);
|
|
1763
|
+
const mimeType = response.headers["x-blob-mimetype"];
|
|
1764
|
+
const originalSize = parseInt(response.headers["x-blob-size"], 10);
|
|
1765
|
+
if (this.encryptionVariant !== "dataKey") {
|
|
1766
|
+
logger.debug("[API] Cannot decrypt blob: session uses legacy encryption");
|
|
1767
|
+
return null;
|
|
1768
|
+
}
|
|
1769
|
+
const decryptedData = decryptBlobWithDataKey(encryptedData, this.encryptionKey);
|
|
1770
|
+
if (!decryptedData) {
|
|
1771
|
+
logger.debug("[API] Failed to decrypt blob");
|
|
1772
|
+
return null;
|
|
1773
|
+
}
|
|
1774
|
+
return {
|
|
1775
|
+
data: decryptedData,
|
|
1776
|
+
mimeType,
|
|
1777
|
+
size: originalSize
|
|
1778
|
+
};
|
|
1779
|
+
} catch (error) {
|
|
1780
|
+
logger.debug("[API] Failed to download blob:", error);
|
|
1781
|
+
return null;
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
/**
|
|
1785
|
+
* Convert an image_ref content block to a Claude API image content block
|
|
1786
|
+
* Downloads, decrypts, and base64 encodes the image
|
|
1787
|
+
* @param imageRef - The image reference content block
|
|
1788
|
+
* @returns Claude API image content block, or null if conversion fails
|
|
1789
|
+
*/
|
|
1790
|
+
async resolveImageRef(imageRef) {
|
|
1791
|
+
const blob = await this.downloadBlob(imageRef.blobId);
|
|
1792
|
+
if (!blob) {
|
|
1793
|
+
return null;
|
|
1794
|
+
}
|
|
1795
|
+
return {
|
|
1796
|
+
type: "image",
|
|
1797
|
+
source: {
|
|
1798
|
+
type: "base64",
|
|
1799
|
+
media_type: blob.mimeType,
|
|
1800
|
+
data: Buffer.from(blob.data).toString("base64")
|
|
1801
|
+
}
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1668
1804
|
}
|
|
1669
1805
|
|
|
1670
1806
|
class ApiMachineClient {
|
|
@@ -18,7 +18,6 @@ var fs$2 = require('fs/promises');
|
|
|
18
18
|
var crypto = require('crypto');
|
|
19
19
|
var path = require('path');
|
|
20
20
|
var url = require('url');
|
|
21
|
-
var os$1 = require('os');
|
|
22
21
|
var expoServerSdk = require('expo-server-sdk');
|
|
23
22
|
|
|
24
23
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
@@ -42,7 +41,7 @@ function _interopNamespaceDefault(e) {
|
|
|
42
41
|
var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
|
|
43
42
|
|
|
44
43
|
var name = "@zhigang1992/happy-cli";
|
|
45
|
-
var version = "0.12.
|
|
44
|
+
var version = "0.12.4";
|
|
46
45
|
var description = "Mobile and Web client for Claude Code and Codex";
|
|
47
46
|
var author = "Kirill Dubovitskiy";
|
|
48
47
|
var license = "MIT";
|
|
@@ -93,7 +92,6 @@ var files = [
|
|
|
93
92
|
"dist",
|
|
94
93
|
"bin",
|
|
95
94
|
"scripts",
|
|
96
|
-
"tools",
|
|
97
95
|
"package.json"
|
|
98
96
|
];
|
|
99
97
|
var scripts = {
|
|
@@ -106,8 +104,7 @@ var scripts = {
|
|
|
106
104
|
"dev:local-server": "yarn build && tsx --env-file .env.dev-local-server src/index.ts",
|
|
107
105
|
"dev:integration-test-env": "yarn build && tsx --env-file .env.integration-test src/index.ts",
|
|
108
106
|
prepublishOnly: "yarn build && yarn test",
|
|
109
|
-
release: "release-it"
|
|
110
|
-
postinstall: "node scripts/unpack-tools.cjs"
|
|
107
|
+
release: "release-it"
|
|
111
108
|
};
|
|
112
109
|
var dependencies = {
|
|
113
110
|
"@anthropic-ai/claude-code": "2.0.55",
|
|
@@ -335,6 +332,31 @@ function decryptWithDataKey(bundle, dataKey) {
|
|
|
335
332
|
return null;
|
|
336
333
|
}
|
|
337
334
|
}
|
|
335
|
+
function decryptBlobWithDataKey(bundle, dataKey) {
|
|
336
|
+
if (bundle.length < 1) {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
if (bundle[0] !== 0) {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
if (bundle.length < 12 + 16 + 1) {
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
const nonce = bundle.slice(1, 13);
|
|
346
|
+
const authTag = bundle.slice(bundle.length - 16);
|
|
347
|
+
const ciphertext = bundle.slice(13, bundle.length - 16);
|
|
348
|
+
try {
|
|
349
|
+
const decipher = node_crypto.createDecipheriv("aes-256-gcm", dataKey, nonce);
|
|
350
|
+
decipher.setAuthTag(authTag);
|
|
351
|
+
const decrypted = Buffer.concat([
|
|
352
|
+
decipher.update(ciphertext),
|
|
353
|
+
decipher.final()
|
|
354
|
+
]);
|
|
355
|
+
return new Uint8Array(decrypted);
|
|
356
|
+
} catch (error) {
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
338
360
|
function encrypt(key, variant, data) {
|
|
339
361
|
if (variant === "legacy") {
|
|
340
362
|
return encryptLegacy(data, key);
|
|
@@ -876,12 +898,27 @@ z.z.object({
|
|
|
876
898
|
agentStateVersion: z.z.number()
|
|
877
899
|
})
|
|
878
900
|
});
|
|
901
|
+
const TextContentSchema = z.z.object({
|
|
902
|
+
type: z.z.literal("text"),
|
|
903
|
+
text: z.z.string()
|
|
904
|
+
});
|
|
905
|
+
const ImageRefContentSchema = z.z.object({
|
|
906
|
+
type: z.z.literal("image_ref"),
|
|
907
|
+
blobId: z.z.string(),
|
|
908
|
+
mimeType: z.z.enum(["image/jpeg", "image/png", "image/gif", "image/webp"]),
|
|
909
|
+
width: z.z.number().optional(),
|
|
910
|
+
height: z.z.number().optional()
|
|
911
|
+
});
|
|
912
|
+
const ContentBlockSchema = z.z.union([TextContentSchema, ImageRefContentSchema]);
|
|
913
|
+
const UserMessageContentSchema = z.z.union([
|
|
914
|
+
TextContentSchema,
|
|
915
|
+
// Legacy: single text content
|
|
916
|
+
z.z.array(ContentBlockSchema)
|
|
917
|
+
// New: array of content blocks
|
|
918
|
+
]);
|
|
879
919
|
const UserMessageSchema = z.z.object({
|
|
880
920
|
role: z.z.literal("user"),
|
|
881
|
-
content:
|
|
882
|
-
type: z.z.literal("text"),
|
|
883
|
-
text: z.z.string()
|
|
884
|
-
}),
|
|
921
|
+
content: UserMessageContentSchema,
|
|
885
922
|
localKey: z.z.string().optional(),
|
|
886
923
|
// Mobile messages include this
|
|
887
924
|
meta: MessageMetaSchema.optional()
|
|
@@ -993,12 +1030,29 @@ class RpcHandlerManager {
|
|
|
993
1030
|
encryptionVariant;
|
|
994
1031
|
logger;
|
|
995
1032
|
socket = null;
|
|
1033
|
+
sessionContext = null;
|
|
996
1034
|
constructor(config) {
|
|
997
1035
|
this.scopePrefix = config.scopePrefix;
|
|
998
1036
|
this.encryptionKey = config.encryptionKey;
|
|
999
1037
|
this.encryptionVariant = config.encryptionVariant;
|
|
1000
1038
|
this.logger = config.logger || ((msg, data) => logger.debug(msg, data));
|
|
1001
1039
|
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Set the session context (path and environment)
|
|
1042
|
+
* This should be called after direnv environment is loaded
|
|
1043
|
+
* @param context - The session context with path and environment
|
|
1044
|
+
*/
|
|
1045
|
+
setSessionContext(context) {
|
|
1046
|
+
this.sessionContext = context;
|
|
1047
|
+
this.logger("[RPC] Session context set", { path: context.path, envVarCount: Object.keys(context.env).length });
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Get the current session context
|
|
1051
|
+
* @returns The session context or null if not set
|
|
1052
|
+
*/
|
|
1053
|
+
getSessionContext() {
|
|
1054
|
+
return this.sessionContext;
|
|
1055
|
+
}
|
|
1002
1056
|
/**
|
|
1003
1057
|
* Register an RPC handler for a specific method
|
|
1004
1058
|
* @param method - The method name (without prefix)
|
|
@@ -1076,7 +1130,7 @@ class RpcHandlerManager {
|
|
|
1076
1130
|
}
|
|
1077
1131
|
}
|
|
1078
1132
|
|
|
1079
|
-
const __dirname$1 = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-
|
|
1133
|
+
const __dirname$1 = path.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('types-Q-euvEmG.cjs', document.baseURI).href))));
|
|
1080
1134
|
function projectPath() {
|
|
1081
1135
|
const path$1 = path.resolve(__dirname$1, "..");
|
|
1082
1136
|
return path$1;
|
|
@@ -1110,14 +1164,35 @@ function run$1(args, options) {
|
|
|
1110
1164
|
});
|
|
1111
1165
|
}
|
|
1112
1166
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1167
|
+
const toolsConfig = require(path.join(projectPath(), "scripts", "tools-config.cjs"));
|
|
1168
|
+
require(path.join(projectPath(), "scripts", "download-tool.cjs"));
|
|
1169
|
+
let cachedBinaryPath = null;
|
|
1170
|
+
function getBinaryPathSync() {
|
|
1171
|
+
if (cachedBinaryPath && fs$1.existsSync(cachedBinaryPath)) {
|
|
1172
|
+
return cachedBinaryPath;
|
|
1173
|
+
}
|
|
1174
|
+
const expectedPath = toolsConfig.getToolPath("difftastic");
|
|
1175
|
+
if (fs$1.existsSync(expectedPath)) {
|
|
1176
|
+
cachedBinaryPath = expectedPath;
|
|
1177
|
+
return expectedPath;
|
|
1178
|
+
}
|
|
1179
|
+
const result = child_process.spawnSync(process.execPath, [
|
|
1180
|
+
path.join(projectPath(), "scripts", "download-tool.cjs"),
|
|
1181
|
+
"difftastic"
|
|
1182
|
+
], {
|
|
1183
|
+
encoding: "utf-8",
|
|
1184
|
+
stdio: ["pipe", "pipe", "inherit"]
|
|
1185
|
+
// Show download progress on stderr
|
|
1186
|
+
});
|
|
1187
|
+
if (result.status !== 0) {
|
|
1188
|
+
throw new Error(`Failed to download difftastic: ${result.stderr || "unknown error"}`);
|
|
1189
|
+
}
|
|
1190
|
+
cachedBinaryPath = result.stdout.trim();
|
|
1191
|
+
return cachedBinaryPath;
|
|
1117
1192
|
}
|
|
1118
1193
|
function run(args, options) {
|
|
1119
|
-
const binaryPath =
|
|
1120
|
-
return new Promise((
|
|
1194
|
+
const binaryPath = getBinaryPathSync();
|
|
1195
|
+
return new Promise((resolve, reject) => {
|
|
1121
1196
|
const child = child_process.spawn(binaryPath, args, {
|
|
1122
1197
|
stdio: ["pipe", "pipe", "pipe"],
|
|
1123
1198
|
cwd: options?.cwd,
|
|
@@ -1136,7 +1211,7 @@ function run(args, options) {
|
|
|
1136
1211
|
stderr += data.toString();
|
|
1137
1212
|
});
|
|
1138
1213
|
child.on("close", (code) => {
|
|
1139
|
-
|
|
1214
|
+
resolve({
|
|
1140
1215
|
exitCode: code || 0,
|
|
1141
1216
|
stdout,
|
|
1142
1217
|
stderr
|
|
@@ -1153,10 +1228,13 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1153
1228
|
rpcHandlerManager.registerHandler("bash", async (data) => {
|
|
1154
1229
|
logger.debug("Shell command request:", data.command);
|
|
1155
1230
|
try {
|
|
1231
|
+
const sessionContext = rpcHandlerManager.getSessionContext();
|
|
1156
1232
|
const options = {
|
|
1157
1233
|
cwd: data.cwd,
|
|
1158
|
-
timeout: data.timeout || 3e4
|
|
1234
|
+
timeout: data.timeout || 3e4,
|
|
1159
1235
|
// Default 30 seconds timeout
|
|
1236
|
+
// Use session environment if available, otherwise inherit process.env
|
|
1237
|
+
env: sessionContext?.env ?? process.env
|
|
1160
1238
|
};
|
|
1161
1239
|
const { stdout, stderr } = await execAsync(data.command, options);
|
|
1162
1240
|
return {
|
|
@@ -1686,6 +1764,64 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
1686
1764
|
logger.debug("[API] socket.close() called");
|
|
1687
1765
|
this.socket.close();
|
|
1688
1766
|
}
|
|
1767
|
+
/**
|
|
1768
|
+
* Download and decrypt a blob from the server
|
|
1769
|
+
* @param blobId - The blob ID to download
|
|
1770
|
+
* @returns The decrypted binary data and metadata, or null if download/decryption fails
|
|
1771
|
+
*/
|
|
1772
|
+
async downloadBlob(blobId) {
|
|
1773
|
+
try {
|
|
1774
|
+
const response = await axios.get(
|
|
1775
|
+
`${configuration.serverUrl}/v1/sessions/${this.sessionId}/blobs/${blobId}`,
|
|
1776
|
+
{
|
|
1777
|
+
headers: {
|
|
1778
|
+
"Authorization": `Bearer ${this.token}`
|
|
1779
|
+
},
|
|
1780
|
+
responseType: "arraybuffer"
|
|
1781
|
+
}
|
|
1782
|
+
);
|
|
1783
|
+
const encryptedData = new Uint8Array(response.data);
|
|
1784
|
+
const mimeType = response.headers["x-blob-mimetype"];
|
|
1785
|
+
const originalSize = parseInt(response.headers["x-blob-size"], 10);
|
|
1786
|
+
if (this.encryptionVariant !== "dataKey") {
|
|
1787
|
+
logger.debug("[API] Cannot decrypt blob: session uses legacy encryption");
|
|
1788
|
+
return null;
|
|
1789
|
+
}
|
|
1790
|
+
const decryptedData = decryptBlobWithDataKey(encryptedData, this.encryptionKey);
|
|
1791
|
+
if (!decryptedData) {
|
|
1792
|
+
logger.debug("[API] Failed to decrypt blob");
|
|
1793
|
+
return null;
|
|
1794
|
+
}
|
|
1795
|
+
return {
|
|
1796
|
+
data: decryptedData,
|
|
1797
|
+
mimeType,
|
|
1798
|
+
size: originalSize
|
|
1799
|
+
};
|
|
1800
|
+
} catch (error) {
|
|
1801
|
+
logger.debug("[API] Failed to download blob:", error);
|
|
1802
|
+
return null;
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
/**
|
|
1806
|
+
* Convert an image_ref content block to a Claude API image content block
|
|
1807
|
+
* Downloads, decrypts, and base64 encodes the image
|
|
1808
|
+
* @param imageRef - The image reference content block
|
|
1809
|
+
* @returns Claude API image content block, or null if conversion fails
|
|
1810
|
+
*/
|
|
1811
|
+
async resolveImageRef(imageRef) {
|
|
1812
|
+
const blob = await this.downloadBlob(imageRef.blobId);
|
|
1813
|
+
if (!blob) {
|
|
1814
|
+
return null;
|
|
1815
|
+
}
|
|
1816
|
+
return {
|
|
1817
|
+
type: "image",
|
|
1818
|
+
source: {
|
|
1819
|
+
type: "base64",
|
|
1820
|
+
media_type: blob.mimeType,
|
|
1821
|
+
data: Buffer.from(blob.data).toString("base64")
|
|
1822
|
+
}
|
|
1823
|
+
};
|
|
1824
|
+
}
|
|
1689
1825
|
}
|
|
1690
1826
|
|
|
1691
1827
|
class ApiMachineClient {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhigang1992/happy-cli",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.4",
|
|
4
4
|
"description": "Mobile and Web client for Claude Code and Codex",
|
|
5
5
|
"author": "Kirill Dubovitskiy",
|
|
6
6
|
"license": "MIT",
|
|
@@ -51,7 +51,6 @@
|
|
|
51
51
|
"dist",
|
|
52
52
|
"bin",
|
|
53
53
|
"scripts",
|
|
54
|
-
"tools",
|
|
55
54
|
"package.json"
|
|
56
55
|
],
|
|
57
56
|
"scripts": {
|
|
@@ -64,8 +63,7 @@
|
|
|
64
63
|
"dev:local-server": "yarn build && tsx --env-file .env.dev-local-server src/index.ts",
|
|
65
64
|
"dev:integration-test-env": "yarn build && tsx --env-file .env.integration-test src/index.ts",
|
|
66
65
|
"prepublishOnly": "yarn build && yarn test",
|
|
67
|
-
"release": "release-it"
|
|
68
|
-
"postinstall": "node scripts/unpack-tools.cjs"
|
|
66
|
+
"release": "release-it"
|
|
69
67
|
},
|
|
70
68
|
"dependencies": {
|
|
71
69
|
"@anthropic-ai/claude-code": "2.0.55",
|