happy-coder 0.3.1-beta.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var chalk = require('chalk');
4
- var types$1 = require('./types-DXK5YldG.cjs');
4
+ var types$1 = require('./types-BBpJNhIN.cjs');
5
5
  var node_crypto = require('node:crypto');
6
6
  var node_child_process = require('node:child_process');
7
7
  var node_path = require('node:path');
@@ -480,6 +480,7 @@ function messageKey(message) {
480
480
  }
481
481
  async function readSessionLog(projectDir, sessionId) {
482
482
  const expectedSessionFile = node_path.join(projectDir, `${sessionId}.jsonl`);
483
+ types$1.logger.debug(`[SESSION_SCANNER] Reading session file: ${expectedSessionFile}`);
483
484
  let file;
484
485
  try {
485
486
  file = await promises$1.readFile(expectedSessionFile, "utf-8");
@@ -1252,6 +1253,12 @@ async function claudeRemote(opts) {
1252
1253
  mcpServers: opts.mcpServers,
1253
1254
  permissionPromptToolName: opts.permissionPromptToolName,
1254
1255
  permissionMode: opts.permissionMode,
1256
+ model: opts.model,
1257
+ fallbackModel: opts.fallbackModel,
1258
+ customSystemPrompt: opts.customSystemPrompt,
1259
+ appendSystemPrompt: opts.appendSystemPrompt,
1260
+ allowedTools: opts.allowedTools,
1261
+ disallowedTools: opts.disallowedTools,
1255
1262
  executable: "node",
1256
1263
  abort: opts.signal,
1257
1264
  pathToClaudeCodeExecutable: (() => {
@@ -1261,7 +1268,7 @@ async function claudeRemote(opts) {
1261
1268
  if (opts.claudeArgs && opts.claudeArgs.length > 0) {
1262
1269
  sdkOptions.executableArgs = [...sdkOptions.executableArgs || [], ...opts.claudeArgs];
1263
1270
  }
1264
- types$1.logger.debug(`[claudeRemote] Starting query with permission mode: ${opts.permissionMode}`);
1271
+ types$1.logger.debug(`[claudeRemote] Starting query with permission mode: ${opts.permissionMode}, model: ${opts.model || "default"}, fallbackModel: ${opts.fallbackModel || "none"}, customSystemPrompt: ${opts.customSystemPrompt ? "set" : "none"}, appendSystemPrompt: ${opts.appendSystemPrompt ? "set" : "none"}, allowedTools: ${opts.allowedTools ? opts.allowedTools.join(",") : "none"}, disallowedTools: ${opts.disallowedTools ? opts.disallowedTools.join(",") : "none"}`);
1265
1272
  let message = new PushableAsyncIterable();
1266
1273
  message.push({
1267
1274
  type: "user",
@@ -1441,9 +1448,9 @@ async function startPermissionResolver(session) {
1441
1448
  if (response.approved) {
1442
1449
  types$1.logger.debug("Plan approved - injecting PLAN_FAKE_RESTART");
1443
1450
  if (response.mode && ["default", "acceptEdits", "bypassPermissions"].includes(response.mode)) {
1444
- session.queue.unshift(PLAN_FAKE_RESTART, response.mode);
1451
+ session.queue.unshift(PLAN_FAKE_RESTART, { permissionMode: response.mode });
1445
1452
  } else {
1446
- session.queue.unshift(PLAN_FAKE_RESTART, "default");
1453
+ session.queue.unshift(PLAN_FAKE_RESTART, { permissionMode: "default" });
1447
1454
  }
1448
1455
  resolve({ approved: false, reason: PLAN_FAKE_REJECT });
1449
1456
  } else {
@@ -2152,7 +2159,13 @@ async function claudeRemoteLauncher(session) {
2152
2159
  }
2153
2160
  },
2154
2161
  permissionPromptToolName: "mcp__permission__" + permissions.server.toolName,
2155
- permissionMode: messageData.mode,
2162
+ permissionMode: messageData.mode.permissionMode,
2163
+ model: messageData.mode.model,
2164
+ fallbackModel: messageData.mode.fallbackModel,
2165
+ customSystemPrompt: messageData.mode.customSystemPrompt,
2166
+ appendSystemPrompt: messageData.mode.appendSystemPrompt,
2167
+ allowedTools: messageData.mode.allowedTools,
2168
+ disallowedTools: messageData.mode.disallowedTools,
2156
2169
  onSessionFound: (sessionId) => {
2157
2170
  sdkToLogConverter.updateSessionId(sessionId);
2158
2171
  session.onSessionFound(sessionId);
@@ -2248,7 +2261,7 @@ async function loop(opts) {
2248
2261
  }
2249
2262
 
2250
2263
  var name = "happy-coder";
2251
- var version = "0.3.1-beta.2";
2264
+ var version = "0.5.0";
2252
2265
  var description = "Claude Code session sharing CLI";
2253
2266
  var author = "Kirill Dubovitskiy";
2254
2267
  var license = "MIT";
@@ -2297,13 +2310,13 @@ var scripts = {
2297
2310
  build: "shx rm -rf dist && tsc --noEmit && pkgroll",
2298
2311
  prepublishOnly: "yarn build && yarn test",
2299
2312
  typecheck: "tsc --noEmit",
2300
- dev: "npx tsx --env-file .env.sample src/index.ts",
2301
- "dev:local-server": "cross-env HANDY_SERVER_URL=http://localhost:3005 tsx --env-file .env.sample src/index.ts",
2313
+ dev: "yarn build && npx tsx --env-file .env.sample src/index.ts",
2314
+ "dev:local-server": "yarn build && cross-env HANDY_SERVER_URL=http://localhost:3005 tsx --env-file .env.sample src/index.ts",
2302
2315
  "version:prerelease": "npm version prerelease --preid=beta",
2303
2316
  "publish:prerelease": "npm publish --tag beta"
2304
2317
  };
2305
2318
  var dependencies = {
2306
- "@anthropic-ai/claude-code": "^1.0.72",
2319
+ "@anthropic-ai/claude-code": "^1.0.73",
2307
2320
  "@anthropic-ai/sdk": "^0.56.0",
2308
2321
  "@modelcontextprotocol/sdk": "^1.15.1",
2309
2322
  "@stablelib/base64": "^2.0.1",
@@ -2828,6 +2841,92 @@ class MessageQueue2 {
2828
2841
  }
2829
2842
  }
2830
2843
 
2844
+ function deterministicStringify(obj, options = {}) {
2845
+ const {
2846
+ undefinedBehavior = "omit",
2847
+ sortArrays = false,
2848
+ replacer,
2849
+ includeSymbols = false
2850
+ } = options;
2851
+ const seen = /* @__PURE__ */ new WeakSet();
2852
+ function processValue(value, key) {
2853
+ if (replacer && key !== void 0) {
2854
+ value = replacer(key, value);
2855
+ }
2856
+ if (value === null) return null;
2857
+ if (value === void 0) {
2858
+ switch (undefinedBehavior) {
2859
+ case "omit":
2860
+ return void 0;
2861
+ case "null":
2862
+ return null;
2863
+ case "throw":
2864
+ throw new Error(`Undefined value at key: ${key}`);
2865
+ }
2866
+ }
2867
+ if (typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
2868
+ return value;
2869
+ }
2870
+ if (value instanceof Date) {
2871
+ return value.toISOString();
2872
+ }
2873
+ if (value instanceof RegExp) {
2874
+ return value.toString();
2875
+ }
2876
+ if (typeof value === "function") {
2877
+ return void 0;
2878
+ }
2879
+ if (typeof value === "symbol") {
2880
+ return includeSymbols ? value.toString() : void 0;
2881
+ }
2882
+ if (typeof value === "bigint") {
2883
+ return value.toString() + "n";
2884
+ }
2885
+ if (seen.has(value)) {
2886
+ throw new Error("Circular reference detected");
2887
+ }
2888
+ seen.add(value);
2889
+ if (Array.isArray(value)) {
2890
+ const processed2 = value.map((item, index) => processValue(item, String(index))).filter((item) => item !== void 0);
2891
+ if (sortArrays) {
2892
+ processed2.sort((a, b) => {
2893
+ const aStr = JSON.stringify(processValue(a));
2894
+ const bStr = JSON.stringify(processValue(b));
2895
+ return aStr.localeCompare(bStr);
2896
+ });
2897
+ }
2898
+ seen.delete(value);
2899
+ return processed2;
2900
+ }
2901
+ if (value.constructor === Object || value.constructor === void 0) {
2902
+ const processed2 = {};
2903
+ const keys = Object.keys(value).sort();
2904
+ for (const k of keys) {
2905
+ const processedValue = processValue(value[k], k);
2906
+ if (processedValue !== void 0) {
2907
+ processed2[k] = processedValue;
2908
+ }
2909
+ }
2910
+ seen.delete(value);
2911
+ return processed2;
2912
+ }
2913
+ try {
2914
+ const plain = { ...value };
2915
+ seen.delete(value);
2916
+ return processValue(plain, key);
2917
+ } catch {
2918
+ seen.delete(value);
2919
+ return String(value);
2920
+ }
2921
+ }
2922
+ const processed = processValue(obj);
2923
+ return JSON.stringify(processed);
2924
+ }
2925
+ function hashObject(obj, options, encoding = "hex") {
2926
+ const jsonString = deterministicStringify(obj, options);
2927
+ return crypto.createHash("sha256").update(jsonString).digest(encoding);
2928
+ }
2929
+
2831
2930
  let caffeinateProcess = null;
2832
2931
  function startCaffeinate() {
2833
2932
  if (process.platform !== "darwin") {
@@ -2962,7 +3061,8 @@ async function start(credentials, options = {}) {
2962
3061
  host: os.hostname(),
2963
3062
  version: packageJson.version,
2964
3063
  os: os.platform(),
2965
- machineId: settings.machineId
3064
+ machineId: settings.machineId,
3065
+ homeDir: os.homedir()
2966
3066
  };
2967
3067
  const response = await api.getOrCreateSession({ tag: sessionTag, metadata, state });
2968
3068
  types$1.logger.debug(`Session created: ${response.id}`);
@@ -2990,9 +3090,15 @@ async function start(credentials, options = {}) {
2990
3090
  if (caffeinateStarted) {
2991
3091
  types$1.logger.infoDeveloper("Sleep prevention enabled (macOS)");
2992
3092
  }
2993
- const messageQueue = new MessageQueue2((mode) => mode);
3093
+ const messageQueue = new MessageQueue2((mode) => hashObject(mode));
2994
3094
  registerHandlers(session);
2995
3095
  let currentPermissionMode = options.permissionMode;
3096
+ let currentModel = options.model;
3097
+ let currentFallbackModel = void 0;
3098
+ let currentCustomSystemPrompt = void 0;
3099
+ let currentAppendSystemPrompt = void 0;
3100
+ let currentAllowedTools = void 0;
3101
+ let currentDisallowedTools = void 0;
2996
3102
  session.onUserMessage((message) => {
2997
3103
  let messagePermissionMode = currentPermissionMode;
2998
3104
  if (message.meta?.permissionMode) {
@@ -3007,7 +3113,64 @@ async function start(credentials, options = {}) {
3007
3113
  } else {
3008
3114
  types$1.logger.debug(`[loop] User message received with no permission mode override, using current: ${currentPermissionMode}`);
3009
3115
  }
3010
- messageQueue.push(message.content.text, messagePermissionMode || "default");
3116
+ let messageModel = currentModel;
3117
+ if (message.meta?.hasOwnProperty("model")) {
3118
+ messageModel = message.meta.model || void 0;
3119
+ currentModel = messageModel;
3120
+ types$1.logger.debug(`[loop] Model updated from user message: ${messageModel || "reset to default"}`);
3121
+ } else {
3122
+ types$1.logger.debug(`[loop] User message received with no model override, using current: ${currentModel || "default"}`);
3123
+ }
3124
+ let messageCustomSystemPrompt = currentCustomSystemPrompt;
3125
+ if (message.meta?.hasOwnProperty("customSystemPrompt")) {
3126
+ messageCustomSystemPrompt = message.meta.customSystemPrompt || void 0;
3127
+ currentCustomSystemPrompt = messageCustomSystemPrompt;
3128
+ types$1.logger.debug(`[loop] Custom system prompt updated from user message: ${messageCustomSystemPrompt ? "set" : "reset to none"}`);
3129
+ } else {
3130
+ types$1.logger.debug(`[loop] User message received with no custom system prompt override, using current: ${currentCustomSystemPrompt ? "set" : "none"}`);
3131
+ }
3132
+ let messageFallbackModel = currentFallbackModel;
3133
+ if (message.meta?.hasOwnProperty("fallbackModel")) {
3134
+ messageFallbackModel = message.meta.fallbackModel || void 0;
3135
+ currentFallbackModel = messageFallbackModel;
3136
+ types$1.logger.debug(`[loop] Fallback model updated from user message: ${messageFallbackModel || "reset to none"}`);
3137
+ } else {
3138
+ types$1.logger.debug(`[loop] User message received with no fallback model override, using current: ${currentFallbackModel || "none"}`);
3139
+ }
3140
+ let messageAppendSystemPrompt = currentAppendSystemPrompt;
3141
+ if (message.meta?.hasOwnProperty("appendSystemPrompt")) {
3142
+ messageAppendSystemPrompt = message.meta.appendSystemPrompt || void 0;
3143
+ currentAppendSystemPrompt = messageAppendSystemPrompt;
3144
+ types$1.logger.debug(`[loop] Append system prompt updated from user message: ${messageAppendSystemPrompt ? "set" : "reset to none"}`);
3145
+ } else {
3146
+ types$1.logger.debug(`[loop] User message received with no append system prompt override, using current: ${currentAppendSystemPrompt ? "set" : "none"}`);
3147
+ }
3148
+ let messageAllowedTools = currentAllowedTools;
3149
+ if (message.meta?.hasOwnProperty("allowedTools")) {
3150
+ messageAllowedTools = message.meta.allowedTools || void 0;
3151
+ currentAllowedTools = messageAllowedTools;
3152
+ types$1.logger.debug(`[loop] Allowed tools updated from user message: ${messageAllowedTools ? messageAllowedTools.join(", ") : "reset to none"}`);
3153
+ } else {
3154
+ types$1.logger.debug(`[loop] User message received with no allowed tools override, using current: ${currentAllowedTools ? currentAllowedTools.join(", ") : "none"}`);
3155
+ }
3156
+ let messageDisallowedTools = currentDisallowedTools;
3157
+ if (message.meta?.hasOwnProperty("disallowedTools")) {
3158
+ messageDisallowedTools = message.meta.disallowedTools || void 0;
3159
+ currentDisallowedTools = messageDisallowedTools;
3160
+ types$1.logger.debug(`[loop] Disallowed tools updated from user message: ${messageDisallowedTools ? messageDisallowedTools.join(", ") : "reset to none"}`);
3161
+ } else {
3162
+ types$1.logger.debug(`[loop] User message received with no disallowed tools override, using current: ${currentDisallowedTools ? currentDisallowedTools.join(", ") : "none"}`);
3163
+ }
3164
+ const enhancedMode = {
3165
+ permissionMode: messagePermissionMode || "default",
3166
+ model: messageModel,
3167
+ fallbackModel: messageFallbackModel,
3168
+ customSystemPrompt: messageCustomSystemPrompt,
3169
+ appendSystemPrompt: messageAppendSystemPrompt,
3170
+ allowedTools: messageAllowedTools,
3171
+ disallowedTools: messageDisallowedTools
3172
+ };
3173
+ messageQueue.push(message.content.text, enhancedMode);
3011
3174
  types$1.logger.debugLargeJson("User message pushed to queue:", message);
3012
3175
  });
3013
3176
  await loop({
@@ -3116,6 +3279,7 @@ class ApiDaemonSession extends node_events.EventEmitter {
3116
3279
  token;
3117
3280
  secret;
3118
3281
  spawnedProcesses = /* @__PURE__ */ new Set();
3282
+ machineRegistered = false;
3119
3283
  constructor(token, secret, machineIdentity) {
3120
3284
  super();
3121
3285
  this.token = token;
@@ -3137,9 +3301,12 @@ class ApiDaemonSession extends node_events.EventEmitter {
3137
3301
  withCredentials: true,
3138
3302
  autoConnect: false
3139
3303
  });
3140
- socket.on("connect", () => {
3304
+ socket.on("connect", async () => {
3141
3305
  types$1.logger.debug("[DAEMON SESSION] Socket connected");
3142
3306
  types$1.logger.debug(`[DAEMON SESSION] Connected with auth - token: ${this.token.substring(0, 10)}..., machineId: ${this.machineIdentity.machineId}`);
3307
+ if (!this.machineRegistered) {
3308
+ await this.registerMachine();
3309
+ }
3143
3310
  const rpcMethod = `${this.machineIdentity.machineId}:spawn-happy-session`;
3144
3311
  socket.emit("rpc-register", { method: rpcMethod });
3145
3312
  types$1.logger.debug(`[DAEMON SESSION] Emitted RPC registration: ${rpcMethod}`);
@@ -3166,16 +3333,11 @@ class ApiDaemonSession extends node_events.EventEmitter {
3166
3333
  args.push("--local");
3167
3334
  }
3168
3335
  types$1.logger.debug(`[DAEMON SESSION] Spawning happy in directory: ${directory} with args: ${args.join(" ")}`);
3169
- const happyPath = process.argv[1];
3170
- const runningFromBuiltBinary = happyPath.endsWith("happy") || happyPath.endsWith("happy.cmd");
3171
- let executable, spawnArgs;
3172
- if (runningFromBuiltBinary) {
3173
- executable = happyPath;
3174
- spawnArgs = args;
3175
- } else {
3176
- executable = "npx";
3177
- spawnArgs = ["tsx", happyPath, ...args];
3178
- }
3336
+ const happyBinPath = path.join(projectPath(), "bin", "happy.mjs");
3337
+ types$1.logger.debug(`[DAEMON SESSION] Using happy binary at: ${happyBinPath}`);
3338
+ const executable = happyBinPath;
3339
+ const spawnArgs = args;
3340
+ types$1.logger.debug(`[DAEMON SESSION] Spawn: executable=${executable}, args=${JSON.stringify(spawnArgs)}, cwd=${directory}`);
3179
3341
  const happyProcess = child_process.spawn(executable, spawnArgs, {
3180
3342
  cwd: directory,
3181
3343
  detached: true,
@@ -3268,7 +3430,7 @@ class ApiDaemonSession extends node_events.EventEmitter {
3268
3430
  types$1.logger.debug(`[DAEMON SESSION] RPC error: ${JSON.stringify(data)}`);
3269
3431
  });
3270
3432
  socket.onAny((event, ...args) => {
3271
- if (!event.startsWith("machine-alive")) {
3433
+ if (!event.startsWith("session-alive") && event !== "ephemeral") {
3272
3434
  types$1.logger.debug(`[DAEMON SESSION] Socket event: ${event}, args: ${JSON.stringify(args)}`);
3273
3435
  }
3274
3436
  });
@@ -3291,12 +3453,47 @@ class ApiDaemonSession extends node_events.EventEmitter {
3291
3453
  });
3292
3454
  this.socket = socket;
3293
3455
  }
3456
+ async registerMachine() {
3457
+ try {
3458
+ const metadata = {
3459
+ host: this.machineIdentity.machineHost,
3460
+ platform: this.machineIdentity.platform,
3461
+ happyCliVersion: this.machineIdentity.happyCliVersion,
3462
+ happyHomeDirectory: this.machineIdentity.happyHomeDirectory
3463
+ };
3464
+ const encrypted = types$1.encrypt(JSON.stringify(metadata), this.secret);
3465
+ const encryptedMetadata = types$1.encodeBase64(encrypted);
3466
+ const response = await fetch(`${types$1.configuration.serverUrl}/v1/machines`, {
3467
+ method: "POST",
3468
+ headers: {
3469
+ "Authorization": `Bearer ${this.token}`,
3470
+ "Content-Type": "application/json"
3471
+ },
3472
+ body: JSON.stringify({
3473
+ id: this.machineIdentity.machineId,
3474
+ metadata: encryptedMetadata
3475
+ })
3476
+ });
3477
+ if (response.ok) {
3478
+ types$1.logger.debug("[DAEMON SESSION] Machine registered/updated successfully");
3479
+ this.machineRegistered = true;
3480
+ } else {
3481
+ types$1.logger.debug(`[DAEMON SESSION] Failed to register machine: ${response.status}`);
3482
+ }
3483
+ } catch (error) {
3484
+ types$1.logger.debug("[DAEMON SESSION] Failed to register machine:", error);
3485
+ }
3486
+ }
3294
3487
  startKeepAlive() {
3295
3488
  this.stopKeepAlive();
3296
3489
  this.keepAliveInterval = setInterval(() => {
3297
- this.socket.volatile.emit("machine-alive", {
3490
+ const payload = {
3491
+ type: "machine-scoped",
3492
+ machineId: this.machineIdentity.machineId,
3298
3493
  time: Date.now()
3299
- });
3494
+ };
3495
+ types$1.logger.debugLargeJson(`[DAEMON SESSION] Emitting session-alive`, payload);
3496
+ this.socket.emit("session-alive", payload);
3300
3497
  }, 2e4);
3301
3498
  }
3302
3499
  stopKeepAlive() {
@@ -3321,15 +3518,26 @@ class ApiDaemonSession extends node_events.EventEmitter {
3321
3518
  connect() {
3322
3519
  this.socket.connect();
3323
3520
  }
3521
+ updateMetadata(updates) {
3522
+ const metadata = {
3523
+ host: this.machineIdentity.machineHost,
3524
+ platform: this.machineIdentity.platform,
3525
+ happyCliVersion: updates.happyCliVersion || this.machineIdentity.happyCliVersion,
3526
+ happyHomeDirectory: updates.happyHomeDirectory || this.machineIdentity.happyHomeDirectory
3527
+ };
3528
+ const encrypted = types$1.encrypt(JSON.stringify(metadata), this.secret);
3529
+ const encryptedMetadata = types$1.encodeBase64(encrypted);
3530
+ this.socket.emit("update-machine-metadata", { metadata: encryptedMetadata });
3531
+ }
3324
3532
  shutdown() {
3325
3533
  types$1.logger.debug(`[DAEMON SESSION] Shutting down daemon, killing ${this.spawnedProcesses.size} spawned processes`);
3326
- for (const process2 of this.spawnedProcesses) {
3534
+ for (const process of this.spawnedProcesses) {
3327
3535
  try {
3328
- types$1.logger.debug(`[DAEMON SESSION] Killing spawned process with PID: ${process2.pid}`);
3329
- process2.kill("SIGTERM");
3536
+ types$1.logger.debug(`[DAEMON SESSION] Killing spawned process with PID: ${process.pid}`);
3537
+ process.kill("SIGTERM");
3330
3538
  setTimeout(() => {
3331
3539
  try {
3332
- process2.kill("SIGKILL");
3540
+ process.kill("SIGKILL");
3333
3541
  } catch (e) {
3334
3542
  }
3335
3543
  }, 1e3);
@@ -3405,7 +3613,8 @@ async function startDaemon() {
3405
3613
  machineId: settings.machineId,
3406
3614
  machineHost: settings.machineHost || os$1.hostname(),
3407
3615
  platform: process.platform,
3408
- version: packageJson.version
3616
+ happyCliVersion: packageJson.version,
3617
+ happyHomeDirectory: process.cwd()
3409
3618
  };
3410
3619
  let credentials = await readCredentials();
3411
3620
  if (!credentials) {
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import chalk from 'chalk';
2
- import { l as logger, d as backoff, e as delay, R as RawJSONLinesSchema, c as configuration, f as encodeBase64, A as ApiClient, g as encodeBase64Url, h as decodeBase64, b as initializeConfiguration, i as initLoggerWithGlobalConfiguration } from './types-BX4xv8Ty.mjs';
2
+ import { l as logger, d as backoff, e as delay, R as RawJSONLinesSchema, c as configuration, f as encodeBase64, A as ApiClient, g as encodeBase64Url, h as decodeBase64, j as encrypt, b as initializeConfiguration, i as initLoggerWithGlobalConfiguration } from './types-DDjn6Ovv.mjs';
3
3
  import { randomUUID, randomBytes } from 'node:crypto';
4
4
  import { spawn, execSync } from 'node:child_process';
5
5
  import { resolve, join, dirname as dirname$1 } from 'node:path';
@@ -459,6 +459,7 @@ function messageKey(message) {
459
459
  }
460
460
  async function readSessionLog(projectDir, sessionId) {
461
461
  const expectedSessionFile = join(projectDir, `${sessionId}.jsonl`);
462
+ logger.debug(`[SESSION_SCANNER] Reading session file: ${expectedSessionFile}`);
462
463
  let file;
463
464
  try {
464
465
  file = await readFile(expectedSessionFile, "utf-8");
@@ -1231,6 +1232,12 @@ async function claudeRemote(opts) {
1231
1232
  mcpServers: opts.mcpServers,
1232
1233
  permissionPromptToolName: opts.permissionPromptToolName,
1233
1234
  permissionMode: opts.permissionMode,
1235
+ model: opts.model,
1236
+ fallbackModel: opts.fallbackModel,
1237
+ customSystemPrompt: opts.customSystemPrompt,
1238
+ appendSystemPrompt: opts.appendSystemPrompt,
1239
+ allowedTools: opts.allowedTools,
1240
+ disallowedTools: opts.disallowedTools,
1234
1241
  executable: "node",
1235
1242
  abort: opts.signal,
1236
1243
  pathToClaudeCodeExecutable: (() => {
@@ -1240,7 +1247,7 @@ async function claudeRemote(opts) {
1240
1247
  if (opts.claudeArgs && opts.claudeArgs.length > 0) {
1241
1248
  sdkOptions.executableArgs = [...sdkOptions.executableArgs || [], ...opts.claudeArgs];
1242
1249
  }
1243
- logger.debug(`[claudeRemote] Starting query with permission mode: ${opts.permissionMode}`);
1250
+ logger.debug(`[claudeRemote] Starting query with permission mode: ${opts.permissionMode}, model: ${opts.model || "default"}, fallbackModel: ${opts.fallbackModel || "none"}, customSystemPrompt: ${opts.customSystemPrompt ? "set" : "none"}, appendSystemPrompt: ${opts.appendSystemPrompt ? "set" : "none"}, allowedTools: ${opts.allowedTools ? opts.allowedTools.join(",") : "none"}, disallowedTools: ${opts.disallowedTools ? opts.disallowedTools.join(",") : "none"}`);
1244
1251
  let message = new PushableAsyncIterable();
1245
1252
  message.push({
1246
1253
  type: "user",
@@ -1420,9 +1427,9 @@ async function startPermissionResolver(session) {
1420
1427
  if (response.approved) {
1421
1428
  logger.debug("Plan approved - injecting PLAN_FAKE_RESTART");
1422
1429
  if (response.mode && ["default", "acceptEdits", "bypassPermissions"].includes(response.mode)) {
1423
- session.queue.unshift(PLAN_FAKE_RESTART, response.mode);
1430
+ session.queue.unshift(PLAN_FAKE_RESTART, { permissionMode: response.mode });
1424
1431
  } else {
1425
- session.queue.unshift(PLAN_FAKE_RESTART, "default");
1432
+ session.queue.unshift(PLAN_FAKE_RESTART, { permissionMode: "default" });
1426
1433
  }
1427
1434
  resolve({ approved: false, reason: PLAN_FAKE_REJECT });
1428
1435
  } else {
@@ -2131,7 +2138,13 @@ async function claudeRemoteLauncher(session) {
2131
2138
  }
2132
2139
  },
2133
2140
  permissionPromptToolName: "mcp__permission__" + permissions.server.toolName,
2134
- permissionMode: messageData.mode,
2141
+ permissionMode: messageData.mode.permissionMode,
2142
+ model: messageData.mode.model,
2143
+ fallbackModel: messageData.mode.fallbackModel,
2144
+ customSystemPrompt: messageData.mode.customSystemPrompt,
2145
+ appendSystemPrompt: messageData.mode.appendSystemPrompt,
2146
+ allowedTools: messageData.mode.allowedTools,
2147
+ disallowedTools: messageData.mode.disallowedTools,
2135
2148
  onSessionFound: (sessionId) => {
2136
2149
  sdkToLogConverter.updateSessionId(sessionId);
2137
2150
  session.onSessionFound(sessionId);
@@ -2227,7 +2240,7 @@ async function loop(opts) {
2227
2240
  }
2228
2241
 
2229
2242
  var name = "happy-coder";
2230
- var version = "0.3.1-beta.2";
2243
+ var version = "0.5.0";
2231
2244
  var description = "Claude Code session sharing CLI";
2232
2245
  var author = "Kirill Dubovitskiy";
2233
2246
  var license = "MIT";
@@ -2276,13 +2289,13 @@ var scripts = {
2276
2289
  build: "shx rm -rf dist && tsc --noEmit && pkgroll",
2277
2290
  prepublishOnly: "yarn build && yarn test",
2278
2291
  typecheck: "tsc --noEmit",
2279
- dev: "npx tsx --env-file .env.sample src/index.ts",
2280
- "dev:local-server": "cross-env HANDY_SERVER_URL=http://localhost:3005 tsx --env-file .env.sample src/index.ts",
2292
+ dev: "yarn build && npx tsx --env-file .env.sample src/index.ts",
2293
+ "dev:local-server": "yarn build && cross-env HANDY_SERVER_URL=http://localhost:3005 tsx --env-file .env.sample src/index.ts",
2281
2294
  "version:prerelease": "npm version prerelease --preid=beta",
2282
2295
  "publish:prerelease": "npm publish --tag beta"
2283
2296
  };
2284
2297
  var dependencies = {
2285
- "@anthropic-ai/claude-code": "^1.0.72",
2298
+ "@anthropic-ai/claude-code": "^1.0.73",
2286
2299
  "@anthropic-ai/sdk": "^0.56.0",
2287
2300
  "@modelcontextprotocol/sdk": "^1.15.1",
2288
2301
  "@stablelib/base64": "^2.0.1",
@@ -2807,6 +2820,92 @@ class MessageQueue2 {
2807
2820
  }
2808
2821
  }
2809
2822
 
2823
+ function deterministicStringify(obj, options = {}) {
2824
+ const {
2825
+ undefinedBehavior = "omit",
2826
+ sortArrays = false,
2827
+ replacer,
2828
+ includeSymbols = false
2829
+ } = options;
2830
+ const seen = /* @__PURE__ */ new WeakSet();
2831
+ function processValue(value, key) {
2832
+ if (replacer && key !== void 0) {
2833
+ value = replacer(key, value);
2834
+ }
2835
+ if (value === null) return null;
2836
+ if (value === void 0) {
2837
+ switch (undefinedBehavior) {
2838
+ case "omit":
2839
+ return void 0;
2840
+ case "null":
2841
+ return null;
2842
+ case "throw":
2843
+ throw new Error(`Undefined value at key: ${key}`);
2844
+ }
2845
+ }
2846
+ if (typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
2847
+ return value;
2848
+ }
2849
+ if (value instanceof Date) {
2850
+ return value.toISOString();
2851
+ }
2852
+ if (value instanceof RegExp) {
2853
+ return value.toString();
2854
+ }
2855
+ if (typeof value === "function") {
2856
+ return void 0;
2857
+ }
2858
+ if (typeof value === "symbol") {
2859
+ return includeSymbols ? value.toString() : void 0;
2860
+ }
2861
+ if (typeof value === "bigint") {
2862
+ return value.toString() + "n";
2863
+ }
2864
+ if (seen.has(value)) {
2865
+ throw new Error("Circular reference detected");
2866
+ }
2867
+ seen.add(value);
2868
+ if (Array.isArray(value)) {
2869
+ const processed2 = value.map((item, index) => processValue(item, String(index))).filter((item) => item !== void 0);
2870
+ if (sortArrays) {
2871
+ processed2.sort((a, b) => {
2872
+ const aStr = JSON.stringify(processValue(a));
2873
+ const bStr = JSON.stringify(processValue(b));
2874
+ return aStr.localeCompare(bStr);
2875
+ });
2876
+ }
2877
+ seen.delete(value);
2878
+ return processed2;
2879
+ }
2880
+ if (value.constructor === Object || value.constructor === void 0) {
2881
+ const processed2 = {};
2882
+ const keys = Object.keys(value).sort();
2883
+ for (const k of keys) {
2884
+ const processedValue = processValue(value[k], k);
2885
+ if (processedValue !== void 0) {
2886
+ processed2[k] = processedValue;
2887
+ }
2888
+ }
2889
+ seen.delete(value);
2890
+ return processed2;
2891
+ }
2892
+ try {
2893
+ const plain = { ...value };
2894
+ seen.delete(value);
2895
+ return processValue(plain, key);
2896
+ } catch {
2897
+ seen.delete(value);
2898
+ return String(value);
2899
+ }
2900
+ }
2901
+ const processed = processValue(obj);
2902
+ return JSON.stringify(processed);
2903
+ }
2904
+ function hashObject(obj, options, encoding = "hex") {
2905
+ const jsonString = deterministicStringify(obj, options);
2906
+ return createHash("sha256").update(jsonString).digest(encoding);
2907
+ }
2908
+
2810
2909
  let caffeinateProcess = null;
2811
2910
  function startCaffeinate() {
2812
2911
  if (process.platform !== "darwin") {
@@ -2941,7 +3040,8 @@ async function start(credentials, options = {}) {
2941
3040
  host: os.hostname(),
2942
3041
  version: packageJson.version,
2943
3042
  os: os.platform(),
2944
- machineId: settings.machineId
3043
+ machineId: settings.machineId,
3044
+ homeDir: os.homedir()
2945
3045
  };
2946
3046
  const response = await api.getOrCreateSession({ tag: sessionTag, metadata, state });
2947
3047
  logger.debug(`Session created: ${response.id}`);
@@ -2969,9 +3069,15 @@ async function start(credentials, options = {}) {
2969
3069
  if (caffeinateStarted) {
2970
3070
  logger.infoDeveloper("Sleep prevention enabled (macOS)");
2971
3071
  }
2972
- const messageQueue = new MessageQueue2((mode) => mode);
3072
+ const messageQueue = new MessageQueue2((mode) => hashObject(mode));
2973
3073
  registerHandlers(session);
2974
3074
  let currentPermissionMode = options.permissionMode;
3075
+ let currentModel = options.model;
3076
+ let currentFallbackModel = void 0;
3077
+ let currentCustomSystemPrompt = void 0;
3078
+ let currentAppendSystemPrompt = void 0;
3079
+ let currentAllowedTools = void 0;
3080
+ let currentDisallowedTools = void 0;
2975
3081
  session.onUserMessage((message) => {
2976
3082
  let messagePermissionMode = currentPermissionMode;
2977
3083
  if (message.meta?.permissionMode) {
@@ -2986,7 +3092,64 @@ async function start(credentials, options = {}) {
2986
3092
  } else {
2987
3093
  logger.debug(`[loop] User message received with no permission mode override, using current: ${currentPermissionMode}`);
2988
3094
  }
2989
- messageQueue.push(message.content.text, messagePermissionMode || "default");
3095
+ let messageModel = currentModel;
3096
+ if (message.meta?.hasOwnProperty("model")) {
3097
+ messageModel = message.meta.model || void 0;
3098
+ currentModel = messageModel;
3099
+ logger.debug(`[loop] Model updated from user message: ${messageModel || "reset to default"}`);
3100
+ } else {
3101
+ logger.debug(`[loop] User message received with no model override, using current: ${currentModel || "default"}`);
3102
+ }
3103
+ let messageCustomSystemPrompt = currentCustomSystemPrompt;
3104
+ if (message.meta?.hasOwnProperty("customSystemPrompt")) {
3105
+ messageCustomSystemPrompt = message.meta.customSystemPrompt || void 0;
3106
+ currentCustomSystemPrompt = messageCustomSystemPrompt;
3107
+ logger.debug(`[loop] Custom system prompt updated from user message: ${messageCustomSystemPrompt ? "set" : "reset to none"}`);
3108
+ } else {
3109
+ logger.debug(`[loop] User message received with no custom system prompt override, using current: ${currentCustomSystemPrompt ? "set" : "none"}`);
3110
+ }
3111
+ let messageFallbackModel = currentFallbackModel;
3112
+ if (message.meta?.hasOwnProperty("fallbackModel")) {
3113
+ messageFallbackModel = message.meta.fallbackModel || void 0;
3114
+ currentFallbackModel = messageFallbackModel;
3115
+ logger.debug(`[loop] Fallback model updated from user message: ${messageFallbackModel || "reset to none"}`);
3116
+ } else {
3117
+ logger.debug(`[loop] User message received with no fallback model override, using current: ${currentFallbackModel || "none"}`);
3118
+ }
3119
+ let messageAppendSystemPrompt = currentAppendSystemPrompt;
3120
+ if (message.meta?.hasOwnProperty("appendSystemPrompt")) {
3121
+ messageAppendSystemPrompt = message.meta.appendSystemPrompt || void 0;
3122
+ currentAppendSystemPrompt = messageAppendSystemPrompt;
3123
+ logger.debug(`[loop] Append system prompt updated from user message: ${messageAppendSystemPrompt ? "set" : "reset to none"}`);
3124
+ } else {
3125
+ logger.debug(`[loop] User message received with no append system prompt override, using current: ${currentAppendSystemPrompt ? "set" : "none"}`);
3126
+ }
3127
+ let messageAllowedTools = currentAllowedTools;
3128
+ if (message.meta?.hasOwnProperty("allowedTools")) {
3129
+ messageAllowedTools = message.meta.allowedTools || void 0;
3130
+ currentAllowedTools = messageAllowedTools;
3131
+ logger.debug(`[loop] Allowed tools updated from user message: ${messageAllowedTools ? messageAllowedTools.join(", ") : "reset to none"}`);
3132
+ } else {
3133
+ logger.debug(`[loop] User message received with no allowed tools override, using current: ${currentAllowedTools ? currentAllowedTools.join(", ") : "none"}`);
3134
+ }
3135
+ let messageDisallowedTools = currentDisallowedTools;
3136
+ if (message.meta?.hasOwnProperty("disallowedTools")) {
3137
+ messageDisallowedTools = message.meta.disallowedTools || void 0;
3138
+ currentDisallowedTools = messageDisallowedTools;
3139
+ logger.debug(`[loop] Disallowed tools updated from user message: ${messageDisallowedTools ? messageDisallowedTools.join(", ") : "reset to none"}`);
3140
+ } else {
3141
+ logger.debug(`[loop] User message received with no disallowed tools override, using current: ${currentDisallowedTools ? currentDisallowedTools.join(", ") : "none"}`);
3142
+ }
3143
+ const enhancedMode = {
3144
+ permissionMode: messagePermissionMode || "default",
3145
+ model: messageModel,
3146
+ fallbackModel: messageFallbackModel,
3147
+ customSystemPrompt: messageCustomSystemPrompt,
3148
+ appendSystemPrompt: messageAppendSystemPrompt,
3149
+ allowedTools: messageAllowedTools,
3150
+ disallowedTools: messageDisallowedTools
3151
+ };
3152
+ messageQueue.push(message.content.text, enhancedMode);
2990
3153
  logger.debugLargeJson("User message pushed to queue:", message);
2991
3154
  });
2992
3155
  await loop({
@@ -3095,6 +3258,7 @@ class ApiDaemonSession extends EventEmitter {
3095
3258
  token;
3096
3259
  secret;
3097
3260
  spawnedProcesses = /* @__PURE__ */ new Set();
3261
+ machineRegistered = false;
3098
3262
  constructor(token, secret, machineIdentity) {
3099
3263
  super();
3100
3264
  this.token = token;
@@ -3116,9 +3280,12 @@ class ApiDaemonSession extends EventEmitter {
3116
3280
  withCredentials: true,
3117
3281
  autoConnect: false
3118
3282
  });
3119
- socket.on("connect", () => {
3283
+ socket.on("connect", async () => {
3120
3284
  logger.debug("[DAEMON SESSION] Socket connected");
3121
3285
  logger.debug(`[DAEMON SESSION] Connected with auth - token: ${this.token.substring(0, 10)}..., machineId: ${this.machineIdentity.machineId}`);
3286
+ if (!this.machineRegistered) {
3287
+ await this.registerMachine();
3288
+ }
3122
3289
  const rpcMethod = `${this.machineIdentity.machineId}:spawn-happy-session`;
3123
3290
  socket.emit("rpc-register", { method: rpcMethod });
3124
3291
  logger.debug(`[DAEMON SESSION] Emitted RPC registration: ${rpcMethod}`);
@@ -3145,16 +3312,11 @@ class ApiDaemonSession extends EventEmitter {
3145
3312
  args.push("--local");
3146
3313
  }
3147
3314
  logger.debug(`[DAEMON SESSION] Spawning happy in directory: ${directory} with args: ${args.join(" ")}`);
3148
- const happyPath = process.argv[1];
3149
- const runningFromBuiltBinary = happyPath.endsWith("happy") || happyPath.endsWith("happy.cmd");
3150
- let executable, spawnArgs;
3151
- if (runningFromBuiltBinary) {
3152
- executable = happyPath;
3153
- spawnArgs = args;
3154
- } else {
3155
- executable = "npx";
3156
- spawnArgs = ["tsx", happyPath, ...args];
3157
- }
3315
+ const happyBinPath = join$1(projectPath(), "bin", "happy.mjs");
3316
+ logger.debug(`[DAEMON SESSION] Using happy binary at: ${happyBinPath}`);
3317
+ const executable = happyBinPath;
3318
+ const spawnArgs = args;
3319
+ logger.debug(`[DAEMON SESSION] Spawn: executable=${executable}, args=${JSON.stringify(spawnArgs)}, cwd=${directory}`);
3158
3320
  const happyProcess = spawn$1(executable, spawnArgs, {
3159
3321
  cwd: directory,
3160
3322
  detached: true,
@@ -3247,7 +3409,7 @@ class ApiDaemonSession extends EventEmitter {
3247
3409
  logger.debug(`[DAEMON SESSION] RPC error: ${JSON.stringify(data)}`);
3248
3410
  });
3249
3411
  socket.onAny((event, ...args) => {
3250
- if (!event.startsWith("machine-alive")) {
3412
+ if (!event.startsWith("session-alive") && event !== "ephemeral") {
3251
3413
  logger.debug(`[DAEMON SESSION] Socket event: ${event}, args: ${JSON.stringify(args)}`);
3252
3414
  }
3253
3415
  });
@@ -3270,12 +3432,47 @@ class ApiDaemonSession extends EventEmitter {
3270
3432
  });
3271
3433
  this.socket = socket;
3272
3434
  }
3435
+ async registerMachine() {
3436
+ try {
3437
+ const metadata = {
3438
+ host: this.machineIdentity.machineHost,
3439
+ platform: this.machineIdentity.platform,
3440
+ happyCliVersion: this.machineIdentity.happyCliVersion,
3441
+ happyHomeDirectory: this.machineIdentity.happyHomeDirectory
3442
+ };
3443
+ const encrypted = encrypt(JSON.stringify(metadata), this.secret);
3444
+ const encryptedMetadata = encodeBase64(encrypted);
3445
+ const response = await fetch(`${configuration.serverUrl}/v1/machines`, {
3446
+ method: "POST",
3447
+ headers: {
3448
+ "Authorization": `Bearer ${this.token}`,
3449
+ "Content-Type": "application/json"
3450
+ },
3451
+ body: JSON.stringify({
3452
+ id: this.machineIdentity.machineId,
3453
+ metadata: encryptedMetadata
3454
+ })
3455
+ });
3456
+ if (response.ok) {
3457
+ logger.debug("[DAEMON SESSION] Machine registered/updated successfully");
3458
+ this.machineRegistered = true;
3459
+ } else {
3460
+ logger.debug(`[DAEMON SESSION] Failed to register machine: ${response.status}`);
3461
+ }
3462
+ } catch (error) {
3463
+ logger.debug("[DAEMON SESSION] Failed to register machine:", error);
3464
+ }
3465
+ }
3273
3466
  startKeepAlive() {
3274
3467
  this.stopKeepAlive();
3275
3468
  this.keepAliveInterval = setInterval(() => {
3276
- this.socket.volatile.emit("machine-alive", {
3469
+ const payload = {
3470
+ type: "machine-scoped",
3471
+ machineId: this.machineIdentity.machineId,
3277
3472
  time: Date.now()
3278
- });
3473
+ };
3474
+ logger.debugLargeJson(`[DAEMON SESSION] Emitting session-alive`, payload);
3475
+ this.socket.emit("session-alive", payload);
3279
3476
  }, 2e4);
3280
3477
  }
3281
3478
  stopKeepAlive() {
@@ -3300,15 +3497,26 @@ class ApiDaemonSession extends EventEmitter {
3300
3497
  connect() {
3301
3498
  this.socket.connect();
3302
3499
  }
3500
+ updateMetadata(updates) {
3501
+ const metadata = {
3502
+ host: this.machineIdentity.machineHost,
3503
+ platform: this.machineIdentity.platform,
3504
+ happyCliVersion: updates.happyCliVersion || this.machineIdentity.happyCliVersion,
3505
+ happyHomeDirectory: updates.happyHomeDirectory || this.machineIdentity.happyHomeDirectory
3506
+ };
3507
+ const encrypted = encrypt(JSON.stringify(metadata), this.secret);
3508
+ const encryptedMetadata = encodeBase64(encrypted);
3509
+ this.socket.emit("update-machine-metadata", { metadata: encryptedMetadata });
3510
+ }
3303
3511
  shutdown() {
3304
3512
  logger.debug(`[DAEMON SESSION] Shutting down daemon, killing ${this.spawnedProcesses.size} spawned processes`);
3305
- for (const process2 of this.spawnedProcesses) {
3513
+ for (const process of this.spawnedProcesses) {
3306
3514
  try {
3307
- logger.debug(`[DAEMON SESSION] Killing spawned process with PID: ${process2.pid}`);
3308
- process2.kill("SIGTERM");
3515
+ logger.debug(`[DAEMON SESSION] Killing spawned process with PID: ${process.pid}`);
3516
+ process.kill("SIGTERM");
3309
3517
  setTimeout(() => {
3310
3518
  try {
3311
- process2.kill("SIGKILL");
3519
+ process.kill("SIGKILL");
3312
3520
  } catch (e) {
3313
3521
  }
3314
3522
  }, 1e3);
@@ -3384,7 +3592,8 @@ async function startDaemon() {
3384
3592
  machineId: settings.machineId,
3385
3593
  machineHost: settings.machineHost || hostname(),
3386
3594
  platform: process.platform,
3387
- version: packageJson.version
3595
+ happyCliVersion: packageJson.version,
3596
+ happyHomeDirectory: process.cwd()
3388
3597
  };
3389
3598
  let credentials = await readCredentials();
3390
3599
  if (!credentials) {
package/dist/lib.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var types = require('./types-DXK5YldG.cjs');
3
+ var types = require('./types-BBpJNhIN.cjs');
4
4
  require('axios');
5
5
  require('chalk');
6
6
  require('fs');
package/dist/lib.d.cts CHANGED
@@ -339,12 +339,30 @@ declare const UserMessageSchema: z.ZodObject<{
339
339
  meta: z.ZodOptional<z.ZodObject<{
340
340
  sentFrom: z.ZodOptional<z.ZodString>;
341
341
  permissionMode: z.ZodOptional<z.ZodString>;
342
+ model: z.ZodOptional<z.ZodNullable<z.ZodString>>;
343
+ fallbackModel: z.ZodOptional<z.ZodNullable<z.ZodString>>;
344
+ customSystemPrompt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
345
+ appendSystemPrompt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
346
+ allowedTools: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString, "many">>>;
347
+ disallowedTools: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString, "many">>>;
342
348
  }, "strip", z.ZodTypeAny, {
343
349
  sentFrom?: string | undefined;
344
350
  permissionMode?: string | undefined;
351
+ model?: string | null | undefined;
352
+ fallbackModel?: string | null | undefined;
353
+ customSystemPrompt?: string | null | undefined;
354
+ appendSystemPrompt?: string | null | undefined;
355
+ allowedTools?: string[] | null | undefined;
356
+ disallowedTools?: string[] | null | undefined;
345
357
  }, {
346
358
  sentFrom?: string | undefined;
347
359
  permissionMode?: string | undefined;
360
+ model?: string | null | undefined;
361
+ fallbackModel?: string | null | undefined;
362
+ customSystemPrompt?: string | null | undefined;
363
+ appendSystemPrompt?: string | null | undefined;
364
+ allowedTools?: string[] | null | undefined;
365
+ disallowedTools?: string[] | null | undefined;
348
366
  }>>;
349
367
  }, "strip", z.ZodTypeAny, {
350
368
  content: {
@@ -356,6 +374,12 @@ declare const UserMessageSchema: z.ZodObject<{
356
374
  meta?: {
357
375
  sentFrom?: string | undefined;
358
376
  permissionMode?: string | undefined;
377
+ model?: string | null | undefined;
378
+ fallbackModel?: string | null | undefined;
379
+ customSystemPrompt?: string | null | undefined;
380
+ appendSystemPrompt?: string | null | undefined;
381
+ allowedTools?: string[] | null | undefined;
382
+ disallowedTools?: string[] | null | undefined;
359
383
  } | undefined;
360
384
  }, {
361
385
  content: {
@@ -367,6 +391,12 @@ declare const UserMessageSchema: z.ZodObject<{
367
391
  meta?: {
368
392
  sentFrom?: string | undefined;
369
393
  permissionMode?: string | undefined;
394
+ model?: string | null | undefined;
395
+ fallbackModel?: string | null | undefined;
396
+ customSystemPrompt?: string | null | undefined;
397
+ appendSystemPrompt?: string | null | undefined;
398
+ allowedTools?: string[] | null | undefined;
399
+ disallowedTools?: string[] | null | undefined;
370
400
  } | undefined;
371
401
  }>;
372
402
  type UserMessage = z.infer<typeof UserMessageSchema>;
@@ -383,6 +413,7 @@ type Metadata = {
383
413
  machineId?: string;
384
414
  tools?: string[];
385
415
  slashCommands?: string[];
416
+ homeDir?: string;
386
417
  };
387
418
  type AgentState = {
388
419
  controlledByUser?: boolean | null | undefined;
package/dist/lib.d.mts CHANGED
@@ -339,12 +339,30 @@ declare const UserMessageSchema: z.ZodObject<{
339
339
  meta: z.ZodOptional<z.ZodObject<{
340
340
  sentFrom: z.ZodOptional<z.ZodString>;
341
341
  permissionMode: z.ZodOptional<z.ZodString>;
342
+ model: z.ZodOptional<z.ZodNullable<z.ZodString>>;
343
+ fallbackModel: z.ZodOptional<z.ZodNullable<z.ZodString>>;
344
+ customSystemPrompt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
345
+ appendSystemPrompt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
346
+ allowedTools: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString, "many">>>;
347
+ disallowedTools: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString, "many">>>;
342
348
  }, "strip", z.ZodTypeAny, {
343
349
  sentFrom?: string | undefined;
344
350
  permissionMode?: string | undefined;
351
+ model?: string | null | undefined;
352
+ fallbackModel?: string | null | undefined;
353
+ customSystemPrompt?: string | null | undefined;
354
+ appendSystemPrompt?: string | null | undefined;
355
+ allowedTools?: string[] | null | undefined;
356
+ disallowedTools?: string[] | null | undefined;
345
357
  }, {
346
358
  sentFrom?: string | undefined;
347
359
  permissionMode?: string | undefined;
360
+ model?: string | null | undefined;
361
+ fallbackModel?: string | null | undefined;
362
+ customSystemPrompt?: string | null | undefined;
363
+ appendSystemPrompt?: string | null | undefined;
364
+ allowedTools?: string[] | null | undefined;
365
+ disallowedTools?: string[] | null | undefined;
348
366
  }>>;
349
367
  }, "strip", z.ZodTypeAny, {
350
368
  content: {
@@ -356,6 +374,12 @@ declare const UserMessageSchema: z.ZodObject<{
356
374
  meta?: {
357
375
  sentFrom?: string | undefined;
358
376
  permissionMode?: string | undefined;
377
+ model?: string | null | undefined;
378
+ fallbackModel?: string | null | undefined;
379
+ customSystemPrompt?: string | null | undefined;
380
+ appendSystemPrompt?: string | null | undefined;
381
+ allowedTools?: string[] | null | undefined;
382
+ disallowedTools?: string[] | null | undefined;
359
383
  } | undefined;
360
384
  }, {
361
385
  content: {
@@ -367,6 +391,12 @@ declare const UserMessageSchema: z.ZodObject<{
367
391
  meta?: {
368
392
  sentFrom?: string | undefined;
369
393
  permissionMode?: string | undefined;
394
+ model?: string | null | undefined;
395
+ fallbackModel?: string | null | undefined;
396
+ customSystemPrompt?: string | null | undefined;
397
+ appendSystemPrompt?: string | null | undefined;
398
+ allowedTools?: string[] | null | undefined;
399
+ disallowedTools?: string[] | null | undefined;
370
400
  } | undefined;
371
401
  }>;
372
402
  type UserMessage = z.infer<typeof UserMessageSchema>;
@@ -383,6 +413,7 @@ type Metadata = {
383
413
  machineId?: string;
384
414
  tools?: string[];
385
415
  slashCommands?: string[];
416
+ homeDir?: string;
386
417
  };
387
418
  type AgentState = {
388
419
  controlledByUser?: boolean | null | undefined;
package/dist/lib.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, i as initLoggerWithGlobalConfiguration, b as initializeConfiguration, l as logger } from './types-BX4xv8Ty.mjs';
1
+ export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, i as initLoggerWithGlobalConfiguration, b as initializeConfiguration, l as logger } from './types-DDjn6Ovv.mjs';
2
2
  import 'axios';
3
3
  import 'chalk';
4
4
  import 'fs';
@@ -245,8 +245,20 @@ z.z.object({
245
245
  const MessageMetaSchema = z.z.object({
246
246
  sentFrom: z.z.string().optional(),
247
247
  // Source identifier
248
- permissionMode: z.z.string().optional()
248
+ permissionMode: z.z.string().optional(),
249
249
  // Permission mode for this message
250
+ model: z.z.string().nullable().optional(),
251
+ // Model name for this message (null = reset)
252
+ fallbackModel: z.z.string().nullable().optional(),
253
+ // Fallback model for this message (null = reset)
254
+ customSystemPrompt: z.z.string().nullable().optional(),
255
+ // Custom system prompt for this message (null = reset)
256
+ appendSystemPrompt: z.z.string().nullable().optional(),
257
+ // Append to system prompt for this message (null = reset)
258
+ allowedTools: z.z.array(z.z.string()).nullable().optional(),
259
+ // Allowed tools for this message (null = reset)
260
+ disallowedTools: z.z.array(z.z.string()).nullable().optional()
261
+ // Disallowed tools for this message (null = reset)
250
262
  });
251
263
  z.z.object({
252
264
  session: z.z.object({
@@ -534,6 +546,7 @@ class ApiSessionClient extends node_events.EventEmitter {
534
546
  */
535
547
  keepAlive(thinking, mode) {
536
548
  this.socket.volatile.emit("session-alive", {
549
+ type: "session-scoped",
537
550
  sid: this.sessionId,
538
551
  time: Date.now(),
539
552
  thinking,
@@ -888,5 +901,6 @@ exports.decodeBase64 = decodeBase64;
888
901
  exports.delay = delay;
889
902
  exports.encodeBase64 = encodeBase64;
890
903
  exports.encodeBase64Url = encodeBase64Url;
904
+ exports.encrypt = encrypt;
891
905
  exports.initLoggerWithGlobalConfiguration = initLoggerWithGlobalConfiguration;
892
906
  exports.initializeConfiguration = initializeConfiguration;
@@ -243,8 +243,20 @@ z.object({
243
243
  const MessageMetaSchema = z.object({
244
244
  sentFrom: z.string().optional(),
245
245
  // Source identifier
246
- permissionMode: z.string().optional()
246
+ permissionMode: z.string().optional(),
247
247
  // Permission mode for this message
248
+ model: z.string().nullable().optional(),
249
+ // Model name for this message (null = reset)
250
+ fallbackModel: z.string().nullable().optional(),
251
+ // Fallback model for this message (null = reset)
252
+ customSystemPrompt: z.string().nullable().optional(),
253
+ // Custom system prompt for this message (null = reset)
254
+ appendSystemPrompt: z.string().nullable().optional(),
255
+ // Append to system prompt for this message (null = reset)
256
+ allowedTools: z.array(z.string()).nullable().optional(),
257
+ // Allowed tools for this message (null = reset)
258
+ disallowedTools: z.array(z.string()).nullable().optional()
259
+ // Disallowed tools for this message (null = reset)
248
260
  });
249
261
  z.object({
250
262
  session: z.object({
@@ -532,6 +544,7 @@ class ApiSessionClient extends EventEmitter {
532
544
  */
533
545
  keepAlive(thinking, mode) {
534
546
  this.socket.volatile.emit("session-alive", {
547
+ type: "session-scoped",
535
548
  sid: this.sessionId,
536
549
  time: Date.now(),
537
550
  thinking,
@@ -878,4 +891,4 @@ const RawJSONLinesSchema = z.discriminatedUnion("type", [
878
891
  }).passthrough()
879
892
  ]);
880
893
 
881
- export { ApiClient as A, RawJSONLinesSchema as R, ApiSessionClient as a, initializeConfiguration as b, configuration as c, backoff as d, delay as e, encodeBase64 as f, encodeBase64Url as g, decodeBase64 as h, initLoggerWithGlobalConfiguration as i, logger as l };
894
+ export { ApiClient as A, RawJSONLinesSchema as R, ApiSessionClient as a, initializeConfiguration as b, configuration as c, backoff as d, delay as e, encodeBase64 as f, encodeBase64Url as g, decodeBase64 as h, initLoggerWithGlobalConfiguration as i, encrypt as j, logger as l };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "happy-coder",
3
- "version": "0.3.1-beta.2",
3
+ "version": "0.5.0",
4
4
  "description": "Claude Code session sharing CLI",
5
5
  "author": "Kirill Dubovitskiy",
6
6
  "license": "MIT",
@@ -49,13 +49,13 @@
49
49
  "build": "shx rm -rf dist && tsc --noEmit && pkgroll",
50
50
  "prepublishOnly": "yarn build && yarn test",
51
51
  "typecheck": "tsc --noEmit",
52
- "dev": "npx tsx --env-file .env.sample src/index.ts",
53
- "dev:local-server": "cross-env HANDY_SERVER_URL=http://localhost:3005 tsx --env-file .env.sample src/index.ts",
52
+ "dev": "yarn build && npx tsx --env-file .env.sample src/index.ts",
53
+ "dev:local-server": "yarn build && cross-env HANDY_SERVER_URL=http://localhost:3005 tsx --env-file .env.sample src/index.ts",
54
54
  "version:prerelease": "npm version prerelease --preid=beta",
55
55
  "publish:prerelease": "npm publish --tag beta"
56
56
  },
57
57
  "dependencies": {
58
- "@anthropic-ai/claude-code": "^1.0.72",
58
+ "@anthropic-ai/claude-code": "^1.0.73",
59
59
  "@anthropic-ai/sdk": "^0.56.0",
60
60
  "@modelcontextprotocol/sdk": "^1.15.1",
61
61
  "@stablelib/base64": "^2.0.1",
@@ -91,4 +91,4 @@
91
91
  "overrides": {
92
92
  "whatwg-url": "14.2.0"
93
93
  }
94
- }
94
+ }