kumo-cli 1.0.1 → 1.0.3

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 (70) hide show
  1. package/README.md +20 -31
  2. package/dist/auth/credentialsStore.js +41 -0
  3. package/dist/auth/credentialsStore.js.map +1 -0
  4. package/dist/claude/generateHookSettings.js +23 -0
  5. package/dist/claude/generateHookSettings.js.map +1 -0
  6. package/dist/claude/hookServer.js +33 -0
  7. package/dist/claude/hookServer.js.map +1 -0
  8. package/dist/claude/sessionScanner.js +250 -0
  9. package/dist/claude/sessionScanner.js.map +1 -0
  10. package/dist/config.js +40 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/core/config.js +48 -0
  13. package/dist/core/config.js.map +1 -0
  14. package/dist/core/loadDotEnv.js +46 -0
  15. package/dist/core/loadDotEnv.js.map +1 -0
  16. package/dist/core/logger.js +29 -0
  17. package/dist/core/logger.js.map +1 -0
  18. package/dist/handlers/messageRouter.js +225 -0
  19. package/dist/handlers/messageRouter.js.map +1 -0
  20. package/dist/http/httpClient.js +41 -0
  21. package/dist/http/httpClient.js.map +1 -0
  22. package/dist/index.js +120 -1364
  23. package/dist/index.js.map +1 -1
  24. package/dist/logger.js +29 -0
  25. package/dist/logger.js.map +1 -0
  26. package/dist/pty/ansiCodes.js +14 -0
  27. package/dist/pty/ansiCodes.js.map +1 -0
  28. package/dist/pty/ptyMenuParser.js +357 -0
  29. package/dist/pty/ptyMenuParser.js.map +1 -0
  30. package/dist/pty/ptySnapshotRenderer.js +71 -0
  31. package/dist/pty/ptySnapshotRenderer.js.map +1 -0
  32. package/dist/pty/spawn.js +77 -0
  33. package/dist/pty/spawn.js.map +1 -0
  34. package/dist/pty/terminalManager.js +66 -0
  35. package/dist/pty/terminalManager.js.map +1 -0
  36. package/dist/services/claudeAdapterProxy.js +104 -0
  37. package/dist/services/claudeAdapterProxy.js.map +1 -0
  38. package/dist/services/claudeService.js +239 -0
  39. package/dist/services/claudeService.js.map +1 -0
  40. package/dist/services/claudeService.test.js +39 -0
  41. package/dist/services/claudeService.test.js.map +1 -0
  42. package/dist/services/effortService.js +89 -0
  43. package/dist/services/effortService.js.map +1 -0
  44. package/dist/services/fileService.js +129 -0
  45. package/dist/services/fileService.js.map +1 -0
  46. package/dist/services/modelService.js +115 -0
  47. package/dist/services/modelService.js.map +1 -0
  48. package/dist/services/modelService.test.js +76 -0
  49. package/dist/services/modelService.test.js.map +1 -0
  50. package/dist/services/pairingService.js +129 -0
  51. package/dist/services/pairingService.js.map +1 -0
  52. package/dist/services/sessionService.js +168 -0
  53. package/dist/services/sessionService.js.map +1 -0
  54. package/dist/services/tunnelService.js +47 -0
  55. package/dist/services/tunnelService.js.map +1 -0
  56. package/dist/sessionScanner.js +130 -4
  57. package/dist/sessionScanner.js.map +1 -1
  58. package/dist/snapshotScanner.js +3 -1
  59. package/dist/snapshotScanner.js.map +1 -1
  60. package/dist/transport/directWsServer.js +135 -0
  61. package/dist/transport/directWsServer.js.map +1 -0
  62. package/dist/transport/wsClient.js +87 -0
  63. package/dist/transport/wsClient.js.map +1 -0
  64. package/dist/utils/ignorePaths.js +54 -0
  65. package/dist/utils/ignorePaths.js.map +1 -0
  66. package/dist/utils/ptyBuffer.js +46 -0
  67. package/dist/utils/ptyBuffer.js.map +1 -0
  68. package/dist/utils/safePath.js +43 -0
  69. package/dist/utils/safePath.js.map +1 -0
  70. package/package.json +6 -5
package/README.md CHANGED
@@ -1,52 +1,40 @@
1
1
  # Kumo CLI
2
2
 
