@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,11 +1,15 @@
1
1
  import chalk from 'chalk';
2
2
  import os$1, { homedir } from 'node:os';
3
3
  import { randomUUID, randomBytes, createHmac } from 'node:crypto';
4
- import { l as logger, p as projectPath, j as backoff, k as delay, R as RawJSONLinesSchema, m as AsyncLock, c as configuration, n as readDaemonState, o as clearDaemonState, i as packageJson, r as readSettings, q as readCredentials, g as encodeBase64, u as updateSettings, s as encodeBase64Url, d as decodeBase64, w as writeCredentialsLegacy, t as writeCredentialsDataKey, v as acquireDaemonLock, x as writeDaemonState, A as ApiClient, y as releaseDaemonLock, z as authChallenge, B as clearCredentials, C as clearMachineId, D as getLatestDaemonLog } from './types-CGco5Y-r.mjs';
4
+ import { l as logger, p as projectPath, j as backoff, k as delay, R as RawJSONLinesSchema, m as AsyncLock, c as configuration, n as readDaemonState, o as clearDaemonState, i as packageJson, r as readSettings, q as readCredentials, g as encodeBase64, u as updateSettings, s as encodeBase64Url, d as decodeBase64, w as writeCredentialsLegacy, t as writeCredentialsDataKey, v as acquireDaemonLock, x as writeDaemonState, A as ApiClient, y as releaseDaemonLock, z as authChallenge, B as clearCredentials, C as clearMachineId, D as getLatestDaemonLog } from './types-Cw6y7GyQ.mjs';
5
5
  import { spawn, execSync, execFileSync } from 'node:child_process';
6
6
  import { resolve, join } from 'node:path';
7
7
  import { createInterface } from 'node:readline';
8
8
  import { existsSync, readFileSync, mkdirSync, watch, readdirSync, statSync, rmSync } from 'node:fs';
9
+ import { exec, spawn as spawn$1, execSync as execSync$1 } from 'child_process';
10
+ import { promisify } from 'util';
11
+ import { existsSync as existsSync$1, readFileSync as readFileSync$1, writeFileSync, chmodSync, unlinkSync } from 'fs';
12
+ import { join as join$1, dirname, basename } from 'path';
9
13
  import { readFile } from 'node:fs/promises';
10
14
  import fs, { watch as watch$1, access } from 'fs/promises';
11
15
  import { useStdout, useInput, Box, Text, render } from 'ink';
@@ -17,9 +21,6 @@ import 'socket.io-client';
17
21
  import tweetnacl from 'tweetnacl';
18
22
  import 'expo-server-sdk';
19
23
  import { createHash, randomBytes as randomBytes$1 } from 'crypto';
20
- import { spawn as spawn$1, execSync as execSync$1, exec } from 'child_process';
21
- import { readFileSync as readFileSync$1, existsSync as existsSync$1, writeFileSync, chmodSync, unlinkSync } from 'fs';
22
- import { join as join$1 } from 'path';
23
24
  import psList from 'ps-list';
24
25
  import spawn$2 from 'cross-spawn';
25
26
  import os from 'os';
@@ -34,7 +35,6 @@ import { createServer } from 'node:http';
34
35
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
35
36
  import * as hex from '@stablelib/hex';
36
37
  import { createServer as createServer$1 } from 'http';
37
- import { promisify } from 'util';
38
38
 
39
39
  class Session {
40
40
  path;
@@ -218,6 +218,99 @@ const systemPrompt = (() => {
218
218
  }
219
219
  })();
220
220
 
