@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.
Files changed (37) hide show
  1. package/dist/{index-DsHtmQqP.mjs → index-BERBU6rR.mjs} +201 -23
  2. package/dist/{index-BOBrKhX5.cjs → index-CHEjP0zg.cjs} +215 -37
  3. package/dist/index.cjs +6 -6
  4. package/dist/index.mjs +6 -6
  5. package/dist/lib.cjs +1 -2
  6. package/dist/lib.d.cts +118 -4
  7. package/dist/lib.d.mts +118 -4
  8. package/dist/lib.mjs +1 -2
  9. package/dist/{list-hET5tyMc.mjs → list-BvtUKVTq.mjs} +1 -1
  10. package/dist/{list-BW6QBLa1.cjs → list-DOsBjFRJ.cjs} +1 -1
  11. package/dist/{prompt-Dz7G8yGx.mjs → prompt-BbMNN7fl.mjs} +1 -2
  12. package/dist/{prompt-DXkgjktW.cjs → prompt-Dh_trad0.cjs} +1 -2
  13. package/dist/{runCodex-CLGYMNs2.mjs → runCodex-CYkmZphO.mjs} +20 -4
  14. package/dist/{runCodex-CylcX5Ug.cjs → runCodex-DUqqO-m8.cjs} +20 -4
  15. package/dist/{types-CGco5Y-r.mjs → types-Cw6y7GyQ.mjs} +156 -20
  16. package/dist/{types-BsjUgWOx.cjs → types-Q-euvEmG.cjs} +154 -18
  17. package/package.json +2 -4
  18. package/scripts/download-tool.cjs +187 -0
  19. package/scripts/ripgrep_launcher.cjs +53 -2
  20. package/scripts/tools-config.cjs +119 -0
  21. package/tools/archives/difftastic-LICENSE +0 -21
  22. package/tools/archives/difftastic-arm64-darwin.tar.gz +0 -0
  23. package/tools/archives/difftastic-arm64-linux.tar.gz +0 -0
  24. package/tools/archives/difftastic-x64-darwin.tar.gz +0 -0
  25. package/tools/archives/difftastic-x64-linux.tar.gz +0 -0
  26. package/tools/archives/difftastic-x64-win32.tar.gz +0 -0
  27. package/tools/archives/ripgrep-LICENSE +0 -3
  28. package/tools/archives/ripgrep-arm64-darwin.tar.gz +0 -0
  29. package/tools/archives/ripgrep-arm64-linux.tar.gz +0 -0
  30. package/tools/archives/ripgrep-x64-darwin.tar.gz +0 -0
  31. package/tools/archives/ripgrep-x64-linux.tar.gz +0 -0
  32. package/tools/archives/ripgrep-x64-win32.tar.gz +0 -0
  33. package/tools/licenses/difftastic-LICENSE +0 -21
  34. package/tools/licenses/ripgrep-LICENSE +0 -3
  35. package/tools/unpacked/difft +0 -0
  36. package/tools/unpacked/rg +0 -0
  37. 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.1";
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: z$1.object({
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
- function getBinaryPath() {
1093
- const platformName = platform();
1094
- const binaryName = platformName === "win32" ? "difft.exe" : "difft";
1095
- return resolve(join$1(projectPath(), "tools", "unpacked", binaryName));
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 = getBinaryPath();
1099
- return new Promise((resolve2, reject) => {
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
- resolve2({
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.1";
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: z.z.object({
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-BsjUgWOx.cjs', document.baseURI).href))));
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
- function getBinaryPath() {
1114
- const platformName = os$1.platform();
1115
- const binaryName = platformName === "win32" ? "difft.exe" : "difft";
1116
- return path.resolve(path.join(projectPath(), "tools", "unpacked", binaryName));
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 = getBinaryPath();
1120
- return new Promise((resolve2, reject) => {
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
- resolve2({
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.1",
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",