happy-coder 0.10.0-3 → 0.10.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/README.md CHANGED
@@ -21,6 +21,15 @@ This will:
21
21
  2. Display a QR code to connect from your mobile device
22
22
  3. Allow real-time session sharing between Claude Code and your mobile app
23
23
 
24
+ ## Commands
25
+
26
+ - `happy auth` – Manage authentication
27
+ - `happy codex` – Start Codex mode
28
+ - `happy connect` – Store AI vendor API keys in Happy cloud
29
+ - `happy notify` – Send a push notification to your devices
30
+ - `happy daemon` – Manage background service
31
+ - `happy doctor` – System diagnostics & troubleshooting
32
+
24
33
  ## Options
25
34
 
26
35
  - `-h, --help` - Show help
@@ -40,4 +49,4 @@ This will:
40
49
 
41
50
  ## License
42
51
 
43
- MIT
52
+ MIT
@@ -1,7 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
  import os$1, { homedir } from 'node:os';
3
3
  import { randomUUID, randomBytes } from 'node:crypto';
4
- import { l as logger, p as projectPath, d as backoff, e as delay, R as RawJSONLinesSchema, f as AsyncLock, g as readDaemonState, h as clearDaemonState, b as packageJson, c as configuration, r as readSettings, i as readCredentials, j as encodeBase64, u as updateSettings, k as encodeBase64Url, m as decodeBase64, w as writeCredentials, n as acquireDaemonLock, o as writeDaemonState, A as ApiClient, q as releaseDaemonLock, s as clearCredentials, t as clearMachineId, v as getLatestDaemonLog } from './types-xds_c-JJ.mjs';
4
+ import { l as logger, p as projectPath, d as backoff, e as delay, R as RawJSONLinesSchema, f as AsyncLock, g as readDaemonState, h as clearDaemonState, b as packageJson, c as configuration, r as readSettings, i as readCredentials, j as encodeBase64, u as updateSettings, k as encodeBase64Url, m as decodeBase64, w as writeCredentialsLegacy, n as writeCredentialsDataKey, o as acquireDaemonLock, q as writeDaemonState, A as ApiClient, s as releaseDaemonLock, t as clearCredentials, v as clearMachineId, x as getLatestDaemonLog } from './types-8Ad05p3x.mjs';
5
5
  import { spawn, execSync, execFileSync } from 'node:child_process';
6
6
  import { resolve, join } from 'node:path';
7
7
  import { createInterface } from 'node:readline';
@@ -23,6 +23,7 @@ import { join as join$1 } from 'path';
23
23
  import psList from 'ps-list';
24
24
  import spawn$2 from 'cross-spawn';
25
25
  import os from 'os';
26
+ import * as tmp from 'tmp';
26
27
  import qrcode from 'qrcode-terminal';
27
28
  import open from 'open';
28
29
  import fastify from 'fastify';
