@office-xyz/claude-code 0.1.0 → 0.1.1

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/index.js +61 -14
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  All notable changes to `@office-xyz/claude-code` will be documented in this file.
4
4
 
5
+ ## [0.1.1] - 2026-02-15
6
+
7
+ ### Fixed
8
+ - Fix crash when running `npx @office-xyz/claude-code` without `--agent` flag (`argv.agent` undefined → `.split()` TypeError)
9
+ - `label` variable now defaults to `'local-host'` in interactive onboarding mode and updates after onboarding completes
10
+
5
11
  ## [0.1.0] - 2026-02-15
6
12
 
7
13
  ### Added
package/index.js CHANGED
@@ -144,7 +144,7 @@ function shellEscapeArg(s) {
144
144
 
145
145
  // ── Logging ────────────────────────────────────────────────────────────────
146
146
 
147
- const label = argv.agent.split('.')[0] || argv.agent
147
+ let label = argv.agent ? (argv.agent.split('.')[0] || argv.agent) : 'local-host'
148
148
  function log(...args) {
149
149
  console.log(chalk.dim(`[${new Date().toISOString()}][${label}]`), ...args)
150
150
  }
@@ -451,6 +451,11 @@ async function handleMessage(message) {
451
451
  args.push(text)
452
452
 
453
453
  log(chalk.blue(`Running: ${cmd} ${args.slice(0, 5).join(' ')}... [${args.length} args]`))
454
+ if (process.env.ANTHROPIC_API_KEY) {
455
+ log(chalk.dim(` Auth: Claude login session (stripped inherited API key from env)`))
456
+ } else {
457
+ log(chalk.dim(` Auth: Claude login session`))
458
+ }
454
459
 
455
460
  // 1. Send streaming.started
456
461
  sendJSON({
@@ -467,9 +472,16 @@ async function handleMessage(message) {
467
472
  // PATH by Node's child_process (works for npm global bins).
468
473
  let child
469
474
  try {
475
+ // Strip ANTHROPIC_API_KEY from child env so Claude Code CLI uses the
476
+ // user's local login session (claude login / Max subscription) instead
477
+ // of consuming API credits from a shared key that may have been loaded
478
+ // by load-aladdin-env.sh or other infra scripts in the same shell.
479
+ const childEnv = { ...process.env }
480
+ delete childEnv.ANTHROPIC_API_KEY
481
+
470
482
  child = spawn(cmd, args, {
471
483
  cwd: workspace,
472
- env: { ...process.env },
484
+ env: childEnv,
473
485
  stdio: ['ignore', 'pipe', 'pipe'],
474
486
  shell: false,
475
487
  })
@@ -892,19 +904,36 @@ function connect() {
892
904
  const ws = new WebSocket(managerUrl.href)
893
905
  wsRef = ws
894
906
 
895
- const pingMs = 10_000
907
+ const PING_INTERVAL_MS = 10_000
908
+ const PONG_TIMEOUT_MS = 8_000 // must be < PING_INTERVAL_MS
896
909
  let pingTimer = null
910
+ let pongTimer = null
911
+ let isAlive = false
912
+
913
+ const stopHeartbeat = () => {
914
+ if (pingTimer) { clearInterval(pingTimer); pingTimer = null }
915
+ if (pongTimer) { clearTimeout(pongTimer); pongTimer = null }
916
+ }
897
917
 
898
918
  ws.on('open', async () => {
899
919
  log(chalk.green('Connected to Virtual Office'))
900
920
  reconnectAttempts = 0
921
+ isAlive = true
901
922
 
902
- // Keepalive pings
923
+ // Heartbeat: ping + pong timeout detection (matches cloud managerHostProxy pattern)
903
924
  pingTimer = setInterval(() => {
904
- if (ws.readyState === WebSocket.OPEN) {
905
- try { ws.ping() } catch { /* ignore */ }
925
+ if (ws.readyState !== WebSocket.OPEN) { stopHeartbeat(); return }
926
+ if (!isAlive) {
927
+ log(chalk.red('Heartbeat timeout — no pong received, forcing reconnect'))
928
+ stopHeartbeat()
929
+ try { ws.terminate() } catch { /* ignore */ }
930
+ return
906
931
  }
907
- }, pingMs)
932
+ isAlive = false
933
+ try { ws.ping() } catch { /* ignore */ }
934
+ }, PING_INTERVAL_MS)
935
+
936
+ ws.on('pong', () => { isAlive = true })
908
937
 
909
938
  sendHostMeta(ws)
910
939
 
@@ -950,7 +979,7 @@ function connect() {
950
979
  const reasonStr = reason?.toString() || ''
951
980
  log(chalk.red(`Disconnected (${code} ${reasonStr})`))
952
981
  wsRef = null
953
- if (pingTimer) clearInterval(pingTimer)
982
+ stopHeartbeat()
954
983
 
955
984
  // Kill all active CLI processes on disconnect
956
985
  for (const [, entry] of activeChildren) {
@@ -1006,8 +1035,17 @@ function connectLocalDevice() {
1006
1035
  const dws = new WebSocket(deviceUrl)
1007
1036
  deviceWsRef = dws
1008
1037
 
1038
+ const DEVICE_PING_INTERVAL_MS = 25_000
1039
+ const DEVICE_PONG_TIMEOUT_MS = 10_000
1040
+ let deviceIsAlive = false
1041
+
1042
+ const stopDeviceHeartbeat = () => {
1043
+ if (devicePingTimer) { clearInterval(devicePingTimer); devicePingTimer = null }
1044
+ }
1045
+
1009
1046
  dws.on('open', () => {
1010
1047
  log(chalk.green('Local device connected'))
1048
+ deviceIsAlive = true
1011
1049
  const agentHandle = argv.agent
1012
1050
  const officeId = agentHandle.split('.').slice(1).join('.')
1013
1051
  // Register as agent-bound device (NOT office-wide).
@@ -1029,13 +1067,21 @@ function connectLocalDevice() {
1029
1067
  },
1030
1068
  }))
1031
1069
 
1032
- // Keepalive ping every 30s to prevent Cloudflare/ALB idle timeout
1033
- if (devicePingTimer) clearInterval(devicePingTimer)
1070
+ // Heartbeat: ping + pong timeout detection
1071
+ stopDeviceHeartbeat()
1034
1072
  devicePingTimer = setInterval(() => {
1035
- if (dws.readyState === WebSocket.OPEN) {
1036
- try { dws.ping() } catch { /* ignore */ }
1073
+ if (dws.readyState !== WebSocket.OPEN) { stopDeviceHeartbeat(); return }
1074
+ if (!deviceIsAlive) {
1075
+ log(chalk.red('[device] Heartbeat timeout — no pong received, forcing reconnect'))
1076
+ stopDeviceHeartbeat()
1077
+ try { dws.terminate() } catch { /* ignore */ }
1078
+ return
1037
1079
  }
1038
- }, 30_000)
1080
+ deviceIsAlive = false
1081
+ try { dws.ping() } catch { /* ignore */ }
1082
+ }, DEVICE_PING_INTERVAL_MS)
1083
+
1084
+ dws.on('pong', () => { deviceIsAlive = true })
1039
1085
  })
1040
1086
 
1041
1087
  dws.on('message', async (data) => {
@@ -1058,7 +1104,7 @@ function connectLocalDevice() {
1058
1104
  dws.on('close', (code) => {
1059
1105
  log(chalk.yellow(`Local device disconnected (${code})`))
1060
1106
  deviceWsRef = null
1061
- if (devicePingTimer) { clearInterval(devicePingTimer); devicePingTimer = null }
1107
+ stopDeviceHeartbeat()
1062
1108
  // Reconnect after delay (only if not shutting down)
1063
1109
  if (code !== 1000) {
1064
1110
  deviceReconnectTimer = setTimeout(connectLocalDevice, 5000)
@@ -1186,6 +1232,7 @@ async function startup() {
1186
1232
  argv.agent = result.agent
1187
1233
  argv.token = result.token
1188
1234
  hostId = result.agent
1235
+ label = result.agent.split('.')[0] || result.agent
1189
1236
 
1190
1237
  // Rebuild manager URL with new agent/token
1191
1238
  const newManagerUrl = new URL(argv.manager)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@office-xyz/claude-code",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Connect Claude Code to your Virtual Office — manage your AI agents from the terminal",
5
5
  "type": "module",
6
6
  "bin": {