@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/lib.d.mts CHANGED
@@ -291,6 +291,16 @@ type RawJSONLines = z.infer<typeof RawJSONLinesSchema>;
291
291
  /**
292
292
  * Common RPC types and interfaces for both session and machine clients
293
293
  */
294
+ /**
295
+ * Session context for RPC handlers
296
+ * Contains environment and path information for the current session
297
+ */
298
+ interface RpcSessionContext {
299
+ /** Working directory path for the session */
300
+ path: string;
301
+ /** Environment variables (includes direnv if available) */
302
+ env: Record<string, string>;
303
+ }
294
304
  /**
295
305
  * Generic RPC handler function type
296
306
  * @template TRequest - The request data type
@@ -326,7 +336,19 @@ declare class RpcHandlerManager {
326
336
  private readonly encryptionVariant;
327
337
  private readonly logger;
328
338
  private socket;
339
+ private sessionContext;
329
340
  constructor(config: RpcHandlerConfig);
341
+ /**
342
+ * Set the session context (path and environment)
343
+ * This should be called after direnv environment is loaded
344
+ * @param context - The session context with path and environment
345
+ */
346
+ setSessionContext(context: RpcSessionContext): void;
347
+ /**
348
+ * Get the current session context
349
+ * @returns The session context or null if not set
350
+ */
351
+ getSessionContext(): RpcSessionContext | null;
330
352
  /**
331
353
  * Register an RPC handler for a specific method
332
354
  * @param method - The method name (without prefix)
@@ -423,6 +445,30 @@ declare class ApiSessionClient extends EventEmitter {
423
445
  */
424
446
  flush(): Promise<void>;
425
447
  close(): Promise<void>;
448
+ /**
449
+ * Download and decrypt a blob from the server
450
+ * @param blobId - The blob ID to download
451
+ * @returns The decrypted binary data and metadata, or null if download/decryption fails
452
+ */
453
+ downloadBlob(blobId: string): Promise<{
454
+ data: Uint8Array;
455
+ mimeType: string;
456
+ size: number;
457
+ } | null>;
458
+ /**
459
+ * Convert an image_ref content block to a Claude API image content block
460
+ * Downloads, decrypts, and base64 encodes the image
461
+ * @param imageRef - The image reference content block
462
+ * @returns Claude API image content block, or null if conversion fails
463
+ */
464
+ resolveImageRef(imageRef: ImageRefContent): Promise<{
465
+ type: 'image';
466
+ source: {
467
+ type: 'base64';
468
+ media_type: string;
469
+ data: string;
470
+ };
471
+ } | null>;
426
472
  }
427
473
 
428
474
  type PermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan';
@@ -505,9 +551,32 @@ type Machine = {
505
551
  daemonState: DaemonState | null;
506
552
  daemonStateVersion: number;
507
553
  };