3
- > _where your code drifts between devices, softly._
4
-
5
- [![npm version](https://img.shields.io/npm/v/kumo-cli.svg?style=flat-square)](https://www.npmjs.com/package/kumo-cli)
6
- [![node](https://img.shields.io/node/v/kumo-cli.svg?style=flat-square)](https://nodejs.org)
7
- [![license](https://img.shields.io/npm/l/kumo-cli.svg?style=flat-square)](./LICENSE)
8
-
9
- **Kumo CLI** is the desktop companion for the [Kumo app](https://github.com/kumo-app/kumo). It pairs your terminal with the mobile/desktop client over a secure WebSocket bridge so you can drive Claude Code (and any PTY session) from anywhere — without giving up the comfort of your local shell.
3
+ A lightweight companion CLI for Kumo that connects your local terminal sessions to the Kumo app.
10
4
 
11
5
  ---
12
6
 
13
7
  ## ✨ Features
14
8
 
15
- - 🔗 **One-tap pairing** — link the CLI to your Kumo account with a 9-digit code from the app.
16
- - 🖥️ **Real PTY streaming** — spawns Claude Code (or any process) via `node-pty` and mirrors the terminal byte-for-byte.
17
- - 📡 **Bi-directional bridge** — keystrokes from the app flow back into the local PTY in real time.
18
- - 🪝 **Session hooks** — auto-injects Claude Code session hooks so your remote views stay in sync.
19
- - 🧠 **Smart snapshots** periodic ANSI snapshots keep newly connected viewers up to date instantly.
20
- - 🌐 **Bonjour discovery** — optional local discovery for zero-config LAN setups.
21
- - 🔒 **Local credentials** — device tokens are stored under your user config dir, never echoed.
9
+ - Pair once with a short code from the app
10
+ - Resume and manage Claude Code sessions remotely
11
+ - Stream PTY output over WebSocket
12
+ - Auto-sync Claude session state and transcript events
13
+ - Optional local hook server integration for Claude session discovery
22
14
 
23
15
  ---
24
16
 
25
- ## 🚀 Quick start
17
+ ## 📦 Installation
26
18
 
27
- ```bash
28
- # run on demand (no install)
29
- npx kumo-cli@latest
19
+ ### Global install
30
20
 
31
- # or install globally
21
+ ```bash
32
22
  npm install -g kumo-cli
33
- kumo
34
23
  ```
35
24
 
36
- On first launch you'll see a prompt:
25
+ ### One-off via npx
37
26
 
27
+ ```bash
28
+ npx kumo-cli@latest
38
29
  ```
39
- ✦ pairing code › ___
40
- ```
41
-
42
- Open the **Kumo app**, sign in, tap **"generate"** on the pairing tab, and type the 9 digits back. That's it — your CLI is paired and a session is live.
43
30
 
44
31
  ---
45
32
 
46
- ## 📦 Requirements
33
+ ## Requirements
47
34
 
48
- - **Node.js 18** (LTS recommended)
49
- - A working C/C++ toolchain for [`node-pty`](https://github.com/microsoft/node-pty#dependencies):
35
+ - Node.js **18+**
36
+ - Internet access to reach your Kumo backend
37
+ - Native build prerequisites for `node-pty`:
50
38
  - **Windows:** Visual Studio Build Tools + Python 3
51
39
  - **macOS:** Xcode Command Line Tools (`xcode-select --install`)
52
40
  - **Linux:** `build-essential`, `python3`, `make`, `gcc`
@@ -56,7 +44,7 @@ Open the **Kumo app**, sign in, tap **"generate"** on the pairing tab, and type
56
44
 
57
45
  ## 🛠️ Configuration
58
46
 
59
- Kumo CLI is configured via environment variables. They can be exported in your shell or placed in a `.env` file in the working directory.
47
+ Kumo CLI is configured via environment variables. Export them in your shell or load them explicitly with `--env-file path/to/.env`.
60
48
 
61
49
  | Variable | Default | Description |
62
50
  | --- | --- | --- |
@@ -72,8 +60,9 @@ Credentials are persisted to your OS config dir (e.g. `%APPDATA%\kumo\credential
72
60
  ## 🧭 Usage
73
61
 
74
62
  ```bash
75
- kumo # interactive: pair if needed, then start a session
76
- kumo --help # show available flags (if applicable)
63
+ kumo --env-file .env
64
+ kumo --env-file .env.local
65
+ kumo --help
77
66
  ```
78
67
 
79
68
  To unpair, simply delete the credentials file printed on first pairing, or run:
@@ -0,0 +1,41 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ const CRED_DIR = path.join(os.homedir(), '.kumo');
5
+ const CRED_FILE = path.join(CRED_DIR, 'credentials.json');
6
+ // load credentials from ~/.kumo/credentials.json if present
7
+ export function loadCredentials() {
8
+ try {
9
+ if (!fs.existsSync(CRED_FILE))
10
+ return null;
11
+ const parsed = JSON.parse(fs.readFileSync(CRED_FILE, 'utf-8'));
12
+ if (!parsed.deviceUuid || !parsed.deviceToken)
13
+ return null;
14
+ return parsed;
15
+ }
16
+ catch {
17
+ return null;
18
+ }
19
+ }
20
+ // persist credentials with restrictive 0600 mode on posix
21
+ export function saveCredentials(creds) {
22
+ if (!fs.existsSync(CRED_DIR))
23
+ fs.mkdirSync(CRED_DIR, { recursive: true });
24
+ fs.writeFileSync(CRED_FILE, JSON.stringify(creds, null, 2), { encoding: 'utf-8', mode: 0o600 });
25
+ }
26
+ // remove credentials file; called by `kumo reset`
27
+ export function clearCredentials() {
28
+ try {
29
+ if (!fs.existsSync(CRED_FILE))
30
+ return false;
31
+ fs.unlinkSync(CRED_FILE);
32
+ return true;
33
+ }
34
+ catch {
35
+ return false;
36
+ }
37
+ }
38
+ export function credentialsPath() {
39
+ return CRED_FILE;
40
+ }
41
+ //# sourceMappingURL=credentialsStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentialsStore.js","sourceRoot":"","sources":["../../src/auth/credentialsStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAS7B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC;AAE1D,4DAA4D;AAC5D,MAAM,UAAU,eAAe;IAC7B,IAAI,CAAC;QACH,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO,IAAI,CAAC;QAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAoB,CAAC;QAClF,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,CAAC,MAAM,CAAC,WAAW;YAAE,OAAO,IAAI,CAAC;QAC3D,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,eAAe,CAAC,KAAsB;IACpD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1E,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AAClG,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,gBAAgB;IAC9B,IAAI,CAAC;QACH,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO,KAAK,CAAC;QAC5C,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,23 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
6
+ // generate a temporary claude settings file pointing SessionStart hook to our local forwarder
7
+ export function generateHookSettingsFile(hookPort) {
8
+ const forwarderPath = path.resolve(moduleDir, '..', '..', 'scripts', 'session_hook_forwarder.cjs');
9
+ const settingsDir = path.join(os.tmpdir(), 'kumo-hooks');
10
+ fs.mkdirSync(settingsDir, { recursive: true });
11
+ const settingsPath = path.join(settingsDir, `settings-${Date.now()}.json`);
12
+ const command = `node "${forwarderPath}" --port ${hookPort}`;
13
+ const settings = {
14
+ hooks: {
15
+ SessionStart: [
16
+ { matcher: '', hooks: [{ type: 'command', command }] },
17
+ ],
18
+ },
19
+ };
20
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
21
+ return settingsPath;
22
+ }
23
+ //# sourceMappingURL=generateHookSettings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generateHookSettings.js","sourceRoot":"","sources":["../../src/claude/generateHookSettings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE/D,8FAA8F;AAC9F,MAAM,UAAU,wBAAwB,CAAC,QAAgB;IACvD,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,4BAA4B,CAAC,CAAC;IACnG,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,YAAY,CAAC,CAAC;IACzD,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/C,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,YAAY,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC3E,MAAM,OAAO,GAAG,SAAS,aAAa,YAAY,QAAQ,EAAE,CAAC;IAC7D,MAAM,QAAQ,GAAG;QACf,KAAK,EAAE;YACL,YAAY,EAAE;gBACZ,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE;aACvD;SACF;KACF,CAAC;IACF,EAAE,CAAC,aAAa,CAAC,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IAC3E,OAAO,YAAY,CAAC;AACtB,CAAC"}
@@ -0,0 +1,33 @@
1
+ import http from 'node:http';
2
+ // start a tiny http server on an ephemeral port that claude will post sessionStart to
3
+ export function startHookServer(onSessionFound) {
4
+ return new Promise((resolve, reject) => {
5
+ const server = http.createServer((req, res) => {
6
+ if (req.method === 'POST' && req.url === '/hook/session-start') {
7
+ let body = '';
8
+ req.on('data', (chunk) => { body += chunk; });
9
+ req.on('end', () => {
10
+ try {
11
+ const data = JSON.parse(body);
12
+ if (data.sessionId)
13
+ onSessionFound(data.sessionId);
14
+ }
15
+ catch { }
16
+ res.writeHead(200);
17
+ res.end('ok');
18
+ });
19
+ }
20
+ else {
21
+ res.writeHead(404);
22
+ res.end();
23
+ }
24
+ });
25
+ server.listen(0, '127.0.0.1', () => {
26
+ const addr = server.address();
27
+ const port = typeof addr === 'object' && addr ? addr.port : 0;
28
+ resolve({ port, close: () => server.close() });
29
+ });
30
+ server.on('error', reject);
31
+ });
32
+ }
33
+ //# sourceMappingURL=hookServer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hookServer.js","sourceRoot":"","sources":["../../src/claude/hookServer.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAO7B,sFAAsF;AACtF,MAAM,UAAU,eAAe,CAAC,cAA2C;IACzE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YAC5C,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,IAAI,GAAG,CAAC,GAAG,KAAK,qBAAqB,EAAE,CAAC;gBAC/D,IAAI,IAAI,GAAG,EAAE,CAAC;gBACd,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,GAAG,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC9C,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;oBACjB,IAAI,CAAC;wBACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAA2B,CAAC;wBACxD,IAAI,IAAI,CAAC,SAAS;4BAAE,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;oBACrD,CAAC;oBAAC,MAAM,CAAC,CAAA,CAAC;oBACV,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;oBACnB,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBAChB,CAAC,CAAC,CAAC;YACL,CAAC;iBAAM,CAAC;gBACN,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;gBACnB,GAAG,CAAC,GAAG,EAAE,CAAC;YACZ,CAAC;QACH,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE;YACjC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;YAC9B,MAAM,IAAI,GAAG,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YAC9D,OAAO,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACjD,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,250 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import crypto from 'node:crypto';
5
+ import { EventEmitter } from 'node:events';
6
+ const NOISE_TAGS_RE = /<local-command-|<\/local-command-|<command-name>|<command-message>|<command-args>/i;
7
+ const NOISE_INTERRUPT_RE = /request interrupted by user|interrupted by user|interrupted the running process/i;
8
+ const UUID_LINE_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\s*$/i;
9
+ // scans claude session jsonl + subagent transcripts incrementally and emits parsed messages
10
+ export class SessionScanner extends EventEmitter {
11
+ sessionId;
12
+ startAtEnd;
13
+ filePath = null;
14
+ sessionDir = null;
15
+ offset = 0;
16
+ watcher = null;
17
+ subagentWatcher = null;
18
+ seenUuids = new Set();
19
+ pollTimer = null;
20
+ subagentScanTimer = null;
21
+ subagentOffsets = new Map();
22
+ subagentWatchers = new Map();
23
+ constructor(sessionId, startAtEnd = false) {
24
+ super();
25
+ this.sessionId = sessionId;
26
+ this.startAtEnd = startAtEnd;
27
+ }
28
+ start() {
29
+ this.filePath = this.findJsonlFile();
30
+ if (!this.filePath) {
31
+ this.pollTimer = setInterval(() => {
32
+ const found = this.findJsonlFile();
33
+ if (found) {
34
+ this.filePath = found;
35
+ clearInterval(this.pollTimer);
36
+ this.pollTimer = null;
37
+ this.watchFile();
38
+ }
39
+ }, 500);
40
+ return;
41
+ }
42
+ this.watchFile();
43
+ }
44
+ stop() {
45
+ this.watcher?.close();
46
+ this.subagentWatcher?.close();
47
+ for (const w of this.subagentWatchers.values())
48
+ w.close();
49
+ this.subagentWatchers.clear();
50
+ if (this.pollTimer)
51
+ clearInterval(this.pollTimer);
52
+ if (this.subagentScanTimer)
53
+ clearInterval(this.subagentScanTimer);
54
+ }
55
+ findJsonlFile() {
56
+ const projectsDir = path.join(os.homedir(), '.claude', 'projects');
57
+ if (!fs.existsSync(projectsDir))
58
+ return null;
59
+ for (const entry of fs.readdirSync(projectsDir, { withFileTypes: true })) {
60
+ if (!entry.isDirectory())
61
+ continue;
62
+ const candidate = path.join(projectsDir, entry.name, `${this.sessionId}.jsonl`);
63
+ if (fs.existsSync(candidate)) {
64
+ this.sessionDir = path.join(projectsDir, entry.name, this.sessionId);
65
+ return candidate;
66
+ }
67
+ }
68
+ return null;
69
+ }
70
+ watchFile() {
71
+ if (!this.filePath)
72
+ return;
73
+ if (this.startAtEnd) {
74
+ try {
75
+ this.offset = fs.statSync(this.filePath).size;
76
+ }
77
+ catch {
78
+ this.offset = 0;
79
+ }
80
+ this.startAtEnd = false;
81
+ }
82
+ else {
83
+ this.readIncremental();
84
+ }
85
+ try {
86
+ this.watcher = fs.watch(this.filePath, () => this.readIncremental());
87
+ }
88
+ catch { }
89
+ this.watchSubagents();
90
+ }
91
+ watchSubagents() {
92
+ if (!this.sessionDir)
93
+ return;
94
+ const subagentsDir = path.join(this.sessionDir, 'subagents');
95
+ const scanSubagents = () => {
96
+ if (!fs.existsSync(subagentsDir))
97
+ return;
98
+ for (const file of fs.readdirSync(subagentsDir).filter((f) => f.endsWith('.jsonl'))) {
99
+ const filePath = path.join(subagentsDir, file);
100
+ if (!this.subagentWatchers.has(filePath)) {
101
+ this.readSubagentFile(filePath);
102
+ try {
103
+ this.subagentWatchers.set(filePath, fs.watch(filePath, () => this.readSubagentFile(filePath)));
104
+ }
105
+ catch { }
106
+ }
107
+ }
108
+ };
109
+ scanSubagents();
110
+ this.subagentScanTimer = setInterval(() => {
111
+ if (fs.existsSync(subagentsDir)) {
112
+ if (!this.subagentWatcher) {
113
+ try {
114
+ this.subagentWatcher = fs.watch(subagentsDir, () => scanSubagents());
115
+ }
116
+ catch { }
117
+ }
118
+ scanSubagents();
119
+ }
120
+ }, 1000);
121
+ }
122
+ // read newly appended bytes from a jsonl file using a tracked offset
123
+ readDelta(filePath, getOffset, setOffset) {
124
+ try {
125
+ const stat = fs.statSync(filePath);
126
+ const offset = getOffset();
127
+ if (stat.size <= offset)
128
+ return;
129
+ const fd = fs.openSync(filePath, 'r');
130
+ const buf = Buffer.alloc(stat.size - offset);
131
+ fs.readSync(fd, buf, 0, buf.length, offset);
132
+ fs.closeSync(fd);
133
+ setOffset(stat.size);
134
+ for (const line of buf.toString('utf-8').split('\n')) {
135
+ if (!line.trim())
136
+ continue;
137
+ try {
138
+ this.processMessage(JSON.parse(line));
139
+ }
140
+ catch { }
141
+ }
142
+ }
143
+ catch { }
144
+ }
145
+ readSubagentFile(filePath) {
146
+ this.readDelta(filePath, () => this.subagentOffsets.get(filePath) ?? 0, (n) => this.subagentOffsets.set(filePath, n));
147
+ }
148
+ readIncremental() {
149
+ if (!this.filePath)
150
+ return;
151
+ this.readDelta(this.filePath, () => this.offset, (n) => { this.offset = n; });
152
+ }
153
+ processMessage(msg) {
154
+ if (msg.type !== 'user' && msg.type !== 'assistant')
155
+ return;
156
+ if (!msg.message)
157
+ return;
158
+ if (msg.isMeta || msg.sourceToolUseID)
159
+ return;
160
+ const role = msg.message.role;
161
+ if (!role || role === 'system')
162
+ return;
163
+ if (msg.uuid && this.seenUuids.has(msg.uuid))
164
+ return;
165
+ if (msg.uuid)
166
+ this.seenUuids.add(msg.uuid);
167
+ let content = '';
168
+ const toolUses = [];
169
+ if (typeof msg.message.content === 'string') {
170
+ content = msg.message.content;
171
+ }
172
+ else if (Array.isArray(msg.message.content)) {
173
+ content = msg.message.content
174
+ .filter((b) => b.type === 'text' && b.text && (b.text ?? '').trim() !== 'No response requested.')
175
+ .map((b) => b.text ?? '')
176
+ .join('');
177
+ for (const b of msg.message.content) {
178
+ if (b.type === 'tool_use' && b.name) {
179
+ const tu = { toolName: b.name, input: b.input ?? {} };
180
+ if (b.name === 'Write')
181
+ this.enrichWriteWithOldContent(tu);
182
+ toolUses.push(tu);
183
+ }
184
+ }
185
+ }
186
+ if (!content && toolUses.length === 0)
187
+ return;
188
+ if (content && this.isNoiseTranscript(content))
189
+ return;
190
+ this.emit('message', {
191
+ role, content,
192
+ id: msg.uuid,
193
+ timestamp: new Date().toISOString(),
194
+ toolUses: toolUses.length > 0 ? toolUses : undefined,
195
+ stopReason: msg.message.stop_reason,
196
+ agentId: msg.agentId,
197
+ isSidechain: msg.isSidechain,
198
+ });
199
+ }
200
+ // inject old_content into write tool use by reading previous version from file-history
201
+ enrichWriteWithOldContent(tu) {
202
+ const fp = tu.input['file_path'] ?? tu.input['path'] ?? '';
203
+ if (!fp)
204
+ return;
205
+ const oldContent = readFileHistoryPrev(this.sessionId, fp);
206
+ if (oldContent !== null)
207
+ tu.input['old_content'] = oldContent;
208
+ }
209
+ isNoiseTranscript(content) {
210
+ if (/<local-command-stdout>/.test(content))
211
+ return false;
212
+ if (NOISE_TAGS_RE.test(content))
213
+ return true;
214
+ if (NOISE_INTERRUPT_RE.test(content))
215
+ return true;
216
+ if (UUID_LINE_RE.test(content.trim()))
217
+ return true;
218
+ return false;
219
+ }
220
+ }
221
+ // convert unix-style drive path (/e/Data/...) to windows path (E:\Data\...) on windows
222
+ function toNativePath(p) {
223
+ if (process.platform === 'win32') {
224
+ const m = p.match(/^\/([a-zA-Z])\//);
225
+ if (m)
226
+ return m[1].toUpperCase() + ':\\' + p.slice(3).replace(/\//g, '\\');
227
+ }
228
+ return p;
229
+ }
230
+ // read the previous version of a file from ~/.claude/file-history; returns null if not found or first write
231
+ export function readFileHistoryPrev(sessionId, filePath) {
232
+ try {
233
+ const native = toNativePath(filePath);
234
+ const resolved = path.resolve(native);
235
+ const hash = crypto.createHash('sha256').update(resolved).digest('hex').slice(0, 16);
236
+ const histDir = path.join(os.homedir(), '.claude', 'file-history', sessionId);
237
+ if (!fs.existsSync(histDir))
238
+ return null;
239
+ const versions = fs.readdirSync(histDir)
240
+ .filter((f) => f.startsWith(hash + '@v'))
241
+ .sort((a, b) => parseInt(a.split('@v')[1], 10) - parseInt(b.split('@v')[1], 10));
242
+ if (versions.length < 2)
243
+ return null;
244
+ return fs.readFileSync(path.join(histDir, versions[versions.length - 2]), 'utf-8');
245
+ }
246
+ catch {
247
+ return null;
248
+ }
249
+ }
250
+ //# sourceMappingURL=sessionScanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionScanner.js","sourceRoot":"","sources":["../../src/claude/sessionScanner.ts"],"names":[],"mappings":"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"}
package/dist/config.js ADDED
@@ -0,0 +1,40 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ // normalize raw user-supplied url into an http/https origin (no trailing /ws)
4
+ export function normalizeServerHttpUrl(input) {
5
+ const u = new URL(input);
6
+ if (u.protocol === 'ws:')
7
+ u.protocol = 'http:';
8
+ if (u.protocol === 'wss:')
9
+ u.protocol = 'https:';
10
+ if (u.pathname === '/ws')
11
+ u.pathname = '/';
12
+ if (u.pathname.endsWith('/ws'))
13
+ u.pathname = u.pathname.slice(0, -3) || '/';
14
+ return u.toString().replace(/\/$/, '');
15
+ }
16
+ // normalize raw user-supplied url into a ws/wss endpoint pointing to /ws
17
+ export function normalizeServerWsUrl(input) {
18
+ const u = new URL(input);
19
+ if (u.protocol === 'http:')
20
+ u.protocol = 'ws:';
21
+ if (u.protocol === 'https:')
22
+ u.protocol = 'wss:';
23
+ if (u.pathname === '/' || u.pathname === '')
24
+ u.pathname = '/ws';
25
+ return u.toString().replace(/\/$/, '');
26
+ }
27
+ const serverUrl = process.env.KUMO_SERVER_URL;
28
+ export const SERVER_HTTP = normalizeServerHttpUrl(process.env.KUMO_SERVER_HTTP_URL ?? serverUrl ?? 'https://kumocli.com');
29
+ export const SERVER_WS = normalizeServerWsUrl(process.env.KUMO_SERVER_WS_URL ?? serverUrl ?? 'wss://kumocli.com/ws');
30
+ export const SERVER_PORT = parseInt(process.env.KUMO_PORT ?? '443', 10);
31
+ // resolve project-relative directories from this module's compiled location
32
+ const moduleDir = path.resolve(fileURLToPath(import.meta.url), '..');
33
+ export const PROJECT_TMP_DIR = path.resolve(moduleDir, '..', 'tmp');
34
+ export const LOGS_DIR = path.resolve(moduleDir, '..', 'logs');
35
+ export const SNAPSHOT_DIR = path.resolve(moduleDir, '..', '..', 'claude-data-snapshot');
36
+ // constants for pty buffering and file watcher debounce
37
+ export const PTY_FLUSH_MS = 120;
38
+ export const PTY_MAX_CHUNK = 8000;
39
+ export const FILE_WATCH_DEBOUNCE_MS = 300;
40
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,8EAA8E;AAC9E,MAAM,UAAU,sBAAsB,CAAC,KAAa;IAClD,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IACzB,IAAI,CAAC,CAAC,QAAQ,KAAK,KAAK;QAAE,CAAC,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC/C,IAAI,CAAC,CAAC,QAAQ,KAAK,MAAM;QAAE,CAAC,CAAC,QAAQ,GAAG,QAAQ,CAAC;IACjD,IAAI,CAAC,CAAC,QAAQ,KAAK,KAAK;QAAE,CAAC,CAAC,QAAQ,GAAG,GAAG,CAAC;IAC3C,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;IAC5E,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AACzC,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IACzB,IAAI,CAAC,CAAC,QAAQ,KAAK,OAAO;QAAE,CAAC,CAAC,QAAQ,GAAG,KAAK,CAAC;IAC/C,IAAI,CAAC,CAAC,QAAQ,KAAK,QAAQ;QAAE,CAAC,CAAC,QAAQ,GAAG,MAAM,CAAC;IACjD,IAAI,CAAC,CAAC,QAAQ,KAAK,GAAG,IAAI,CAAC,CAAC,QAAQ,KAAK,EAAE;QAAE,CAAC,CAAC,QAAQ,GAAG,KAAK,CAAC;IAChE,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;AAC9C,MAAM,CAAC,MAAM,WAAW,GAAG,sBAAsB,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,SAAS,IAAI,qBAAqB,CAAC,CAAC;AAC1H,MAAM,CAAC,MAAM,SAAS,GAAG,oBAAoB,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,SAAS,IAAI,sBAAsB,CAAC,CAAC;AACrH,MAAM,CAAC,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,KAAK,EAAE,EAAE,CAAC,CAAC;AAExE,4EAA4E;AAC5E,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;AACrE,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;AACpE,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;AAC9D,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,sBAAsB,CAAC,CAAC;AAExF,wDAAwD;AACxD,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAC;AAChC,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,CAAC;AAClC,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAC"}
@@ -0,0 +1,48 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ // normalize raw user-supplied url into an http/https origin (no trailing /ws)
4
+ export function normalizeServerHttpUrl(input) {
5
+ const u = new URL(input);
6
+ if (u.protocol === 'ws:')
7
+ u.protocol = 'http:';
8
+ if (u.protocol === 'wss:')
9
+ u.protocol = 'https:';
10
+ if (u.pathname === '/ws')
11
+ u.pathname = '/';
12
+ if (u.pathname.endsWith('/ws'))
13
+ u.pathname = u.pathname.slice(0, -3) || '/';
14
+ return u.toString().replace(/\/$/, '');
15
+ }
16
+ // normalize raw user-supplied url into a ws/wss endpoint pointing to /ws
17
+ export function normalizeServerWsUrl(input) {
18
+ const u = new URL(input);
19
+ if (u.protocol === 'http:')
20
+ u.protocol = 'ws:';
21
+ if (u.protocol === 'https:')
22
+ u.protocol = 'wss:';
23
+ if (u.pathname === '/' || u.pathname === '')
24
+ u.pathname = '/ws';
25
+ return u.toString().replace(/\/$/, '');
26
+ }
27
+ // lazy-evaluated so env vars injected via --env-file are available
28
+ let _serverHttp = null;
29
+ let _serverWs = null;
30
+ export function SERVER_HTTP() {
31
+ if (!_serverHttp)
32
+ _serverHttp = normalizeServerHttpUrl(process.env.KUMO_SERVER_HTTP_URL ?? process.env.KUMO_SERVER_URL ?? 'https://kumocli.com');
33
+ return _serverHttp;
34
+ }
35
+ export function SERVER_WS() {
36
+ if (!_serverWs)
37
+ _serverWs = normalizeServerWsUrl(process.env.KUMO_SERVER_WS_URL ?? process.env.KUMO_SERVER_URL ?? 'wss://kumocli.com/ws');
38
+ return _serverWs;
39
+ }
40
+ // resolve project-relative directories from this module's compiled location (dist/core/config.js)
41
+ const moduleDir = path.resolve(fileURLToPath(import.meta.url), '..');
42
+ export const PROJECT_TMP_DIR = path.resolve(moduleDir, '..', '..', 'tmp');
43
+ export const LOGS_DIR = path.resolve(moduleDir, '..', '..', 'logs');
44
+ // constants for pty buffering and file watcher debounce
45
+ export const PTY_FLUSH_MS = 120;
46
+ export const PTY_MAX_CHUNK = 8000;
47
+ export const FILE_WATCH_DEBOUNCE_MS = 300;
48
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,8EAA8E;AAC9E,MAAM,UAAU,sBAAsB,CAAC,KAAa;IAClD,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IACzB,IAAI,CAAC,CAAC,QAAQ,KAAK,KAAK;QAAE,CAAC,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC/C,IAAI,CAAC,CAAC,QAAQ,KAAK,MAAM;QAAE,CAAC,CAAC,QAAQ,GAAG,QAAQ,CAAC;IACjD,IAAI,CAAC,CAAC,QAAQ,KAAK,KAAK;QAAE,CAAC,CAAC,QAAQ,GAAG,GAAG,CAAC;IAC3C,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;IAC5E,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AACzC,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IACzB,IAAI,CAAC,CAAC,QAAQ,KAAK,OAAO;QAAE,CAAC,CAAC,QAAQ,GAAG,KAAK,CAAC;IAC/C,IAAI,CAAC,CAAC,QAAQ,KAAK,QAAQ;QAAE,CAAC,CAAC,QAAQ,GAAG,MAAM,CAAC;IACjD,IAAI,CAAC,CAAC,QAAQ,KAAK,GAAG,IAAI,CAAC,CAAC,QAAQ,KAAK,EAAE;QAAE,CAAC,CAAC,QAAQ,GAAG,KAAK,CAAC;IAChE,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AACzC,CAAC;AAED,mEAAmE;AACnE,IAAI,WAAW,GAAkB,IAAI,CAAC;AACtC,IAAI,SAAS,GAAkB,IAAI,CAAC;AAEpC,MAAM,UAAU,WAAW;IACzB,IAAI,CAAC,WAAW;QAAE,WAAW,GAAG,sBAAsB,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,qBAAqB,CAAC,CAAC;IACjJ,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,IAAI,CAAC,SAAS;QAAE,SAAS,GAAG,oBAAoB,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,sBAAsB,CAAC,CAAC;IAC1I,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,kGAAkG;AAClG,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;AACrE,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;AAC1E,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;AAEpE,wDAAwD;AACxD,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,CAAC;AAChC,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,CAAC;AAClC,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAC"}
@@ -0,0 +1,46 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ function parseDotEnv(text) {
4
+ const out = {};
5
+ for (const rawLine of text.split(/\r?\n/)) {
6
+ const line = rawLine.trim();
7
+ if (!line || line.startsWith('#'))
8
+ continue;
9
+ const normalized = line.startsWith('export ') ? line.slice('export '.length).trimStart() : line;
10
+ const eqIdx = normalized.indexOf('=');
11
+ if (eqIdx <= 0)
12
+ continue;
13
+ const key = normalized.slice(0, eqIdx).trim();
14
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
15
+ continue;
16
+ let value = normalized.slice(eqIdx + 1).trim();
17
+ if (!value) {
18
+ out[key] = '';
19
+ continue;
20
+ }
21
+ const first = value[0];
22
+ if (first === '"' || first === "'") {
23
+ const quote = first;
24
+ const rest = value.slice(1);
25
+ const endIdx = rest.lastIndexOf(quote);
26
+ const inner = endIdx >= 0 ? rest.slice(0, endIdx) : rest;
27
+ out[key] = quote === '"'
28
+ ? inner.replace(/\\n/g, '\n').replace(/\\r/g, '\r').replace(/\\t/g, '\t').replace(/\\\\/g, '\\').replace(/\\"/g, '"')
29
+ : inner;
30
+ continue;
31
+ }
32
+ out[key] = value.replace(/\s+#.*$/, '').trim();
33
+ }
34
+ return out;
35
+ }
36
+ export function loadDotEnv(envFilePath) {
37
+ const fullPath = path.resolve(envFilePath);
38
+ if (!fs.existsSync(fullPath)) {
39
+ throw new Error(`[kumo-cli] env file not found: ${fullPath}`);
40
+ }
41
+ const parsed = parseDotEnv(fs.readFileSync(fullPath, 'utf-8'));
42
+ for (const [k, v] of Object.entries(parsed)) {
43
+ process.env[k] = v;
44
+ }
45
+ }
46
+ //# sourceMappingURL=loadDotEnv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadDotEnv.js","sourceRoot":"","sources":["../../src/core/loadDotEnv.ts"],"names":[],"mappings":"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"}