@@ -3681,7 +3682,7 @@ function displayQRCode(url) {
3681
3682
 
3682
3683
  function generateWebAuthUrl(publicKey) {
3683
3684
  const publicKeyBase64 = encodeBase64(publicKey, "base64url");
3684
- return `https://app.happy.engineering/terminal/connect#key=${publicKeyBase64}`;
3685
+ return `${configuration.webappUrl}/terminal/connect#key=${publicKeyBase64}`;
3685
3686
  }
3686
3687
 
3687
3688
  async function openBrowser(url) {
@@ -3748,7 +3749,8 @@ async function doAuth() {
3748
3749
  console.log(`[AUTH DEBUG] Sending auth request to: ${configuration.serverUrl}/v1/auth/request`);
3749
3750
  console.log(`[AUTH DEBUG] Public key: ${encodeBase64(keypair.publicKey).substring(0, 20)}...`);
3750
3751
  await axios.post(`${configuration.serverUrl}/v1/auth/request`, {
3751
- publicKey: encodeBase64(keypair.publicKey)
3752
+ publicKey: encodeBase64(keypair.publicKey),
3753
+ supportsV2: true
3752
3754
  });
3753
3755
  console.log(`[AUTH DEBUG] Auth request sent successfully`);
3754
3756
  } catch (error) {
@@ -3827,20 +3829,50 @@ async function waitForAuthentication(keypair) {
3827
3829
  while (!cancelled) {
3828
3830
  try {
3829
3831
  const response = await axios.post(`${configuration.serverUrl}/v1/auth/request`, {
3830
- publicKey: encodeBase64(keypair.publicKey)
3832
+ publicKey: encodeBase64(keypair.publicKey),
3833
+ supportsV2: true
3831
3834
  });
3832
3835
  if (response.data.state === "authorized") {
3833
3836
  let token = response.data.token;
3834
3837
  let r = decodeBase64(response.data.response);
3835
3838
  let decrypted = decryptWithEphemeralKey(r, keypair.secretKey);
3836
3839
  if (decrypted) {
3837
- const credentials = {
3838
- secret: decrypted,
3839
- token
3840
- };
3841
- await writeCredentials(credentials);
3842
- console.log("\n\n\u2713 Authentication successful\n");
3843
- return credentials;
3840
+ if (decrypted.length === 32) {
3841
+ const credentials = {
3842
+ secret: decrypted,
3843
+ token
3844
+ };
3845
+ await writeCredentialsLegacy(credentials);
3846
+ console.log("\n\n\u2713 Authentication successful\n");
3847
+ return {
3848
+ encryption: {
3849
+ type: "legacy",
3850
+ secret: decrypted
3851
+ },
3852
+ token
3853
+ };
3854
+ } else {
3855
+ if (decrypted[0] === 0) {
3856
+ const credentials = {
3857
+ publicKey: decrypted.slice(1, 33),
3858
+ machineKey: randomBytes(32),
3859
+ token
3860
+ };
3861
+ await writeCredentialsDataKey(credentials);
3862
+ console.log("\n\n\u2713 Authentication successful\n");
3863
+ return {
3864
+ encryption: {
3865
+ type: "dataKey",
3866
+ publicKey: credentials.publicKey,
3867
+ machineKey: credentials.machineKey
3868
+ },
3869
+ token
3870
+ };
3871
+ } else {
3872
+ console.log("\n\nFailed to decrypt response. Please try again.");
3873
+ return null;
3874
+ }
3875
+ }
3844
3876
  } else {
3845
3877
  console.log("\n\nFailed to decrypt response. Please try again.");
3846
3878
  return null;
@@ -3872,6 +3904,7 @@ function decryptWithEphemeralKey(encryptedBundle, recipientSecretKey) {
3872
3904
  async function authAndSetupMachineIfNeeded() {
3873
3905
  logger.debug("[AUTH] Starting auth and machine setup...");
3874
3906
  let credentials = await readCredentials();
3907
+ let newAuth = false;
3875
3908
  if (!credentials) {
3876
3909
  logger.debug("[AUTH] No credentials found, starting authentication flow...");
3877
3910
  const authResult = await doAuth();
@@ -3879,13 +3912,12 @@ async function authAndSetupMachineIfNeeded() {
3879
3912
  throw new Error("Authentication failed or was cancelled");
3880
3913
  }
3881
3914
  credentials = authResult;
3915
+ newAuth = true;
3882
3916
  } else {
3883
3917
  logger.debug("[AUTH] Using existing credentials");
3884
3918
  }
3885
3919
  const settings = await updateSettings(async (s) => {
3886
- if (!s.machineId) {
3887
- const newMachineId = randomUUID();
3888
- logger.debug(`[AUTH] No machine ID found, generating new one: ${newMachineId}; We will not create machine on startup since we don't have api client intialized`);
3920
+ if (newAuth || !s.machineId) {
3889
3921
  return {
3890
3922
  ...s,
3891
3923
  machineId: randomUUID()
@@ -4234,7 +4266,22 @@ async function startDaemon() {
4234
4266
  }
4235
4267
  }
4236
4268
  try {
4269
+ let extraEnv = {};
4270
+ if (options.token) {
4271
+ if (options.agent === "codex") {
4272
+ const codexHomeDir = tmp.dirSync();
4273
+ fs.writeFile(join$1(codexHomeDir.name, "auth.json"), options.token);
4274
+ extraEnv = {
4275
+ CODEX_HOME: codexHomeDir.name
4276
+ };
4277
+ } else {
4278
+ extraEnv = {
4279
+ CLAUDE_CODE_OAUTH_TOKEN: options.token
4280
+ };
4281
+ }
4282
+ }
4237
4283
  const args = [
4284
+ options.agent === "claude" ? "claude" : "codex",
4238
4285
  "--happy-starting-mode",
4239
4286
  "remote",
4240
4287
  "--started-by",
@@ -4244,9 +4291,12 @@ async function startDaemon() {
4244
4291
  cwd: directory,
4245
4292
  detached: true,
4246
4293
  // Sessions stay alive when daemon stops
4247
- stdio: ["ignore", "pipe", "pipe"]
4294
+ stdio: ["ignore", "pipe", "pipe"],
4248
4295
  // Capture stdout/stderr for debugging
4249
- // env is inherited automatically from parent process
4296
+ env: {
4297
+ ...process.env,
4298
+ ...extraEnv
4299
+ }
4250
4300
  });
4251
4301
  if (process.env.DEBUG) {
4252
4302
  happyProcess.stdout?.on("data", (data) => {
@@ -4365,7 +4415,7 @@ async function startDaemon() {
4365
4415
  httpPort: controlPort,
4366
4416
  startedAt: Date.now()
4367
4417
  };
4368
- const api = new ApiClient(credentials.token, credentials.secret);
4418
+ const api = await ApiClient.create(credentials);
4369
4419
  const machine = await api.getOrCreateMachine({
4370
4420
  machineId,
4371
4421
  metadata: initialMachineMetadata,
@@ -4569,7 +4619,7 @@ async function runClaude(credentials, options = {}) {
4569
4619
  logger.debug("Daemon spawn requested with local mode - forcing remote mode");
4570
4620
  options.startingMode = "remote";
4571
4621
  }
4572
- const api = new ApiClient(credentials.token, credentials.secret);
4622
+ const api = await ApiClient.create(credentials);
4573
4623
  let state = {};
4574
4624
  const settings = await readSettings();
4575
4625
  let machineId = settings?.machineId;
@@ -4597,7 +4647,8 @@ async function runClaude(credentials, options = {}) {
4597
4647
  startedBy: options.startedBy || "terminal",
4598
4648
  // Initialize lifecycle state
4599
4649
  lifecycleState: "running",
4600
- lifecycleStateSince: Date.now()
4650
+ lifecycleStateSince: Date.now(),
4651
+ flavor: "claude"
4601
4652
  };
4602
4653
  const response = await api.getOrCreateSession({ tag: sessionTag, metadata, state });
4603
4654
  logger.debug(`Session created: ${response.id}`);
@@ -4939,33 +4990,6 @@ async function uninstall() {
4939
4990
  await uninstall$1();
4940
4991
  }
4941
4992
 
4942
- const BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
4943
- function bytesToBase32(bytes) {
4944
- let result = "";
4945
- let buffer = 0;
4946
- let bufferLength = 0;
4947
- for (const byte of bytes) {
4948
- buffer = buffer << 8 | byte;
4949
- bufferLength += 8;
4950
- while (bufferLength >= 5) {
4951
- bufferLength -= 5;
4952
- result += BASE32_ALPHABET[buffer >> bufferLength & 31];
4953
- }
4954
- }
4955
- if (bufferLength > 0) {
4956
- result += BASE32_ALPHABET[buffer << 5 - bufferLength & 31];
4957
- }
4958
- return result;
4959
- }
4960
- function formatSecretKeyForBackup(secretBytes) {
4961
- const base32 = bytesToBase32(secretBytes);
4962
- const groups = [];
4963
- for (let i = 0; i < base32.length; i += 5) {
4964
- groups.push(base32.slice(i, i + 5));
4965
- }
4966
- return groups.join("-");
4967
- }
4968
-
4969
4993
  async function handleAuthCommand(args) {
4970
4994
  const subcommand = args[0];
4971
4995
  if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
@@ -4979,9 +5003,9 @@ async function handleAuthCommand(args) {
4979
5003
  case "logout":
4980
5004
  await handleAuthLogout();
4981
5005
  break;
4982
- case "show-backup":
4983
- await handleAuthShowBackup();
4984
- break;
5006
+ // case 'backup':
5007
+ // await handleAuthShowBackup();
5008
+ // break;
4985
5009
  case "status":
4986
5010
  await handleAuthStatus();
4987
5011
  break;
@@ -5004,11 +5028,6 @@ ${chalk.bold("Usage:")}
5004
5028
 
5005
5029
  ${chalk.bold("Options:")}
5006
5030
  --force Clear credentials, machine ID, and stop daemon before re-auth
5007
-
5008
- ${chalk.bold("Notes:")}
5009
- \u2022 Use 'auth login --force' when you need to re-register your machine
5010
- \u2022 'auth show-backup' displays the key format expected by mobile/web clients
5011
- \u2022 The backup key allows linking multiple devices to the same account
5012
5031
  `);
5013
5032
  }
5014
5033
  async function handleAuthLogin(args) {
@@ -5093,31 +5112,6 @@ async function handleAuthLogout() {
5093
5112
  console.log(chalk.blue("Logout cancelled"));
5094
5113
  }
5095
5114
  }
5096
- async function handleAuthShowBackup() {
5097
- const credentials = await readCredentials();
5098
- const settings = await readSettings();
5099
- if (!credentials) {
5100
- console.log(chalk.yellow("Not authenticated"));
5101
- console.log(chalk.gray('Run "happy auth login" to authenticate first'));
5102
- return;
5103
- }
5104
- const formattedBackupKey = formatSecretKeyForBackup(credentials.secret);
5105
- console.log(chalk.bold("\n\u{1F4F1} Backup Key\n"));
5106
- console.log(chalk.cyan("Your backup key:"));
5107
- console.log(chalk.bold(formattedBackupKey));
5108
- console.log("");
5109
- console.log(chalk.cyan("Machine Information:"));
5110
- console.log(` Machine ID: ${settings?.machineId || "not set"}`);
5111
- console.log(` Host: ${os$1.hostname()}`);
5112
- console.log("");
5113
- console.log(chalk.bold("How to use this backup key:"));
5114
- console.log(chalk.gray("\u2022 In Happy mobile app: Go to restore/link device and enter this key"));
5115
- console.log(chalk.gray("\u2022 This key format matches what the mobile app expects"));
5116
- console.log(chalk.gray("\u2022 You can type it with or without dashes - the app will normalize it"));
5117
- console.log(chalk.gray("\u2022 Common typos (0\u2192O, 1\u2192I) are automatically corrected"));
5118
- console.log("");
5119
- console.log(chalk.yellow("\u26A0\uFE0F Keep this key secure - it provides full access to your account"));
5120
- }
5121
5115
  async function handleAuthStatus() {
5122
5116
  const credentials = await readCredentials();
5123
5117
  const settings = await readSettings();
@@ -5654,7 +5648,7 @@ ${chalk.bold("happy connect")} - Connect AI vendor API keys to Happy cloud
5654
5648
 
5655
5649
  ${chalk.bold("Usage:")}
5656
5650
  happy connect codex Store your Codex API key in Happy cloud
5657
- happy connect anthropic Store your Anthropic API key in Happy cloud
5651
+ happy connect claude Store your Anthropic API key in Happy cloud
5658
5652
  happy connect gemini Store your Gemini API key in Happy cloud
5659
5653
  happy connect help Show this help message
5660
5654
 
@@ -5665,7 +5659,7 @@ ${chalk.bold("Description:")}
5665
5659
 
5666
5660
  ${chalk.bold("Examples:")}
5667
5661
  happy connect codex
5668
- happy connect anthropic
5662
+ happy connect claude
5669
5663
  happy connect gemini
5670
5664
 
5671
5665
  ${chalk.bold("Notes:")}
@@ -5684,7 +5678,7 @@ async function handleConnectVendor(vendor, displayName) {
5684
5678
  console.log(chalk.gray(' Please run "happy auth login" first'));
5685
5679
  process.exit(1);
5686
5680
  }
5687
- const api = new ApiClient(credentials.token, credentials.secret);
5681
+ const api = await ApiClient.create(credentials);
5688
5682
  if (vendor === "codex") {
5689
5683
  console.log("\u{1F680} Registering Codex token with server");
5690
5684
  const codexAuthTokens = await authenticateCodex();
@@ -5749,11 +5743,17 @@ async function handleConnectVendor(vendor, displayName) {
5749
5743
  return;
5750
5744
  } else if (subcommand === "codex") {
5751
5745
  try {
5752
- const { runCodex } = await import('./runCodex-C07HQlsW.mjs');
5746
+ const { runCodex } = await import('./runCodex-HlLNepHI.mjs');
5747
+ let startedBy = void 0;
5748
+ for (let i = 1; i < args.length; i++) {
5749
+ if (args[i] === "--started-by") {
5750
+ startedBy = args[++i];
5751
+ }
5752
+ }
5753
5753
  const {
5754
5754
  credentials
5755
5755
  } = await authAndSetupMachineIfNeeded();
5756
- await runCodex(credentials);
5756
+ await runCodex({ credentials, startedBy });
5757
5757
  } catch (error) {
5758
5758
  console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
5759
5759
  if (process.env.DEBUG) {
@@ -5886,6 +5886,9 @@ ${chalk.bold("To clean up runaway processes:")} Use ${chalk.cyan("happy doctor c
5886
5886
  }
5887
5887
  return;
5888
5888
  } else {
5889
+ if (args.length > 0 && args[0] === "claude") {
5890
+ args.shift();
5891
+ }
5889
5892
  const options = {};
5890
5893
  let showHelp = false;
5891
5894
  let showVersion = false;
@@ -5921,6 +5924,9 @@ ${chalk.bold("happy")} - Claude Code On the Go
5921
5924
  ${chalk.bold("Usage:")}
5922
5925
  happy [options] Start Claude with mobile control
5923
5926
  happy auth Manage authentication
5927
+ happy codex Start Codex mode
5928
+ happy connect Connect AI vendor API keys
5929
+ happy notify Send push notification
5924
5930
  happy daemon Manage background service that allows
5925
5931
  to spawn new sessions away from your computer
5926
5932
  happy doctor System diagnostics & troubleshooting
@@ -6019,12 +6025,12 @@ ${chalk.bold("Examples:")}
6019
6025
  }
6020
6026
  let credentials = await readCredentials();
6021
6027
  if (!credentials) {
6022
- console.error(chalk.red('Error: Not authenticated. Please run "happy --auth" first.'));
6028
+ console.error(chalk.red('Error: Not authenticated. Please run "happy auth login" first.'));
6023
6029
  process.exit(1);
6024
6030
  }
6025
6031
  console.log(chalk.blue("\u{1F4F1} Sending push notification..."));
6026
6032
  try {
6027
- const api = new ApiClient(credentials.token, credentials.secret);
6033
+ const api = await ApiClient.create(credentials);
6028
6034
  const notificationTitle = title || "Happy";
6029
6035
  api.push().sendToAllDevices(
6030
6036
  notificationTitle,
@@ -6045,4 +6051,4 @@ ${chalk.bold("Examples:")}
6045
6051
  }
6046
6052
  }
6047
6053
 
6048
- export { MessageQueue2 as M, MessageBuffer as a, hashObject as h, initialMachineMetadata as i, startHappyServer as s, trimIdent as t };
6054
+ export { MessageQueue2 as M, MessageBuffer as a, stopCaffeinate as b, hashObject as h, initialMachineMetadata as i, notifyDaemonSessionStarted as n, registerKillSessionHandler as r, startHappyServer as s, trimIdent as t };