554
+ /**
555
+ * Image reference content block for user messages (E2E encrypted images)
556
+ */
557
+ declare const ImageRefContentSchema: z.ZodObject<{
558
+ type: z.ZodLiteral<"image_ref">;
559
+ blobId: z.ZodString;
560
+ mimeType: z.ZodEnum<["image/jpeg", "image/png", "image/gif", "image/webp"]>;
561
+ width: z.ZodOptional<z.ZodNumber>;
562
+ height: z.ZodOptional<z.ZodNumber>;
563
+ }, "strip", z.ZodTypeAny, {
564
+ type: "image_ref";
565
+ blobId: string;
566
+ mimeType: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
567
+ width?: number | undefined;
568
+ height?: number | undefined;
569
+ }, {
570
+ type: "image_ref";
571
+ blobId: string;
572
+ mimeType: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
573
+ width?: number | undefined;
574
+ height?: number | undefined;
575
+ }>;
576
+ type ImageRefContent = z.infer<typeof ImageRefContentSchema>;
508
577
  declare const UserMessageSchema: z.ZodObject<{
509
578
  role: z.ZodLiteral<"user">;
510
- content: z.ZodObject<{
579
+ content: z.ZodUnion<[z.ZodObject<{
511
580
  type: z.ZodLiteral<"text">;
512
581
  text: z.ZodString;
513
582
  }, "strip", z.ZodTypeAny, {
@@ -516,7 +585,34 @@ declare const UserMessageSchema: z.ZodObject<{
516
585
  }, {
517
586
  type: "text";
518
587
  text: string;
519
- }>;
588
+ }>, z.ZodArray<z.ZodUnion<[z.ZodObject<{
589
+ type: z.ZodLiteral<"text">;
590
+ text: z.ZodString;
591
+ }, "strip", z.ZodTypeAny, {
592
+ type: "text";
593
+ text: string;
594
+ }, {
595
+ type: "text";
596
+ text: string;
597
+ }>, z.ZodObject<{
598
+ type: z.ZodLiteral<"image_ref">;
599
+ blobId: z.ZodString;
600
+ mimeType: z.ZodEnum<["image/jpeg", "image/png", "image/gif", "image/webp"]>;
601
+ width: z.ZodOptional<z.ZodNumber>;
602
+ height: z.ZodOptional<z.ZodNumber>;
603
+ }, "strip", z.ZodTypeAny, {
604
+ type: "image_ref";
605
+ blobId: string;
606
+ mimeType: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
607
+ width?: number | undefined;
608
+ height?: number | undefined;
609
+ }, {
610
+ type: "image_ref";
611
+ blobId: string;
612
+ mimeType: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
613
+ width?: number | undefined;
614
+ height?: number | undefined;
615
+ }>]>, "many">]>;
520
616
  localKey: z.ZodOptional<z.ZodString>;
521
617
  meta: z.ZodOptional<z.ZodObject<{
522
618
  sentFrom: z.ZodOptional<z.ZodString>;
@@ -550,7 +646,16 @@ declare const UserMessageSchema: z.ZodObject<{
550
646
  content: {
551
647
  type: "text";
552
648
  text: string;
553
- };
649
+ } | ({
650
+ type: "text";
651
+ text: string;
652
+ } | {
653
+ type: "image_ref";
654
+ blobId: string;
655
+ mimeType: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
656
+ width?: number | undefined;
657
+ height?: number | undefined;
658
+ })[];
554
659
  role: "user";
555
660
  localKey?: string | undefined;
556
661
  meta?: {
@@ -567,7 +672,16 @@ declare const UserMessageSchema: z.ZodObject<{
567
672
  content: {
568
673
  type: "text";
569
674
  text: string;
570
- };
675
+ } | ({
676
+ type: "text";
677
+ text: string;
678
+ } | {
679
+ type: "image_ref";
680
+ blobId: string;
681
+ mimeType: "image/jpeg" | "image/png" | "image/gif" | "image/webp";
682
+ width?: number | undefined;
683
+ height?: number | undefined;
684
+ })[];
571
685
  role: "user";
572
686
  localKey?: string | undefined;
573
687
  meta?: {
package/dist/lib.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, l as logger } from './types-D4_aCy-H.mjs';
1
+ export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, l as logger } from './types-BmcSXhbq.mjs';
2
2
  import 'axios';
3
3
  import 'chalk';
4
4
  import 'fs';
@@ -1,4 +1,4 @@
1
- import { c as configuration, l as logger, d as decodeBase64, b as decrypt, f as formatTimeAgo, e as libsodiumDecryptFromPublicKey } from './types-D4_aCy-H.mjs';
1
+ import { c as configuration, l as logger, d as decodeBase64, b as decrypt, f as formatTimeAgo, e as libsodiumDecryptFromPublicKey } from './types-BmcSXhbq.mjs';
2
2
  import axios from 'axios';
3
3
  import { existsSync, readdirSync, statSync, readFileSync } from 'fs';
4
4
  import { join } from 'path';
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var types = require('./types-CllU28mx.cjs');
3
+ var types = require('./types-0ILrOpLt.cjs');
4
4
  var axios = require('axios');
5
5
  var fs = require('fs');
6
6
  var path = require('path');
@@ -1,4 +1,4 @@
1
- import { c as configuration, b as decrypt, d as decodeBase64, l as logger, g as encodeBase64, h as encrypt } from './types-D4_aCy-H.mjs';
1
+ import { c as configuration, b as decrypt, d as decodeBase64, l as logger, g as encodeBase64, h as encrypt } from './types-BmcSXhbq.mjs';
2
2
  import axios from 'axios';
3
3
  import { io } from 'socket.io-client';
4
4
  import 'chalk';
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var types = require('./types-CllU28mx.cjs');
3
+ var types = require('./types-0ILrOpLt.cjs');
4
4
  var axios = require('axios');
5
5
  var socket_ioClient = require('socket.io-client');
6
6
  require('chalk');
@@ -2,14 +2,14 @@
2
2
 
3
3
  var ink = require('ink');
4
4
  var React = require('react');
5
- var types = require('./types-CllU28mx.cjs');
5
+ var types = require('./types-0ILrOpLt.cjs');
6
6
  var index_js = require('@modelcontextprotocol/sdk/client/index.js');
7
7
  var stdio_js = require('@modelcontextprotocol/sdk/client/stdio.js');
8
8
  var z = require('zod');
9
9
  var types_js = require('@modelcontextprotocol/sdk/types.js');
10
10
  var child_process = require('child_process');
11
11
  var node_crypto = require('node:crypto');
12
- var index = require('./index-y8CVImEp.cjs');
12
+ var index = require('./index-B8KwpPnt.cjs');
13
13
  var os = require('node:os');
14
14
  var node_path = require('node:path');
15
15
  var fs = require('node:fs');
@@ -754,6 +754,21 @@ const CodexDisplay = ({ messageBuffer, logPath, onExit }) => {
754
754
  ));
755
755
  };
756
756
 
757
+ function extractTextFromContent(content) {
758
+ if (!Array.isArray(content)) {
759
+ if (content.type === "text") {
760
+ return content.text;
761
+ }
762
+ return "";
763
+ }
764
+ const textParts = [];
765
+ for (const block of content) {
766
+ if (block.type === "text") {
767
+ textParts.push(block.text);
768
+ }
769
+ }
770
+ return textParts.join("\n");
771
+ }
757
772
  function emitReadyIfIdle({ pending, queueSize, shouldExit, sendReady, notify }) {
758
773
  if (shouldExit) {
759
774
  return false;
@@ -849,7 +864,8 @@ async function runCodex(opts) {
849
864
  permissionMode: messagePermissionMode || "default",
850
865
  model: messageModel
851
866
  };
852
- messageQueue.push(message.content.text, enhancedMode);
867
+ const textContent = extractTextFromContent(message.content);
868
+ messageQueue.push(textContent, enhancedMode);
853
869
  });
854
870
  let thinking = false;
855
871
  session.keepAlive(thinking, "remote");
@@ -1,13 +1,13 @@
1
1
  import { useStdout, useInput, Box, Text, render } from 'ink';
2
2
  import React, { useState, useRef, useEffect, useCallback } from 'react';
3
- import { l as logger, A as ApiClient, r as readSettings, p as projectPath, c as configuration, i as packageJson } from './types-D4_aCy-H.mjs';
3
+ import { l as logger, A as ApiClient, r as readSettings, p as projectPath, c as configuration, i as packageJson } from './types-BmcSXhbq.mjs';
4
4
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
5
5
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
6
6
  import { z } from 'zod';
7
7
  import { ElicitRequestSchema } from '@modelcontextprotocol/sdk/types.js';
8
8
  import { execSync } from 'child_process';
9
9
  import { randomUUID } from 'node:crypto';
10
- import { i as initialMachineMetadata, n as notifyDaemonSessionStarted, M as MessageQueue2, h as hashObject, r as registerKillSessionHandler, a as MessageBuffer, s as startHappyServer, t as trimIdent, b as stopCaffeinate } from './index-DNeoLdzx.mjs';
10
+ import { i as initialMachineMetadata, n as notifyDaemonSessionStarted, M as MessageQueue2, h as hashObject, r as registerKillSessionHandler, a as MessageBuffer, s as startHappyServer, t as trimIdent, b as stopCaffeinate } from './index-BfGA--2C.mjs';
11
11
  import os from 'node:os';
12
12
  import { resolve, join } from 'node:path';
13
13
  import fs from 'node:fs';
@@ -752,6 +752,21 @@ const CodexDisplay = ({ messageBuffer, logPath, onExit }) => {
752
752
  ));
