@zhigang1992/happy-cli 0.12.3 → 0.12.5
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-y8CVImEp.cjs → index-B8KwpPnt.cjs} +212 -35
- package/dist/{index-DNeoLdzx.mjs → index-BfGA--2C.mjs} +198 -21
- package/dist/index.cjs +6 -6
- package/dist/index.mjs +6 -6
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.cts +118 -4
- package/dist/lib.d.mts +118 -4
- package/dist/lib.mjs +1 -1
- package/dist/{list-D_NjiLPx.mjs → list-B0EZS1nk.mjs} +1 -1
- package/dist/{list-DiamEbqL.cjs → list-BmyKI4UO.cjs} +1 -1
- package/dist/{prompt-CJh1Mo2A.mjs → prompt-COmkBtjd.mjs} +1 -1
- package/dist/{prompt-Cu47wZlI.cjs → prompt-SOUeW7vB.cjs} +1 -1
- package/dist/{runCodex-DErgypij.cjs → runCodex-CLxMptoC.cjs} +19 -3
- package/dist/{runCodex-B5ZlUUec.mjs → runCodex-Cxhf8ktE.mjs} +19 -3
- package/dist/{types-CllU28mx.cjs → types-0ILrOpLt.cjs} +140 -7
- package/dist/{types-D4_aCy-H.mjs → types-BmcSXhbq.mjs} +139 -6
- package/package.json +1 -1
|
@@ -20,7 +20,7 @@ import { fileURLToPath } from 'url';
|
|
|
20
20
|
import { Expo } from 'expo-server-sdk';
|
|
21
21
|
|
|
22
22
|
var name = "@zhigang1992/happy-cli";
|
|
23
|
-
var version = "0.12.
|
|
23
|
+
var version = "0.12.5";
|
|
24
24
|
var description = "Mobile and Web client for Claude Code and Codex";
|
|
25
25
|
var author = "Kirill Dubovitskiy";
|
|
26
26
|
var license = "MIT";
|
|
@@ -311,6 +311,43 @@ function decryptWithDataKey(bundle, dataKey) {
|
|
|
311
311
|
return null;
|
|
312
312
|
}
|
|
313
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
|
+
}
|
|
339
|
+
function decryptBlobWithSecretBox(bundle, secret) {
|
|
340
|
+
if (bundle.length < 1 + tweetnacl.secretbox.nonceLength) {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
if (bundle[0] !== 0) {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
const nonce = bundle.slice(1, 1 + tweetnacl.secretbox.nonceLength);
|
|
347
|
+
const ciphertext = bundle.slice(1 + tweetnacl.secretbox.nonceLength);
|
|
348
|
+
const decrypted = tweetnacl.secretbox.open(ciphertext, nonce, secret);
|
|
349
|
+
return decrypted || null;
|
|
350
|
+
}
|
|
314
351
|
function encrypt(key, variant, data) {
|
|
315
352
|
if (variant === "legacy") {
|
|
316
353
|
return encryptLegacy(data, key);
|
|
@@ -325,6 +362,13 @@ function decrypt(key, variant, data) {
|
|
|
325
362
|
return decryptWithDataKey(data, key);
|
|
326
363
|
}
|
|
327
364
|
}
|
|
365
|
+
function decryptBlob(key, variant, bundle) {
|
|
366
|
+
if (variant === "legacy") {
|
|
367
|
+
return decryptBlobWithSecretBox(bundle, key);
|
|
368
|
+
} else {
|
|
369
|
+
return decryptBlobWithDataKey(bundle, key);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
328
372
|
function authChallenge(secret) {
|
|
329
373
|
const keypair = tweetnacl.sign.keyPair.fromSeed(secret);
|
|
330
374
|
const challenge = getRandomBytes(32);
|
|
@@ -852,12 +896,27 @@ z$1.object({
|
|
|
852
896
|
agentStateVersion: z$1.number()
|
|
853
897
|
})
|
|
854
898
|
});
|
|
899
|
+
const TextContentSchema = z$1.object({
|
|
900
|
+
type: z$1.literal("text"),
|
|
901
|
+
text: z$1.string()
|
|
902
|
+
});
|
|
903
|
+
const ImageRefContentSchema = z$1.object({
|
|
904
|
+
type: z$1.literal("image_ref"),
|
|
905
|
+
blobId: z$1.string(),
|
|
906
|
+
mimeType: z$1.enum(["image/jpeg", "image/png", "image/gif", "image/webp"]),
|
|
907
|
+
width: z$1.number().optional(),
|
|
908
|
+
height: z$1.number().optional()
|
|
909
|
+
});
|
|
910
|
+
const ContentBlockSchema = z$1.union([TextContentSchema, ImageRefContentSchema]);
|
|
911
|
+
const UserMessageContentSchema = z$1.union([
|
|
912
|
+
TextContentSchema,
|
|
913
|
+
// Legacy: single text content
|
|
914
|
+
z$1.array(ContentBlockSchema)
|
|
915
|
+
// New: array of content blocks
|
|
916
|
+
]);
|
|
855
917
|
const UserMessageSchema = z$1.object({
|
|
856
918
|
role: z$1.literal("user"),
|
|
857
|
-
content:
|
|
858
|
-
type: z$1.literal("text"),
|
|
859
|
-
text: z$1.string()
|
|
860
|
-
}),
|
|
919
|
+
content: UserMessageContentSchema,
|
|
861
920
|
localKey: z$1.string().optional(),
|
|
862
921
|
// Mobile messages include this
|
|
863
922
|
meta: MessageMetaSchema.optional()
|
|
@@ -969,12 +1028,29 @@ class RpcHandlerManager {
|
|
|
969
1028
|
encryptionVariant;
|
|
970
1029
|
logger;
|
|
971
1030
|
socket = null;
|
|
1031
|
+
sessionContext = null;
|
|
972
1032
|
constructor(config) {
|
|
973
1033
|
this.scopePrefix = config.scopePrefix;
|
|
974
1034
|
this.encryptionKey = config.encryptionKey;
|
|
975
1035
|
this.encryptionVariant = config.encryptionVariant;
|
|
976
1036
|
this.logger = config.logger || ((msg, data) => logger.debug(msg, data));
|
|
977
1037
|
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Set the session context (path and environment)
|
|
1040
|
+
* This should be called after direnv environment is loaded
|
|
1041
|
+
* @param context - The session context with path and environment
|
|
1042
|
+
*/
|
|
1043
|
+
setSessionContext(context) {
|
|
1044
|
+
this.sessionContext = context;
|
|
1045
|
+
this.logger("[RPC] Session context set", { path: context.path, envVarCount: Object.keys(context.env).length });
|
|
1046
|
+
}
|
|
1047
|
+
/**
|
|
1048
|
+
* Get the current session context
|
|
1049
|
+
* @returns The session context or null if not set
|
|
1050
|
+
*/
|
|
1051
|
+
getSessionContext() {
|
|
1052
|
+
return this.sessionContext;
|
|
1053
|
+
}
|
|
978
1054
|
/**
|
|
979
1055
|
* Register an RPC handler for a specific method
|
|
980
1056
|
* @param method - The method name (without prefix)
|
|
@@ -1150,10 +1226,13 @@ function registerCommonHandlers(rpcHandlerManager) {
|
|
|
1150
1226
|
rpcHandlerManager.registerHandler("bash", async (data) => {
|
|
1151
1227
|
logger.debug("Shell command request:", data.command);
|
|
1152
1228
|
try {
|
|
1229
|
+
const sessionContext = rpcHandlerManager.getSessionContext();
|
|
1153
1230
|
const options = {
|
|
1154
1231
|
cwd: data.cwd,
|
|
1155
|
-
timeout: data.timeout || 3e4
|
|
1232
|
+
timeout: data.timeout || 3e4,
|
|
1156
1233
|
// Default 30 seconds timeout
|
|
1234
|
+
// Use session environment if available, otherwise inherit process.env
|
|
1235
|
+
env: sessionContext?.env ?? process.env
|
|
1157
1236
|
};
|
|
1158
1237
|
const { stdout, stderr } = await execAsync(data.command, options);
|
|
1159
1238
|
return {
|
|
@@ -1683,6 +1762,60 @@ class ApiSessionClient extends EventEmitter {
|
|
|
1683
1762
|
logger.debug("[API] socket.close() called");
|
|
1684
1763
|
this.socket.close();
|
|
1685
1764
|
}
|
|
1765
|
+
/**
|
|
1766
|
+
* Download and decrypt a blob from the server
|
|
1767
|
+
* @param blobId - The blob ID to download
|
|
1768
|
+
* @returns The decrypted binary data and metadata, or null if download/decryption fails
|
|
1769
|
+
*/
|
|
1770
|
+
async downloadBlob(blobId) {
|
|
1771
|
+
try {
|
|
1772
|
+
const response = await axios.get(
|
|
1773
|
+
`${configuration.serverUrl}/v1/sessions/${this.sessionId}/blobs/${blobId}`,
|
|
1774
|
+
{
|
|
1775
|
+
headers: {
|
|
1776
|
+
"Authorization": `Bearer ${this.token}`
|
|
1777
|
+
},
|
|
1778
|
+
responseType: "arraybuffer"
|
|
1779
|
+
}
|
|
1780
|
+
);
|
|
1781
|
+
const encryptedData = new Uint8Array(response.data);
|
|
1782
|
+
const mimeType = response.headers["x-blob-mimetype"];
|
|
1783
|
+
const originalSize = parseInt(response.headers["x-blob-size"], 10);
|
|
1784
|
+
const decryptedData = decryptBlob(this.encryptionKey, this.encryptionVariant, encryptedData);
|
|
1785
|
+
if (!decryptedData) {
|
|
1786
|
+
logger.debug("[API] Failed to decrypt blob");
|
|
1787
|
+
return null;
|
|
1788
|
+
}
|
|
1789
|
+
return {
|
|
1790
|
+
data: decryptedData,
|
|
1791
|
+
mimeType,
|
|
1792
|
+
size: originalSize
|
|
1793
|
+
};
|
|
1794
|
+
} catch (error) {
|
|
1795
|
+
logger.debug("[API] Failed to download blob:", error);
|
|
1796
|
+
return null;
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
/**
|
|
1800
|
+
* Convert an image_ref content block to a Claude API image content block
|
|
1801
|
+
* Downloads, decrypts, and base64 encodes the image
|
|
1802
|
+
* @param imageRef - The image reference content block
|
|
1803
|
+
* @returns Claude API image content block, or null if conversion fails
|
|
1804
|
+
*/
|
|
1805
|
+
async resolveImageRef(imageRef) {
|
|
1806
|
+
const blob = await this.downloadBlob(imageRef.blobId);
|
|
1807
|
+
if (!blob) {
|
|
1808
|
+
return null;
|
|
1809
|
+
}
|
|
1810
|
+
return {
|
|
1811
|
+
type: "image",
|
|
1812
|
+
source: {
|
|
1813
|
+
type: "base64",
|
|
1814
|
+
media_type: blob.mimeType,
|
|
1815
|
+
data: Buffer.from(blob.data).toString("base64")
|
|
1816
|
+
}
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1686
1819
|
}
|
|
1687
1820
|
|
|
1688
1821
|
class ApiMachineClient {
|