happy-coder 0.4.0 → 0.6.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-eN-YHsuj.cjs');
4
+ var types$1 = require('./types-Bkw2UUhb.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');
@@ -28,6 +28,7 @@ var child_process = require('child_process');
28
28
  var util = require('util');
29
29
  var crypto = require('crypto');
30
30
  var qrcode = require('qrcode-terminal');
31
+ var open = require('open');
31
32
  var fs = require('fs');
32
33
  var os$1 = require('os');
33
34
 
@@ -480,6 +481,7 @@ function messageKey(message) {
480
481
  }
481
482
  async function readSessionLog(projectDir, sessionId) {
482
483
  const expectedSessionFile = node_path.join(projectDir, `${sessionId}.jsonl`);
484
+ types$1.logger.debug(`[SESSION_SCANNER] Reading session file: ${expectedSessionFile}`);
483
485
  let file;
484
486
  try {
485
487
  file = await promises$1.readFile(expectedSessionFile, "utf-8");
@@ -1252,6 +1254,12 @@ async function claudeRemote(opts) {
1252
1254
  mcpServers: opts.mcpServers,
1253
1255
  permissionPromptToolName: opts.permissionPromptToolName,
1254
1256
  permissionMode: opts.permissionMode,
1257
+ model: opts.model,
1258
+ fallbackModel: opts.fallbackModel,
1259
+ customSystemPrompt: opts.customSystemPrompt,
1260
+ appendSystemPrompt: opts.appendSystemPrompt,
1261
+ allowedTools: opts.allowedTools,
1262
+ disallowedTools: opts.disallowedTools,
1255
1263
  executable: "node",
1256
1264
  abort: opts.signal,
1257
1265
  pathToClaudeCodeExecutable: (() => {
@@ -1261,7 +1269,7 @@ async function claudeRemote(opts) {
1261
1269
  if (opts.claudeArgs && opts.claudeArgs.length > 0) {
1262
1270
  sdkOptions.executableArgs = [...sdkOptions.executableArgs || [], ...opts.claudeArgs];
1263
1271
  }
1264
- types$1.logger.debug(`[claudeRemote] Starting query with permission mode: ${opts.permissionMode}`);
1272
+ 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
1273
  let message = new PushableAsyncIterable();
1266
1274
  message.push({
1267
1275
  type: "user",
@@ -1441,9 +1449,9 @@ async function startPermissionResolver(session) {
1441
1449
  if (response.approved) {
1442
1450
  types$1.logger.debug("Plan approved - injecting PLAN_FAKE_RESTART");
1443
1451
  if (response.mode && ["default", "acceptEdits", "bypassPermissions"].includes(response.mode)) {
1444
- session.queue.unshift(PLAN_FAKE_RESTART, response.mode);
1452
+ session.queue.unshift(PLAN_FAKE_RESTART, { permissionMode: response.mode });
1445
1453
  } else {
1446
- session.queue.unshift(PLAN_FAKE_RESTART, "default");
1454
+ session.queue.unshift(PLAN_FAKE_RESTART, { permissionMode: "default" });
1447
1455
  }
1448
1456
  resolve({ approved: false, reason: PLAN_FAKE_REJECT });
1449
1457
  } else {
@@ -2152,7 +2160,13 @@ async function claudeRemoteLauncher(session) {
2152
2160
  }
2153
2161
  },
2154
2162
  permissionPromptToolName: "mcp__permission__" + permissions.server.toolName,
2155
- permissionMode: messageData.mode,
2163
+ permissionMode: messageData.mode.permissionMode,
2164
+ model: messageData.mode.model,
2165
+ fallbackModel: messageData.mode.fallbackModel,
2166
+ customSystemPrompt: messageData.mode.customSystemPrompt,
2167
+ appendSystemPrompt: messageData.mode.appendSystemPrompt,
2168
+ allowedTools: messageData.mode.allowedTools,
2169
+ disallowedTools: messageData.mode.disallowedTools,
2156
2170
  onSessionFound: (sessionId) => {
2157
2171
  sdkToLogConverter.updateSessionId(sessionId);
2158
2172
  session.onSessionFound(sessionId);
@@ -2248,7 +2262,7 @@ async function loop(opts) {
2248
2262
  }
2249
2263
 
2250
2264
  var name = "happy-coder";
2251
- var version = "0.4.0";
2265
+ var version = "0.6.0";
2252
2266
  var description = "Claude Code session sharing CLI";
2253
2267
  var author = "Kirill Dubovitskiy";
2254
2268
  var license = "MIT";
@@ -2316,7 +2330,7 @@ var dependencies = {
2316
2330
  "http-proxy": "^1.18.1",
2317
2331
  "http-proxy-middleware": "^3.0.5",
2318
2332
  ink: "^6.1.0",
2319
- "ink-box": "^2.0.0",
2333
+ open: "^10.2.0",
2320
2334
  "qrcode-terminal": "^0.12.0",
2321
2335
  react: "^19.1.1",
2322
2336
  "socket.io-client": "^4.8.1",
@@ -2828,6 +2842,92 @@ class MessageQueue2 {
2828
2842
  }
2829
2843
  }
2830
2844
 
2845
+ function deterministicStringify(obj, options = {}) {
2846
+ const {
2847
+ undefinedBehavior = "omit",
2848
+ sortArrays = false,
2849
+ replacer,
2850
+ includeSymbols = false
2851
+ } = options;
2852
+ const seen = /* @__PURE__ */ new WeakSet();
2853
+ function processValue(value, key) {
2854
+ if (replacer && key !== void 0) {
2855
+ value = replacer(key, value);
2856
+ }
2857
+ if (value === null) return null;
2858
+ if (value === void 0) {
2859
+ switch (undefinedBehavior) {
2860
+ case "omit":
2861
+ return void 0;
2862
+ case "null":
2863
+ return null;
2864
+ case "throw":
2865
+ throw new Error(`Undefined value at key: ${key}`);
2866
+ }
2867
+ }
2868
+ if (typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
2869
+ return value;
2870
+ }
2871
+ if (value instanceof Date) {
2872
+ return value.toISOString();
2873
+ }
2874
+ if (value instanceof RegExp) {
2875
+ return value.toString();
2876
+ }
2877
+ if (typeof value === "function") {
2878
+ return void 0;
2879
+ }
2880
+ if (typeof value === "symbol") {
2881
+ return includeSymbols ? value.toString() : void 0;
2882
+ }
2883
+ if (typeof value === "bigint") {
2884
+ return value.toString() + "n";
2885
+ }
2886
+ if (seen.has(value)) {
2887
+ throw new Error("Circular reference detected");
2888
+ }
2889
+ seen.add(value);
2890
+ if (Array.isArray(value)) {
2891
+ const processed2 = value.map((item, index) => processValue(item, String(index))).filter((item) => item !== void 0);
2892
+ if (sortArrays) {
2893
+ processed2.sort((a, b) => {
2894
+ const aStr = JSON.stringify(processValue(a));
2895
+ const bStr = JSON.stringify(processValue(b));
2896
+ return aStr.localeCompare(bStr);
2897
+ });
2898
+ }
2899
+ seen.delete(value);
2900
+ return processed2;
2901
+ }
2902
+ if (value.constructor === Object || value.constructor === void 0) {
2903
+ const processed2 = {};
2904
+ const keys = Object.keys(value).sort();
2905
+ for (const k of keys) {
2906
+ const processedValue = processValue(value[k], k);
2907
+ if (processedValue !== void 0) {
2908
+ processed2[k] = processedValue;
2909
+ }
2910
+ }
2911
+ seen.delete(value);
2912
+ return processed2;
2913
+ }
2914
+ try {
2915
+ const plain = { ...value };
2916
+ seen.delete(value);
2917
+ return processValue(plain, key);
2918
+ } catch {
2919
+ seen.delete(value);
2920
+ return String(value);
2921
+ }
2922
+ }
2923
+ const processed = processValue(obj);
2924
+ return JSON.stringify(processed);
2925
+ }
2926
+ function hashObject(obj, options, encoding = "hex") {
2927
+ const jsonString = deterministicStringify(obj, options);
2928
+ return crypto.createHash("sha256").update(jsonString).digest(encoding);
2929
+ }
2930
+
2831
2931
  let caffeinateProcess = null;
2832
2932
  function startCaffeinate() {
2833
2933
  if (process.platform !== "darwin") {
@@ -2991,9 +3091,15 @@ async function start(credentials, options = {}) {
2991
3091
  if (caffeinateStarted) {
2992
3092
  types$1.logger.infoDeveloper("Sleep prevention enabled (macOS)");
2993
3093
  }
2994
- const messageQueue = new MessageQueue2((mode) => mode);
3094
+ const messageQueue = new MessageQueue2((mode) => hashObject(mode));
2995
3095
  registerHandlers(session);
2996
3096
  let currentPermissionMode = options.permissionMode;
3097
+ let currentModel = options.model;
3098
+ let currentFallbackModel = void 0;
3099
+ let currentCustomSystemPrompt = void 0;
3100
+ let currentAppendSystemPrompt = void 0;
3101
+ let currentAllowedTools = void 0;
3102
+ let currentDisallowedTools = void 0;
2997
3103
  session.onUserMessage((message) => {
2998
3104
  let messagePermissionMode = currentPermissionMode;
2999
3105
  if (message.meta?.permissionMode) {
@@ -3008,7 +3114,64 @@ async function start(credentials, options = {}) {
3008
3114
  } else {
3009
3115
  types$1.logger.debug(`[loop] User message received with no permission mode override, using current: ${currentPermissionMode}`);
3010
3116
  }
3011
- messageQueue.push(message.content.text, messagePermissionMode || "default");
3117
+ let messageModel = currentModel;
3118
+ if (message.meta?.hasOwnProperty("model")) {
3119
+ messageModel = message.meta.model || void 0;
3120
+ currentModel = messageModel;
3121
+ types$1.logger.debug(`[loop] Model updated from user message: ${messageModel || "reset to default"}`);
3122
+ } else {
3123
+ types$1.logger.debug(`[loop] User message received with no model override, using current: ${currentModel || "default"}`);
3124
+ }
3125
+ let messageCustomSystemPrompt = currentCustomSystemPrompt;
3126
+ if (message.meta?.hasOwnProperty("customSystemPrompt")) {
3127
+ messageCustomSystemPrompt = message.meta.customSystemPrompt || void 0;
3128
+ currentCustomSystemPrompt = messageCustomSystemPrompt;
3129
+ types$1.logger.debug(`[loop] Custom system prompt updated from user message: ${messageCustomSystemPrompt ? "set" : "reset to none"}`);
3130
+ } else {
3131
+ types$1.logger.debug(`[loop] User message received with no custom system prompt override, using current: ${currentCustomSystemPrompt ? "set" : "none"}`);
3132
+ }
3133
+ let messageFallbackModel = currentFallbackModel;
3134
+ if (message.meta?.hasOwnProperty("fallbackModel")) {
3135
+ messageFallbackModel = message.meta.fallbackModel || void 0;
3136
+ currentFallbackModel = messageFallbackModel;
3137
+ types$1.logger.debug(`[loop] Fallback model updated from user message: ${messageFallbackModel || "reset to none"}`);
3138
+ } else {
3139
+ types$1.logger.debug(`[loop] User message received with no fallback model override, using current: ${currentFallbackModel || "none"}`);
3140
+ }
3141
+ let messageAppendSystemPrompt = currentAppendSystemPrompt;
3142
+ if (message.meta?.hasOwnProperty("appendSystemPrompt")) {
3143
+ messageAppendSystemPrompt = message.meta.appendSystemPrompt || void 0;
3144
+ currentAppendSystemPrompt = messageAppendSystemPrompt;
3145
+ types$1.logger.debug(`[loop] Append system prompt updated from user message: ${messageAppendSystemPrompt ? "set" : "reset to none"}`);
3146
+ } else {
3147
+ types$1.logger.debug(`[loop] User message received with no append system prompt override, using current: ${currentAppendSystemPrompt ? "set" : "none"}`);
3148
+ }
3149
+ let messageAllowedTools = currentAllowedTools;
3150
+ if (message.meta?.hasOwnProperty("allowedTools")) {
3151
+ messageAllowedTools = message.meta.allowedTools || void 0;
3152
+ currentAllowedTools = messageAllowedTools;
3153
+ types$1.logger.debug(`[loop] Allowed tools updated from user message: ${messageAllowedTools ? messageAllowedTools.join(", ") : "reset to none"}`);
3154
+ } else {
3155
+ types$1.logger.debug(`[loop] User message received with no allowed tools override, using current: ${currentAllowedTools ? currentAllowedTools.join(", ") : "none"}`);
3156
+ }
3157
+ let messageDisallowedTools = currentDisallowedTools;
3158
+ if (message.meta?.hasOwnProperty("disallowedTools")) {
3159
+ messageDisallowedTools = message.meta.disallowedTools || void 0;
3160
+ currentDisallowedTools = messageDisallowedTools;
3161
+ types$1.logger.debug(`[loop] Disallowed tools updated from user message: ${messageDisallowedTools ? messageDisallowedTools.join(", ") : "reset to none"}`);
3162
+ } else {
3163
+ types$1.logger.debug(`[loop] User message received with no disallowed tools override, using current: ${currentDisallowedTools ? currentDisallowedTools.join(", ") : "none"}`);
3164
+ }
3165
+ const enhancedMode = {
3166
+ permissionMode: messagePermissionMode || "default",
3167
+ model: messageModel,
3168
+ fallbackModel: messageFallbackModel,
3169
+ customSystemPrompt: messageCustomSystemPrompt,
3170
+ appendSystemPrompt: messageAppendSystemPrompt,
3171
+ allowedTools: messageAllowedTools,
3172
+ disallowedTools: messageDisallowedTools
3173
+ };
3174
+ messageQueue.push(message.content.text, enhancedMode);
3012
3175
  types$1.logger.debugLargeJson("User message pushed to queue:", message);
3013
3176
  });
3014
3177
  await loop({
@@ -3052,8 +3215,69 @@ function displayQRCode(url) {
3052
3215
  console.log("=".repeat(80));
3053
3216
  }
3054
3217
 
3218
+ function generateWebAuthUrl(publicKey) {
3219
+ const publicKeyBase64 = types$1.encodeBase64(publicKey, "base64url");
3220
+ return `https://app.happy.engineering/terminal/connect#key=${publicKeyBase64}`;
3221
+ }
3222
+
3223
+ async function openBrowser(url) {
3224
+ try {
3225
+ if (!process.stdout.isTTY || process.env.CI || process.env.HEADLESS) {
3226
+ types$1.logger.debug("[browser] Headless environment detected, skipping browser open");
3227
+ return false;
3228
+ }
3229
+ types$1.logger.debug(`[browser] Attempting to open URL: ${url}`);
3230
+ await open(url);
3231
+ types$1.logger.debug("[browser] Browser opened successfully");
3232
+ return true;
3233
+ } catch (error) {
3234
+ types$1.logger.debug("[browser] Failed to open browser:", error);
3235
+ return false;
3236
+ }
3237
+ }
3238
+
3239
+ const AuthSelector = ({ onSelect, onCancel }) => {
3240
+ const [selectedIndex, setSelectedIndex] = React.useState(0);
3241
+ const options = [
3242
+ {
3243
+ method: "mobile",
3244
+ label: "Mobile App"
3245
+ },
3246
+ {
3247
+ method: "web",
3248
+ label: "Web Browser"
3249
+ }
3250
+ ];
3251
+ ink.useInput((input, key) => {
3252
+ if (key.upArrow) {
3253
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
3254
+ } else if (key.downArrow) {
3255
+ setSelectedIndex((prev) => Math.min(options.length - 1, prev + 1));
3256
+ } else if (key.return) {
3257
+ onSelect(options[selectedIndex].method);
3258
+ } else if (key.escape || key.ctrl && input === "c") {
3259
+ onCancel();
3260
+ } else if (input === "1") {
3261
+ setSelectedIndex(0);
3262
+ onSelect("mobile");
3263
+ } else if (input === "2") {
3264
+ setSelectedIndex(1);
3265
+ onSelect("web");
3266
+ }
3267
+ });
3268
+ return /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column", paddingY: 1 }, /* @__PURE__ */ React.createElement(ink.Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(ink.Text, null, "How would you like to authenticate?")), /* @__PURE__ */ React.createElement(ink.Box, { flexDirection: "column" }, options.map((option, index) => {
3269
+ const isSelected = selectedIndex === index;
3270
+ return /* @__PURE__ */ React.createElement(ink.Box, { key: option.method, marginY: 0 }, /* @__PURE__ */ React.createElement(ink.Text, { color: isSelected ? "cyan" : "gray" }, isSelected ? "\u203A " : " ", index + 1, ". ", option.label));
3271
+ })), /* @__PURE__ */ React.createElement(ink.Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(ink.Text, { dimColor: true }, "Use arrows or 1-2 to select, Enter to confirm")));
3272
+ };
3273
+
3055
3274
  async function doAuth() {
3056
- console.log("Starting authentication...");
3275
+ console.clear();
3276
+ const authMethod = await selectAuthenticationMethod();
3277
+ if (!authMethod) {
3278
+ console.log("\nAuthentication cancelled.\n");
3279
+ return null;
3280
+ }
3057
3281
  const secret = new Uint8Array(node_crypto.randomBytes(32));
3058
3282
  const keypair = tweetnacl.box.keyPair.fromSecretKey(secret);
3059
3283
  try {
@@ -3064,38 +3288,106 @@ async function doAuth() {
3064
3288
  console.log("Failed to create authentication request, please try again later.");
3065
3289
  return null;
3066
3290
  }
3067
- console.log("Please, authenticate using mobile app");
3291
+ if (authMethod === "mobile") {
3292
+ return await doMobileAuth(keypair);
3293
+ } else {
3294
+ return await doWebAuth(keypair);
3295
+ }
3296
+ }
3297
+ function selectAuthenticationMethod() {
3298
+ return new Promise((resolve) => {
3299
+ let hasResolved = false;
3300
+ const onSelect = (method) => {
3301
+ if (!hasResolved) {
3302
+ hasResolved = true;
3303
+ app.unmount();
3304
+ resolve(method);
3305
+ }
3306
+ };
3307
+ const onCancel = () => {
3308
+ if (!hasResolved) {
3309
+ hasResolved = true;
3310
+ app.unmount();
3311
+ resolve(null);
3312
+ }
3313
+ };
3314
+ const app = ink.render(React.createElement(AuthSelector, { onSelect, onCancel }), {
3315
+ exitOnCtrlC: false,
3316
+ patchConsole: false
3317
+ });
3318
+ });
3319
+ }
3320
+ async function doMobileAuth(keypair) {
3321
+ console.clear();
3322
+ console.log("\nMobile Authentication\n");
3323
+ console.log("Scan this QR code with your Happy mobile app:\n");
3068
3324
  const authUrl = "happy://terminal?" + types$1.encodeBase64Url(keypair.publicKey);
3069
3325
  displayQRCode(authUrl);
3070
- console.log("\n\u{1F4CB} For manual entry, copy this URL:");
3326
+ console.log("\nOr manually enter this URL:");
3071
3327
  console.log(authUrl);
3072
- let credentials = null;
3073
- while (true) {
3074
- try {
3075
- const response = await axios.post(`${types$1.configuration.serverUrl}/v1/auth/request`, {
3076
- publicKey: types$1.encodeBase64(keypair.publicKey)
3077
- });
3078
- if (response.data.state === "authorized") {
3079
- let token = response.data.token;
3080
- let r = types$1.decodeBase64(response.data.response);
3081
- let decrypted = decryptWithEphemeralKey(r, keypair.secretKey);
3082
- if (decrypted) {
3083
- credentials = {
3084
- secret: decrypted,
3085
- token
3086
- };
3087
- await writeCredentials(credentials);
3088
- return credentials;
3089
- } else {
3090
- console.log("Failed to decrypt response, please try again later.");
3091
- return null;
3328
+ console.log("");
3329
+ return await waitForAuthentication(keypair);
3330
+ }
3331
+ async function doWebAuth(keypair) {
3332
+ console.clear();
3333
+ console.log("\nWeb Authentication\n");
3334
+ const webUrl = generateWebAuthUrl(keypair.publicKey);
3335
+ console.log("Opening your browser...");
3336
+ const browserOpened = await openBrowser(webUrl);
3337
+ if (browserOpened) {
3338
+ console.log("\u2713 Browser opened\n");
3339
+ console.log("Complete authentication in your browser window.");
3340
+ } else {
3341
+ console.log("Could not open browser automatically.\n");
3342
+ console.log("Please open this URL manually:");
3343
+ console.log(webUrl);
3344
+ }
3345
+ console.log("");
3346
+ return await waitForAuthentication(keypair);
3347
+ }
3348
+ async function waitForAuthentication(keypair) {
3349
+ process.stdout.write("Waiting for authentication");
3350
+ let dots = 0;
3351
+ let cancelled = false;
3352
+ const handleInterrupt = () => {
3353
+ cancelled = true;
3354
+ console.log("\n\nAuthentication cancelled.");
3355
+ process.exit(0);
3356
+ };
3357
+ process.on("SIGINT", handleInterrupt);
3358
+ try {
3359
+ while (!cancelled) {
3360
+ try {
3361
+ const response = await axios.post(`${types$1.configuration.serverUrl}/v1/auth/request`, {
3362
+ publicKey: types$1.encodeBase64(keypair.publicKey)
3363
+ });
3364
+ if (response.data.state === "authorized") {
3365
+ let token = response.data.token;
3366
+ let r = types$1.decodeBase64(response.data.response);
3367
+ let decrypted = decryptWithEphemeralKey(r, keypair.secretKey);
3368
+ if (decrypted) {
3369
+ const credentials = {
3370
+ secret: decrypted,
3371
+ token
3372
+ };
3373
+ await writeCredentials(credentials);
3374
+ console.log("\n\n\u2713 Authentication successful\n");
3375
+ return credentials;
3376
+ } else {
3377
+ console.log("\n\nFailed to decrypt response. Please try again.");
3378
+ return null;
3379
+ }
3092
3380
  }
3381
+ } catch (error) {
3382
+ console.log("\n\nFailed to check authentication status. Please try again.");
3383
+ return null;
3093
3384
  }
3094
- } catch (error) {
3095
- console.log("Failed to create authentication request, please try again later.");
3096
- return null;
3385
+ process.stdout.write("\rWaiting for authentication" + ".".repeat(dots % 3 + 1) + " ");
3386
+ dots++;
3387
+ await types$1.delay(1e3);
3097
3388
  }
3098
- await types$1.delay(1e3);
3389
+ } finally {
3390
+ process.off("SIGINT", handleInterrupt);
3099
3391
  }
3100
3392
  return null;
3101
3393
  }
@@ -3326,12 +3618,11 @@ class ApiDaemonSession extends node_events.EventEmitter {
3326
3618
  this.stopKeepAlive();
3327
3619
  this.keepAliveInterval = setInterval(() => {
3328
3620
  const payload = {
3329
- type: "machine-scoped",
3330
3621
  machineId: this.machineIdentity.machineId,
3331
3622
  time: Date.now()
3332
3623
  };
3333
- types$1.logger.debugLargeJson(`[DAEMON SESSION] Emitting session-alive`, payload);
3334
- this.socket.emit("session-alive", payload);
3624
+ types$1.logger.debugLargeJson(`[DAEMON SESSION] Emitting machine-alive`, payload);
3625
+ this.socket.emit("machine-alive", payload);
3335
3626
  }, 2e4);
3336
3627
  }
3337
3628
  stopKeepAlive() {
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, j as encrypt, b as initializeConfiguration, i as initLoggerWithGlobalConfiguration } from './types-VkaGP8up.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-Cqy5Dx2C.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';
@@ -27,6 +27,7 @@ import { spawn as spawn$1, exec, execSync as execSync$1 } from 'child_process';
27
27
  import { promisify } from 'util';
28
28
  import crypto, { createHash } from 'crypto';
29
29
  import qrcode from 'qrcode-terminal';
30
+ import open from 'open';
30
31
  import { existsSync as existsSync$1, readFileSync as readFileSync$1, writeFileSync, unlinkSync, mkdirSync as mkdirSync$1, chmodSync } from 'fs';
31
32
  import { hostname, homedir as homedir$1 } from 'os';
32
33
 
@@ -459,6 +460,7 @@ function messageKey(message) {
459
460
  }
460
461
  async function readSessionLog(projectDir, sessionId) {
461
462
  const expectedSessionFile = join(projectDir, `${sessionId}.jsonl`);
463
+ logger.debug(`[SESSION_SCANNER] Reading session file: ${expectedSessionFile}`);
462
464
  let file;
463
465
  try {
464
466
  file = await readFile(expectedSessionFile, "utf-8");
@@ -1231,6 +1233,12 @@ async function claudeRemote(opts) {
1231
1233
  mcpServers: opts.mcpServers,
1232
1234
  permissionPromptToolName: opts.permissionPromptToolName,
1233
1235
  permissionMode: opts.permissionMode,
1236
+ model: opts.model,
1237
+ fallbackModel: opts.fallbackModel,
1238
+ customSystemPrompt: opts.customSystemPrompt,
1239
+ appendSystemPrompt: opts.appendSystemPrompt,
1240
+ allowedTools: opts.allowedTools,
1241
+ disallowedTools: opts.disallowedTools,
1234
1242
  executable: "node",
1235
1243
  abort: opts.signal,
1236
1244
  pathToClaudeCodeExecutable: (() => {
@@ -1240,7 +1248,7 @@ async function claudeRemote(opts) {
1240
1248
  if (opts.claudeArgs && opts.claudeArgs.length > 0) {
1241
1249
  sdkOptions.executableArgs = [...sdkOptions.executableArgs || [], ...opts.claudeArgs];
1242
1250
  }
1243
- logger.debug(`[claudeRemote] Starting query with permission mode: ${opts.permissionMode}`);
1251
+ 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
1252
  let message = new PushableAsyncIterable();
1245
1253
  message.push({
1246
1254
  type: "user",
@@ -1420,9 +1428,9 @@ async function startPermissionResolver(session) {
1420
1428
  if (response.approved) {
1421
1429
  logger.debug("Plan approved - injecting PLAN_FAKE_RESTART");
1422
1430
  if (response.mode && ["default", "acceptEdits", "bypassPermissions"].includes(response.mode)) {
1423
- session.queue.unshift(PLAN_FAKE_RESTART, response.mode);
1431
+ session.queue.unshift(PLAN_FAKE_RESTART, { permissionMode: response.mode });
1424
1432
  } else {
1425
- session.queue.unshift(PLAN_FAKE_RESTART, "default");
1433
+ session.queue.unshift(PLAN_FAKE_RESTART, { permissionMode: "default" });
1426
1434
  }
1427
1435
  resolve({ approved: false, reason: PLAN_FAKE_REJECT });
1428
1436
  } else {
@@ -2131,7 +2139,13 @@ async function claudeRemoteLauncher(session) {
2131
2139
  }
2132
2140
  },
2133
2141
  permissionPromptToolName: "mcp__permission__" + permissions.server.toolName,
2134
- permissionMode: messageData.mode,
2142
+ permissionMode: messageData.mode.permissionMode,
2143
+ model: messageData.mode.model,
2144
+ fallbackModel: messageData.mode.fallbackModel,
2145
+ customSystemPrompt: messageData.mode.customSystemPrompt,
2146
+ appendSystemPrompt: messageData.mode.appendSystemPrompt,
2147
+ allowedTools: messageData.mode.allowedTools,
2148
+ disallowedTools: messageData.mode.disallowedTools,
2135
2149
  onSessionFound: (sessionId) => {
2136
2150
  sdkToLogConverter.updateSessionId(sessionId);
2137
2151
  session.onSessionFound(sessionId);
@@ -2227,7 +2241,7 @@ async function loop(opts) {
2227
2241
  }
2228
2242
 
2229
2243
  var name = "happy-coder";
2230
- var version = "0.4.0";
2244
+ var version = "0.6.0";
2231
2245
  var description = "Claude Code session sharing CLI";
2232
2246
  var author = "Kirill Dubovitskiy";
2233
2247
  var license = "MIT";
@@ -2295,7 +2309,7 @@ var dependencies = {
2295
2309
  "http-proxy": "^1.18.1",
2296
2310
  "http-proxy-middleware": "^3.0.5",
2297
2311
  ink: "^6.1.0",
2298
- "ink-box": "^2.0.0",
2312
+ open: "^10.2.0",
2299
2313
  "qrcode-terminal": "^0.12.0",
2300
2314
  react: "^19.1.1",
2301
2315
  "socket.io-client": "^4.8.1",
@@ -2807,6 +2821,92 @@ class MessageQueue2 {
2807
2821
  }
2808
2822
  }
2809
2823
 
2824
+ function deterministicStringify(obj, options = {}) {
2825
+ const {
2826
+ undefinedBehavior = "omit",
2827
+ sortArrays = false,
2828
+ replacer,
2829
+ includeSymbols = false
2830
+ } = options;
2831
+ const seen = /* @__PURE__ */ new WeakSet();
2832
+ function processValue(value, key) {
2833
+ if (replacer && key !== void 0) {
2834
+ value = replacer(key, value);
2835
+ }
2836
+ if (value === null) return null;
2837
+ if (value === void 0) {
2838
+ switch (undefinedBehavior) {
2839
+ case "omit":
2840
+ return void 0;
2841
+ case "null":
2842
+ return null;
2843
+ case "throw":
2844
+ throw new Error(`Undefined value at key: ${key}`);
2845
+ }
2846
+ }
2847
+ if (typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
2848
+ return value;
2849
+ }
2850
+ if (value instanceof Date) {
2851
+ return value.toISOString();
2852
+ }
2853
+ if (value instanceof RegExp) {
2854
+ return value.toString();
2855
+ }
2856
+ if (typeof value === "function") {
2857
+ return void 0;
2858
+ }
2859
+ if (typeof value === "symbol") {
2860
+ return includeSymbols ? value.toString() : void 0;
2861
+ }
2862
+ if (typeof value === "bigint") {
2863
+ return value.toString() + "n";
2864
+ }
2865
+ if (seen.has(value)) {
2866
+ throw new Error("Circular reference detected");
2867
+ }
2868
+ seen.add(value);
2869
+ if (Array.isArray(value)) {
2870
+ const processed2 = value.map((item, index) => processValue(item, String(index))).filter((item) => item !== void 0);
2871
+ if (sortArrays) {
2872
+ processed2.sort((a, b) => {
2873
+ const aStr = JSON.stringify(processValue(a));
2874
+ const bStr = JSON.stringify(processValue(b));
2875
+ return aStr.localeCompare(bStr);
2876
+ });
2877
+ }
2878
+ seen.delete(value);
2879
+ return processed2;
2880
+ }
2881
+ if (value.constructor === Object || value.constructor === void 0) {
2882
+ const processed2 = {};
2883
+ const keys = Object.keys(value).sort();
2884
+ for (const k of keys) {
2885
+ const processedValue = processValue(value[k], k);
2886
+ if (processedValue !== void 0) {
2887
+ processed2[k] = processedValue;
2888
+ }
2889
+ }
2890
+ seen.delete(value);
2891
+ return processed2;
2892
+ }
2893
+ try {
2894
+ const plain = { ...value };
2895
+ seen.delete(value);
2896
+ return processValue(plain, key);
2897
+ } catch {
2898
+ seen.delete(value);
2899
+ return String(value);
2900
+ }
2901
+ }
2902
+ const processed = processValue(obj);
2903
+ return JSON.stringify(processed);
2904
+ }
2905
+ function hashObject(obj, options, encoding = "hex") {
2906
+ const jsonString = deterministicStringify(obj, options);
2907
+ return createHash("sha256").update(jsonString).digest(encoding);
2908
+ }
2909
+
2810
2910
  let caffeinateProcess = null;
2811
2911
  function startCaffeinate() {
2812
2912
  if (process.platform !== "darwin") {
@@ -2970,9 +3070,15 @@ async function start(credentials, options = {}) {
2970
3070
  if (caffeinateStarted) {
2971
3071
  logger.infoDeveloper("Sleep prevention enabled (macOS)");
2972
3072
  }
2973
- const messageQueue = new MessageQueue2((mode) => mode);
3073
+ const messageQueue = new MessageQueue2((mode) => hashObject(mode));
2974
3074
  registerHandlers(session);
2975
3075
  let currentPermissionMode = options.permissionMode;
3076
+ let currentModel = options.model;
3077
+ let currentFallbackModel = void 0;
3078
+ let currentCustomSystemPrompt = void 0;
3079
+ let currentAppendSystemPrompt = void 0;
3080
+ let currentAllowedTools = void 0;
3081
+ let currentDisallowedTools = void 0;
2976
3082
  session.onUserMessage((message) => {
2977
3083
  let messagePermissionMode = currentPermissionMode;
2978
3084
  if (message.meta?.permissionMode) {
@@ -2987,7 +3093,64 @@ async function start(credentials, options = {}) {
2987
3093
  } else {
2988
3094
  logger.debug(`[loop] User message received with no permission mode override, using current: ${currentPermissionMode}`);
2989
3095
  }
2990
- messageQueue.push(message.content.text, messagePermissionMode || "default");
3096
+ let messageModel = currentModel;
3097
+ if (message.meta?.hasOwnProperty("model")) {
3098
+ messageModel = message.meta.model || void 0;
3099
+ currentModel = messageModel;
3100
+ logger.debug(`[loop] Model updated from user message: ${messageModel || "reset to default"}`);
3101
+ } else {
3102
+ logger.debug(`[loop] User message received with no model override, using current: ${currentModel || "default"}`);
3103
+ }
3104
+ let messageCustomSystemPrompt = currentCustomSystemPrompt;
3105
+ if (message.meta?.hasOwnProperty("customSystemPrompt")) {
3106
+ messageCustomSystemPrompt = message.meta.customSystemPrompt || void 0;
3107
+ currentCustomSystemPrompt = messageCustomSystemPrompt;
3108
+ logger.debug(`[loop] Custom system prompt updated from user message: ${messageCustomSystemPrompt ? "set" : "reset to none"}`);
3109
+ } else {
3110
+ logger.debug(`[loop] User message received with no custom system prompt override, using current: ${currentCustomSystemPrompt ? "set" : "none"}`);
3111
+ }
3112
+ let messageFallbackModel = currentFallbackModel;
3113
+ if (message.meta?.hasOwnProperty("fallbackModel")) {
3114
+ messageFallbackModel = message.meta.fallbackModel || void 0;
3115
+ currentFallbackModel = messageFallbackModel;
3116
+ logger.debug(`[loop] Fallback model updated from user message: ${messageFallbackModel || "reset to none"}`);
3117
+ } else {
3118
+ logger.debug(`[loop] User message received with no fallback model override, using current: ${currentFallbackModel || "none"}`);
3119
+ }
3120
+ let messageAppendSystemPrompt = currentAppendSystemPrompt;
3121
+ if (message.meta?.hasOwnProperty("appendSystemPrompt")) {
3122
+ messageAppendSystemPrompt = message.meta.appendSystemPrompt || void 0;
3123
+ currentAppendSystemPrompt = messageAppendSystemPrompt;
3124
+ logger.debug(`[loop] Append system prompt updated from user message: ${messageAppendSystemPrompt ? "set" : "reset to none"}`);
3125
+ } else {
3126
+ logger.debug(`[loop] User message received with no append system prompt override, using current: ${currentAppendSystemPrompt ? "set" : "none"}`);
3127
+ }
3128
+ let messageAllowedTools = currentAllowedTools;
3129
+ if (message.meta?.hasOwnProperty("allowedTools")) {
3130
+ messageAllowedTools = message.meta.allowedTools || void 0;
3131
+ currentAllowedTools = messageAllowedTools;
3132
+ logger.debug(`[loop] Allowed tools updated from user message: ${messageAllowedTools ? messageAllowedTools.join(", ") : "reset to none"}`);
3133
+ } else {
3134
+ logger.debug(`[loop] User message received with no allowed tools override, using current: ${currentAllowedTools ? currentAllowedTools.join(", ") : "none"}`);
3135
+ }
3136
+ let messageDisallowedTools = currentDisallowedTools;
3137
+ if (message.meta?.hasOwnProperty("disallowedTools")) {
3138
+ messageDisallowedTools = message.meta.disallowedTools || void 0;
3139
+ currentDisallowedTools = messageDisallowedTools;
3140
+ logger.debug(`[loop] Disallowed tools updated from user message: ${messageDisallowedTools ? messageDisallowedTools.join(", ") : "reset to none"}`);
3141
+ } else {
3142
+ logger.debug(`[loop] User message received with no disallowed tools override, using current: ${currentDisallowedTools ? currentDisallowedTools.join(", ") : "none"}`);
3143
+ }
3144
+ const enhancedMode = {
3145
+ permissionMode: messagePermissionMode || "default",
3146
+ model: messageModel,
3147
+ fallbackModel: messageFallbackModel,
3148
+ customSystemPrompt: messageCustomSystemPrompt,
3149
+ appendSystemPrompt: messageAppendSystemPrompt,
3150
+ allowedTools: messageAllowedTools,
3151
+ disallowedTools: messageDisallowedTools
3152
+ };
3153
+ messageQueue.push(message.content.text, enhancedMode);
2991
3154
  logger.debugLargeJson("User message pushed to queue:", message);
2992
3155
  });
2993
3156
  await loop({
@@ -3031,8 +3194,69 @@ function displayQRCode(url) {
3031
3194
  console.log("=".repeat(80));
3032
3195
  }
3033
3196
 
3197
+ function generateWebAuthUrl(publicKey) {
3198
+ const publicKeyBase64 = encodeBase64(publicKey, "base64url");
3199
+ return `https://app.happy.engineering/terminal/connect#key=${publicKeyBase64}`;
3200
+ }
3201
+
3202
+ async function openBrowser(url) {
3203
+ try {
3204
+ if (!process.stdout.isTTY || process.env.CI || process.env.HEADLESS) {
3205
+ logger.debug("[browser] Headless environment detected, skipping browser open");
3206
+ return false;
3207
+ }
3208
+ logger.debug(`[browser] Attempting to open URL: ${url}`);
3209
+ await open(url);
3210
+ logger.debug("[browser] Browser opened successfully");
3211
+ return true;
3212
+ } catch (error) {
3213
+ logger.debug("[browser] Failed to open browser:", error);
3214
+ return false;
3215
+ }
3216
+ }
3217
+
3218
+ const AuthSelector = ({ onSelect, onCancel }) => {
3219
+ const [selectedIndex, setSelectedIndex] = useState(0);
3220
+ const options = [
3221
+ {
3222
+ method: "mobile",
3223
+ label: "Mobile App"
3224
+ },
3225
+ {
3226
+ method: "web",
3227
+ label: "Web Browser"
3228
+ }
3229
+ ];
3230
+ useInput((input, key) => {
3231
+ if (key.upArrow) {
3232
+ setSelectedIndex((prev) => Math.max(0, prev - 1));
3233
+ } else if (key.downArrow) {
3234
+ setSelectedIndex((prev) => Math.min(options.length - 1, prev + 1));
3235
+ } else if (key.return) {
3236
+ onSelect(options[selectedIndex].method);
3237
+ } else if (key.escape || key.ctrl && input === "c") {
3238
+ onCancel();
3239
+ } else if (input === "1") {
3240
+ setSelectedIndex(0);
3241
+ onSelect("mobile");
3242
+ } else if (input === "2") {
3243
+ setSelectedIndex(1);
3244
+ onSelect("web");
3245
+ }
3246
+ });
3247
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", paddingY: 1 }, /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, null, "How would you like to authenticate?")), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, options.map((option, index) => {
3248
+ const isSelected = selectedIndex === index;
3249
+ return /* @__PURE__ */ React.createElement(Box, { key: option.method, marginY: 0 }, /* @__PURE__ */ React.createElement(Text, { color: isSelected ? "cyan" : "gray" }, isSelected ? "\u203A " : " ", index + 1, ". ", option.label));
3250
+ })), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Use arrows or 1-2 to select, Enter to confirm")));
3251
+ };
3252
+
3034
3253
  async function doAuth() {
3035
- console.log("Starting authentication...");
3254
+ console.clear();
3255
+ const authMethod = await selectAuthenticationMethod();
3256
+ if (!authMethod) {
3257
+ console.log("\nAuthentication cancelled.\n");
3258
+ return null;
3259
+ }
3036
3260
  const secret = new Uint8Array(randomBytes(32));
3037
3261
  const keypair = tweetnacl.box.keyPair.fromSecretKey(secret);
3038
3262
  try {
@@ -3043,38 +3267,106 @@ async function doAuth() {
3043
3267
  console.log("Failed to create authentication request, please try again later.");
3044
3268
  return null;
3045
3269
  }
3046
- console.log("Please, authenticate using mobile app");
3270
+ if (authMethod === "mobile") {
3271
+ return await doMobileAuth(keypair);
3272
+ } else {
3273
+ return await doWebAuth(keypair);
3274
+ }
3275
+ }
3276
+ function selectAuthenticationMethod() {
3277
+ return new Promise((resolve) => {
3278
+ let hasResolved = false;
3279
+ const onSelect = (method) => {
3280
+ if (!hasResolved) {
3281
+ hasResolved = true;
3282
+ app.unmount();
3283
+ resolve(method);
3284
+ }
3285
+ };
3286
+ const onCancel = () => {
3287
+ if (!hasResolved) {
3288
+ hasResolved = true;
3289
+ app.unmount();
3290
+ resolve(null);
3291
+ }
3292
+ };
3293
+ const app = render(React.createElement(AuthSelector, { onSelect, onCancel }), {
3294
+ exitOnCtrlC: false,
3295
+ patchConsole: false
3296
+ });
3297
+ });
3298
+ }
3299
+ async function doMobileAuth(keypair) {
3300
+ console.clear();
3301
+ console.log("\nMobile Authentication\n");
3302
+ console.log("Scan this QR code with your Happy mobile app:\n");
3047
3303
  const authUrl = "happy://terminal?" + encodeBase64Url(keypair.publicKey);
3048
3304
  displayQRCode(authUrl);
3049
- console.log("\n\u{1F4CB} For manual entry, copy this URL:");
3305
+ console.log("\nOr manually enter this URL:");
3050
3306
  console.log(authUrl);
3051
- let credentials = null;
3052
- while (true) {
3053
- try {
3054
- const response = await axios.post(`${configuration.serverUrl}/v1/auth/request`, {
3055
- publicKey: encodeBase64(keypair.publicKey)
3056
- });
3057
- if (response.data.state === "authorized") {
3058
- let token = response.data.token;
3059
- let r = decodeBase64(response.data.response);
3060
- let decrypted = decryptWithEphemeralKey(r, keypair.secretKey);
3061
- if (decrypted) {
3062
- credentials = {
3063
- secret: decrypted,
3064
- token
3065
- };
3066
- await writeCredentials(credentials);
3067
- return credentials;
3068
- } else {
3069
- console.log("Failed to decrypt response, please try again later.");
3070
- return null;
3307
+ console.log("");
3308
+ return await waitForAuthentication(keypair);
3309
+ }
3310
+ async function doWebAuth(keypair) {
3311
+ console.clear();
3312
+ console.log("\nWeb Authentication\n");
3313
+ const webUrl = generateWebAuthUrl(keypair.publicKey);
3314
+ console.log("Opening your browser...");
3315
+ const browserOpened = await openBrowser(webUrl);
3316
+ if (browserOpened) {
3317
+ console.log("\u2713 Browser opened\n");
3318
+ console.log("Complete authentication in your browser window.");
3319
+ } else {
3320
+ console.log("Could not open browser automatically.\n");
3321
+ console.log("Please open this URL manually:");
3322
+ console.log(webUrl);
3323
+ }
3324
+ console.log("");
3325
+ return await waitForAuthentication(keypair);
3326
+ }
3327
+ async function waitForAuthentication(keypair) {
3328
+ process.stdout.write("Waiting for authentication");
3329
+ let dots = 0;
3330
+ let cancelled = false;
3331
+ const handleInterrupt = () => {
3332
+ cancelled = true;
3333
+ console.log("\n\nAuthentication cancelled.");
3334
+ process.exit(0);
3335
+ };
3336
+ process.on("SIGINT", handleInterrupt);
3337
+ try {
3338
+ while (!cancelled) {
3339
+ try {
3340
+ const response = await axios.post(`${configuration.serverUrl}/v1/auth/request`, {
3341
+ publicKey: encodeBase64(keypair.publicKey)
3342
+ });
3343
+ if (response.data.state === "authorized") {
3344
+ let token = response.data.token;
3345
+ let r = decodeBase64(response.data.response);
3346
+ let decrypted = decryptWithEphemeralKey(r, keypair.secretKey);
3347
+ if (decrypted) {
3348
+ const credentials = {
3349
+ secret: decrypted,
3350
+ token
3351
+ };
3352
+ await writeCredentials(credentials);
3353
+ console.log("\n\n\u2713 Authentication successful\n");
3354
+ return credentials;
3355
+ } else {
3356
+ console.log("\n\nFailed to decrypt response. Please try again.");
3357
+ return null;
3358
+ }
3071
3359
  }
3360
+ } catch (error) {
3361
+ console.log("\n\nFailed to check authentication status. Please try again.");
3362
+ return null;
3072
3363
  }
3073
- } catch (error) {
3074
- console.log("Failed to create authentication request, please try again later.");
3075
- return null;
3364
+ process.stdout.write("\rWaiting for authentication" + ".".repeat(dots % 3 + 1) + " ");
3365
+ dots++;
3366
+ await delay(1e3);
3076
3367
  }
3077
- await delay(1e3);
3368
+ } finally {
3369
+ process.off("SIGINT", handleInterrupt);
3078
3370
  }
3079
3371
  return null;
3080
3372
  }
@@ -3305,12 +3597,11 @@ class ApiDaemonSession extends EventEmitter {
3305
3597
  this.stopKeepAlive();
3306
3598
  this.keepAliveInterval = setInterval(() => {
3307
3599
  const payload = {
3308
- type: "machine-scoped",
3309
3600
  machineId: this.machineIdentity.machineId,
3310
3601
  time: Date.now()
3311
3602
  };
3312
- logger.debugLargeJson(`[DAEMON SESSION] Emitting session-alive`, payload);
3313
- this.socket.emit("session-alive", payload);
3603
+ logger.debugLargeJson(`[DAEMON SESSION] Emitting machine-alive`, payload);
3604
+ this.socket.emit("machine-alive", payload);
3314
3605
  }, 2e4);
3315
3606
  }
3316
3607
  stopKeepAlive() {
package/dist/lib.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var types = require('./types-eN-YHsuj.cjs');
3
+ var types = require('./types-Bkw2UUhb.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>;
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>;
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-VkaGP8up.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-Cqy5Dx2C.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,7 +546,6 @@ class ApiSessionClient extends node_events.EventEmitter {
534
546
  */
535
547
  keepAlive(thinking, mode) {
536
548
  this.socket.volatile.emit("session-alive", {
537
- type: "session-scoped",
538
549
  sid: this.sessionId,
539
550
  time: Date.now(),
540
551
  thinking,
@@ -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,7 +544,6 @@ class ApiSessionClient extends EventEmitter {
532
544
  */
533
545
  keepAlive(thinking, mode) {
534
546
  this.socket.volatile.emit("session-alive", {
535
- type: "session-scoped",
536
547
  sid: this.sessionId,
537
548
  time: Date.now(),
538
549
  thinking,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "happy-coder",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Claude Code session sharing CLI",
5
5
  "author": "Kirill Dubovitskiy",
6
6
  "license": "MIT",
@@ -68,7 +68,7 @@
68
68
  "http-proxy": "^1.18.1",
69
69
  "http-proxy-middleware": "^3.0.5",
70
70
  "ink": "^6.1.0",
71
- "ink-box": "^2.0.0",
71
+ "open": "^10.2.0",
72
72
  "qrcode-terminal": "^0.12.0",
73
73
  "react": "^19.1.1",
74
74
  "socket.io-client": "^4.8.1",