happy-coder 0.1.1 → 0.1.2

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
@@ -5,7 +5,7 @@ var fs = require('node:fs');
5
5
  var promises = require('node:fs/promises');
6
6
  var node_crypto = require('node:crypto');
7
7
  var tweetnacl = require('tweetnacl');
8
- var node_os = require('node:os');
8
+ var os = require('node:os');
9
9
  var node_path = require('node:path');
10
10
  var chalk = require('chalk');
11
11
  var node_events = require('node:events');
@@ -74,14 +74,14 @@ function authChallenge(secret) {
74
74
  }
75
75
 
76
76
  async function getOrCreateSecretKey() {
77
- const keyPath = node_path.join(node_os.homedir(), ".handy", "access.key");
77
+ const keyPath = node_path.join(os.homedir(), ".handy", "access.key");
78
78
  if (fs.existsSync(keyPath)) {
79
79
  const keyBase642 = fs.readFileSync(keyPath, "utf8").trim();
80
80
  return new Uint8Array(Buffer.from(keyBase642, "base64"));
81
81
  }
82
82
  const secret = getRandomBytes(32);
83
83
  const keyBase64 = encodeBase64(secret);
84
- fs.mkdirSync(node_path.join(node_os.homedir(), ".handy"), { recursive: true });
84
+ fs.mkdirSync(node_path.join(os.homedir(), ".handy"), { recursive: true });
85
85
  fs.writeFileSync(keyPath, keyBase64);
86
86
  await promises.chmod(keyPath, 384);
87
87
  return secret;
@@ -291,11 +291,11 @@ class ApiClient {
291
291
  /**
292
292
  * Create a new session or load existing one with the given tag
293
293
  */
294
- async getOrCreateSession(tag) {
294
+ async getOrCreateSession(opts) {
295
295
  try {
296
296
  const response = await axios.post(
297
297
  `https://handy-api.korshakov.org/v1/sessions`,
298
- { tag },
298
+ { tag: opts.tag, metadata: encodeBase64(encrypt(opts.metadata, this.secret)) },
299
299
  {
300
300
  headers: {
301
301
  "Authorization": `Bearer ${this.token}`,
@@ -303,7 +303,7 @@ class ApiClient {
303
303
  }
304
304
  }
305
305
  );
306
- logger.info(`Session created/loaded: ${response.data.session.id} (tag: ${tag})`);
306
+ logger.info(`Session created/loaded: ${response.data.session.id} (tag: ${opts.tag})`);
307
307
  return response.data;
308
308
  } catch (error) {
309
309
  logger.error("Failed to get or create session:", error);
@@ -468,6 +468,7 @@ function startClaudeLoop(opts, session) {
468
468
  let exiting = false;
469
469
  const messageQueue = [];
470
470
  let messageResolve = null;
471
+ let sessionId;
471
472
  let promise = (async () => {
472
473
  session.onUserMessage((message) => {
473
474
  messageQueue.push(message);
@@ -484,7 +485,8 @@ function startClaudeLoop(opts, session) {
484
485
  command: message.content.text,
485
486
  workingDirectory: opts.path,
486
487
  model: opts.model,
487
- permissionMode: opts.permissionMode
488
+ permissionMode: opts.permissionMode,
489
+ sessionId
488
490
  })) {
489
491
  if (output.type === "exit") {
490
492
  if (output.code !== 0 || output.code === void 0) {
@@ -505,6 +507,9 @@ function startClaudeLoop(opts, session) {
505
507
  type: "output"
506
508
  });
507
509
  }
510
+ if (output.type === "json" && output.data.type === "system" && output.data.subtype === "init") {
511
+ sessionId = output.data.sessionId;
512
+ }
508
513
  }
509
514
  }
510
515
  }
@@ -532,7 +537,7 @@ async function start(options = {}) {
532
537
  const token = await authGetToken(secret);
533
538
  logger.info("Authenticated with handy server");
534
539
  const api = new ApiClient(token, secret);
535
- const response = await api.getOrCreateSession(sessionTag);
540
+ const response = await api.getOrCreateSession({ tag: sessionTag, metadata: { path: workingDirectory, host: os.hostname() } });
536
541
  logger.info(`Session created: ${response.session.id}`);
537
542
  const handyUrl = generateAppUrl(secret);
538
543
  displayQRCode(handyUrl);
package/dist/index.mjs CHANGED
@@ -4,7 +4,7 @@ import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
4
4
  import { chmod } from 'node:fs/promises';
5
5
  import { randomBytes, randomUUID } from 'node:crypto';
6
6
  import tweetnacl from 'tweetnacl';
7
- import { homedir } from 'node:os';
7
+ import os, { homedir } from 'node:os';
8
8
  import { join, basename } from 'node:path';
9
9
  import chalk from 'chalk';
10
10
  import { EventEmitter } from 'node:events';
@@ -271,11 +271,11 @@ class ApiClient {
271
271
  /**
272
272
  * Create a new session or load existing one with the given tag
273
273
  */
274
- async getOrCreateSession(tag) {
274
+ async getOrCreateSession(opts) {
275
275
  try {
276
276
  const response = await axios.post(
277
277
  `https://handy-api.korshakov.org/v1/sessions`,
278
- { tag },
278
+ { tag: opts.tag, metadata: encodeBase64(encrypt(opts.metadata, this.secret)) },
279
279
  {
280
280
  headers: {
281
281
  "Authorization": `Bearer ${this.token}`,
@@ -283,7 +283,7 @@ class ApiClient {
283
283
  }
284
284
  }
285
285
  );
286
- logger.info(`Session created/loaded: ${response.data.session.id} (tag: ${tag})`);
286
+ logger.info(`Session created/loaded: ${response.data.session.id} (tag: ${opts.tag})`);
287
287
  return response.data;
288
288
  } catch (error) {
289
289
  logger.error("Failed to get or create session:", error);
@@ -448,6 +448,7 @@ function startClaudeLoop(opts, session) {
448
448
  let exiting = false;
449
449
  const messageQueue = [];
450
450
  let messageResolve = null;
451
+ let sessionId;
451
452
  let promise = (async () => {
452
453
  session.onUserMessage((message) => {
453
454
  messageQueue.push(message);
@@ -464,7 +465,8 @@ function startClaudeLoop(opts, session) {
464
465
  command: message.content.text,
465
466
  workingDirectory: opts.path,
466
467
  model: opts.model,
467
- permissionMode: opts.permissionMode
468
+ permissionMode: opts.permissionMode,
469
+ sessionId
468
470
  })) {
469
471
  if (output.type === "exit") {
470
472
  if (output.code !== 0 || output.code === void 0) {
@@ -485,6 +487,9 @@ function startClaudeLoop(opts, session) {
485
487
  type: "output"
486
488
  });
487
489
  }
490
+ if (output.type === "json" && output.data.type === "system" && output.data.subtype === "init") {
491
+ sessionId = output.data.sessionId;
492
+ }
488
493
  }
489
494
  }
490
495
  }
@@ -512,7 +517,7 @@ async function start(options = {}) {
512
517
  const token = await authGetToken(secret);
513
518
  logger.info("Authenticated with handy server");
514
519
  const api = new ApiClient(token, secret);
515
- const response = await api.getOrCreateSession(sessionTag);
520
+ const response = await api.getOrCreateSession({ tag: sessionTag, metadata: { path: workingDirectory, host: os.hostname() } });
516
521
  logger.info(`Session created: ${response.session.id}`);
517
522
  const handyUrl = generateAppUrl(secret);
518
523
  displayQRCode(handyUrl);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "happy-coder",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Claude Code session sharing CLI",
5
5
  "author": "Kirill Dubovitskiy",
6
6
  "license": "MIT",