@zhigang1992/happy-cli 0.12.1 → 0.12.3

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 (35) hide show
  1. package/dist/{index-DsHtmQqP.mjs → index-DNeoLdzx.mjs} +8 -7
  2. package/dist/{index-BOBrKhX5.cjs → index-y8CVImEp.cjs} +9 -8
  3. package/dist/index.cjs +3 -3
  4. package/dist/index.mjs +3 -3
  5. package/dist/lib.cjs +1 -2
  6. package/dist/lib.mjs +1 -2
  7. package/dist/{list-hET5tyMc.mjs → list-D_NjiLPx.mjs} +1 -1
  8. package/dist/{list-BW6QBLa1.cjs → list-DiamEbqL.cjs} +1 -1
  9. package/dist/{prompt-Dz7G8yGx.mjs → prompt-CJh1Mo2A.mjs} +1 -2
  10. package/dist/{prompt-DXkgjktW.cjs → prompt-Cu47wZlI.cjs} +1 -2
  11. package/dist/{runCodex-CLGYMNs2.mjs → runCodex-B5ZlUUec.mjs} +3 -3
  12. package/dist/{runCodex-CylcX5Ug.cjs → runCodex-DErgypij.cjs} +3 -3
  13. package/dist/{types-BsjUgWOx.cjs → types-CllU28mx.cjs} +31 -13
  14. package/dist/{types-CGco5Y-r.mjs → types-D4_aCy-H.mjs} +33 -15
  15. package/package.json +2 -4
  16. package/scripts/download-tool.cjs +187 -0
  17. package/scripts/ripgrep_launcher.cjs +53 -2
  18. package/scripts/tools-config.cjs +119 -0
  19. package/tools/archives/difftastic-LICENSE +0 -21
  20. package/tools/archives/difftastic-arm64-darwin.tar.gz +0 -0
  21. package/tools/archives/difftastic-arm64-linux.tar.gz +0 -0
  22. package/tools/archives/difftastic-x64-darwin.tar.gz +0 -0
  23. package/tools/archives/difftastic-x64-linux.tar.gz +0 -0
  24. package/tools/archives/difftastic-x64-win32.tar.gz +0 -0
  25. package/tools/archives/ripgrep-LICENSE +0 -3
  26. package/tools/archives/ripgrep-arm64-darwin.tar.gz +0 -0
  27. package/tools/archives/ripgrep-arm64-linux.tar.gz +0 -0
  28. package/tools/archives/ripgrep-x64-darwin.tar.gz +0 -0
  29. package/tools/archives/ripgrep-x64-linux.tar.gz +0 -0
  30. package/tools/archives/ripgrep-x64-win32.tar.gz +0 -0
  31. package/tools/licenses/difftastic-LICENSE +0 -21
  32. package/tools/licenses/ripgrep-LICENSE +0 -3
  33. package/tools/unpacked/difft +0 -0
  34. package/tools/unpacked/rg +0 -0
  35. package/tools/unpacked/ripgrep.node +0 -0
@@ -1,7 +1,7 @@
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-D4_aCy-H.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';
@@ -16,10 +16,10 @@ import 'node:events';
16
16
  import 'socket.io-client';
17
17
  import tweetnacl from 'tweetnacl';
18
18
  import 'expo-server-sdk';
19
+ import { basename, join as join$1 } from 'path';
19
20
  import { createHash, randomBytes as randomBytes$1 } from 'crypto';
20
21
  import { spawn as spawn$1, execSync as execSync$1, exec } from 'child_process';
21
22
  import { readFileSync as readFileSync$1, existsSync as existsSync$1, writeFileSync, chmodSync, unlinkSync } from 'fs';
22
- import { join as join$1 } from 'path';
23
23
  import psList from 'ps-list';
24
24
  import spawn$2 from 'cross-spawn';
25
25
  import os from 'os';