753
753
  };
754
754
 
755
+ function extractTextFromContent(content) {
756
+ if (!Array.isArray(content)) {
757
+ if (content.type === "text") {
758
+ return content.text;
759
+ }
760
+ return "";
761
+ }
762
+ const textParts = [];
763
+ for (const block of content) {
764
+ if (block.type === "text") {
765
+ textParts.push(block.text);
766
+ }
767
+ }
768
+ return textParts.join("\n");
769
+ }
755
770
  function emitReadyIfIdle({ pending, queueSize, shouldExit, sendReady, notify }) {
756
771
  if (shouldExit) {
757
772
  return false;
@@ -847,7 +862,8 @@ async function runCodex(opts) {
847
862
  permissionMode: messagePermissionMode || "default",
848
863
  model: messageModel
849
864
  };
850
- messageQueue.push(message.content.text, enhancedMode);
865
+ const textContent = extractTextFromContent(message.content);
866
+ messageQueue.push(textContent, enhancedMode);
851
867
  });
852
868
  let thinking = false;
853
869
  session.keepAlive(thinking, "remote");
@@ -41,7 +41,7 @@ function _interopNamespaceDefault(e) {
41
41
  var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
42
42
 
43
43
  var name = "@zhigang1992/happy-cli";
44
- var version = "0.12.2";
44
+ var version = "0.12.5";
45
45
  var description = "Mobile and Web client for Claude Code and Codex";
46
46
  var author = "Kirill Dubovitskiy";
47
47
  var license = "MIT";
@@ -332,6 +332,43 @@ function decryptWithDataKey(bundle, dataKey) {
332
332
  return null;
333
333
  }
334
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
+ }
360
+ function decryptBlobWithSecretBox(bundle, secret) {
361
+ if (bundle.length < 1 + tweetnacl.secretbox.nonceLength) {
362
+ return null;
363
+ }
364
+ if (bundle[0] !== 0) {
365
+ return null;
366
+ }
367
+ const nonce = bundle.slice(1, 1 + tweetnacl.secretbox.nonceLength);
368
+ const ciphertext = bundle.slice(1 + tweetnacl.secretbox.nonceLength);
369
+ const decrypted = tweetnacl.secretbox.open(ciphertext, nonce, secret);
370
+ return decrypted || null;
371
+ }
335
372
  function encrypt(key, variant, data) {
336
373
  if (variant === "legacy") {
337
374
  return encryptLegacy(data, key);
@@ -346,6 +383,13 @@ function decrypt(key, variant, data) {
346
383
  return decryptWithDataKey(data, key);
347
384
  }
348
385
  }
386
+ function decryptBlob(key, variant, bundle) {
387
+ if (variant === "legacy") {
388
+ return decryptBlobWithSecretBox(bundle, key);
389
+ } else {
390
+ return decryptBlobWithDataKey(bundle, key);
391
+ }
392
+ }
349
393
  function authChallenge(secret) {
350
394
  const keypair = tweetnacl.sign.keyPair.fromSeed(secret);
351
395
  const challenge = getRandomBytes(32);
@@ -873,12 +917,27 @@ z.z.object({
873
917
  agentStateVersion: z.z.number()
874
918
  })
875
919
  });
