kumo-cli 1.0.3 → 1.0.4
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 +13 -5
- package/dist/auth/credentialsStore.js +61 -14
- package/dist/auth/credentialsStore.js.map +1 -1
- package/dist/auth/credentialsStore.test.js +60 -0
- package/dist/auth/credentialsStore.test.js.map +1 -0
- package/dist/claude/fileHistory.js +35 -0
- package/dist/claude/fileHistory.js.map +1 -0
- package/dist/claude/sessionScanner.js +7 -95
- package/dist/claude/sessionScanner.js.map +1 -1
- package/dist/claude/sessionScanner.test.js +24 -0
- package/dist/claude/sessionScanner.test.js.map +1 -0
- package/dist/claude/transcriptParser.js +83 -0
- package/dist/claude/transcriptParser.js.map +1 -0
- package/dist/claude/transcriptParser.test.js +78 -0
- package/dist/claude/transcriptParser.test.js.map +1 -0
- package/dist/commands/agentPrompt.js +18 -0
- package/dist/commands/agentPrompt.js.map +1 -0
- package/dist/commands/cliArgs.js +56 -0
- package/dist/commands/cliArgs.js.map +1 -0
- package/dist/commands/cliArgs.test.js +25 -0
- package/dist/commands/cliArgs.test.js.map +1 -0
- package/dist/core/logger.js +7 -2
- package/dist/core/logger.js.map +1 -1
- package/dist/core/projectFileWatcher.js +56 -0
- package/dist/core/projectFileWatcher.js.map +1 -0
- package/dist/handlers/fileHandlerPayloads.js +72 -0
- package/dist/handlers/fileHandlerPayloads.js.map +1 -0
- package/dist/handlers/fileHandlerPayloads.test.js +55 -0
- package/dist/handlers/fileHandlerPayloads.test.js.map +1 -0
- package/dist/handlers/fileHandlers.js +74 -0
- package/dist/handlers/fileHandlers.js.map +1 -0
- package/dist/handlers/messageRouter.js +8 -205
- package/dist/handlers/messageRouter.js.map +1 -1
- package/dist/handlers/router.types.js +2 -0
- package/dist/handlers/router.types.js.map +1 -0
- package/dist/handlers/runtimeHandlers.js +55 -0
- package/dist/handlers/runtimeHandlers.js.map +1 -0
- package/dist/handlers/sessionHandlers.js +68 -0
- package/dist/handlers/sessionHandlers.js.map +1 -0
- package/dist/handlers/terminalHandlers.js +40 -0
- package/dist/handlers/terminalHandlers.js.map +1 -0
- package/dist/index.js +59 -145
- package/dist/index.js.map +1 -1
- package/dist/pty/notificationDetector.js +29 -0
- package/dist/pty/notificationDetector.js.map +1 -0
- package/dist/pty/notificationDetector.test.js +24 -0
- package/dist/pty/notificationDetector.test.js.map +1 -0
- package/dist/pty/ptyMenuParser.js +11 -277
- package/dist/pty/ptyMenuParser.js.map +1 -1
- package/dist/pty/ptyMenuParser.test.js +26 -0
- package/dist/pty/ptyMenuParser.test.js.map +1 -0
- package/dist/pty/selectionMenuDetector.js +49 -0
- package/dist/pty/selectionMenuDetector.js.map +1 -0
- package/dist/pty/selectionMenuDetector.test.js +62 -0
- package/dist/pty/selectionMenuDetector.test.js.map +1 -0
- package/dist/pty/selectionMenuMeta.js +54 -0
- package/dist/pty/selectionMenuMeta.js.map +1 -0
- package/dist/pty/selectionMenuMeta.test.js +26 -0
- package/dist/pty/selectionMenuMeta.test.js.map +1 -0
- package/dist/pty/selectionMenuParser.js +83 -0
- package/dist/pty/selectionMenuParser.js.map +1 -0
- package/dist/pty/selectionMenuParser.test.js +30 -0
- package/dist/pty/selectionMenuParser.test.js.map +1 -0
- package/dist/pty/statusSessionDetector.js +28 -0
- package/dist/pty/statusSessionDetector.js.map +1 -0
- package/dist/pty/statusSessionDetector.test.js +23 -0
- package/dist/pty/statusSessionDetector.test.js.map +1 -0
- package/dist/pty/terminalManager.js +11 -6
- package/dist/pty/terminalManager.js.map +1 -1
- package/dist/pty/textInputDetector.js +60 -0
- package/dist/pty/textInputDetector.js.map +1 -0
- package/dist/pty/textInputDetector.test.js +32 -0
- package/dist/pty/textInputDetector.test.js.map +1 -0
- package/dist/services/bootstrapService.js +28 -0
- package/dist/services/bootstrapService.js.map +1 -0
- package/dist/services/claudeMenuBridge.js +116 -0
- package/dist/services/claudeMenuBridge.js.map +1 -0
- package/dist/services/claudeMenuBridge.test.js +90 -0
- package/dist/services/claudeMenuBridge.test.js.map +1 -0
- package/dist/services/claudeService.js +5 -98
- package/dist/services/claudeService.js.map +1 -1
- package/dist/services/cliRuntime.js +49 -0
- package/dist/services/cliRuntime.js.map +1 -0
- package/dist/services/cliRuntime.test.js +55 -0
- package/dist/services/cliRuntime.test.js.map +1 -0
- package/dist/services/fileService.js +19 -23
- package/dist/services/fileService.js.map +1 -1
- package/dist/services/modelAdapter.test.js +47 -0
- package/dist/services/modelAdapter.test.js.map +1 -0
- package/dist/services/modelArgs.test.js +64 -0
- package/dist/services/modelArgs.test.js.map +1 -0
- package/dist/services/modelBaseUrl.test.js +32 -0
- package/dist/services/modelBaseUrl.test.js.map +1 -0
- package/dist/services/modelService.js +20 -9
- package/dist/services/modelService.js.map +1 -1
- package/dist/services/modelService.test.js +60 -0
- package/dist/services/modelService.test.js.map +1 -1
- package/dist/services/notificationDedupe.js +23 -0
- package/dist/services/notificationDedupe.js.map +1 -0
- package/dist/services/notificationDedupe.test.js +20 -0
- package/dist/services/notificationDedupe.test.js.map +1 -0
- package/dist/services/pairingService.js +17 -8
- package/dist/services/pairingService.js.map +1 -1
- package/dist/services/sessionListService.js +19 -0
- package/dist/services/sessionListService.js.map +1 -0
- package/dist/services/sessionListService.test.js +22 -0
- package/dist/services/sessionListService.test.js.map +1 -0
- package/dist/services/sessionService.js +8 -45
- package/dist/services/sessionService.js.map +1 -1
- package/dist/services/tunnelHelpers.js +69 -0
- package/dist/services/tunnelHelpers.js.map +1 -0
- package/dist/services/tunnelHelpers.test.js +42 -0
- package/dist/services/tunnelHelpers.test.js.map +1 -0
- package/dist/services/tunnelService.js +69 -25
- package/dist/services/tunnelService.js.map +1 -1
- package/dist/services/tunnelService.test.js +91 -0
- package/dist/services/tunnelService.test.js.map +1 -0
- package/dist/transport/webrtcTransport.js +145 -0
- package/dist/transport/webrtcTransport.js.map +1 -0
- package/dist/transport/webrtcTransport.test.js +104 -0
- package/dist/transport/webrtcTransport.test.js.map +1 -0
- package/dist/transport/webrtcTypes.js +29 -0
- package/dist/transport/webrtcTypes.js.map +1 -0
- package/dist/transport/webrtcTypes.test.js +20 -0
- package/dist/transport/webrtcTypes.test.js.map +1 -0
- package/dist/transport/wsClient.js +16 -3
- package/dist/transport/wsClient.js.map +1 -1
- package/dist/utils/safePath.js +18 -6
- package/dist/utils/safePath.js.map +1 -1
- package/package.json +4 -1
- package/scripts/run-tests.mjs +45 -0
package/README.md
CHANGED
|
@@ -53,24 +53,32 @@ Kumo CLI is configured via environment variables. Export them in your shell or l
|
|
|
53
53
|
| `KUMO_SERVER_WS_URL` | derived | Override the WebSocket endpoint explicitly. |
|
|
54
54
|
| `KUMO_PORT` | `3579` | Local port hint (used by some discovery flows). |
|
|
55
55
|
|
|
56
|
-
Credentials are persisted to
|
|
56
|
+
Credentials are persisted to `~/.kumo/credentials.json`.
|
|
57
|
+
|
|
58
|
+
The CLI also keeps a stable device identity in `~/.kumo/device.json` so pairing the same machine again after `kumo reset` reuses the existing machine entry instead of creating a duplicate.
|
|
57
59
|
|
|
58
60
|
---
|
|
59
61
|
|
|
60
62
|
## 🧭 Usage
|
|
61
63
|
|
|
62
64
|
```bash
|
|
63
|
-
kumo --env-file .env
|
|
64
|
-
kumo --env-file .env.local
|
|
65
|
+
kumo claude --env-file .env
|
|
66
|
+
kumo claude --env-file .env.local
|
|
67
|
+
kumo codex
|
|
65
68
|
kumo --help
|
|
66
69
|
```
|
|
67
70
|
|
|
68
|
-
|
|
71
|
+
Run `kumo` without `claude` or `codex` to choose an agent from the menu. Codex support is currently under development.
|
|
72
|
+
|
|
73
|
+
To clear one agent's current token and pair again on the same machine, run:
|
|
69
74
|
|
|
70
75
|
```bash
|
|
71
|
-
|
|
76
|
+
kumo reset claude
|
|
77
|
+
kumo reset codex
|
|
72
78
|
```
|
|
73
79
|
|
|
80
|
+
Run `kumo reset` without an agent to choose which credentials to reset. Reset removes the selected credentials file only. It intentionally keeps `~/.kumo/device.json` so the backend can still recognize the same machine on the next pairing.
|
|
81
|
+
|
|
74
82
|
---
|
|
75
83
|
|
|
76
84
|
## 🧱 How it works
|
|
@@ -1,14 +1,30 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
|
|
5
|
-
const
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
const KUMO_DIR_ENV = 'KUMO_HOME_DIR';
|
|
6
|
+
const CRED_FILE_NAME = 'credentials.json';
|
|
7
|
+
const DEVICE_FILE_NAME = 'device.json';
|
|
8
|
+
function credentialsFileName(agent = 'claude') {
|
|
9
|
+
return agent === 'claude' ? CRED_FILE_NAME : `${agent}-credentials.json`;
|
|
10
|
+
}
|
|
11
|
+
// resolve the local kumo state directory and allow tests to override it
|
|
12
|
+
function kumoDir() {
|
|
13
|
+
return process.env[KUMO_DIR_ENV] || path.join(os.homedir(), '.kumo');
|
|
14
|
+
}
|
|
15
|
+
// ensure the local kumo directory exists before writing state files
|
|
16
|
+
function ensureKumoDir() {
|
|
17
|
+
const dir = kumoDir();
|
|
18
|
+
if (!fs.existsSync(dir))
|
|
19
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
20
|
+
}
|
|
6
21
|
// load credentials from ~/.kumo/credentials.json if present
|
|
7
|
-
export function loadCredentials() {
|
|
22
|
+
export function loadCredentials(agent = 'claude') {
|
|
8
23
|
try {
|
|
9
|
-
|
|
24
|
+
const file = credentialsPath(agent);
|
|
25
|
+
if (!fs.existsSync(file))
|
|
10
26
|
return null;
|
|
11
|
-
const parsed = JSON.parse(fs.readFileSync(
|
|
27
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
12
28
|
if (!parsed.deviceUuid || !parsed.deviceToken)
|
|
13
29
|
return null;
|
|
14
30
|
return parsed;
|
|
@@ -18,24 +34,55 @@ export function loadCredentials() {
|
|
|
18
34
|
}
|
|
19
35
|
}
|
|
20
36
|
// persist credentials with restrictive 0600 mode on posix
|
|
21
|
-
export function saveCredentials(creds) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
37
|
+
export function saveCredentials(creds, agent = 'claude') {
|
|
38
|
+
ensureKumoDir();
|
|
39
|
+
fs.writeFileSync(credentialsPath(agent), JSON.stringify(creds, null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
40
|
+
}
|
|
41
|
+
// load the persisted device identity if present and valid
|
|
42
|
+
export function loadDeviceIdentity() {
|
|
43
|
+
try {
|
|
44
|
+
const file = deviceIdentityPath();
|
|
45
|
+
if (!fs.existsSync(file))
|
|
46
|
+
return null;
|
|
47
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
48
|
+
if (!parsed.deviceUuid)
|
|
49
|
+
return null;
|
|
50
|
+
return parsed;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// persist the stable device identity used across credential resets
|
|
57
|
+
export function saveDeviceIdentity(deviceUuid) {
|
|
58
|
+
ensureKumoDir();
|
|
59
|
+
fs.writeFileSync(deviceIdentityPath(), JSON.stringify({ deviceUuid }, null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
60
|
+
return deviceUuid;
|
|
61
|
+
}
|
|
62
|
+
// return the stable device uuid, seeding from a known uuid or generating one once
|
|
63
|
+
export function ensureDeviceUuid(seedDeviceUuid) {
|
|
64
|
+
const existing = loadDeviceIdentity()?.deviceUuid;
|
|
65
|
+
if (existing)
|
|
66
|
+
return existing;
|
|
67
|
+
return saveDeviceIdentity(seedDeviceUuid ?? randomUUID());
|
|
25
68
|
}
|
|
26
69
|
// remove credentials file; called by `kumo reset`
|
|
27
|
-
export function clearCredentials() {
|
|
70
|
+
export function clearCredentials(agent = 'claude') {
|
|
28
71
|
try {
|
|
29
|
-
|
|
72
|
+
const file = credentialsPath(agent);
|
|
73
|
+
if (!fs.existsSync(file))
|
|
30
74
|
return false;
|
|
31
|
-
fs.unlinkSync(
|
|
75
|
+
fs.unlinkSync(file);
|
|
32
76
|
return true;
|
|
33
77
|
}
|
|
34
78
|
catch {
|
|
35
79
|
return false;
|
|
36
80
|
}
|
|
37
81
|
}
|
|
38
|
-
export function credentialsPath() {
|
|
39
|
-
return
|
|
82
|
+
export function credentialsPath(agent = 'claude') {
|
|
83
|
+
return path.join(kumoDir(), credentialsFileName(agent));
|
|
84
|
+
}
|
|
85
|
+
export function deviceIdentityPath() {
|
|
86
|
+
return path.join(kumoDir(), DEVICE_FILE_NAME);
|
|
40
87
|
}
|
|
41
88
|
//# sourceMappingURL=credentialsStore.js.map
|
|
@@ -1 +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;
|
|
1
|
+
{"version":3,"file":"credentialsStore.js","sourceRoot":"","sources":["../../src/auth/credentialsStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import test from 'node:test';
|
|
6
|
+
import { clearCredentials, credentialsPath, deviceIdentityPath, ensureDeviceUuid, loadDeviceIdentity, saveCredentials, } from './credentialsStore.js';
|
|
7
|
+
function withTempKumoHome(run) {
|
|
8
|
+
const previous = process.env.KUMO_HOME_DIR;
|
|
9
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kumo-credentials-store-'));
|
|
10
|
+
process.env.KUMO_HOME_DIR = tempDir;
|
|
11
|
+
try {
|
|
12
|
+
run();
|
|
13
|
+
}
|
|
14
|
+
finally {
|
|
15
|
+
if (previous)
|
|
16
|
+
process.env.KUMO_HOME_DIR = previous;
|
|
17
|
+
else
|
|
18
|
+
delete process.env.KUMO_HOME_DIR;
|
|
19
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
test('ensureDeviceUuid persists and reuses the generated identity', () => {
|
|
23
|
+
withTempKumoHome(() => {
|
|
24
|
+
const first = ensureDeviceUuid();
|
|
25
|
+
const second = ensureDeviceUuid();
|
|
26
|
+
assert.equal(second, first);
|
|
27
|
+
assert.equal(loadDeviceIdentity()?.deviceUuid, first);
|
|
28
|
+
assert.equal(fs.existsSync(deviceIdentityPath()), true);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
test('ensureDeviceUuid seeds the identity from a known device uuid', () => {
|
|
32
|
+
withTempKumoHome(() => {
|
|
33
|
+
const seeded = ensureDeviceUuid('9ee8cfc6-27e8-4cb7-9c67-2b3c596a8ca8');
|
|
34
|
+
assert.equal(seeded, '9ee8cfc6-27e8-4cb7-9c67-2b3c596a8ca8');
|
|
35
|
+
assert.equal(loadDeviceIdentity()?.deviceUuid, seeded);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
test('clearCredentials removes only credentials and keeps the device identity', () => {
|
|
39
|
+
withTempKumoHome(() => {
|
|
40
|
+
const deviceUuid = ensureDeviceUuid('cf31b51d-8ea9-4698-8b7a-0919e5def77a');
|
|
41
|
+
saveCredentials({ deviceUuid, deviceToken: 'token' });
|
|
42
|
+
const cleared = clearCredentials();
|
|
43
|
+
assert.equal(cleared, true);
|
|
44
|
+
assert.equal(fs.existsSync(credentialsPath()), false);
|
|
45
|
+
assert.equal(loadDeviceIdentity()?.deviceUuid, deviceUuid);
|
|
46
|
+
assert.equal(fs.existsSync(deviceIdentityPath()), true);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
test('agent credentials are stored and cleared independently', () => {
|
|
50
|
+
withTempKumoHome(() => {
|
|
51
|
+
const deviceUuid = ensureDeviceUuid('cf31b51d-8ea9-4698-8b7a-0919e5def77a');
|
|
52
|
+
saveCredentials({ deviceUuid, deviceToken: 'claude-token' }, 'claude');
|
|
53
|
+
saveCredentials({ deviceUuid, deviceToken: 'codex-token' }, 'codex');
|
|
54
|
+
const cleared = clearCredentials('codex');
|
|
55
|
+
assert.equal(cleared, true);
|
|
56
|
+
assert.equal(fs.existsSync(credentialsPath('claude')), true);
|
|
57
|
+
assert.equal(fs.existsSync(credentialsPath('codex')), false);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
//# sourceMappingURL=credentialsStore.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentialsStore.test.js","sourceRoot":"","sources":["../../src/auth/credentialsStore.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
// convert unix-style drive path (/e/Data/...) to windows path (E:\Data\...) on windows
|
|
6
|
+
function toNativePath(filePath) {
|
|
7
|
+
if (process.platform === 'win32') {
|
|
8
|
+
const match = filePath.match(/^\/([a-zA-Z])\//);
|
|
9
|
+
if (match) {
|
|
10
|
+
return `${match[1].toUpperCase()}:\\${filePath.slice(3).replace(/\//g, '\\')}`;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return filePath;
|
|
14
|
+
}
|
|
15
|
+
// read the previous version of a file from ~/.claude/file-history; returns null if not found or first write
|
|
16
|
+
export function readFileHistoryPrev(sessionId, filePath) {
|
|
17
|
+
try {
|
|
18
|
+
const native = toNativePath(filePath);
|
|
19
|
+
const resolved = path.resolve(native);
|
|
20
|
+
const hash = crypto.createHash('sha256').update(resolved).digest('hex').slice(0, 16);
|
|
21
|
+
const historyDir = path.join(os.homedir(), '.claude', 'file-history', sessionId);
|
|
22
|
+
if (!fs.existsSync(historyDir))
|
|
23
|
+
return null;
|
|
24
|
+
const versions = fs.readdirSync(historyDir)
|
|
25
|
+
.filter((entry) => entry.startsWith(`${hash}@v`))
|
|
26
|
+
.sort((left, right) => parseInt(left.split('@v')[1], 10) - parseInt(right.split('@v')[1], 10));
|
|
27
|
+
if (versions.length < 2)
|
|
28
|
+
return null;
|
|
29
|
+
return fs.readFileSync(path.join(historyDir, versions[versions.length - 2]), 'utf-8');
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=fileHistory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileHistory.js","sourceRoot":"","sources":["../../src/claude/fileHistory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,MAAM,MAAM,aAAa,CAAC;AAEjC,uFAAuF;AACvF,SAAS,YAAY,CAAC,QAAgB;IACpC,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;QAChD,IAAI,KAAK,EAAE,CAAC;YACV,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,MAAM,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC;QACjF,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,4GAA4G;AAC5G,MAAM,UAAU,mBAAmB,CAAC,SAAiB,EAAE,QAAgB;IACrE,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;QACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACtC,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACrF,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,SAAS,CAAC,CAAC;QACjF,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,OAAO,IAAI,CAAC;QAC5C,MAAM,QAAQ,GAAG,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC;aACxC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;aAChD,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QACjG,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QACrC,OAAO,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IACxF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import os from 'node:os';
|
|
4
|
-
import crypto from 'node:crypto';
|
|
5
4
|
import { EventEmitter } from 'node:events';
|
|
6
|
-
|
|
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;
|
|
5
|
+
import { parseTranscriptMessage } from './transcriptParser.js';
|
|
9
6
|
// scans claude session jsonl + subagent transcripts incrementally and emits parsed messages
|
|
10
7
|
export class SessionScanner extends EventEmitter {
|
|
11
8
|
sessionId;
|
|
@@ -151,100 +148,15 @@ export class SessionScanner extends EventEmitter {
|
|
|
151
148
|
this.readDelta(this.filePath, () => this.offset, (n) => { this.offset = n; });
|
|
152
149
|
}
|
|
153
150
|
processMessage(msg) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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,
|
|
151
|
+
const parsed = parseTranscriptMessage(msg, {
|
|
152
|
+
sessionId: this.sessionId,
|
|
153
|
+
seenUuids: this.seenUuids,
|
|
154
|
+
includeNoise: false,
|
|
193
155
|
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
156
|
});
|
|
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)
|
|
157
|
+
if (!parsed)
|
|
204
158
|
return;
|
|
205
|
-
|
|
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;
|
|
159
|
+
this.emit('message', parsed);
|
|
248
160
|
}
|
|
249
161
|
}
|
|
250
162
|
//# sourceMappingURL=sessionScanner.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessionScanner.js","sourceRoot":"","sources":["../../src/claude/sessionScanner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,
|
|
1
|
+
{"version":3,"file":"sessionScanner.js","sourceRoot":"","sources":["../../src/claude/sessionScanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { SessionScanner } from "./sessionScanner.js";
|
|
4
|
+
test("processMessage emits assistant model from transcript jsonl", async () => {
|
|
5
|
+
const scanner = new SessionScanner("session-id");
|
|
6
|
+
const emitted = await new Promise((resolve) => {
|
|
7
|
+
scanner.once("message", (msg) => resolve(msg));
|
|
8
|
+
const processMessage = Reflect.get(scanner, "processMessage");
|
|
9
|
+
processMessage.call(scanner, {
|
|
10
|
+
type: "assistant",
|
|
11
|
+
uuid: "11111111-1111-1111-1111-111111111111",
|
|
12
|
+
message: {
|
|
13
|
+
role: "assistant",
|
|
14
|
+
model: "claude-sonnet-4-6",
|
|
15
|
+
content: [{ type: "text", text: "done" }],
|
|
16
|
+
stop_reason: "end_turn",
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
assert.equal(emitted.role, "assistant");
|
|
21
|
+
assert.equal(emitted.model, "claude-sonnet-4-6");
|
|
22
|
+
assert.equal(emitted.content, "done");
|
|
23
|
+
});
|
|
24
|
+
//# sourceMappingURL=sessionScanner.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionScanner.test.js","sourceRoot":"","sources":["../../src/claude/sessionScanner.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,IAAI,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;IAC5E,MAAM,OAAO,GAAG,IAAI,cAAc,CAAC,YAAY,CAAC,CAAC;IACjD,MAAM,OAAO,GAAG,MAAM,IAAI,OAAO,CAA0B,CAAC,OAAO,EAAE,EAAE;QACrE,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,GAA8B,CAAC,CAAC,CAAC;QAC1E,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,gBAAgB,CAEnD,CAAC;QACV,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE;YAC3B,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,sCAAsC;YAC5C,OAAO,EAAE;gBACP,IAAI,EAAE,WAAW;gBACjB,KAAK,EAAE,mBAAmB;gBAC1B,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;gBACzC,WAAW,EAAE,UAAU;aACxB;SACF,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IACxC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,mBAAmB,CAAC,CAAC;IACjD,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;AACxC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { readFileHistoryPrev } from './fileHistory.js';
|
|
2
|
+
const NOISE_TAGS_RE = /<local-command-|<\/local-command-|<command-name>|<command-message>|<command-args>/i;
|
|
3
|
+
const NOISE_INTERRUPT_RE = /request interrupted by user|interrupted by user|interrupted the running process/i;
|
|
4
|
+
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;
|
|
5
|
+
const NO_RESPONSE_TEXT = 'No response requested.';
|
|
6
|
+
// parse one claude jsonl message into the app-facing transcript shape
|
|
7
|
+
export function parseTranscriptMessage(message, options) {
|
|
8
|
+
if (message.type !== 'user' && message.type !== 'assistant')
|
|
9
|
+
return null;
|
|
10
|
+
if (!message.message)
|
|
11
|
+
return null;
|
|
12
|
+
if (message.isMeta || message.sourceToolUseID)
|
|
13
|
+
return null;
|
|
14
|
+
const role = message.message.role;
|
|
15
|
+
if (!role || role === 'system')
|
|
16
|
+
return null;
|
|
17
|
+
if (message.uuid && options.seenUuids?.has(message.uuid))
|
|
18
|
+
return null;
|
|
19
|
+
if (message.uuid)
|
|
20
|
+
options.seenUuids?.add(message.uuid);
|
|
21
|
+
const { content, toolUses } = parseMessageContent(message.message.content ?? '', options.sessionId);
|
|
22
|
+
if (!content && toolUses.length === 0)
|
|
23
|
+
return null;
|
|
24
|
+
if (!options.includeNoise && content && isNoiseTranscript(content))
|
|
25
|
+
return null;
|
|
26
|
+
return {
|
|
27
|
+
role,
|
|
28
|
+
content,
|
|
29
|
+
id: message.uuid,
|
|
30
|
+
timestamp: options.timestamp ?? message.timestamp,
|
|
31
|
+
toolUses: toolUses.length > 0 ? toolUses : undefined,
|
|
32
|
+
stopReason: message.message.stop_reason,
|
|
33
|
+
agentId: message.agentId,
|
|
34
|
+
isSidechain: message.isSidechain,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
// detect whether a transcript chunk is only terminal-noise metadata
|
|
38
|
+
export function isNoiseTranscript(content) {
|
|
39
|
+
if (/<local-command-stdout>/.test(content))
|
|
40
|
+
return false;
|
|
41
|
+
if (NOISE_TAGS_RE.test(content))
|
|
42
|
+
return true;
|
|
43
|
+
if (NOISE_INTERRUPT_RE.test(content))
|
|
44
|
+
return true;
|
|
45
|
+
if (UUID_LINE_RE.test(content.trim()))
|
|
46
|
+
return true;
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
// flatten mixed text/tool blocks into user-visible content and tool metadata
|
|
50
|
+
function parseMessageContent(content, sessionId) {
|
|
51
|
+
if (typeof content === 'string') {
|
|
52
|
+
return { content, toolUses: [] };
|
|
53
|
+
}
|
|
54
|
+
if (!Array.isArray(content)) {
|
|
55
|
+
return { content: '', toolUses: [] };
|
|
56
|
+
}
|
|
57
|
+
const textContent = content
|
|
58
|
+
.filter((block) => block.type === 'text' && block.text && block.text.trim() !== NO_RESPONSE_TEXT)
|
|
59
|
+
.map((block) => block.text ?? '')
|
|
60
|
+
.join('');
|
|
61
|
+
const toolUses = [];
|
|
62
|
+
for (const block of content) {
|
|
63
|
+
if (block.type !== 'tool_use' || !block.name)
|
|
64
|
+
continue;
|
|
65
|
+
const toolUse = { toolName: block.name, input: block.input ?? {} };
|
|
66
|
+
enrichWriteWithOldContent(toolUse, sessionId);
|
|
67
|
+
toolUses.push(toolUse);
|
|
68
|
+
}
|
|
69
|
+
return { content: textContent, toolUses };
|
|
70
|
+
}
|
|
71
|
+
// inject old_content into write tool use by reading previous version from file-history
|
|
72
|
+
function enrichWriteWithOldContent(toolUse, sessionId) {
|
|
73
|
+
if (toolUse.toolName !== 'Write')
|
|
74
|
+
return;
|
|
75
|
+
const filePath = toolUse.input['file_path'] ?? toolUse.input['path'] ?? '';
|
|
76
|
+
if (!filePath)
|
|
77
|
+
return;
|
|
78
|
+
const oldContent = readFileHistoryPrev(sessionId, filePath);
|
|
79
|
+
if (oldContent !== null) {
|
|
80
|
+
toolUse.input['old_content'] = oldContent;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=transcriptParser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcriptParser.js","sourceRoot":"","sources":["../../src/claude/transcriptParser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { isNoiseTranscript, parseTranscriptMessage } from './transcriptParser.js';
|
|
4
|
+
test('parseTranscriptMessage flattens text and tool blocks', () => {
|
|
5
|
+
const parsed = parseTranscriptMessage({
|
|
6
|
+
uuid: 'd8d8342a-13e4-4d43-a6a8-3295706f5fb9',
|
|
7
|
+
type: 'assistant',
|
|
8
|
+
timestamp: '2026-06-22T10:00:00.000Z',
|
|
9
|
+
message: {
|
|
10
|
+
role: 'assistant',
|
|
11
|
+
content: [
|
|
12
|
+
{ type: 'text', text: 'hello ' },
|
|
13
|
+
{ type: 'tool_use', name: 'Read', input: { file_path: 'README.md' } },
|
|
14
|
+
{ type: 'text', text: 'world' },
|
|
15
|
+
],
|
|
16
|
+
stop_reason: 'end_turn',
|
|
17
|
+
},
|
|
18
|
+
}, {
|
|
19
|
+
sessionId: 'session-1',
|
|
20
|
+
includeNoise: true,
|
|
21
|
+
});
|
|
22
|
+
assert.deepEqual(parsed, {
|
|
23
|
+
role: 'assistant',
|
|
24
|
+
content: 'hello world',
|
|
25
|
+
id: 'd8d8342a-13e4-4d43-a6a8-3295706f5fb9',
|
|
26
|
+
timestamp: '2026-06-22T10:00:00.000Z',
|
|
27
|
+
stopReason: 'end_turn',
|
|
28
|
+
toolUses: [{ toolName: 'Read', input: { file_path: 'README.md' } }],
|
|
29
|
+
agentId: undefined,
|
|
30
|
+
isSidechain: undefined,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
test('parseTranscriptMessage filters noise by default', () => {
|
|
34
|
+
const parsed = parseTranscriptMessage({
|
|
35
|
+
uuid: 'd8d8342a-13e4-4d43-a6a8-3295706f5fb8',
|
|
36
|
+
type: 'assistant',
|
|
37
|
+
message: {
|
|
38
|
+
role: 'assistant',
|
|
39
|
+
content: '<command-message>build done</command-message>',
|
|
40
|
+
},
|
|
41
|
+
}, {
|
|
42
|
+
sessionId: 'session-1',
|
|
43
|
+
});
|
|
44
|
+
assert.equal(parsed, null);
|
|
45
|
+
});
|
|
46
|
+
test('parseTranscriptMessage keeps noise when includeNoise is enabled', () => {
|
|
47
|
+
const parsed = parseTranscriptMessage({
|
|
48
|
+
uuid: 'd8d8342a-13e4-4d43-a6a8-3295706f5fb7',
|
|
49
|
+
type: 'assistant',
|
|
50
|
+
message: {
|
|
51
|
+
role: 'assistant',
|
|
52
|
+
content: '<command-message>build done</command-message>',
|
|
53
|
+
},
|
|
54
|
+
}, {
|
|
55
|
+
sessionId: 'session-1',
|
|
56
|
+
includeNoise: true,
|
|
57
|
+
});
|
|
58
|
+
assert.equal(parsed?.content, '<command-message>build done</command-message>');
|
|
59
|
+
});
|
|
60
|
+
test('parseTranscriptMessage dedupes messages by uuid when a seen set is provided', () => {
|
|
61
|
+
const seenUuids = new Set();
|
|
62
|
+
const message = {
|
|
63
|
+
uuid: 'd8d8342a-13e4-4d43-a6a8-3295706f5fb6',
|
|
64
|
+
type: 'assistant',
|
|
65
|
+
message: {
|
|
66
|
+
role: 'assistant',
|
|
67
|
+
content: 'hello',
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
const first = parseTranscriptMessage(message, { sessionId: 'session-1', seenUuids, includeNoise: true });
|
|
71
|
+
const second = parseTranscriptMessage(message, { sessionId: 'session-1', seenUuids, includeNoise: true });
|
|
72
|
+
assert.equal(first?.content, 'hello');
|
|
73
|
+
assert.equal(second, null);
|
|
74
|
+
});
|
|
75
|
+
test('isNoiseTranscript keeps local stdout blocks visible', () => {
|
|
76
|
+
assert.equal(isNoiseTranscript('<local-command-stdout>hello</local-command-stdout>'), false);
|
|
77
|
+
});
|
|
78
|
+
//# sourceMappingURL=transcriptParser.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcriptParser.test.js","sourceRoot":"","sources":["../../src/claude/transcriptParser.test.ts"],"names":[],"mappings":"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"}
|