221
+ const execAsync$1 = promisify(exec);
222
+ let direnvAvailable = null;
223
+ async function isDirenvAvailable() {
224
+ if (direnvAvailable !== null) {
225
+ return direnvAvailable;
226
+ }
227
+ try {
228
+ await execAsync$1("direnv version", { timeout: 5e3 });
229
+ direnvAvailable = true;
230
+ logger.debug("[direnv] direnv is available");
231
+ } catch {
232
+ direnvAvailable = false;
233
+ logger.debug("[direnv] direnv is not available");
234
+ }
235
+ return direnvAvailable;
236
+ }
237
+ function findEnvrcDirectory(startDir) {
238
+ let dir = startDir;
239
+ const root = "/";
240
+ while (dir !== root) {
241
+ const envrcPath = join$1(dir, ".envrc");
242
+ if (existsSync$1(envrcPath)) {
243
+ logger.debug(`[direnv] Found .envrc at: ${envrcPath}`);
244
+ return dir;
245
+ }
246
+ const parent = dirname(dir);
247
+ if (parent === dir) {
248
+ break;
249
+ }
250
+ dir = parent;
251
+ }
252
+ logger.debug(`[direnv] No .envrc found in directory tree for: ${startDir}`);
253
+ return null;
254
+ }
255
+ async function loadDirenvEnvironment(cwd) {
256
+ if (!await isDirenvAvailable()) {
257
+ return {};
258
+ }
259
+ const envrcDir = findEnvrcDirectory(cwd);
260
+ if (!envrcDir) {
261
+ return {};
262
+ }
263
+ try {
264
+ logger.debug(`[direnv] Loading environment for: ${cwd}`);
265
+ const { stdout, stderr } = await execAsync$1("direnv export json", {
266
+ cwd,
267
+ timeout: 1e4,
268
+ // 10 second timeout
269
+ env: {
270
+ ...process.env,
271
+ // Ensure direnv doesn't prompt for allowance interactively
272
+ DIRENV_LOG_FORMAT: ""
273
+ }
274
+ });
275
+ if (stderr) {
276
+ logger.debug(`[direnv] stderr: ${stderr}`);
277
+ }
278
+ const trimmed = stdout.trim();
279
+ if (!trimmed) {
280
+ logger.debug("[direnv] No environment changes from direnv");
281
+ return {};
282
+ }
283
+ const direnvVars = JSON.parse(trimmed);
284
+ const varCount = Object.keys(direnvVars).length;
285
+ logger.debug(`[direnv] Loaded ${varCount} environment variables`);
286
+ return direnvVars;
287
+ } catch (error) {
288
+ if (error instanceof Error) {
289
+ logger.debug(`[direnv] Failed to load environment: ${error.message}`);
290
+ if (error.message.includes("is blocked")) {
291
+ logger.debug('[direnv] Hint: Run "direnv allow" in the project directory to allow the .envrc');
292
+ }
293
+ }
294
+ return {};
295
+ }
296
+ }
297
+ async function loadAndMergeEnvironment(cwd, existingEnv = process.env, overrideEnv = {}) {
298
+ const direnvVars = await loadDirenvEnvironment(cwd);
299
+ const merged = {};
300
+ for (const [key, value] of Object.entries(existingEnv)) {
301
+ if (value !== void 0) {
302
+ merged[key] = value;
303
+ }
304
+ }
305
+ for (const [key, value] of Object.entries(direnvVars)) {
306
+ merged[key] = value;
307
+ }
308
+ for (const [key, value] of Object.entries(overrideEnv)) {
309
+ merged[key] = value;
310
+ }
311
+ return merged;
312
+ }
313
+
221
314
  const claudeCliPath = resolve(join(projectPath(), "scripts", "claude_local_launcher.cjs"));
222
315
  async function claudeLocal(opts) {
223
316
  const projectDir = getProjectPath(opts.path);
@@ -259,6 +352,15 @@ async function claudeLocal(opts) {
259
352
  }
260
353
  };
