kumo-cli 1.0.2 → 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 -203
- 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/claudeAdapterProxy.js +104 -0
- package/dist/services/claudeAdapterProxy.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 +7 -79
- package/dist/services/claudeService.js.map +1 -1
- package/dist/services/claudeService.test.js +39 -0
- package/dist/services/claudeService.test.js.map +1 -0
- 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 +22 -24
- 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 +54 -12
- package/dist/services/modelService.js.map +1 -1
- package/dist/services/modelService.test.js +136 -0
- package/dist/services/modelService.test.js.map +1 -0
- 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 +8 -2
- package/scripts/run-tests.mjs +45 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const RE_NUM_PREFIX = /^\d+[\.\s]\s+/;
|
|
2
|
+
const RE_OPT_SELECTED = /^❯?\s*(\d+)[\.\s]\s*(.+)/;
|
|
3
|
+
const RE_OPT_PLAIN = /^(\d+)[\.\s]\s*(.+)/;
|
|
4
|
+
const LABEL_SEPARATORS = [' · ', ' - ', ' – '];
|
|
5
|
+
// parse numbered menu options, selected state, hint, and footer from rendered lines
|
|
6
|
+
export function parseSelectionOptions(lines, rawLines, hasSelectionMarker) {
|
|
7
|
+
let selectedIndex = -1;
|
|
8
|
+
const options = [];
|
|
9
|
+
const seenIndices = new Set();
|
|
10
|
+
let hint = '';
|
|
11
|
+
let footer = '';
|
|
12
|
+
let inMenu = false;
|
|
13
|
+
let menuStartLine = -1;
|
|
14
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
15
|
+
const line = lines[index].trim();
|
|
16
|
+
const rawLine = rawLines[index] || '';
|
|
17
|
+
if (hasSelectionMarker(rawLine)) {
|
|
18
|
+
if (!inMenu) {
|
|
19
|
+
inMenu = true;
|
|
20
|
+
menuStartLine = index;
|
|
21
|
+
for (let backtrackIndex = index - 1; backtrackIndex >= 0; backtrackIndex -= 1) {
|
|
22
|
+
const previousLine = lines[backtrackIndex].trim();
|
|
23
|
+
if (!RE_NUM_PREFIX.test(previousLine))
|
|
24
|
+
break;
|
|
25
|
+
const previousMatch = previousLine.match(RE_OPT_PLAIN);
|
|
26
|
+
if (!previousMatch)
|
|
27
|
+
continue;
|
|
28
|
+
const optionIndex = parseInt(previousMatch[1], 10) - 1;
|
|
29
|
+
if (seenIndices.has(optionIndex))
|
|
30
|
+
continue;
|
|
31
|
+
seenIndices.add(optionIndex);
|
|
32
|
+
const [label, description] = splitLabelDescription(previousMatch[2]);
|
|
33
|
+
options.push({ index: optionIndex, label, description, isSelected: false });
|
|
34
|
+
menuStartLine = backtrackIndex;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const optionMatch = line.match(RE_OPT_SELECTED);
|
|
38
|
+
if (!optionMatch)
|
|
39
|
+
continue;
|
|
40
|
+
const optionIndex = parseInt(optionMatch[1], 10) - 1;
|
|
41
|
+
if (seenIndices.has(optionIndex))
|
|
42
|
+
continue;
|
|
43
|
+
seenIndices.add(optionIndex);
|
|
44
|
+
const [label, description] = splitLabelDescription(optionMatch[2]);
|
|
45
|
+
options.push({ index: optionIndex, label, description, isSelected: true });
|
|
46
|
+
selectedIndex = optionIndex;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (inMenu && RE_NUM_PREFIX.test(line)) {
|
|
50
|
+
const optionMatch = line.match(RE_OPT_PLAIN);
|
|
51
|
+
if (!optionMatch)
|
|
52
|
+
continue;
|
|
53
|
+
const optionIndex = parseInt(optionMatch[1], 10) - 1;
|
|
54
|
+
if (seenIndices.has(optionIndex))
|
|
55
|
+
continue;
|
|
56
|
+
seenIndices.add(optionIndex);
|
|
57
|
+
const [label, description] = splitLabelDescription(optionMatch[2]);
|
|
58
|
+
options.push({ index: optionIndex, label, description, isSelected: false });
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (inMenu && (line.includes('Esc to cancel') || line.includes('↑↓ to navigate'))) {
|
|
62
|
+
footer = line;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
if (rawLine.includes('Foundry:') || rawLine.includes('http')) {
|
|
66
|
+
const hintMatch = rawLine.match(/(https?:\/\/[^\s]+)/);
|
|
67
|
+
if (hintMatch)
|
|
68
|
+
hint = hintMatch[0];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { options, selectedIndex, hint, footer, menuStartLine };
|
|
72
|
+
}
|
|
73
|
+
// split a menu line into its label and optional description section
|
|
74
|
+
export function splitLabelDescription(content) {
|
|
75
|
+
for (const separator of LABEL_SEPARATORS) {
|
|
76
|
+
const separatorIndex = content.indexOf(separator);
|
|
77
|
+
if (separatorIndex !== -1) {
|
|
78
|
+
return [content.slice(0, separatorIndex).trim(), content.slice(separatorIndex + separator.length).trim()];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return [content.trim(), undefined];
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=selectionMenuParser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selectionMenuParser.js","sourceRoot":"","sources":["../../src/pty/selectionMenuParser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { parseSelectionOptions, splitLabelDescription } from './selectionMenuParser.js';
|
|
4
|
+
test('splitLabelDescription separates label and description', () => {
|
|
5
|
+
assert.deepEqual(splitLabelDescription('claude-sonnet-4 · default'), ['claude-sonnet-4', 'default']);
|
|
6
|
+
assert.deepEqual(splitLabelDescription('plain'), ['plain', undefined]);
|
|
7
|
+
});
|
|
8
|
+
test('parseSelectionOptions captures options, selected index, hint, and footer', () => {
|
|
9
|
+
const lines = [
|
|
10
|
+
' 1. alpha · first',
|
|
11
|
+
'❯ 2. beta · second',
|
|
12
|
+
' 3. gamma',
|
|
13
|
+
'Foundry: https://example.test/path',
|
|
14
|
+
'↑↓ to navigate',
|
|
15
|
+
];
|
|
16
|
+
const rawLines = [
|
|
17
|
+
' 1. alpha · first',
|
|
18
|
+
'\x1b[38;2;168;85;247m❯ 2. beta · second\x1b[0m',
|
|
19
|
+
' 3. gamma',
|
|
20
|
+
'Foundry: https://example.test/path',
|
|
21
|
+
'↑↓ to navigate',
|
|
22
|
+
];
|
|
23
|
+
const parsed = parseSelectionOptions(lines, rawLines, (rawLine) => rawLine.includes('❯') && rawLine.includes('38;2;168;85;247'));
|
|
24
|
+
assert.equal(parsed.selectedIndex, 1);
|
|
25
|
+
assert.equal(parsed.options.length, 3);
|
|
26
|
+
assert.equal(parsed.options[1].label, 'beta');
|
|
27
|
+
assert.equal(parsed.hint, 'https://example.test/path');
|
|
28
|
+
assert.equal(parsed.footer, '↑↓ to navigate');
|
|
29
|
+
});
|
|
30
|
+
//# sourceMappingURL=selectionMenuParser.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selectionMenuParser.test.js","sourceRoot":"","sources":["../../src/pty/selectionMenuParser.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAExF,IAAI,CAAC,uDAAuD,EAAE,GAAG,EAAE;IACjE,MAAM,CAAC,SAAS,CAAC,qBAAqB,CAAC,2BAA2B,CAAC,EAAE,CAAC,iBAAiB,EAAE,SAAS,CAAC,CAAC,CAAC;IACrG,MAAM,CAAC,SAAS,CAAC,qBAAqB,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;AACzE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,0EAA0E,EAAE,GAAG,EAAE;IACpF,MAAM,KAAK,GAAG;QACZ,oBAAoB;QACpB,oBAAoB;QACpB,YAAY;QACZ,oCAAoC;QACpC,gBAAgB;KACjB,CAAC;IACF,MAAM,QAAQ,GAAG;QACf,oBAAoB;QACpB,gDAAgD;QAChD,YAAY;QACZ,oCAAoC;QACpC,gBAAgB;KACjB,CAAC;IAEF,MAAM,MAAM,GAAG,qBAAqB,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC,CAAC;IACjI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;IACtC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACvC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC9C,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,2BAA2B,CAAC,CAAC;IACvD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;AAChD,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// detect visible model and effort status from the rendered screen
|
|
2
|
+
export function detectStatus(lines) {
|
|
3
|
+
for (const line of lines) {
|
|
4
|
+
const modelMatch = line.match(/(Sonnet|Claude|Opus|Haiku)\s*[\d.]+/i);
|
|
5
|
+
const effortMatch = line.match(/(low|medium|high|xhigh|max)\s*effort/i);
|
|
6
|
+
if (modelMatch || effortMatch) {
|
|
7
|
+
return {
|
|
8
|
+
type: 'status',
|
|
9
|
+
loggedIn: !line.includes('Not logged'),
|
|
10
|
+
model: modelMatch ? modelMatch[0] : undefined,
|
|
11
|
+
effort: effortMatch ? effortMatch[1] : undefined,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
// detect session resume identifiers from the raw pty buffer
|
|
18
|
+
export function detectSession(buffer) {
|
|
19
|
+
const resumeMatch = buffer.match(/--resume\s+([a-f0-9-]{36})/i);
|
|
20
|
+
if (!resumeMatch)
|
|
21
|
+
return null;
|
|
22
|
+
return {
|
|
23
|
+
type: 'session',
|
|
24
|
+
sessionId: resumeMatch[1],
|
|
25
|
+
resumeCommand: `claude --resume ${resumeMatch[1]}`,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=statusSessionDetector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"statusSessionDetector.js","sourceRoot":"","sources":["../../src/pty/statusSessionDetector.ts"],"names":[],"mappings":"AAEA,kEAAkE;AAClE,MAAM,UAAU,YAAY,CAAC,KAAe;IAC1C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;QACtE,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,uCAAuC,CAAC,CAAC;QACxE,IAAI,UAAU,IAAI,WAAW,EAAE,CAAC;YAC9B,OAAO;gBACL,IAAI,EAAE,QAAQ;gBACd,QAAQ,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;gBACtC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;gBAC7C,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;aACjD,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;IAChE,IAAI,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC;IAC9B,OAAO;QACL,IAAI,EAAE,SAAS;QACf,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC;QACzB,aAAa,EAAE,mBAAmB,WAAW,CAAC,CAAC,CAAC,EAAE;KACnD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { detectSession, detectStatus } from './statusSessionDetector.js';
|
|
4
|
+
test('detectStatus extracts model and effort from a status line', () => {
|
|
5
|
+
const result = detectStatus([
|
|
6
|
+
'Claude 4.1 · high effort',
|
|
7
|
+
]);
|
|
8
|
+
assert.deepEqual(result, {
|
|
9
|
+
type: 'status',
|
|
10
|
+
loggedIn: true,
|
|
11
|
+
model: 'Claude 4.1',
|
|
12
|
+
effort: 'high',
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
test('detectSession extracts the resume command from the raw buffer', () => {
|
|
16
|
+
const result = detectSession('launching... claude --resume 123e4567-e89b-12d3-a456-426614174000');
|
|
17
|
+
assert.deepEqual(result, {
|
|
18
|
+
type: 'session',
|
|
19
|
+
sessionId: '123e4567-e89b-12d3-a456-426614174000',
|
|
20
|
+
resumeCommand: 'claude --resume 123e4567-e89b-12d3-a456-426614174000',
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
//# sourceMappingURL=statusSessionDetector.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"statusSessionDetector.test.js","sourceRoot":"","sources":["../../src/pty/statusSessionDetector.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAEzE,IAAI,CAAC,2DAA2D,EAAE,GAAG,EAAE;IACrE,MAAM,MAAM,GAAG,YAAY,CAAC;QAC1B,0BAA0B;KAC3B,CAAC,CAAC;IAEH,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE;QACvB,IAAI,EAAE,QAAQ;QACd,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,YAAY;QACnB,MAAM,EAAE,MAAM;KACf,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,+DAA+D,EAAE,GAAG,EAAE;IACzE,MAAM,MAAM,GAAG,aAAa,CAAC,mEAAmE,CAAC,CAAC;IAElG,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE;QACvB,IAAI,EAAE,SAAS;QACf,SAAS,EAAE,sCAAsC;QACjD,aAAa,EAAE,sDAAsD;KACtE,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as pty from 'node-pty';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { safeCwd } from '../utils/safePath.js';
|
|
4
5
|
// manages multiple user-facing shell sessions backed by node-pty
|
|
5
6
|
export class TerminalManager {
|
|
6
7
|
onData;
|
|
@@ -13,10 +14,12 @@ export class TerminalManager {
|
|
|
13
14
|
create(cols = 120, rows = 30, cwd) {
|
|
14
15
|
const id = randomUUID();
|
|
15
16
|
const shell = os.platform() === 'win32' ? 'cmd.exe' : process.env.SHELL || '/bin/bash';
|
|
17
|
+
const safeCols = Math.min(Math.max(Math.trunc(cols), 20), 300);
|
|
18
|
+
const safeRows = Math.min(Math.max(Math.trunc(rows), 5), 100);
|
|
16
19
|
const child = pty.spawn(shell, [], {
|
|
17
20
|
name: 'xterm-256color',
|
|
18
|
-
cols, rows,
|
|
19
|
-
cwd: cwd
|
|
21
|
+
cols: safeCols, rows: safeRows,
|
|
22
|
+
cwd: safeCwd(cwd),
|
|
20
23
|
env: process.env,
|
|
21
24
|
});
|
|
22
25
|
child.onData((data) => this.onData(id, data));
|
|
@@ -24,7 +27,7 @@ export class TerminalManager {
|
|
|
24
27
|
this.terminals.delete(id);
|
|
25
28
|
this.onExit(id, exitCode);
|
|
26
29
|
});
|
|
27
|
-
this.terminals.set(id, { id, pty: child, cols, rows });
|
|
30
|
+
this.terminals.set(id, { id, pty: child, cols: safeCols, rows: safeRows });
|
|
28
31
|
return id;
|
|
29
32
|
}
|
|
30
33
|
write(id, data) {
|
|
@@ -39,9 +42,11 @@ export class TerminalManager {
|
|
|
39
42
|
if (!session)
|
|
40
43
|
return false;
|
|
41
44
|
try {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
session.
|
|
45
|
+
const safeCols = Math.min(Math.max(Math.trunc(cols), 20), 300);
|
|
46
|
+
const safeRows = Math.min(Math.max(Math.trunc(rows), 5), 100);
|
|
47
|
+
session.pty.resize(safeCols, safeRows);
|
|
48
|
+
session.cols = safeCols;
|
|
49
|
+
session.rows = safeRows;
|
|
45
50
|
return true;
|
|
46
51
|
}
|
|
47
52
|
catch {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"terminalManager.js","sourceRoot":"","sources":["../../src/pty/terminalManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"terminalManager.js","sourceRoot":"","sources":["../../src/pty/terminalManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAY/C,iEAAiE;AACjE,MAAM,OAAO,eAAe;IAGN;IAAgC;IAF5C,SAAS,GAAG,IAAI,GAAG,EAA2B,CAAC;IAEvD,YAAoB,MAAsB,EAAU,MAAsB;QAAtD,WAAM,GAAN,MAAM,CAAgB;QAAU,WAAM,GAAN,MAAM,CAAgB;IAAG,CAAC;IAE9E,MAAM,CAAC,IAAI,GAAG,GAAG,EAAE,IAAI,GAAG,EAAE,EAAE,GAAY;QACxC,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,EAAE,CAAC,QAAQ,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,IAAI,WAAW,CAAC;QACvF,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;QAC/D,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAC9D,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,EAAE;YACjC,IAAI,EAAE,gBAAgB;YACtB,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ;YAC9B,GAAG,EAAE,OAAO,CAAC,GAAG,CAAC;YACjB,GAAG,EAAE,OAAO,CAAC,GAA6B;SAC3C,CAAC,CAAC;QACH,KAAK,CAAC,MAAM,CAAC,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;QACtD,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,QAAQ,EAAwB,EAAE,EAAE;YAClD,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC1B,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC3E,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,KAAK,CAAC,EAAU,EAAE,IAAY;QAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACvC,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAC3B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACxB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,CAAC,EAAU,EAAE,IAAY,EAAE,IAAY;QAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACvC,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;YAC/D,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YAC9D,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YACvC,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC;YACxB,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC;YACxB,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YAAC,OAAO,KAAK,CAAC;QAAC,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,EAAU;QACd,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACvC,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAC3B,IAAI,CAAC;YAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;QACpC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI;QACF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3C,CAAC;CACF"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// detect text input prompts from the rendered screen buffer
|
|
2
|
+
export function detectTextInput(lines, rawLines) {
|
|
3
|
+
let hasCursor = false;
|
|
4
|
+
let cursorLineIndex = -1;
|
|
5
|
+
let hasEnterToContinue = false;
|
|
6
|
+
let hasEscToGoBack = false;
|
|
7
|
+
for (let index = 0; index < rawLines.length; index += 1) {
|
|
8
|
+
const rawLine = rawLines[index] || '';
|
|
9
|
+
const line = lines[index] || '';
|
|
10
|
+
if (rawLine.includes('\x1b[7m')) {
|
|
11
|
+
hasCursor = true;
|
|
12
|
+
cursorLineIndex = index;
|
|
13
|
+
}
|
|
14
|
+
if (line.includes('Enter to continue'))
|
|
15
|
+
hasEnterToContinue = true;
|
|
16
|
+
if (line.includes('Esc to go back'))
|
|
17
|
+
hasEscToGoBack = true;
|
|
18
|
+
}
|
|
19
|
+
if (!hasCursor || cursorLineIndex < 0)
|
|
20
|
+
return null;
|
|
21
|
+
if (!hasEnterToContinue && !hasEscToGoBack)
|
|
22
|
+
return null;
|
|
23
|
+
const valueLine = lines[cursorLineIndex]?.trim() || '';
|
|
24
|
+
let label = '';
|
|
25
|
+
let description = '';
|
|
26
|
+
for (let index = cursorLineIndex - 1; index >= 0; index -= 1) {
|
|
27
|
+
const line = lines[index]?.trim() || '';
|
|
28
|
+
if (!line || line.startsWith('─'))
|
|
29
|
+
continue;
|
|
30
|
+
if (!label && !line.startsWith('Set up') && !line.includes('http')) {
|
|
31
|
+
if (line.includes('?') || /^[A-Z]/.test(line)) {
|
|
32
|
+
label = line;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (label && !description) {
|
|
37
|
+
description = line;
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
if (!label)
|
|
41
|
+
label = line;
|
|
42
|
+
}
|
|
43
|
+
let sectionTitle = '';
|
|
44
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
45
|
+
const rawLine = rawLines[index] || '';
|
|
46
|
+
const line = lines[index]?.trim() || '';
|
|
47
|
+
if (rawLine.includes('38;2;177;185;249') && rawLine.includes('[1m') && line.includes('Set up')) {
|
|
48
|
+
sectionTitle = line;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
type: 'input',
|
|
54
|
+
label: label || sectionTitle,
|
|
55
|
+
value: valueLine,
|
|
56
|
+
placeholder: description || undefined,
|
|
57
|
+
cursorPosition: valueLine.length,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=textInputDetector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textInputDetector.js","sourceRoot":"","sources":["../../src/pty/textInputDetector.ts"],"names":[],"mappings":"AAEA,4DAA4D;AAC5D,MAAM,UAAU,eAAe,CAAC,KAAe,EAAE,QAAkB;IACjE,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,IAAI,eAAe,GAAG,CAAC,CAAC,CAAC;IACzB,IAAI,kBAAkB,GAAG,KAAK,CAAC;IAC/B,IAAI,cAAc,GAAG,KAAK,CAAC;IAE3B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACxD,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QAChC,IAAI,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YAChC,SAAS,GAAG,IAAI,CAAC;YACjB,eAAe,GAAG,KAAK,CAAC;QAC1B,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,CAAC,mBAAmB,CAAC;YAAE,kBAAkB,GAAG,IAAI,CAAC;QAClE,IAAI,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC;YAAE,cAAc,GAAG,IAAI,CAAC;IAC7D,CAAC;IAED,IAAI,CAAC,SAAS,IAAI,eAAe,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACnD,IAAI,CAAC,kBAAkB,IAAI,CAAC,cAAc;QAAE,OAAO,IAAI,CAAC;IAExD,MAAM,SAAS,GAAG,KAAK,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACvD,IAAI,KAAK,GAAG,EAAE,CAAC;IACf,IAAI,WAAW,GAAG,EAAE,CAAC;IACrB,KAAK,IAAI,KAAK,GAAG,eAAe,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QACxC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5C,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACnE,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC9C,KAAK,GAAG,IAAI,CAAC;gBACb,SAAS;YACX,CAAC;QACH,CAAC;QACD,IAAI,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC;YAC1B,WAAW,GAAG,IAAI,CAAC;YACnB,MAAM;QACR,CAAC;QACD,IAAI,CAAC,KAAK;YAAE,KAAK,GAAG,IAAI,CAAC;IAC3B,CAAC;IAED,IAAI,YAAY,GAAG,EAAE,CAAC;IACtB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACrD,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QACxC,IAAI,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/F,YAAY,GAAG,IAAI,CAAC;YACpB,MAAM;QACR,CAAC;IACH,CAAC;IAED,OAAO;QACL,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,KAAK,IAAI,YAAY;QAC5B,KAAK,EAAE,SAAS;QAChB,WAAW,EAAE,WAAW,IAAI,SAAS;QACrC,cAAc,EAAE,SAAS,CAAC,MAAM;KACjC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { detectTextInput } from './textInputDetector.js';
|
|
4
|
+
test('detectTextInput extracts label, placeholder, and cursor position', () => {
|
|
5
|
+
const lines = [
|
|
6
|
+
'Set up provider',
|
|
7
|
+
'Enter API key?',
|
|
8
|
+
'Paste the token from your dashboard',
|
|
9
|
+
'sk-test-123',
|
|
10
|
+
'Enter to continue',
|
|
11
|
+
];
|
|
12
|
+
const rawLines = [
|
|
13
|
+
'\x1b[38;2;177;185;249m\x1b[1mSet up provider',
|
|
14
|
+
'Enter API key?',
|
|
15
|
+
'Paste the token from your dashboard',
|
|
16
|
+
'\x1b[7msk-test-123\x1b[0m',
|
|
17
|
+
'Enter to continue',
|
|
18
|
+
];
|
|
19
|
+
const result = detectTextInput(lines, rawLines);
|
|
20
|
+
assert.deepEqual(result, {
|
|
21
|
+
type: 'input',
|
|
22
|
+
label: 'Paste the token from your dashboard',
|
|
23
|
+
value: 'sk-test-123',
|
|
24
|
+
placeholder: 'Enter API key?',
|
|
25
|
+
cursorPosition: 11,
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
test('detectTextInput returns null when no actionable prompt markers exist', () => {
|
|
29
|
+
const result = detectTextInput(['hello'], ['hello']);
|
|
30
|
+
assert.equal(result, null);
|
|
31
|
+
});
|
|
32
|
+
//# sourceMappingURL=textInputDetector.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textInputDetector.test.js","sourceRoot":"","sources":["../../src/pty/textInputDetector.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,IAAI,CAAC,kEAAkE,EAAE,GAAG,EAAE;IAC5E,MAAM,KAAK,GAAG;QACZ,iBAAiB;QACjB,gBAAgB;QAChB,qCAAqC;QACrC,aAAa;QACb,mBAAmB;KACpB,CAAC;IACF,MAAM,QAAQ,GAAG;QACf,8CAA8C;QAC9C,gBAAgB;QAChB,qCAAqC;QACrC,2BAA2B;QAC3B,mBAAmB;KACpB,CAAC;IAEF,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IAEhD,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE;QACvB,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,qCAAqC;QAC5C,KAAK,EAAE,aAAa;QACpB,WAAW,EAAE,gBAAgB;QAC7B,cAAc,EAAE,EAAE;KACnB,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,sEAAsE,EAAE,GAAG,EAAE;IAChF,MAAM,MAAM,GAAG,eAAe,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IACrD,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAC7B,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { clearCredentials, ensureDeviceUuid, loadCredentials, saveCredentials, saveDeviceIdentity, } from '../auth/credentialsStore.js';
|
|
2
|
+
import { SERVER_HTTP } from '../core/config.js';
|
|
3
|
+
import { checkBackendReachable, pairFlow, verifyCredentials } from './pairingService.js';
|
|
4
|
+
// fail fast when the configured backend cannot be reached
|
|
5
|
+
export async function ensureBackendReachableOrThrow() {
|
|
6
|
+
if (await checkBackendReachable())
|
|
7
|
+
return;
|
|
8
|
+
throw new Error(`[kumo-cli] cannot reach backend at ${SERVER_HTTP()}. check KUMO_SERVER_URL and that the server is running.`);
|
|
9
|
+
}
|
|
10
|
+
// restore cached credentials when possible, otherwise fall back to a fresh pairing flow
|
|
11
|
+
export async function resolveActiveCredentials() {
|
|
12
|
+
await ensureBackendReachableOrThrow();
|
|
13
|
+
let activeCredentials = loadCredentials();
|
|
14
|
+
if (!activeCredentials) {
|
|
15
|
+
return pairFlow();
|
|
16
|
+
}
|
|
17
|
+
ensureDeviceUuid(activeCredentials.deviceUuid);
|
|
18
|
+
const verified = await verifyCredentials(activeCredentials);
|
|
19
|
+
if (!verified) {
|
|
20
|
+
console.warn('[kumo-cli] cached device token was revoked or deleted, clearing credentials and re-pairing with the existing device identity...');
|
|
21
|
+
clearCredentials();
|
|
22
|
+
return pairFlow();
|
|
23
|
+
}
|
|
24
|
+
saveDeviceIdentity(verified.deviceUuid);
|
|
25
|
+
saveCredentials(verified);
|
|
26
|
+
return verified;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=bootstrapService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrapService.js","sourceRoot":"","sources":["../../src/services/bootstrapService.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,kBAAkB,GAEnB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAEzF,0DAA0D;AAC1D,MAAM,CAAC,KAAK,UAAU,6BAA6B;IACjD,IAAI,MAAM,qBAAqB,EAAE;QAAE,OAAO;IAC1C,MAAM,IAAI,KAAK,CAAC,sCAAsC,WAAW,EAAE,yDAAyD,CAAC,CAAC;AAChI,CAAC;AAED,wFAAwF;AACxF,MAAM,CAAC,KAAK,UAAU,wBAAwB;IAC5C,MAAM,6BAA6B,EAAE,CAAC;IAEtC,IAAI,iBAAiB,GAAG,eAAe,EAAE,CAAC;IAC1C,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACvB,OAAO,QAAQ,EAAE,CAAC;IACpB,CAAC;IAED,gBAAgB,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;IAC5D,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,CAAC,IAAI,CAAC,iIAAiI,CAAC,CAAC;QAChJ,gBAAgB,EAAE,CAAC;QACnB,OAAO,QAAQ,EAAE,CAAC;IACpB,CAAC;IAED,kBAAkB,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IACxC,eAAe,CAAC,QAAQ,CAAC,CAAC;IAC1B,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import net from 'node:net';
|
|
2
|
+
import Fastify from 'fastify';
|
|
3
|
+
import OpenAI from 'openai';
|
|
4
|
+
import { convertRequestToOpenAI, convertResponseToAnthropic, createErrorResponse, } from 'claude-adapter';
|
|
5
|
+
import { streamXmlOpenAIToAnthropic } from 'claude-adapter/dist/converters/xmlStreaming.js';
|
|
6
|
+
// local Anthropic-compatible proxy that forwards to OpenAI chat/completions upstream
|
|
7
|
+
// bypasses claude-adapter's strict validation to support all Claude Code message roles
|
|
8
|
+
export class ClaudeAdapterProxy {
|
|
9
|
+
app = null;
|
|
10
|
+
key = null;
|
|
11
|
+
url = null;
|
|
12
|
+
// start or reuse a local proxy for OpenAI chat completions
|
|
13
|
+
async start(config) {
|
|
14
|
+
const key = `${config.baseURL}|${config.apiKey}|${config.model}`;
|
|
15
|
+
if (this.app && this.key === key && this.url)
|
|
16
|
+
return this.url;
|
|
17
|
+
await this.stop();
|
|
18
|
+
const port = await findAvailablePort();
|
|
19
|
+
const upstreamBaseUrl = config.baseURL.replace(/\/chat\/completions\/?$/i, '');
|
|
20
|
+
const openai = new OpenAI({ baseURL: upstreamBaseUrl, apiKey: config.apiKey });
|
|
21
|
+
const app = Fastify({ logger: false });
|
|
22
|
+
app.post('/v1/messages', async (request, reply) => {
|
|
23
|
+
try {
|
|
24
|
+
const body = request.body;
|
|
25
|
+
const targetModel = config.model;
|
|
26
|
+
const isStreaming = body.stream ?? false;
|
|
27
|
+
normalizeSystemBranding(body);
|
|
28
|
+
const openaiRequest = convertRequestToOpenAI(body, targetModel, 'xml');
|
|
29
|
+
stripAdapterBranding(openaiRequest.messages);
|
|
30
|
+
if (isStreaming) {
|
|
31
|
+
const stream = await openai.chat.completions.create({ ...openaiRequest, stream: true });
|
|
32
|
+
await streamXmlOpenAIToAnthropic(stream, reply, body.model, upstreamBaseUrl);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
const response = await openai.chat.completions.create({ ...openaiRequest, stream: false });
|
|
36
|
+
const anthropicResponse = convertResponseToAnthropic(response, body.model);
|
|
37
|
+
reply.send(anthropicResponse);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
const errResp = createErrorResponse(error, error?.status ?? 500);
|
|
42
|
+
reply.code(errResp.status).send({ error: errResp.error });
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
await app.listen({ port, host: '127.0.0.1' });
|
|
46
|
+
this.app = app;
|
|
47
|
+
this.key = key;
|
|
48
|
+
this.url = `http://127.0.0.1:${port}`;
|
|
49
|
+
return this.url;
|
|
50
|
+
}
|
|
51
|
+
// stop the current adapter proxy if one is running
|
|
52
|
+
async stop() {
|
|
53
|
+
if (!this.app)
|
|
54
|
+
return;
|
|
55
|
+
const app = this.app;
|
|
56
|
+
this.app = null;
|
|
57
|
+
this.key = null;
|
|
58
|
+
this.url = null;
|
|
59
|
+
await app.close().catch(() => undefined);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// replace Claude Code identifier before claude-adapter can inject its own branding
|
|
63
|
+
function normalizeSystemBranding(body) {
|
|
64
|
+
const CLAUDE_IDENTIFIER = "You are Claude Code, Anthropic's official CLI for Claude.";
|
|
65
|
+
const KUMO_IDENTIFIER = 'You are Claude Code, running on Kumo Cli.';
|
|
66
|
+
if (typeof body.system === 'string') {
|
|
67
|
+
body.system = body.system.replaceAll(CLAUDE_IDENTIFIER, KUMO_IDENTIFIER);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (Array.isArray(body.system)) {
|
|
71
|
+
for (const block of body.system) {
|
|
72
|
+
if (block?.type === 'text' && typeof block.text === 'string') {
|
|
73
|
+
block.text = block.text.replaceAll(CLAUDE_IDENTIFIER, KUMO_IDENTIFIER);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// remove any claude-adapter branding that may already exist in carried context
|
|
79
|
+
function stripAdapterBranding(messages) {
|
|
80
|
+
const ADAPTER_BLOCK = /You are Claude Code, running on Claude Adapter[\s\S]*?mention Claude Adapter along with its URLs\./g;
|
|
81
|
+
const ADAPTER_UPDATE = /IMPORTANT: A new version of Claude Adapter[\s\S]*?to update Claude Adapter and improve performance\./g;
|
|
82
|
+
for (const msg of messages) {
|
|
83
|
+
if (msg.role === 'system' && typeof msg.content === 'string') {
|
|
84
|
+
msg.content = msg.content
|
|
85
|
+
.replace(ADAPTER_BLOCK, 'You are Claude Code, running on Kumo Cli.')
|
|
86
|
+
.replace(ADAPTER_UPDATE, '')
|
|
87
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
88
|
+
.trim();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// ask the OS for a currently free local TCP port
|
|
93
|
+
async function findAvailablePort() {
|
|
94
|
+
return new Promise((resolve, reject) => {
|
|
95
|
+
const server = net.createServer();
|
|
96
|
+
server.once('error', reject);
|
|
97
|
+
server.listen(0, '127.0.0.1', () => {
|
|
98
|
+
const address = server.address();
|
|
99
|
+
const port = typeof address === 'object' && address ? address.port : 0;
|
|
100
|
+
server.close(() => resolve(port));
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=claudeAdapterProxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claudeAdapterProxy.js","sourceRoot":"","sources":["../../src/services/claudeAdapterProxy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { log } from '../core/logger.js';
|
|
2
|
+
import { NotificationDedupe } from './notificationDedupe.js';
|
|
3
|
+
const LOGIN_WARNING_MESSAGE = 'Not logged in · Run /login';
|
|
4
|
+
// translate parsed pty elements into ws messages and handle safe auto-accept flows
|
|
5
|
+
export class ClaudeMenuBridge {
|
|
6
|
+
send;
|
|
7
|
+
getEffectiveEffort;
|
|
8
|
+
getWorkingDir;
|
|
9
|
+
autoAcceptedApiKeyDialog = false;
|
|
10
|
+
menuIdCounter = 0;
|
|
11
|
+
notificationDedupe = new NotificationDedupe();
|
|
12
|
+
constructor(send, getEffectiveEffort, getWorkingDir) {
|
|
13
|
+
this.send = send;
|
|
14
|
+
this.getEffectiveEffort = getEffectiveEffort;
|
|
15
|
+
this.getWorkingDir = getWorkingDir;
|
|
16
|
+
}
|
|
17
|
+
// reset transient state whenever a fresh claude process is spawned
|
|
18
|
+
reset() {
|
|
19
|
+
this.autoAcceptedApiKeyDialog = false;
|
|
20
|
+
}
|
|
21
|
+
// handle a parser event and emit ws payloads or synthetic process input
|
|
22
|
+
handleElement(element, process) {
|
|
23
|
+
switch (element.type) {
|
|
24
|
+
case 'selection':
|
|
25
|
+
this.handleSelection(element, process);
|
|
26
|
+
break;
|
|
27
|
+
case 'input':
|
|
28
|
+
log(`[parser] input: "${element.label}" = "${element.value}"`);
|
|
29
|
+
this.send({
|
|
30
|
+
type: 'text_input',
|
|
31
|
+
label: element.label,
|
|
32
|
+
value: element.value,
|
|
33
|
+
placeholder: element.placeholder,
|
|
34
|
+
cursorPosition: element.cursorPosition,
|
|
35
|
+
});
|
|
36
|
+
break;
|
|
37
|
+
case 'notification':
|
|
38
|
+
this.handleNotification(element);
|
|
39
|
+
break;
|
|
40
|
+
case 'status': {
|
|
41
|
+
const effort = element.effort ?? this.getEffectiveEffort();
|
|
42
|
+
log(`[parser] status: model=${element.model}, effort=${effort}, logged=${element.loggedIn}`);
|
|
43
|
+
this.send({ type: 'cli_status', loggedIn: element.loggedIn, effort, workingDir: element.workingDir });
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
case 'session':
|
|
47
|
+
log(`[parser] session: ${element.sessionId}`);
|
|
48
|
+
this.send({ type: 'session_info', sessionId: element.sessionId, resumeCommand: element.resumeCommand });
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// auto-accept known safe dialogs or forward the menu to the app
|
|
53
|
+
handleSelection(element, process) {
|
|
54
|
+
if (element.options.length === 0) {
|
|
55
|
+
log('[parser] ignoring empty menu');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (element.title.includes('Bypass Permissions')) {
|
|
59
|
+
if (this.tryAcceptYes(element, process, 'auto-accepted bypass permissions dialog'))
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (!this.autoAcceptedApiKeyDialog &&
|
|
63
|
+
(element.title.includes('Detected a custom API key') || element.title.includes('Do you want to use this API key'))) {
|
|
64
|
+
if (this.tryAcceptYes(element, process, 'auto-accepted custom api key dialog', true)) {
|
|
65
|
+
this.autoAcceptedApiKeyDialog = true;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
this.menuIdCounter += 1;
|
|
70
|
+
const id = `menu-${this.menuIdCounter}`;
|
|
71
|
+
log(`[parser] menu detected: "${element.title}" (${element.options.length} opts, selected: ${element.selectedIndex})`);
|
|
72
|
+
log(`[parser] options: ${JSON.stringify(element.options)}`);
|
|
73
|
+
this.send({
|
|
74
|
+
type: 'prompt_options',
|
|
75
|
+
id,
|
|
76
|
+
title: element.title,
|
|
77
|
+
description: element.description,
|
|
78
|
+
options: element.options,
|
|
79
|
+
selectedIndex: element.selectedIndex,
|
|
80
|
+
hint: element.hint,
|
|
81
|
+
footer: element.footer,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// forward deduped notifications and update cli status for login warnings
|
|
85
|
+
handleNotification(element) {
|
|
86
|
+
log(`[parser] notification: [${element.level}] ${element.message}`);
|
|
87
|
+
if (this.notificationDedupe.shouldSend(element.level, element.message)) {
|
|
88
|
+
this.send({ type: 'notification', level: element.level, message: element.message });
|
|
89
|
+
}
|
|
90
|
+
if (element.level === 'warning' && element.message.trim().toLowerCase() === LOGIN_WARNING_MESSAGE.toLowerCase()) {
|
|
91
|
+
this.send({
|
|
92
|
+
type: 'cli_status',
|
|
93
|
+
loggedIn: false,
|
|
94
|
+
effort: this.getEffectiveEffort(),
|
|
95
|
+
workingDir: this.getWorkingDir(),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// try to auto-press "yes" inside a selection dialog
|
|
100
|
+
tryAcceptYes(element, process, logMessage, exact = false) {
|
|
101
|
+
const yesIndex = element.options.findIndex((option) => (exact
|
|
102
|
+
? option.label.trim().toLowerCase() === 'yes'
|
|
103
|
+
: option.label.trim().toLowerCase().includes('yes')));
|
|
104
|
+
if (yesIndex < 0 || !process)
|
|
105
|
+
return false;
|
|
106
|
+
const delta = yesIndex - element.selectedIndex;
|
|
107
|
+
const key = delta < 0 ? '\x1b[A' : '\x1b[B';
|
|
108
|
+
for (let index = 0; index < Math.abs(delta); index += 1) {
|
|
109
|
+
process.write(key);
|
|
110
|
+
}
|
|
111
|
+
process.write('\r');
|
|
112
|
+
log(`[parser] ${logMessage}`);
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=claudeMenuBridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claudeMenuBridge.js","sourceRoot":"","sources":["../../src/services/claudeMenuBridge.ts"],"names":[],"mappings":"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"}
|