920
+ const TextContentSchema = z.z.object({
921
+ type: z.z.literal("text"),
922
+ text: z.z.string()
923
+ });
924
+ const ImageRefContentSchema = z.z.object({
925
+ type: z.z.literal("image_ref"),
926
+ blobId: z.z.string(),
927
+ mimeType: z.z.enum(["image/jpeg", "image/png", "image/gif", "image/webp"]),
928
+ width: z.z.number().optional(),
929
+ height: z.z.number().optional()
930
+ });
931
+ const ContentBlockSchema = z.z.union([TextContentSchema, ImageRefContentSchema]);
932
+ const UserMessageContentSchema = z.z.union([
933
+ TextContentSchema,
934
+ // Legacy: single text content
935
+ z.z.array(ContentBlockSchema)
936
+ // New: array of content blocks
937
+ ]);
876
938
  const UserMessageSchema = z.z.object({
877
939
  role: z.z.literal("user"),
878
- content: z.z.object({
879
- type: z.z.literal("text"),
880
- text: z.z.string()
881
- }),
940
+ content: UserMessageContentSchema,
882
941
  localKey: z.z.string().optional(),
883
942
  // Mobile messages include this
884
943
  meta: MessageMetaSchema.optional()
@@ -990,12 +1049,29 @@ class RpcHandlerManager {
990
1049
  encryptionVariant;
991
1050
  logger;
992
1051
  socket = null;
1052
+ sessionContext = null;
993
1053
  constructor(config) {
994
1054
  this.scopePrefix = config.scopePrefix;
995
1055
  this.encryptionKey = config.encryptionKey;
996
1056
  this.encryptionVariant = config.encryptionVariant;
997
1057
  this.logger = config.logger || ((msg, data) => logger.debug(msg, data));
998
1058
  }
1059
+ /**
1060
+ * Set the session context (path and environment)
1061
+ * This should be called after direnv environment is loaded
1062
+ * @param context - The session context with path and environment
1063
+ */
1064
+ setSessionContext(context) {
1065
+ this.sessionContext = context;
1066
+ this.logger("[RPC] Session context set", { path: context.path, envVarCount: Object.keys(context.env).length });
1067
+ }
1068
+ /**
1069
+ * Get the current session context
1070
+ * @returns The session context or null if not set
1071
+ */
1072
+ getSessionContext() {
1073
+ return this.sessionContext;
1074
+ }
999
1075
  /**
1000
1076
  * Register an RPC handler for a specific method
1001
1077
  * @param method - The method name (without prefix)
@@ -1073,7 +1149,7 @@ class RpcHandlerManager {
1073
1149
  }
1074
1150
  }
1075
1151
 
1076
- 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-CllU28mx.cjs', document.baseURI).href))));
1152
+ 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-0ILrOpLt.cjs', document.baseURI).href))));
1077
1153
  function projectPath() {
1078
1154
  const path$1 = path.resolve(__dirname$1, "..");
1079
1155
  return path$1;
@@ -1171,10 +1247,13 @@ function registerCommonHandlers(rpcHandlerManager) {
1171
1247
  rpcHandlerManager.registerHandler("bash", async (data) => {
1172
1248
  logger.debug("Shell command request:", data.command);
1173
1249
  try {
1250
+ const sessionContext = rpcHandlerManager.getSessionContext();
1174
1251
  const options = {
1175
1252
  cwd: data.cwd,
1176
- timeout: data.timeout || 3e4
1253
+ timeout: data.timeout || 3e4,
1177
1254
  // Default 30 seconds timeout
1255
+ // Use session environment if available, otherwise inherit process.env
1256
+ env: sessionContext?.env ?? process.env
1178
1257
  };
1179
1258
  const { stdout, stderr } = await execAsync(data.command, options);
1180
1259
  return {
@@ -1704,6 +1783,60 @@ class ApiSessionClient extends node_events.EventEmitter {
1704
1783
  logger.debug("[API] socket.close() called");
1705
1784
  this.socket.close();
1706
1785
  }
1786
+ /**
1787
+ * Download and decrypt a blob from the server
1788
+ * @param blobId - The blob ID to download
1789
+ * @returns The decrypted binary data and metadata, or null if download/decryption fails
1790
+ */
1791
+ async downloadBlob(blobId) {
1792
+ try {
1793
+ const response = await axios.get(
1794
+ `${configuration.serverUrl}/v1/sessions/${this.sessionId}/blobs/${blobId}`,
1795
+ {
1796
+ headers: {
1797
+ "Authorization": `Bearer ${this.token}`
1798
+ },
1799
+ responseType: "arraybuffer"
1800
+ }
1801
+ );
1802
+ const encryptedData = new Uint8Array(response.data);
1803
+ const mimeType = response.headers["x-blob-mimetype"];
1804
+ const originalSize = parseInt(response.headers["x-blob-size"], 10);
1805
+ const decryptedData = decryptBlob(this.encryptionKey, this.encryptionVariant, encryptedData);
1806
+ if (!decryptedData) {
1807
+ logger.debug("[API] Failed to decrypt blob");
1808
+ return null;
1809
+ }
1810
+ return {
1811
+ data: decryptedData,
1812
+ mimeType,
1813
+ size: originalSize
1814
+ };
1815
+ } catch (error) {
1816
+ logger.debug("[API] Failed to download blob:", error);
1817
+ return null;
1818
+ }
1819
+ }
1820
+ /**
1821
+ * Convert an image_ref content block to a Claude API image content block
1822
+ * Downloads, decrypts, and base64 encodes the image
1823
+ * @param imageRef - The image reference content block
1824
+ * @returns Claude API image content block, or null if conversion fails
1825
+ */
1826
+ async resolveImageRef(imageRef) {
1827
+ const blob = await this.downloadBlob(imageRef.blobId);
1828
+ if (!blob) {
1829
+ return null;
1830
+ }
1831
+ return {
1832
+ type: "image",
1833
+ source: {
1834
+ type: "base64",
1835
+ media_type: blob.mimeType,
1836
+ data: Buffer.from(blob.data).toString("base64")
1837
+ }
1838
+ };
1839
+ }
1707
1840
  }
1708
1841
 
1709
1842
  class ApiMachineClient {