@zhigang1992/happy-cli 0.12.13 → 0.12.14

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.
@@ -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-DwjUGi0J.cjs');
6
+ var types = require('./types-Cxw1JC-9.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');
@@ -782,20 +782,27 @@ async function claudeLocalLauncher(session) {
782
782
  };
783
783
  session.addSessionFoundCallback(scannerSessionCallback);
784
784
  let exitReason = null;
785
- const processAbortController = new AbortController();
785
+ let abortRequested = false;
786
+ let processAbortController = new AbortController();
786
787
  let exutFuture = new Future();
787
788
  try {
789
+ let getAbortController2 = function() {
790
+ return processAbortController;
791
+ }, getExitFuture2 = function() {
792
+ return exutFuture;
793
+ };
794
+ var getAbortController = getAbortController2, getExitFuture = getExitFuture2;
788
795
  async function abort() {
789
- if (!processAbortController.signal.aborted) {
790
- processAbortController.abort();
796
+ const controller = getAbortController2();
797
+ const exitFuture = getExitFuture2();
798
+ if (!controller.signal.aborted) {
799
+ controller.abort();
791
800
  }
792
- await exutFuture.promise;
801
+ await exitFuture.promise;
793
802
  }
794
803
  async function doAbort() {
795
804
  types.logger.debug("[local]: doAbort");
796
- if (!exitReason) {
797
- exitReason = "switch";
798
- }
805
+ abortRequested = true;
799
806
  session.queue.reset();
800
807
  await abort();
801
808
  }
@@ -837,6 +844,13 @@ async function claudeLocalLauncher(session) {
837
844
  hookSettingsPath: session.hookSettingsPath
838
845
  });
839
846
  session.consumeOneTimeFlags();
847
+ if (abortRequested) {
848
+ types.logger.debug("[local]: Aborting current operation, continuing local mode");
849
+ abortRequested = false;
850
+ processAbortController = new AbortController();
851
+ exutFuture = new Future();
852
+ continue;
853
+ }
840
854
  if (!exitReason) {
841
855
  exitReason = "exit";
842
856
  break;
@@ -844,6 +858,13 @@ async function claudeLocalLauncher(session) {
844
858
  } catch (e) {
845
859
  const errorMessage = e instanceof Error ? e.message : String(e);
846
860
  types.logger.debug("[local]: launch error", e);
861
+ if (abortRequested) {
862
+ types.logger.debug("[local]: Aborting after error, continuing local mode");
863
+ abortRequested = false;
864
+ processAbortController = new AbortController();
865
+ exutFuture = new Future();
866
+ continue;
867
+ }
847
868
  const reason = exitReason;
848
869
  if (reason === "switch") {
849
870
  session.client.sendSessionEvent({ type: "message", message: `Error during mode switch: ${errorMessage}` });
@@ -1163,7 +1184,7 @@ class AbortError extends Error {
1163
1184
  }
1164
1185
  }
1165
1186
 
1166
- 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-VlluCzOw.cjs', document.baseURI).href)));
1187
+ 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-DGfkEaE6.cjs', document.baseURI).href)));
1167
1188
  const __dirname$1 = node_path.join(__filename$1, "..");
1168
1189
  function getGlobalClaudeVersion() {
1169
1190
  try {
@@ -1479,6 +1500,7 @@ function query(config) {
1479
1500
  fallbackModel,
1480
1501
  strictMcpConfig,
1481
1502
  canCallTool,
1503
+ settingsPath,
1482
1504
  onStderr
1483
1505
  } = {}
1484
1506
  } = config;
@@ -1505,6 +1527,7 @@ function query(config) {
1505
1527
  }
1506
1528
  if (strictMcpConfig) args.push("--strict-mcp-config");
1507
1529
  if (permissionMode) args.push("--permission-mode", permissionMode);
1530
+ if (settingsPath) args.push("--settings", settingsPath);
1508
1531
  if (fallbackModel) {
1509
1532
  if (model && fallbackModel === model) {
1510
1533
  throw new Error("Fallback model cannot be the same as the main model. Please specify a different model for fallbackModel option.");
@@ -2027,6 +2050,7 @@ Echo message: ${echoMessage}` : "");
2027
2050
  pathToClaudeCodeExecutable: (() => {
2028
2051
  return node_path.resolve(node_path.join(types.projectPath(), "scripts", "claude_remote_launcher.cjs"));
2029
2052
  })(),
2053
+ settingsPath: opts.hookSettingsPath,
2030
2054
  onStderr: opts.onStderr
2031
2055
  };
2032
2056
  let thinking = false;
@@ -2062,6 +2086,8 @@ Echo message: ${echoMessage}` : "");
2062
2086
  const initialContent = await buildMessageContent(initial.message, initial.mode.imageRefs);
2063
2087
  messages.push({
2064
2088
  type: "user",
2089
+ uuid: node_crypto.randomUUID(),
2090
+ // UUID is required for Claude CLI streaming mode
2065
2091
  message: {
2066
2092
  role: "user",
2067
2093
  content: initialContent
@@ -2106,7 +2132,12 @@ Echo message: ${echoMessage}` : "");
2106
2132
  }
2107
2133
  mode = next.mode;
2108
2134
  const nextContent = await buildMessageContent(next.message, next.mode.imageRefs);
2109
- messages.push({ type: "user", message: { role: "user", content: nextContent } });
2135
+ messages.push({
2136
+ type: "user",
2137
+ uuid: node_crypto.randomUUID(),
2138
+ // UUID is required for Claude CLI streaming mode
2139
+ message: { role: "user", content: nextContent }
2140
+ });
2110
2141
  }
2111
2142
  if (message.type === "user") {
2112
2143
  const msg = message;
@@ -3027,6 +3058,7 @@ async function claudeRemoteLauncher(session) {
3027
3058
  let exitReason = null;
3028
3059
  let abortController = null;
3029
3060
  let abortFuture = null;
3061
+ let abortRequested = false;
3030
3062
  async function abort() {
3031
3063
  if (abortController && !abortController.signal.aborted) {
3032
3064
  abortController.abort();
@@ -3035,6 +3067,7 @@ async function claudeRemoteLauncher(session) {
3035
3067
  }
3036
3068
  async function doAbort() {
3037
3069
  types.logger.debug("[remote]: doAbort");
3070
+ abortRequested = true;
3038
3071
  await abort();
3039
3072
  }
3040
3073
  async function doSwitch() {
@@ -3220,6 +3253,7 @@ async function claudeRemoteLauncher(session) {
3220
3253
  path: session.path,
3221
3254
  allowedTools: session.allowedTools ?? [],
3222
3255
  mcpServers: session.mcpServers,
3256
+ hookSettingsPath: session.hookSettingsPath,
3223
3257
  canCallTool: permissionHandler.handleToolCall,
3224
3258
  isAborted: (toolCallId) => {
3225
3259
  return permissionHandler.isAborted(toolCallId);
@@ -3299,16 +3333,30 @@ async function claudeRemoteLauncher(session) {
3299
3333
  signal: abortController.signal
3300
3334
  });
3301
3335
  session.consumeOneTimeFlags();
3336
+ if (abortRequested && abortController.signal.aborted) {
3337
+ types.logger.debug("[remote]: Operation aborted by user, continuing remote mode");
3338
+ session.client.sendSessionEvent({ type: "message", message: "Aborted" });
3339
+ abortRequested = false;
3340
+ continue;
3341
+ }
3302
3342
  if (!exitReason && abortController.signal.aborted) {
3303
3343
  session.client.sendSessionEvent({ type: "message", message: "Aborted by user" });
3304
3344
  }
3305
3345
  } catch (e) {
3306
3346
  const errorMessage = e instanceof Error ? e.message : String(e);
3307
3347
  types.logger.debug("[remote]: launch error", e);
3348
+ if (abortRequested) {
3349
+ types.logger.debug("[remote]: Aborting after error, continuing remote mode");
3350
+ session.client.sendSessionEvent({ type: "message", message: "Aborted" });
3351
+ abortRequested = false;
3352
+ continue;
3353
+ }
3308
3354
  if (exitReason === "switch") {
3309
3355
  session.client.sendSessionEvent({ type: "message", message: `Error during mode switch: ${errorMessage}` });
3356
+ break;
3310
3357
  } else if (exitReason === "exit") {
3311
3358
  session.client.sendSessionEvent({ type: "message", message: `Error during exit: ${errorMessage}` });
3359
+ break;
3312
3360
  } else {
3313
3361
  session.client.sendSessionEvent({ type: "message", message: `Process error: ${errorMessage}` });
3314
3362
  continue;
@@ -3874,6 +3922,139 @@ function extractSDKMetadataAsync(onComplete) {
3874
3922
  });
3875
3923
  }
3876
3924
 
3925
+ function parseFrontmatter(content) {
3926
+ const trimmed = content.trim();
3927
+ if (!trimmed.startsWith("---")) {
3928
+ return { frontmatter: null, body: content };
3929
+ }
3930
+ const endIndex = trimmed.indexOf("---", 3);
3931
+ if (endIndex === -1) {
3932
+ return { frontmatter: null, body: content };
3933
+ }
3934
+ const frontmatterStr = trimmed.substring(3, endIndex).trim();
3935
+ const body = trimmed.substring(endIndex + 3).trim();
3936
+ const frontmatter = {};
3937
+ const lines = frontmatterStr.split("\n");
3938
+ for (const line of lines) {
3939
+ const colonIndex = line.indexOf(":");
3940
+ if (colonIndex === -1) continue;
3941
+ const key = line.substring(0, colonIndex).trim();
3942
+ let value = line.substring(colonIndex + 1).trim();
3943
+ if (key === "allowed-tools" && value) {
3944
+ frontmatter["allowed-tools"] = value.split(",").map((s) => s.trim());
3945
+ } else if (key === "description") {
3946
+ frontmatter.description = value;
3947
+ } else if (key === "argument-hint") {
3948
+ frontmatter["argument-hint"] = value;
3949
+ } else if (key === "model") {
3950
+ frontmatter.model = value;
3951
+ }
3952
+ }
3953
+ return { frontmatter, body };
3954
+ }
3955
+ function extractDescriptionFromContent(content) {
3956
+ const lines = content.split("\n");
3957
+ for (const line of lines) {
3958
+ const trimmed = line.trim();
3959
+ if (trimmed && !trimmed.startsWith("#") && !trimmed.startsWith("!")) {
3960
+ return trimmed.length > 100 ? trimmed.substring(0, 100) + "..." : trimmed;
3961
+ }
3962
+ }
3963
+ return void 0;
3964
+ }
3965
+ function findMarkdownFiles(dir, baseDir = dir) {
3966
+ const results = [];
3967
+ if (!fs.existsSync(dir)) {
3968
+ return results;
3969
+ }
3970
+ try {
3971
+ const entries = fs.readdirSync(dir);
3972
+ for (const entry of entries) {
3973
+ const fullPath = node_path.join(dir, entry);
3974
+ const stat = fs.statSync(fullPath);
3975
+ if (stat.isDirectory()) {
3976
+ const subResults = findMarkdownFiles(fullPath, baseDir);
3977
+ results.push(...subResults);
3978
+ } else if (stat.isFile() && node_path.extname(entry).toLowerCase() === ".md") {
3979
+ const relativePath = fullPath.substring(baseDir.length + 1);
3980
+ const namespace = relativePath.includes("/") ? relativePath.substring(0, relativePath.lastIndexOf("/")) : void 0;
3981
+ results.push({ filePath: fullPath, namespace });
3982
+ }
3983
+ }
3984
+ } catch (error) {
3985
+ types.logger.debug("[customCommands] Error reading directory:", dir, error);
3986
+ }
3987
+ return results;
3988
+ }
3989
+ function parseCommandFile(filePath, namespace, scope) {
3990
+ try {
3991
+ const content = fs.readFileSync(filePath, "utf-8");
3992
+ const { frontmatter, body } = parseFrontmatter(content);
3993
+ const name = node_path.basename(filePath, ".md");
3994
+ const description = frontmatter?.description || extractDescriptionFromContent(body);
3995
+ let allowedTools;
3996
+ if (frontmatter?.["allowed-tools"]) {
3997
+ const at = frontmatter["allowed-tools"];
3998
+ allowedTools = Array.isArray(at) ? at : [at];
3999
+ }
4000
+ return {
4001
+ name,
4002
+ description,
4003
+ argumentHint: frontmatter?.["argument-hint"],
4004
+ allowedTools,
4005
+ model: frontmatter?.model,
4006
+ scope,
4007
+ namespace,
4008
+ filePath,
4009
+ content: body
4010
+ };
4011
+ } catch (error) {
4012
+ types.logger.debug("[customCommands] Error parsing command file:", filePath, error);
4013
+ return null;
4014
+ }
4015
+ }
4016
+ function discoverCustomCommands(projectDir) {
4017
+ const commands = [];
4018
+ const projectCommandsDir = node_path.join(projectDir, ".claude", "commands");
4019
+ if (fs.existsSync(projectCommandsDir)) {
4020
+ types.logger.debug("[customCommands] Scanning project commands:", projectCommandsDir);
4021
+ const files = findMarkdownFiles(projectCommandsDir);
4022
+ for (const { filePath, namespace } of files) {
4023
+ const command = parseCommandFile(filePath, namespace, "project");
4024
+ if (command) {
4025
+ commands.push(command);
4026
+ }
4027
+ }
4028
+ }
4029
+ const personalCommandsDir = node_path.join(os.homedir(), ".claude", "commands");
4030
+ if (fs.existsSync(personalCommandsDir)) {
4031
+ types.logger.debug("[customCommands] Scanning personal commands:", personalCommandsDir);
4032
+ const files = findMarkdownFiles(personalCommandsDir);
4033
+ for (const { filePath, namespace } of files) {
4034
+ const command = parseCommandFile(filePath, namespace, "personal");
4035
+ if (command) {
4036
+ const existingIndex = commands.findIndex((c) => c.name === command.name);
4037
+ if (existingIndex === -1) {
4038
+ commands.push(command);
4039
+ } else {
4040
+ types.logger.debug(`[customCommands] Skipping personal command "${command.name}" - project command takes precedence`);
4041
+ }
4042
+ }
4043
+ }
4044
+ }
4045
+ types.logger.debug(`[customCommands] Discovered ${commands.length} custom commands`);
4046
+ return commands;
4047
+ }
4048
+ function commandsToMetadata(commands) {
4049
+ return commands.map((cmd) => ({
4050
+ name: cmd.name,
4051
+ description: cmd.description,
4052
+ argumentHint: cmd.argumentHint,
4053
+ scope: cmd.scope,
4054
+ namespace: cmd.namespace
4055
+ }));
4056
+ }
4057
+
3877
4058
  async function daemonPost(path, body) {
3878
4059
  const state = await types.readDaemonState();
3879
4060
  if (!state?.httpPort) {
@@ -5469,6 +5650,20 @@ async function runClaude(credentials, options = {}) {
5469
5650
  } catch (error) {
5470
5651
  types.logger.debug("[START] Failed to report to daemon (may not be running):", error);
5471
5652
  }
5653
+ const customCommands = discoverCustomCommands(workingDirectory);
5654
+ const customCommandsMetadata = commandsToMetadata(customCommands);
5655
+ types.logger.debug(`[start] Discovered ${customCommands.length} custom commands`);
5656
+ if (customCommandsMetadata.length > 0) {
5657
+ try {
5658
+ api.sessionSyncClient(response).updateMetadata((currentMetadata) => ({
5659
+ ...currentMetadata,
5660
+ customCommands: customCommandsMetadata
5661
+ }));
5662
+ types.logger.debug("[start] Session metadata updated with custom commands");
5663
+ } catch (error) {
5664
+ types.logger.debug("[start] Failed to update session metadata with custom commands:", error);
5665
+ }
5666
+ }
5472
5667
  extractSDKMetadataAsync(async (sdkMetadata) => {
5473
5668
  types.logger.debug("[start] SDK metadata extracted, updating session:", sdkMetadata);
5474
5669
  try {
@@ -6833,7 +7028,7 @@ async function handleConnectVendor(vendor, displayName) {
6833
7028
  return;
6834
7029
  } else if (subcommand === "codex") {
6835
7030
  try {
6836
- const { runCodex } = await Promise.resolve().then(function () { return require('./runCodex-3jsfzjVM.cjs'); });
7031
+ const { runCodex } = await Promise.resolve().then(function () { return require('./runCodex-DJSRJ2Pg.cjs'); });
6837
7032
  let startedBy = void 0;
6838
7033
  for (let i = 1; i < args.length; i++) {
6839
7034
  if (args[i] === "--started-by") {
@@ -6878,7 +7073,7 @@ async function handleConnectVendor(vendor, displayName) {
6878
7073
  } else if (subcommand === "list") {
6879
7074
  try {
6880
7075
  const { credentials } = await authAndSetupMachineIfNeeded();
6881
- const { listSessions } = await Promise.resolve().then(function () { return require('./list-DXxyuwT9.cjs'); });
7076
+ const { listSessions } = await Promise.resolve().then(function () { return require('./list-S3T6MByP.cjs'); });
6882
7077
  let sessionId;
6883
7078
  let titleFilter;
6884
7079
  let recentMsgs;
@@ -6980,7 +7175,7 @@ Examples:
6980
7175
  process.exit(1);
6981
7176
  }
6982
7177
  const { credentials } = await authAndSetupMachineIfNeeded();
6983
- const { promptSession } = await Promise.resolve().then(function () { return require('./prompt-LEVV0Hst.cjs'); });
7178
+ const { promptSession } = await Promise.resolve().then(function () { return require('./prompt-5iJXZPz1.cjs'); });
6984
7179
  await promptSession(credentials, sessionId, promptText, timeoutMinutes ?? void 0);
6985
7180
  } catch (error) {
6986
7181
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
@@ -1,9 +1,9 @@
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-C1SMg54t.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-HBw6XZbv.mjs';
5
5
  import { spawn, execSync, exec as exec$1, execFileSync } from 'node:child_process';
6
- import { resolve, join } from 'node:path';
6
+ import { resolve, join, extname, basename as basename$1 } from 'node:path';
7
7
  import { createInterface } from 'node:readline';
8
8
  import { existsSync, readFileSync, mkdirSync, readdirSync, statSync, writeFileSync, unlinkSync, rmSync } from 'node:fs';
9
9
  import { exec, spawn as spawn$1, execSync as execSync$1 } from 'child_process';
@@ -759,20 +759,27 @@ async function claudeLocalLauncher(session) {
759
759
  };
760
760
  session.addSessionFoundCallback(scannerSessionCallback);
761
761
  let exitReason = null;
762
- const processAbortController = new AbortController();
762
+ let abortRequested = false;
763
+ let processAbortController = new AbortController();
763
764
  let exutFuture = new Future();
764
765
  try {
766
+ let getAbortController2 = function() {
767
+ return processAbortController;
768
+ }, getExitFuture2 = function() {
769
+ return exutFuture;
770
+ };
771
+ var getAbortController = getAbortController2, getExitFuture = getExitFuture2;
765
772
  async function abort() {
766
- if (!processAbortController.signal.aborted) {
767
- processAbortController.abort();
773
+ const controller = getAbortController2();
774
+ const exitFuture = getExitFuture2();
775
+ if (!controller.signal.aborted) {
776
+ controller.abort();
768
777
  }
769
- await exutFuture.promise;
778
+ await exitFuture.promise;
770
779
  }
771
780
  async function doAbort() {
772
781
  logger.debug("[local]: doAbort");
773
- if (!exitReason) {
774
- exitReason = "switch";
775
- }
782
+ abortRequested = true;
776
783
  session.queue.reset();
777
784
  await abort();
778
785
  }
@@ -814,6 +821,13 @@ async function claudeLocalLauncher(session) {
814
821
  hookSettingsPath: session.hookSettingsPath
815
822
  });
816
823
  session.consumeOneTimeFlags();
824
+ if (abortRequested) {
825
+ logger.debug("[local]: Aborting current operation, continuing local mode");
826
+ abortRequested = false;
827
+ processAbortController = new AbortController();
828
+ exutFuture = new Future();
829
+ continue;
830
+ }
817
831
  if (!exitReason) {
818
832
  exitReason = "exit";
819
833
  break;
@@ -821,6 +835,13 @@ async function claudeLocalLauncher(session) {
821
835
  } catch (e) {
822
836
  const errorMessage = e instanceof Error ? e.message : String(e);
823
837
  logger.debug("[local]: launch error", e);
838
+ if (abortRequested) {
839
+ logger.debug("[local]: Aborting after error, continuing local mode");
840
+ abortRequested = false;
841
+ processAbortController = new AbortController();
842
+ exutFuture = new Future();
843
+ continue;
844
+ }
824
845
  const reason = exitReason;
825
846
  if (reason === "switch") {
826
847
  session.client.sendSessionEvent({ type: "message", message: `Error during mode switch: ${errorMessage}` });
@@ -1456,6 +1477,7 @@ function query(config) {
1456
1477
  fallbackModel,
1457
1478
  strictMcpConfig,
1458
1479
  canCallTool,
1480
+ settingsPath,
1459
1481
  onStderr
1460
1482
  } = {}
1461
1483
  } = config;
@@ -1482,6 +1504,7 @@ function query(config) {
1482
1504
  }
1483
1505
  if (strictMcpConfig) args.push("--strict-mcp-config");
1484
1506
  if (permissionMode) args.push("--permission-mode", permissionMode);
1507
+ if (settingsPath) args.push("--settings", settingsPath);
1485
1508
  if (fallbackModel) {
1486
1509
  if (model && fallbackModel === model) {
1487
1510
  throw new Error("Fallback model cannot be the same as the main model. Please specify a different model for fallbackModel option.");
@@ -2004,6 +2027,7 @@ Echo message: ${echoMessage}` : "");
2004
2027
  pathToClaudeCodeExecutable: (() => {
2005
2028
  return resolve(join(projectPath(), "scripts", "claude_remote_launcher.cjs"));
2006
2029
  })(),
2030
+ settingsPath: opts.hookSettingsPath,
2007
2031
  onStderr: opts.onStderr
2008
2032
  };
2009
2033
  let thinking = false;
@@ -2039,6 +2063,8 @@ Echo message: ${echoMessage}` : "");
2039
2063
  const initialContent = await buildMessageContent(initial.message, initial.mode.imageRefs);
2040
2064
  messages.push({
2041
2065
  type: "user",
2066
+ uuid: randomUUID(),
2067
+ // UUID is required for Claude CLI streaming mode
2042
2068
  message: {
2043
2069
  role: "user",
2044
2070
  content: initialContent
@@ -2083,7 +2109,12 @@ Echo message: ${echoMessage}` : "");
2083
2109
  }
2084
2110
  mode = next.mode;
2085
2111
  const nextContent = await buildMessageContent(next.message, next.mode.imageRefs);
2086
- messages.push({ type: "user", message: { role: "user", content: nextContent } });
2112
+ messages.push({
2113
+ type: "user",
2114
+ uuid: randomUUID(),
2115
+ // UUID is required for Claude CLI streaming mode
2116
+ message: { role: "user", content: nextContent }
2117
+ });
2087
2118
  }
2088
2119
  if (message.type === "user") {
2089
2120
  const msg = message;
@@ -3004,6 +3035,7 @@ async function claudeRemoteLauncher(session) {
3004
3035
  let exitReason = null;
3005
3036
  let abortController = null;
3006
3037
  let abortFuture = null;
3038
+ let abortRequested = false;
3007
3039
  async function abort() {
3008
3040
  if (abortController && !abortController.signal.aborted) {
3009
3041
  abortController.abort();
@@ -3012,6 +3044,7 @@ async function claudeRemoteLauncher(session) {
3012
3044
  }
3013
3045
  async function doAbort() {
3014
3046
  logger.debug("[remote]: doAbort");
3047
+ abortRequested = true;
3015
3048
  await abort();
3016
3049
  }
3017
3050
  async function doSwitch() {
@@ -3197,6 +3230,7 @@ async function claudeRemoteLauncher(session) {
3197
3230
  path: session.path,
3198
3231
  allowedTools: session.allowedTools ?? [],
3199
3232
  mcpServers: session.mcpServers,
3233
+ hookSettingsPath: session.hookSettingsPath,
3200
3234
  canCallTool: permissionHandler.handleToolCall,
3201
3235
  isAborted: (toolCallId) => {
3202
3236
  return permissionHandler.isAborted(toolCallId);
@@ -3276,16 +3310,30 @@ async function claudeRemoteLauncher(session) {
3276
3310
  signal: abortController.signal
3277
3311
  });
3278
3312
  session.consumeOneTimeFlags();
3313
+ if (abortRequested && abortController.signal.aborted) {
3314
+ logger.debug("[remote]: Operation aborted by user, continuing remote mode");
3315
+ session.client.sendSessionEvent({ type: "message", message: "Aborted" });
3316
+ abortRequested = false;
3317
+ continue;
3318
+ }
3279
3319
  if (!exitReason && abortController.signal.aborted) {
3280
3320
  session.client.sendSessionEvent({ type: "message", message: "Aborted by user" });
3281
3321
  }
3282
3322
  } catch (e) {
3283
3323
  const errorMessage = e instanceof Error ? e.message : String(e);
3284
3324
  logger.debug("[remote]: launch error", e);
3325
+ if (abortRequested) {
3326
+ logger.debug("[remote]: Aborting after error, continuing remote mode");
3327
+ session.client.sendSessionEvent({ type: "message", message: "Aborted" });
3328
+ abortRequested = false;
3329
+ continue;
3330
+ }
3285
3331
  if (exitReason === "switch") {
3286
3332
  session.client.sendSessionEvent({ type: "message", message: `Error during mode switch: ${errorMessage}` });
3333
+ break;
3287
3334
  } else if (exitReason === "exit") {
3288
3335
  session.client.sendSessionEvent({ type: "message", message: `Error during exit: ${errorMessage}` });
3336
+ break;
3289
3337
  } else {
3290
3338
  session.client.sendSessionEvent({ type: "message", message: `Process error: ${errorMessage}` });
3291
3339
  continue;
@@ -3851,6 +3899,139 @@ function extractSDKMetadataAsync(onComplete) {
3851
3899
  });
3852
3900
  }
3853
3901
 
3902
+ function parseFrontmatter(content) {
3903
+ const trimmed = content.trim();
3904
+ if (!trimmed.startsWith("---")) {
3905
+ return { frontmatter: null, body: content };
3906
+ }
3907
+ const endIndex = trimmed.indexOf("---", 3);
3908
+ if (endIndex === -1) {
3909
+ return { frontmatter: null, body: content };
3910
+ }
3911
+ const frontmatterStr = trimmed.substring(3, endIndex).trim();
3912
+ const body = trimmed.substring(endIndex + 3).trim();
3913
+ const frontmatter = {};
3914
+ const lines = frontmatterStr.split("\n");
3915
+ for (const line of lines) {
3916
+ const colonIndex = line.indexOf(":");
3917
+ if (colonIndex === -1) continue;
3918
+ const key = line.substring(0, colonIndex).trim();
3919
+ let value = line.substring(colonIndex + 1).trim();
3920
+ if (key === "allowed-tools" && value) {
3921
+ frontmatter["allowed-tools"] = value.split(",").map((s) => s.trim());
3922
+ } else if (key === "description") {
3923
+ frontmatter.description = value;
3924
+ } else if (key === "argument-hint") {
3925
+ frontmatter["argument-hint"] = value;
3926
+ } else if (key === "model") {
3927
+ frontmatter.model = value;
3928
+ }
3929
+ }
3930
+ return { frontmatter, body };
3931
+ }
3932
+ function extractDescriptionFromContent(content) {
3933
+ const lines = content.split("\n");
3934
+ for (const line of lines) {
3935
+ const trimmed = line.trim();
3936
+ if (trimmed && !trimmed.startsWith("#") && !trimmed.startsWith("!")) {
3937
+ return trimmed.length > 100 ? trimmed.substring(0, 100) + "..." : trimmed;
3938
+ }
3939
+ }
3940
+ return void 0;
3941
+ }
3942
+ function findMarkdownFiles(dir, baseDir = dir) {
3943
+ const results = [];
3944
+ if (!existsSync(dir)) {
3945
+ return results;
3946
+ }
3947
+ try {
3948
+ const entries = readdirSync(dir);
3949
+ for (const entry of entries) {
3950
+ const fullPath = join(dir, entry);
3951
+ const stat = statSync(fullPath);
3952
+ if (stat.isDirectory()) {
3953
+ const subResults = findMarkdownFiles(fullPath, baseDir);
3954
+ results.push(...subResults);
3955
+ } else if (stat.isFile() && extname(entry).toLowerCase() === ".md") {
3956
+ const relativePath = fullPath.substring(baseDir.length + 1);
3957
+ const namespace = relativePath.includes("/") ? relativePath.substring(0, relativePath.lastIndexOf("/")) : void 0;
3958
+ results.push({ filePath: fullPath, namespace });
3959
+ }
3960
+ }
3961
+ } catch (error) {
3962
+ logger.debug("[customCommands] Error reading directory:", dir, error);
3963
+ }
3964
+ return results;
3965
+ }
3966
+ function parseCommandFile(filePath, namespace, scope) {
3967
+ try {
3968
+ const content = readFileSync(filePath, "utf-8");
3969
+ const { frontmatter, body } = parseFrontmatter(content);
3970
+ const name = basename$1(filePath, ".md");
3971
+ const description = frontmatter?.description || extractDescriptionFromContent(body);
3972
+ let allowedTools;
3973
+ if (frontmatter?.["allowed-tools"]) {
3974
+ const at = frontmatter["allowed-tools"];
3975
+ allowedTools = Array.isArray(at) ? at : [at];
3976
+ }
3977
+ return {
3978
+ name,
3979
+ description,
3980
+ argumentHint: frontmatter?.["argument-hint"],
3981
+ allowedTools,
3982
+ model: frontmatter?.model,
3983
+ scope,
3984
+ namespace,
3985
+ filePath,
3986
+ content: body
3987
+ };
3988
+ } catch (error) {
3989
+ logger.debug("[customCommands] Error parsing command file:", filePath, error);
3990
+ return null;
3991
+ }
3992
+ }
3993
+ function discoverCustomCommands(projectDir) {
3994
+ const commands = [];
3995
+ const projectCommandsDir = join(projectDir, ".claude", "commands");
3996
+ if (existsSync(projectCommandsDir)) {
3997
+ logger.debug("[customCommands] Scanning project commands:", projectCommandsDir);
3998
+ const files = findMarkdownFiles(projectCommandsDir);
3999
+ for (const { filePath, namespace } of files) {
4000
+ const command = parseCommandFile(filePath, namespace, "project");
4001
+ if (command) {
4002
+ commands.push(command);
4003
+ }
4004
+ }
4005
+ }
4006
+ const personalCommandsDir = join(homedir(), ".claude", "commands");
4007
+ if (existsSync(personalCommandsDir)) {
4008
+ logger.debug("[customCommands] Scanning personal commands:", personalCommandsDir);
4009
+ const files = findMarkdownFiles(personalCommandsDir);
4010
+ for (const { filePath, namespace } of files) {
4011
+ const command = parseCommandFile(filePath, namespace, "personal");
4012
+ if (command) {
4013
+ const existingIndex = commands.findIndex((c) => c.name === command.name);
4014
+ if (existingIndex === -1) {
4015
+ commands.push(command);
4016
+ } else {
4017
+ logger.debug(`[customCommands] Skipping personal command "${command.name}" - project command takes precedence`);
4018
+ }
4019
+ }
4020
+ }
4021
+ }
4022
+ logger.debug(`[customCommands] Discovered ${commands.length} custom commands`);
4023
+ return commands;
4024
+ }
4025
+ function commandsToMetadata(commands) {
4026
+ return commands.map((cmd) => ({
4027
+ name: cmd.name,
4028
+ description: cmd.description,
4029
+ argumentHint: cmd.argumentHint,
4030
+ scope: cmd.scope,
4031
+ namespace: cmd.namespace
4032
+ }));
4033
+ }
4034
+
3854
4035
  async function daemonPost(path, body) {
3855
4036
  const state = await readDaemonState();
3856
4037
  if (!state?.httpPort) {
@@ -5446,6 +5627,20 @@ async function runClaude(credentials, options = {}) {
5446
5627
  } catch (error) {
5447
5628
  logger.debug("[START] Failed to report to daemon (may not be running):", error);
5448
5629
  }
5630
+ const customCommands = discoverCustomCommands(workingDirectory);
5631
+ const customCommandsMetadata = commandsToMetadata(customCommands);
5632
+ logger.debug(`[start] Discovered ${customCommands.length} custom commands`);
5633
+ if (customCommandsMetadata.length > 0) {
5634
+ try {
5635
+ api.sessionSyncClient(response).updateMetadata((currentMetadata) => ({
5636
+ ...currentMetadata,
5637
+ customCommands: customCommandsMetadata
5638
+ }));
5639
+ logger.debug("[start] Session metadata updated with custom commands");
5640
+ } catch (error) {
5641
+ logger.debug("[start] Failed to update session metadata with custom commands:", error);
5642
+ }
5643
+ }
5449
5644
  extractSDKMetadataAsync(async (sdkMetadata) => {
5450
5645
  logger.debug("[start] SDK metadata extracted, updating session:", sdkMetadata);
5451
5646
  try {
@@ -6810,7 +7005,7 @@ async function handleConnectVendor(vendor, displayName) {
6810
7005
  return;
6811
7006
  } else if (subcommand === "codex") {
6812
7007
  try {
6813
- const { runCodex } = await import('./runCodex-DO1tAsMY.mjs');
7008
+ const { runCodex } = await import('./runCodex-DcdBh2dO.mjs');
6814
7009
  let startedBy = void 0;
6815
7010
  for (let i = 1; i < args.length; i++) {
6816
7011
  if (args[i] === "--started-by") {
@@ -6855,7 +7050,7 @@ async function handleConnectVendor(vendor, displayName) {
6855
7050
  } else if (subcommand === "list") {
6856
7051
  try {
6857
7052
  const { credentials } = await authAndSetupMachineIfNeeded();
6858
- const { listSessions } = await import('./list-C0F4TAPa.mjs');
7053
+ const { listSessions } = await import('./list-Bwy2qrWu.mjs');
6859
7054
  let sessionId;
6860
7055
  let titleFilter;
6861
7056
  let recentMsgs;
@@ -6957,7 +7152,7 @@ Examples:
6957
7152
  process.exit(1);
6958
7153
  }
6959
7154
  const { credentials } = await authAndSetupMachineIfNeeded();
6960
- const { promptSession } = await import('./prompt-Br-GiaVj.mjs');
7155
+ const { promptSession } = await import('./prompt-Bfs9-NO9.mjs');
6961
7156
  await promptSession(credentials, sessionId, promptText, timeoutMinutes ?? void 0);
6962
7157
  } catch (error) {
6963
7158
  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-VlluCzOw.cjs');
5
- require('./types-DwjUGi0J.cjs');
4
+ require('./index-DGfkEaE6.cjs');
5
+ require('./types-Cxw1JC-9.cjs');
6
6
  require('zod');
7
7
  require('node:child_process');
8
8
  require('node:os');
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import 'chalk';
2
- import './index-Bzl-ixRP.mjs';
3
- import './types-C1SMg54t.mjs';
2
+ import './index-DVKGbEA2.mjs';
3
+ import './types-HBw6XZbv.mjs';
4
4
  import 'zod';
5
5
  import 'node:child_process';
6
6
  import 'node:os';
package/dist/lib.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var types = require('./types-DwjUGi0J.cjs');
3
+ var types = require('./types-Cxw1JC-9.cjs');
4
4
  require('axios');
5
5
  require('chalk');
6
6
  require('fs');
package/dist/lib.d.cts CHANGED
@@ -715,6 +715,13 @@ type Metadata = {
715
715
  claudeSessionId?: string;
716
716
  tools?: string[];
717
717
  slashCommands?: string[];
718
+ customCommands?: Array<{
719
+ name: string;
720
+ description?: string;
721
+ argumentHint?: string;
722
+ scope: 'project' | 'personal';
723
+ namespace?: string;
724
+ }>;
718
725
  homeDir: string;
719
726
  happyHomeDir: string;
720
727
  happyLibDir: string;
package/dist/lib.d.mts CHANGED
@@ -715,6 +715,13 @@ type Metadata = {
715
715
  claudeSessionId?: string;
716
716
  tools?: string[];
717
717
  slashCommands?: string[];
718
+ customCommands?: Array<{
719
+ name: string;
720
+ description?: string;
721
+ argumentHint?: string;
722
+ scope: 'project' | 'personal';
723
+ namespace?: string;
724
+ }>;
718
725
  homeDir: string;
719
726
  happyHomeDir: string;
720
727
  happyLibDir: string;
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-C1SMg54t.mjs';
1
+ export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, l as logger } from './types-HBw6XZbv.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-C1SMg54t.mjs';
1
+ import { c as configuration, l as logger, d as decodeBase64, b as decrypt, f as formatTimeAgo, e as libsodiumDecryptFromPublicKey } from './types-HBw6XZbv.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-DwjUGi0J.cjs');
3
+ var types = require('./types-Cxw1JC-9.cjs');
4
4
  var axios = require('axios');
5
5
  var fs = require('fs');
6
6
  var path = require('path');
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var types = require('./types-DwjUGi0J.cjs');
3
+ var types = require('./types-Cxw1JC-9.cjs');
4
4
  var axios = require('axios');
5
5
  var socket_ioClient = require('socket.io-client');
6
6
  require('chalk');
@@ -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-C1SMg54t.mjs';
1
+ import { c as configuration, b as decrypt, d as decodeBase64, l as logger, g as encodeBase64, h as encrypt } from './types-HBw6XZbv.mjs';
2
2
  import axios from 'axios';
3
3
  import { io } from 'socket.io-client';
4
4
  import 'chalk';
@@ -2,14 +2,14 @@
2
2
 
3
3
  var ink = require('ink');
4
4
  var React = require('react');
5
- var types = require('./types-DwjUGi0J.cjs');
5
+ var types = require('./types-Cxw1JC-9.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-VlluCzOw.cjs');
12
+ var index = require('./index-DGfkEaE6.cjs');
13
13
  var os = require('node:os');
14
14
  var node_path = require('node:path');
15
15
  var fs = require('node:fs');
@@ -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-C1SMg54t.mjs';
3
+ import { l as logger, A as ApiClient, r as readSettings, p as projectPath, c as configuration, i as packageJson } from './types-HBw6XZbv.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-Bzl-ixRP.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-DVKGbEA2.mjs';
11
11
  import os from 'node:os';
12
12
  import { resolve, join } from 'node:path';
13
13
  import fs from 'node:fs';
@@ -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.13";
44
+ var version = "0.12.14";
45
45
  var description = "Mobile and Web client for Claude Code and Codex";
46
46
  var author = "Kirill Dubovitskiy";
47
47
  var license = "MIT";
@@ -1150,7 +1150,7 @@ class RpcHandlerManager {
1150
1150
  }
1151
1151
  }
1152
1152
 
1153
- 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-DwjUGi0J.cjs', document.baseURI).href))));
1153
+ 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-Cxw1JC-9.cjs', document.baseURI).href))));
1154
1154
  function projectPath() {
1155
1155
  const path$1 = path.resolve(__dirname$1, "..");
1156
1156
  return path$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.13";
23
+ var version = "0.12.14";
24
24
  var description = "Mobile and Web client for Claude Code and Codex";
25
25
  var author = "Kirill Dubovitskiy";
26
26
  var license = "MIT";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhigang1992/happy-cli",
3
- "version": "0.12.13",
3
+ "version": "0.12.14",
4
4
  "description": "Mobile and Web client for Claude Code and Codex",
5
5
  "author": "Kirill Dubovitskiy",
6
6
  "license": "MIT",
@@ -330,7 +330,7 @@ function getClaudeCliPath() {
330
330
 
331
331
  const version = getVersion(result.path);
332
332
  const versionStr = version ? ` v${version}` : '';
333
- console.error(`\x1b[90mUsing Claude Code${versionStr} from ${result.source}\x1b[0m`);
333
+ console.error(`Using Claude Code${versionStr} from ${result.source}`);
334
334
 
335
335
  return result.path;
336
336
  }