261
354
  try {
355
+ const direnvVars = await loadDirenvEnvironment(opts.path);
356
+ if (Object.keys(direnvVars).length > 0) {
357
+ logger.debug(`[ClaudeLocal] Loaded ${Object.keys(direnvVars).length} direnv environment variables`);
358
+ }
359
+ const env = {
360
+ ...process.env,
361
+ ...direnvVars,
362
+ ...opts.claudeEnvVars
363
+ };
262
364
  process.stdin.pause();
263
365
  await new Promise((r, reject) => {
264
366
  const args = [];
@@ -278,10 +380,6 @@ async function claudeLocal(opts) {
278
380
  if (!claudeCliPath || !existsSync(claudeCliPath)) {
279
381
  throw new Error("Claude local launcher not found. Please ensure HAPPY_PROJECT_ROOT is set correctly for development.");
280
382
  }
281
- const env = {
282
- ...process.env,
283
- ...opts.claudeEnvVars
284
- };
285
383
  const child = spawn("node", [claudeCliPath, ...args], {
286
384
  stdio: ["inherit", "inherit", "inherit", "pipe"],
287
385
  signal: opts.abort,
@@ -1522,6 +1620,13 @@ async function claudeRemote(opts) {
1522
1620
  }
1523
1621
  }
1524
1622
  }
1623
+ const direnvVars = await loadDirenvEnvironment(opts.path);
1624
+ if (Object.keys(direnvVars).length > 0) {
1625
+ logger.debug(`[claudeRemote] Loaded ${Object.keys(direnvVars).length} direnv environment variables`);
1626
+ }
1627
+ Object.entries(direnvVars).forEach(([key, value]) => {
1628
+ process.env[key] = value;
1629
+ });
1525
1630
  if (opts.claudeEnvVars) {
1526
1631
  Object.entries(opts.claudeEnvVars).forEach(([key, value]) => {
1527
1632
  process.env[key] = value;
@@ -1628,12 +1733,32 @@ Echo message: ${echoMessage}` : "");
1628
1733
  }
1629
1734
  }
1630
1735
  };
1736
+ async function buildMessageContent(text, imageRefs) {
1737
+ if (!imageRefs || imageRefs.length === 0 || !opts.resolveImageRefs) {
1738
+ return text;
1739
+ }
1740
+ logger.debug(`[claudeRemote] Resolving ${imageRefs.length} image references`);
1741
+ const resolvedImages = await opts.resolveImageRefs(imageRefs);
1742
+ logger.debug(`[claudeRemote] Resolved ${resolvedImages.length} images`);
1743
+ if (resolvedImages.length === 0) {
1744
+ return text;
1745
+ }
1746
+ const content = [];
1747
+ for (const img of resolvedImages) {
1748
+ content.push(img);
1749
+ }
1750
+ if (text) {
1751
+ content.push({ type: "text", text });
1752
+ }
1753
+ return content;
1754
+ }
1631
1755
  let messages = new PushableAsyncIterable();
1756
+ const initialContent = await buildMessageContent(initial.message, initial.mode.imageRefs);
1632
1757
  messages.push({
1633
1758
  type: "user",
1634
1759
  message: {
1635
1760
  role: "user",
1636
- content: initial.message
1761
+ content: initialContent
1637
1762
  }
1638
1763
  });
1639
1764
  const response = query({
@@ -1674,7 +1799,8 @@ Echo message: ${echoMessage}` : "");
1674
1799
  return;
1675
1800
  }
1676
1801
  mode = next.mode;
1677
- messages.push({ type: "user", message: { role: "user", content: next.message } });
1802
+ const nextContent = await buildMessageContent(next.message, next.mode.imageRefs);
1803
+ messages.push({ type: "user", message: { role: "user", content: nextContent } });
1678
1804
  }
1679
1805
  if (message.type === "user") {
1680
1806
  const msg = message;
@@ -2780,6 +2906,18 @@ async function claudeRemoteLauncher(session) {
2780
2906
  isAborted: (toolCallId) => {
2781
2907
  return permissionHandler.isAborted(toolCallId);
2782
2908
  },
2909
+ resolveImageRefs: async (imageRefs) => {
2910
+ const resolved = [];
2911
+ for (const imageRef of imageRefs) {
2912
+ const image = await session.client.resolveImageRef(imageRef);
2913
+ if (image) {
2914
+ resolved.push(image);
2915
+ } else {
2916
+ logger.debug(`[remote]: Failed to resolve image ref: ${imageRef.blobId}`);
2917
+ }
2918
+ }
2919
+ return resolved;
2920
+ },
2783
2921
  nextMessage: async () => {
2784
2922
  if (pending) {
2785
2923
  let p = pending;
@@ -2823,9 +2961,10 @@ async function claudeRemoteLauncher(session) {
2823
2961
  onReady: () => {
2824
2962
  if (!pending && session.queue.size() === 0) {
2825
2963
  session.client.sendSessionEvent({ type: "ready" });
2964
+ const folderName = basename(session.path);
2826
2965
  session.api.push().sendToAllDevices(
2827
- "It's ready!",
2828
- `Claude is waiting for your command`,
2966
+ `Ready`,
2967
+ folderName,
2829
2968
  { sessionId: session.client.sessionId }
2830
2969
  );
2831
2970
  }
@@ -2884,6 +3023,16 @@ async function claudeRemoteLauncher(session) {
2884
3023
 
2885
3024
  async function loop(opts) {
2886
3025
  const logPath = logger.logFilePath;
3026
+ const sessionEnv = await loadAndMergeEnvironment(
3027
+ opts.path,
3028
+ process.env,
3029
+ opts.claudeEnvVars ?? {}
3030
+ );
3031
+ logger.debug(`[loop] Loaded session environment with ${Object.keys(sessionEnv).length} variables`);
3032
+ opts.session.rpcHandlerManager.setSessionContext({
3033
+ path: opts.path,
3034
+ env: sessionEnv
3035
+ });
2887
3036
  let session = new Session({
2888
3037
  api: opts.api,
2889
3038
  client: opts.session,
@@ -4807,6 +4956,27 @@ function registerKillSessionHandler(rpcHandlerManager, killThisHappy) {
4807
4956
  });
4808
4957
  }
4809
4958
 
4959
+ function extractTextFromContent(content) {
4960
+ if (!Array.isArray(content)) {
4961
+ if (content.type === "text") {
4962
+ return content.text;
4963
+ }
4964
+ return "";
4965
+ }
4966
+ const textParts = [];
4967
+ for (const block of content) {
4968
+ if (block.type === "text") {
4969
+ textParts.push(block.text);
4970
+ }
4971
+ }
4972
+ return textParts.join("\n");
4973
+ }
4974
+ function extractImageRefs(content) {
4975
+ if (!Array.isArray(content)) {
4976
+ return [];
4977
+ }
4978
+ return content.filter((block) => block.type === "image_ref");
4979
+ }
4810
4980
  async function runClaude(credentials, options = {}) {
4811
4981
  const workingDirectory = process.cwd();
4812
4982
  const sessionTag = randomUUID();
@@ -4973,7 +5143,12 @@ async function runClaude(credentials, options = {}) {
4973
5143
  } else {
4974
5144
  logger.debug(`[loop] User message received with no disallowed tools override, using current: ${currentDisallowedTools ? currentDisallowedTools.join(", ") : "none"}`);
4975
5145
  }
4976
- const specialCommand = parseSpecialCommand(message.content.text);
5146
+ const textContent = extractTextFromContent(message.content);
5147
+ const imageRefs = extractImageRefs(message.content);
5148
+ if (imageRefs.length > 0) {
5149
+ logger.debug(`[loop] User message contains ${imageRefs.length} image(s)`);
5150
+ }
5151
+ const specialCommand = parseSpecialCommand(textContent);
4977
5152
  if (specialCommand.type === "compact") {
4978
5153
  logger.debug("[start] Detected /compact command");
4979
5154
  const enhancedMode2 = {
@@ -4983,9 +5158,10 @@ async function runClaude(credentials, options = {}) {
4983
5158
  customSystemPrompt: messageCustomSystemPrompt,
4984
5159
  appendSystemPrompt: messageAppendSystemPrompt,
4985
5160
  allowedTools: messageAllowedTools,
4986
- disallowedTools: messageDisallowedTools
5161
+ disallowedTools: messageDisallowedTools,
5162
+ imageRefs: imageRefs.length > 0 ? imageRefs : void 0
4987
5163
  };
4988
- messageQueue.pushIsolateAndClear(specialCommand.originalMessage || message.content.text, enhancedMode2);
5164
+ messageQueue.pushIsolateAndClear(specialCommand.originalMessage || textContent, enhancedMode2);
4989
5165
  logger.debugLargeJson("[start] /compact command pushed to queue:", message);
4990
5166
  return;
4991
5167
  }
@@ -4998,9 +5174,10 @@ async function runClaude(credentials, options = {}) {
4998
5174
  customSystemPrompt: messageCustomSystemPrompt,
4999
5175
  appendSystemPrompt: messageAppendSystemPrompt,
5000
5176
  allowedTools: messageAllowedTools,
5001
- disallowedTools: messageDisallowedTools
5177
+ disallowedTools: messageDisallowedTools,
5178
+ imageRefs: imageRefs.length > 0 ? imageRefs : void 0
5002
5179
  };
5003
- messageQueue.pushIsolateAndClear(specialCommand.originalMessage || message.content.text, enhancedMode2);
5180
+ messageQueue.pushIsolateAndClear(specialCommand.originalMessage || textContent, enhancedMode2);
5004
5181
  logger.debugLargeJson("[start] /compact command pushed to queue:", message);
5005
5182
  return;
5006
5183
  }
@@ -5011,9 +5188,10 @@ async function runClaude(credentials, options = {}) {
5011
5188
  customSystemPrompt: messageCustomSystemPrompt,
5012
5189
  appendSystemPrompt: messageAppendSystemPrompt,
5013
5190
  allowedTools: messageAllowedTools,
5014
- disallowedTools: messageDisallowedTools
5191
+ disallowedTools: messageDisallowedTools,
5192
+ imageRefs: imageRefs.length > 0 ? imageRefs : void 0
5015
5193
  };
5016
- messageQueue.push(message.content.text, enhancedMode);
5194
+ messageQueue.push(textContent, enhancedMode);
5017
5195
  logger.debugLargeJson("User message pushed to queue:", message);
5018
5196
  });
5019
5197
  const cleanup = async () => {
@@ -6192,7 +6370,7 @@ async function handleConnectVendor(vendor, displayName) {
6192
6370
  return;
6193
6371
  } else if (subcommand === "codex") {
6194
6372
  try {
6195
- const { runCodex } = await import('./runCodex-CLGYMNs2.mjs');
6373
+ const { runCodex } = await import('./runCodex-CYkmZphO.mjs');
6196
6374
  let startedBy = void 0;
6197
6375
  for (let i = 1; i < args.length; i++) {
6198
6376
  if (args[i] === "--started-by") {
@@ -6237,7 +6415,7 @@ async function handleConnectVendor(vendor, displayName) {
6237
6415
  } else if (subcommand === "list") {
6238
6416
  try {
6239
6417
  const { credentials } = await authAndSetupMachineIfNeeded();
6240
- const { listSessions } = await import('./list-hET5tyMc.mjs');
6418
+ const { listSessions } = await import('./list-BvtUKVTq.mjs');
6241
6419
  let sessionId;
6242
6420
  let titleFilter;
6243
6421
  let recentMsgs;
@@ -6339,7 +6517,7 @@ Examples:
6339
6517
  process.exit(1);
6340
6518
  }
6341
6519
  const { credentials } = await authAndSetupMachineIfNeeded();
6342
- const { promptSession } = await import('./prompt-Dz7G8yGx.mjs');
6520
+ const { promptSession } = await import('./prompt-BbMNN7fl.mjs');
6343
6521
  await promptSession(credentials, sessionId, promptText, timeoutMinutes ?? void 0);
6344
6522
  } catch (error) {
6345
6523
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");