@@ -2823,9 +2823,10 @@ async function claudeRemoteLauncher(session) {
2823
2823
  onReady: () => {
2824
2824
  if (!pending && session.queue.size() === 0) {
2825
2825
  session.client.sendSessionEvent({ type: "ready" });
2826
+ const folderName = basename(session.path);
2826
2827
  session.api.push().sendToAllDevices(
2827
- "It's ready!",
2828
- `Claude is waiting for your command`,
2828
+ `Ready`,
2829
+ folderName,
2829
2830
  { sessionId: session.client.sessionId }
2830
2831
  );
2831
2832
  }
@@ -6192,7 +6193,7 @@ async function handleConnectVendor(vendor, displayName) {
6192
6193
  return;
6193
6194
  } else if (subcommand === "codex") {
6194
6195
  try {
6195
- const { runCodex } = await import('./runCodex-CLGYMNs2.mjs');
6196
+ const { runCodex } = await import('./runCodex-B5ZlUUec.mjs');
6196
6197
  let startedBy = void 0;
6197
6198
  for (let i = 1; i < args.length; i++) {
6198
6199
  if (args[i] === "--started-by") {
@@ -6237,7 +6238,7 @@ async function handleConnectVendor(vendor, displayName) {
6237
6238
  } else if (subcommand === "list") {
6238
6239
  try {
6239
6240
  const { credentials } = await authAndSetupMachineIfNeeded();
6240
- const { listSessions } = await import('./list-hET5tyMc.mjs');
6241
+ const { listSessions } = await import('./list-D_NjiLPx.mjs');
6241
6242
  let sessionId;
6242
6243
  let titleFilter;
6243
6244
  let recentMsgs;
@@ -6339,7 +6340,7 @@ Examples:
6339
6340
  process.exit(1);
6340
6341
  }
6341
6342
  const { credentials } = await authAndSetupMachineIfNeeded();
6342
- const { promptSession } = await import('./prompt-Dz7G8yGx.mjs');
6343
+ const { promptSession } = await import('./prompt-CJh1Mo2A.mjs');
6343
6344
  await promptSession(credentials, sessionId, promptText, timeoutMinutes ?? void 0);
6344
6345
  } catch (error) {
6345
6346
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -3,7 +3,7 @@
3
3
  var chalk = require('chalk');
4
4
  var os = require('node:os');
5
5
  var node_crypto = require('node:crypto');
6
- var types = require('./types-BsjUgWOx.cjs');
6
+ var types = require('./types-CllU28mx.cjs');
7
7
  var node_child_process = require('node:child_process');
8
8
  var node_path = require('node:path');
9
9
  var node_readline = require('node:readline');
@@ -18,10 +18,10 @@ require('node:events');
18
18
  require('socket.io-client');
19
19
  var tweetnacl = require('tweetnacl');
20
20
  require('expo-server-sdk');
21
+ var path = require('path');
21
22
  var crypto = require('crypto');
22
23
  var child_process = require('child_process');
23
24
  var fs$2 = require('fs');
24
- var path = require('path');
25
25
  var psList = require('ps-list');
26
26
  var spawn = require('cross-spawn');
27
27
  var os$1 = require('os');
@@ -984,7 +984,7 @@ class AbortError extends Error {
984
984
  }
985
985
  }
986
986
 
987
- const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-BOBrKhX5.cjs', document.baseURI).href)));
987
+ const __filename$1 = node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index-y8CVImEp.cjs', document.baseURI).href)));
988
988
  const __dirname$1 = node_path.join(__filename$1, "..");
989
989
  function getDefaultClaudeCodePath() {
990
990
  return node_path.join(__dirname$1, "..", "..", "..", "node_modules", "@anthropic-ai", "claude-code", "cli.js");
@@ -2846,9 +2846,10 @@ async function claudeRemoteLauncher(session) {
2846
2846
  onReady: () => {
2847
2847
  if (!pending && session.queue.size() === 0) {
2848
2848
  session.client.sendSessionEvent({ type: "ready" });
2849
+ const folderName = path.basename(session.path);
2849
2850
  session.api.push().sendToAllDevices(
2850
- "It's ready!",
2851
- `Claude is waiting for your command`,
2851
+ `Ready`,
2852
+ folderName,
2852
2853
  { sessionId: session.client.sessionId }
2853
2854
  );
2854
2855
  }
@@ -6215,7 +6216,7 @@ async function handleConnectVendor(vendor, displayName) {
6215
6216
  return;
6216
6217
  } else if (subcommand === "codex") {
6217
6218
  try {
6218
- const { runCodex } = await Promise.resolve().then(function () { return require('./runCodex-CylcX5Ug.cjs'); });
6219
+ const { runCodex } = await Promise.resolve().then(function () { return require('./runCodex-DErgypij.cjs'); });
6219
6220
  let startedBy = void 0;
6220
6221
  for (let i = 1; i < args.length; i++) {
6221
6222
  if (args[i] === "--started-by") {
@@ -6260,7 +6261,7 @@ async function handleConnectVendor(vendor, displayName) {
6260
6261
  } else if (subcommand === "list") {
6261
6262
  try {
6262
6263
  const { credentials } = await authAndSetupMachineIfNeeded();
6263
- const { listSessions } = await Promise.resolve().then(function () { return require('./list-BW6QBLa1.cjs'); });
6264
+ const { listSessions } = await Promise.resolve().then(function () { return require('./list-DiamEbqL.cjs'); });
6264
6265
  let sessionId;
6265
6266
  let titleFilter;
6266
6267
  let recentMsgs;
@@ -6362,7 +6363,7 @@ Examples:
6362
6363
  process.exit(1);
6363
6364
  }
6364
6365
  const { credentials } = await authAndSetupMachineIfNeeded();
6365
- const { promptSession } = await Promise.resolve().then(function () { return require('./prompt-DXkgjktW.cjs'); });
6366
+ const { promptSession } = await Promise.resolve().then(function () { return require('./prompt-Cu47wZlI.cjs'); });
6366
6367
  await promptSession(credentials, sessionId, promptText, timeoutMinutes ?? void 0);
6367
6368
  } catch (error) {
6368
6369
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
package/dist/index.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  require('chalk');
4
- require('./index-BOBrKhX5.cjs');
5
- require('./types-BsjUgWOx.cjs');
4
+ require('./index-y8CVImEp.cjs');
5
+ require('./types-CllU28mx.cjs');
6
6
  require('zod');
7
7
  require('node:child_process');
8
8
  require('node:os');
@@ -20,10 +20,10 @@ require('node:events');
20
20
  require('socket.io-client');
21
21
  require('tweetnacl');
22
22
  require('expo-server-sdk');
23
+ require('path');
23
24
  require('crypto');
24
25
  require('child_process');
25
26
  require('fs');
26
- require('path');
27
27
  require('ps-list');
28
28
  require('cross-spawn');
29
29
  require('os');
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import 'chalk';
2
- import './index-DsHtmQqP.mjs';
3
- import './types-CGco5Y-r.mjs';
2
+ import './index-DNeoLdzx.mjs';
3
+ import './types-D4_aCy-H.mjs';
4
4
  import 'zod';
5
5
  import 'node:child_process';
6
6
  import 'node:os';
@@ -18,10 +18,10 @@ import 'node:events';
18
18
  import 'socket.io-client';
19
19
  import 'tweetnacl';
20
20
  import 'expo-server-sdk';
21
+ import 'path';
21
22
  import 'crypto';
22
23
  import 'child_process';
23
24
  import 'fs';
24
- import 'path';
25
25
  import 'ps-list';
26
26
  import 'cross-spawn';
27
27
  import 'os';
package/dist/lib.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var types = require('./types-BsjUgWOx.cjs');
3
+ var types = require('./types-CllU28mx.cjs');
4
4
  require('axios');
5
5
  require('chalk');
6
6
  require('fs');
@@ -19,7 +19,6 @@ require('fs/promises');
19
19
  require('crypto');
20
20
  require('path');
21
21
  require('url');
22
- require('os');
23
22
  require('expo-server-sdk');
24
23
 
25
24
 
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-CGco5Y-r.mjs';
1
+ export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, l as logger } from './types-D4_aCy-H.mjs';
2
2
  import 'axios';
3
3
  import 'chalk';
4
4
  import 'fs';
@@ -17,5 +17,4 @@ import 'fs/promises';
17
17
  import 'crypto';
18
18
  import 'path';
19
19
  import 'url';
20
- import 'os';
21
20
  import 'expo-server-sdk';
@@ -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-CGco5Y-r.mjs';
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';
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-BsjUgWOx.cjs');
3
+ var types = require('./types-CllU28mx.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-CGco5Y-r.mjs';
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';
2
2
  import axios from 'axios';
3
3
  import { io } from 'socket.io-client';
4
4
  import 'chalk';
@@ -17,7 +17,6 @@ import 'fs/promises';
17
17
  import 'crypto';
18
18
  import 'path';
19
19
  import 'url';
20
- import 'os';
21
20
  import 'expo-server-sdk';
22
21
 
23
22
  const DEFAULT_TIMEOUT_MINUTES = 20;
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var types = require('./types-BsjUgWOx.cjs');
3
+ var types = require('./types-CllU28mx.cjs');
4
4
  var axios = require('axios');
5
5
  var socket_ioClient = require('socket.io-client');
6
6
  require('chalk');
@@ -19,7 +19,6 @@ require('fs/promises');
19
19
  require('crypto');
20
20
  require('path');
21
21
  require('url');
22
- require('os');
23
22
  require('expo-server-sdk');
24
23
 
25
24
  const DEFAULT_TIMEOUT_MINUTES = 20;
@@ -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-CGco5Y-r.mjs';
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';
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-DsHtmQqP.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-DNeoLdzx.mjs';
11
11
  import os from 'node:os';
12
12
  import { resolve, join } from 'node:path';
13
13
  import fs from 'node:fs';
@@ -23,13 +23,13 @@ import 'fs/promises';
23
23
  import 'crypto';
24
24
  import 'path';
25
25
  import 'url';
26
- import 'os';
27
26
  import 'expo-server-sdk';
28
27
  import 'node:child_process';
29
28
  import 'node:readline';
30
29
  import 'node:url';
31
30
  import 'ps-list';
32
31
  import 'cross-spawn';
32
+ import 'os';
33
33
  import 'tmp';
34
34
  import 'qrcode-terminal';
35
35
  import 'open';
@@ -2,14 +2,14 @@
2
2
 
3
3
  var ink = require('ink');
4
4
  var React = require('react');
5
- var types = require('./types-BsjUgWOx.cjs');
5
+ var types = require('./types-CllU28mx.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-BOBrKhX5.cjs');
12
+ var index = require('./index-y8CVImEp.cjs');
13
13
  var os = require('node:os');
14
14
  var node_path = require('node:path');
15
15
  var fs = require('node:fs');
@@ -25,13 +25,13 @@ require('fs/promises');
25
25
  require('crypto');
26
26
  require('path');
27
27
  require('url');
28
- require('os');
29
28
  require('expo-server-sdk');
30
29
  require('node:child_process');
31
30
  require('node:readline');
32
31
  require('node:url');
33
32
  require('ps-list');
34
33
  require('cross-spawn');
34
+ require('os');
35
35
  require('tmp');
36
36
  require('qrcode-terminal');
37
37
  require('open');
@@ -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.2";
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",
@@ -1076,7 +1073,7 @@ class RpcHandlerManager {
1076
1073
  }
1077
1074
  }
1078
1075
 
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))));
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))));
1080
1077
  function projectPath() {
1081
1078
  const path$1 = path.resolve(__dirname$1, "..");
1082
1079
  return path$1;
@@ -1110,14 +1107,35 @@ function run$1(args, options) {
1110
1107
  });
1111
1108
  }
1112
1109
 
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));
1110
+ const toolsConfig = require(path.join(projectPath(), "scripts", "tools-config.cjs"));
1111
+ require(path.join(projectPath(), "scripts", "download-tool.cjs"));
1112
+ let cachedBinaryPath = null;
1113
+ function getBinaryPathSync() {
1114
+ if (cachedBinaryPath && fs$1.existsSync(cachedBinaryPath)) {
1115
+ return cachedBinaryPath;
1116
+ }
1117
+ const expectedPath = toolsConfig.getToolPath("difftastic");
1118
+ if (fs$1.existsSync(expectedPath)) {
1119
+ cachedBinaryPath = expectedPath;
1120
+ return expectedPath;
1121
+ }
1122
+ const result = child_process.spawnSync(process.execPath, [
1123
+ path.join(projectPath(), "scripts", "download-tool.cjs"),
1124
+ "difftastic"
1125
+ ], {
1126
+ encoding: "utf-8",
1127
+ stdio: ["pipe", "pipe", "inherit"]
1128
+ // Show download progress on stderr
1129
+ });
1130
+ if (result.status !== 0) {
1131
+ throw new Error(`Failed to download difftastic: ${result.stderr || "unknown error"}`);
1132
+ }
1133
+ cachedBinaryPath = result.stdout.trim();
1134
+ return cachedBinaryPath;
1117
1135
  }
1118
1136
  function run(args, options) {
1119
- const binaryPath = getBinaryPath();
1120
- return new Promise((resolve2, reject) => {
1137
+ const binaryPath = getBinaryPathSync();
1138
+ return new Promise((resolve, reject) => {
1121
1139
  const child = child_process.spawn(binaryPath, args, {
1122
1140
  stdio: ["pipe", "pipe", "pipe"],
1123
1141
  cwd: options?.cwd,
@@ -1136,7 +1154,7 @@ function run(args, options) {
1136
1154
  stderr += data.toString();
1137
1155
  });
1138
1156
  child.on("close", (code) => {
1139
- resolve2({
1157
+ resolve({
1140
1158
  exitCode: code || 0,
1141
1159
  stdout,
1142
1160
  stderr
@@ -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.2";
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",
@@ -1089,14 +1086,35 @@ function run$1(args, options) {
1089
1086
  });
1090
1087
  }
1091
1088
 
1092
- function getBinaryPath() {
1093
- const platformName = platform();
1094
- const binaryName = platformName === "win32" ? "difft.exe" : "difft";
1095
- return resolve(join$1(projectPath(), "tools", "unpacked", binaryName));
1089
+ const toolsConfig = require(join$1(projectPath(), "scripts", "tools-config.cjs"));
1090
+ require(join$1(projectPath(), "scripts", "download-tool.cjs"));
1091
+ let cachedBinaryPath = null;
1092
+ function getBinaryPathSync() {
1093
+ if (cachedBinaryPath && existsSync$1(cachedBinaryPath)) {
1094
+ return cachedBinaryPath;
1095
+ }
1096
+ const expectedPath = toolsConfig.getToolPath("difftastic");
1097
+ if (existsSync$1(expectedPath)) {
1098
+ cachedBinaryPath = expectedPath;
1099
+ return expectedPath;
1100
+ }
1101
+ const result = spawnSync(process.execPath, [
1102
+ join$1(projectPath(), "scripts", "download-tool.cjs"),
1103
+ "difftastic"
1104
+ ], {
1105
+ encoding: "utf-8",
1106
+ stdio: ["pipe", "pipe", "inherit"]
1107
+ // Show download progress on stderr
1108
+ });
1109
+ if (result.status !== 0) {
1110
+ throw new Error(`Failed to download difftastic: ${result.stderr || "unknown error"}`);
1111
+ }
1112
+ cachedBinaryPath = result.stdout.trim();
1113
+ return cachedBinaryPath;
1096
1114
  }
1097
1115
  function run(args, options) {
1098
- const binaryPath = getBinaryPath();
1099
- return new Promise((resolve2, reject) => {
1116
+ const binaryPath = getBinaryPathSync();
1117
+ return new Promise((resolve, reject) => {
1100
1118
  const child = spawn(binaryPath, args, {
1101
1119
  stdio: ["pipe", "pipe", "pipe"],
1102
1120
  cwd: options?.cwd,
@@ -1115,7 +1133,7 @@ function run(args, options) {
1115
1133
  stderr += data.toString();
1116
1134
  });
1117
1135
  child.on("close", (code) => {
1118
- resolve2({
1136
+ resolve({
1119
1137
  exitCode: code || 0,
1120
1138
  stdout,
1121
1139
  stderr
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.3",
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",
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Downloads and extracts a tool binary on first use
5
+ * Usage: node download-tool.cjs <toolName>
6
+ *
7
+ * This script is designed to be called before using a tool.
8
+ * It's idempotent - if the tool is already downloaded, it does nothing.
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const os = require('os');
14
+ const https = require('https');
15
+ const { createGunzip } = require('zlib');
16
+ const { pipeline } = require('stream/promises');
17
+ const { getDownloadUrl, getBinaryName, getToolPath, TOOLS } = require('./tools-config.cjs');
18
+
19
+ /**
20
+ * Follow redirects and download a file
21
+ */
22
+ function downloadFile(url, destPath, maxRedirects = 5) {
23
+ return new Promise((resolve, reject) => {
24
+ if (maxRedirects <= 0) {
25
+ reject(new Error('Too many redirects'));
26
+ return;
27
+ }
28
+
29
+ const protocol = url.startsWith('https') ? https : require('http');
30
+
31
+ protocol.get(url, (response) => {
32
+ // Handle redirects
33
+ if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
34
+ downloadFile(response.headers.location, destPath, maxRedirects - 1)
35
+ .then(resolve)
36
+ .catch(reject);
37
+ return;
38
+ }
39
+
40
+ if (response.statusCode !== 200) {
41
+ reject(new Error(`Download failed with status ${response.statusCode}`));
42
+ return;
43
+ }
44
+
45
+ const fileStream = fs.createWriteStream(destPath);
46
+ response.pipe(fileStream);
47
+
48
+ fileStream.on('finish', () => {
49
+ fileStream.close();
50
+ resolve();
51
+ });
52
+
53
+ fileStream.on('error', (err) => {
54
+ fs.unlink(destPath, () => {}); // Clean up partial file
55
+ reject(err);
56
+ });
57
+ }).on('error', reject);
58
+ });
59
+ }
60
+
61
+ /**
62
+ * Extract a tar.gz file
63
+ */
64
+ async function extractTarGz(archivePath, destDir, toolName) {
65
+ const tar = require('tar');
66
+ const tool = TOOLS[toolName];
67
+
68
+ await tar.extract({
69
+ file: archivePath,
70
+ cwd: destDir,
71
+ filter: (entryPath) => {
72
+ // Only extract the binary we need
73
+ const binaryName = getBinaryName(toolName, os.platform());
74
+ return entryPath.endsWith(binaryName);
75
+ },
76
+ strip: tool.archiveStructure === 'nested' ? 1 : 0
77
+ });
78
+ }
79
+
80
+ /**
81
+ * Extract a zip file (for Windows)
82
+ */
83
+ async function extractZip(archivePath, destDir, toolName) {
84
+ const AdmZip = require('adm-zip');
85
+ const tool = TOOLS[toolName];
86
+ const binaryName = getBinaryName(toolName, os.platform());
87
+
88
+ const zip = new AdmZip(archivePath);
89
+ const entries = zip.getEntries();
90
+
91
+ for (const entry of entries) {
92
+ if (entry.entryName.endsWith(binaryName)) {
93
+ const content = entry.getData();
94
+ const destPath = path.join(destDir, binaryName);
95
+ fs.writeFileSync(destPath, content);
96
+ break;
97
+ }
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Ensure a tool is downloaded and available
103
+ */
104
+ async function ensureTool(toolName) {
105
+ const tool = TOOLS[toolName];
106
+ if (!tool) {
107
+ throw new Error(`Unknown tool: ${toolName}`);
108
+ }
109
+
110
+ const platform = os.platform();
111
+ const arch = os.arch();
112
+ const toolPath = getToolPath(toolName);
113
+
114
+ // Check if already downloaded
115
+ if (fs.existsSync(toolPath)) {
116
+ return toolPath;
117
+ }
118
+
119
+ const toolDir = path.dirname(toolPath);
120
+ fs.mkdirSync(toolDir, { recursive: true });
121
+
122
+ // Get download URL
123
+ const url = getDownloadUrl(toolName, platform, arch);
124
+ const isZip = url.endsWith('.zip');
125
+ const archivePath = path.join(toolDir, `archive${isZip ? '.zip' : '.tar.gz'}`);
126
+
127
+ console.error(`[happy-cli] Downloading ${toolName} v${tool.version}...`);
128
+
129
+ try {
130
+ // Download the archive
131
+ await downloadFile(url, archivePath);
132
+
133
+ // Extract
134
+ if (isZip) {
135
+ await extractZip(archivePath, toolDir, toolName);
136
+ } else {
137
+ await extractTarGz(archivePath, toolDir, toolName);
138
+ }
139
+
140
+ // Set executable permission on Unix
141
+ if (platform !== 'win32') {
142
+ fs.chmodSync(toolPath, 0o755);
143
+ }
144
+
145
+ // Clean up archive
146
+ fs.unlinkSync(archivePath);
147
+
148
+ console.error(`[happy-cli] ${toolName} v${tool.version} installed to ${toolPath}`);
149
+ return toolPath;
150
+ } catch (error) {
151
+ // Clean up on failure
152
+ try {
153
+ fs.unlinkSync(archivePath);
154
+ } catch {}
155
+ throw error;
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Run a tool, downloading it first if necessary
161
+ * Returns the path to the binary
162
+ */
163
+ async function getOrDownloadTool(toolName) {
164
+ return ensureTool(toolName);
165
+ }
166
+
167
+ // If run directly, download the specified tool
168
+ if (require.main === module) {
169
+ const toolName = process.argv[2];
170
+ if (!toolName) {
171
+ console.error('Usage: node download-tool.cjs <toolName>');
172
+ console.error('Available tools:', Object.keys(TOOLS).join(', '));
173
+ process.exit(1);
174
+ }
175
+
176
+ ensureTool(toolName)
177
+ .then((toolPath) => {
178
+ // Output the path so callers can use it
179
+ console.log(toolPath);
180
+ })
181
+ .catch((error) => {
182
+ console.error(`Failed to download ${toolName}:`, error.message);
183
+ process.exit(1);
184
+ });
185
+ }
186
+
187
+ module.exports = { ensureTool, getOrDownloadTool };
@@ -3,12 +3,63 @@
3
3
  /**
4
4
  * Ripgrep runner - executed as a subprocess to run the native module
5
5
  * This file is intentionally written in CommonJS to avoid ESM complexities
6
+ *
7
+ * Uses the ripgrep.node native module bundled with @anthropic-ai/claude-code
6
8
  */
7
9
 
8
10
  const path = require('path');
11
+ const os = require('os');
12
+ const fs = require('fs');
9
13
 
10
- // Load the native module from unpacked directory
11
- const modulePath = path.join(__dirname, '..', 'tools', 'unpacked', 'ripgrep.node');
14
+ /**
15
+ * Get the platform-specific directory name for claude-code vendor
16
+ */
17
+ function getPlatformDir() {
18
+ const platform = os.platform();
19
+ const arch = os.arch();
20
+
21
+ if (platform === 'darwin') {
22
+ return arch === 'arm64' ? 'arm64-darwin' : 'x64-darwin';
23
+ } else if (platform === 'linux') {
24
+ return arch === 'arm64' ? 'arm64-linux' : 'x64-linux';
25
+ } else if (platform === 'win32') {
26
+ return 'x64-win32';
27
+ }
28
+ throw new Error(`Unsupported platform: ${platform}-${arch}`);
29
+ }
30
+
31
+ /**
32
+ * Find the ripgrep.node native module
33
+ * First checks claude-code vendor directory, falls back to local tools
34
+ */
35
+ function findRipgrepModule() {
36
+ const platformDir = getPlatformDir();
37
+
38
+ // Try claude-code vendor first (preferred - it's always up to date)
39
+ const claudeCodePaths = [
40
+ // When installed as a dependency
41
+ path.join(__dirname, '..', 'node_modules', '@anthropic-ai', 'claude-code', 'vendor', 'ripgrep', platformDir, 'ripgrep.node'),
42
+ // When running from workspace root
43
+ path.join(__dirname, '..', '..', 'node_modules', '@anthropic-ai', 'claude-code', 'vendor', 'ripgrep', platformDir, 'ripgrep.node'),
44
+ ];
45
+
46
+ for (const modulePath of claudeCodePaths) {
47
+ if (fs.existsSync(modulePath)) {
48
+ return modulePath;
49
+ }
50
+ }
51
+
52
+ // Fallback to local tools/unpacked (for development)
53
+ const localPath = path.join(__dirname, '..', 'tools', 'unpacked', 'ripgrep.node');
54
+ if (fs.existsSync(localPath)) {
55
+ return localPath;
56
+ }
57
+
58
+ throw new Error('Could not find ripgrep.node native module. Make sure @anthropic-ai/claude-code is installed.');
59
+ }
60
+
61
+ // Load the native module
62
+ const modulePath = findRipgrepModule();
12
63
  const ripgrepNative = require(modulePath);
13
64
 
14
65
  // Get arguments from command line (skip node and script name)
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Configuration for external tool binaries
3
+ * These are downloaded on first use, not bundled with the package
4
+ */
5
+
6
+ const TOOLS = {
7
+ difftastic: {
8
+ version: '0.64.0',
9
+ repo: 'Wilfred/difftastic',
10
+ // Map platform/arch to GitHub release asset names
11
+ assets: {
12
+ 'darwin-arm64': 'difft-aarch64-apple-darwin.tar.gz',
13
+ 'darwin-x64': 'difft-x86_64-apple-darwin.tar.gz',
14
+ 'linux-arm64': 'difft-aarch64-unknown-linux-gnu.tar.gz',
15
+ 'linux-x64': 'difft-x86_64-unknown-linux-gnu.tar.gz',
16
+ 'win32-x64': 'difft-x86_64-pc-windows-msvc.zip',
17
+ },
18
+ // Binary name inside the archive (or after extraction)
19
+ binaryName: {
20
+ 'win32': 'difft.exe',
21
+ 'default': 'difft'
22
+ },
23
+ // Some archives have the binary in a subdirectory
24
+ archiveStructure: 'flat' // binary is at root of archive
25
+ },
26
+ ripgrep: {
27
+ version: '14.1.1',
28
+ repo: 'BurntSushi/ripgrep',
29
+ assets: {
30
+ 'darwin-arm64': 'ripgrep-14.1.1-aarch64-apple-darwin.tar.gz',
31
+ 'darwin-x64': 'ripgrep-14.1.1-x86_64-apple-darwin.tar.gz',
32
+ 'linux-arm64': 'ripgrep-14.1.1-aarch64-unknown-linux-gnu.tar.gz',
33
+ 'linux-x64': 'ripgrep-14.1.1-x86_64-unknown-linux-musl.tar.gz',
34
+ 'win32-x64': 'ripgrep-14.1.1-x86_64-pc-windows-msvc.zip',
35
+ },
36
+ binaryName: {
37
+ 'win32': 'rg.exe',
38
+ 'default': 'rg'
39
+ },
40
+ // ripgrep archives have files in a subdirectory named after the archive
41
+ archiveStructure: 'nested' // binary is in a subdirectory
42
+ }
43
+ };
44
+
45
+ /**
46
+ * Get download URL for a tool
47
+ */
48
+ function getDownloadUrl(toolName, platform, arch) {
49
+ const tool = TOOLS[toolName];
50
+ if (!tool) {
51
+ throw new Error(`Unknown tool: ${toolName}`);
52
+ }
53
+
54
+ const key = `${platform}-${arch}`;
55
+ const asset = tool.assets[key];
56
+ if (!asset) {
57
+ throw new Error(`Unsupported platform for ${toolName}: ${key}`);
58
+ }
59
+
60
+ return `https://github.com/${tool.repo}/releases/download/${tool.version}/${asset}`;
61
+ }
62
+
63
+ /**
64
+ * Get binary name for a tool on the current platform
65
+ */
66
+ function getBinaryName(toolName, platform) {
67
+ const tool = TOOLS[toolName];
68
+ if (!tool) {
69
+ throw new Error(`Unknown tool: ${toolName}`);
70
+ }
71
+
72
+ return tool.binaryName[platform] || tool.binaryName['default'];
73
+ }
74
+
75
+ /**
76
+ * Get cache directory for tools
77
+ * Uses ~/.cache/happy-cli/tools on Unix, %LOCALAPPDATA%/happy-cli/tools on Windows
78
+ */
79
+ function getCacheDir() {
80
+ const os = require('os');
81
+ const path = require('path');
82
+
83
+ const platform = os.platform();
84
+ const homeDir = os.homedir();
85
+
86
+ if (platform === 'win32') {
87
+ const localAppData = process.env.LOCALAPPDATA || path.join(homeDir, 'AppData', 'Local');
88
+ return path.join(localAppData, 'happy-cli', 'tools');
89
+ } else {
90
+ const cacheHome = process.env.XDG_CACHE_HOME || path.join(homeDir, '.cache');
91
+ return path.join(cacheHome, 'happy-cli', 'tools');
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Get the expected path for a tool binary
97
+ */
98
+ function getToolPath(toolName) {
99
+ const os = require('os');
100
+ const path = require('path');
101
+
102
+ const tool = TOOLS[toolName];
103
+ if (!tool) {
104
+ throw new Error(`Unknown tool: ${toolName}`);
105
+ }
106
+
107
+ const cacheDir = getCacheDir();
108
+ const binaryName = getBinaryName(toolName, os.platform());
109
+
110
+ return path.join(cacheDir, toolName, tool.version, binaryName);
111
+ }
112
+
113
+ module.exports = {
114
+ TOOLS,
115
+ getDownloadUrl,
116
+ getBinaryName,
117
+ getCacheDir,
118
+ getToolPath
119
+ };
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2021-2025 Wilfred Hughes
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,3 +0,0 @@
1
- This project is dual-licensed under the Unlicense and MIT licenses.
2
-
3
- You may use this code under the terms of either license.
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2021-2025 Wilfred Hughes
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,3 +0,0 @@
1
- This project is dual-licensed under the Unlicense and MIT licenses.
2
-
3
- You may use this code under the terms of either license.
Binary file
package/tools/unpacked/rg DELETED
Binary file
Binary file