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.
Files changed (135) hide show
  1. package/README.md +13 -5
  2. package/dist/auth/credentialsStore.js +61 -14
  3. package/dist/auth/credentialsStore.js.map +1 -1
  4. package/dist/auth/credentialsStore.test.js +60 -0
  5. package/dist/auth/credentialsStore.test.js.map +1 -0
  6. package/dist/claude/fileHistory.js +35 -0
  7. package/dist/claude/fileHistory.js.map +1 -0
  8. package/dist/claude/sessionScanner.js +7 -95
  9. package/dist/claude/sessionScanner.js.map +1 -1
  10. package/dist/claude/sessionScanner.test.js +24 -0
  11. package/dist/claude/sessionScanner.test.js.map +1 -0
  12. package/dist/claude/transcriptParser.js +83 -0
  13. package/dist/claude/transcriptParser.js.map +1 -0
  14. package/dist/claude/transcriptParser.test.js +78 -0
  15. package/dist/claude/transcriptParser.test.js.map +1 -0
  16. package/dist/commands/agentPrompt.js +18 -0
  17. package/dist/commands/agentPrompt.js.map +1 -0
  18. package/dist/commands/cliArgs.js +56 -0
  19. package/dist/commands/cliArgs.js.map +1 -0
  20. package/dist/commands/cliArgs.test.js +25 -0
  21. package/dist/commands/cliArgs.test.js.map +1 -0
  22. package/dist/core/logger.js +7 -2
  23. package/dist/core/logger.js.map +1 -1
  24. package/dist/core/projectFileWatcher.js +56 -0
  25. package/dist/core/projectFileWatcher.js.map +1 -0
  26. package/dist/handlers/fileHandlerPayloads.js +72 -0
  27. package/dist/handlers/fileHandlerPayloads.js.map +1 -0
  28. package/dist/handlers/fileHandlerPayloads.test.js +55 -0
  29. package/dist/handlers/fileHandlerPayloads.test.js.map +1 -0
  30. package/dist/handlers/fileHandlers.js +74 -0
  31. package/dist/handlers/fileHandlers.js.map +1 -0
  32. package/dist/handlers/messageRouter.js +8 -203
  33. package/dist/handlers/messageRouter.js.map +1 -1
  34. package/dist/handlers/router.types.js +2 -0
  35. package/dist/handlers/router.types.js.map +1 -0
  36. package/dist/handlers/runtimeHandlers.js +55 -0
  37. package/dist/handlers/runtimeHandlers.js.map +1 -0
  38. package/dist/handlers/sessionHandlers.js +68 -0
  39. package/dist/handlers/sessionHandlers.js.map +1 -0
  40. package/dist/handlers/terminalHandlers.js +40 -0
  41. package/dist/handlers/terminalHandlers.js.map +1 -0
  42. package/dist/index.js +59 -145
  43. package/dist/index.js.map +1 -1
  44. package/dist/pty/notificationDetector.js +29 -0
  45. package/dist/pty/notificationDetector.js.map +1 -0
  46. package/dist/pty/notificationDetector.test.js +24 -0
  47. package/dist/pty/notificationDetector.test.js.map +1 -0
  48. package/dist/pty/ptyMenuParser.js +11 -277
  49. package/dist/pty/ptyMenuParser.js.map +1 -1
  50. package/dist/pty/ptyMenuParser.test.js +26 -0
  51. package/dist/pty/ptyMenuParser.test.js.map +1 -0
  52. package/dist/pty/selectionMenuDetector.js +49 -0
  53. package/dist/pty/selectionMenuDetector.js.map +1 -0
  54. package/dist/pty/selectionMenuDetector.test.js +62 -0
  55. package/dist/pty/selectionMenuDetector.test.js.map +1 -0
  56. package/dist/pty/selectionMenuMeta.js +54 -0
  57. package/dist/pty/selectionMenuMeta.js.map +1 -0
  58. package/dist/pty/selectionMenuMeta.test.js +26 -0
  59. package/dist/pty/selectionMenuMeta.test.js.map +1 -0
  60. package/dist/pty/selectionMenuParser.js +83 -0
  61. package/dist/pty/selectionMenuParser.js.map +1 -0
  62. package/dist/pty/selectionMenuParser.test.js +30 -0
  63. package/dist/pty/selectionMenuParser.test.js.map +1 -0
  64. package/dist/pty/statusSessionDetector.js +28 -0
  65. package/dist/pty/statusSessionDetector.js.map +1 -0
  66. package/dist/pty/statusSessionDetector.test.js +23 -0
  67. package/dist/pty/statusSessionDetector.test.js.map +1 -0
  68. package/dist/pty/terminalManager.js +11 -6
  69. package/dist/pty/terminalManager.js.map +1 -1
  70. package/dist/pty/textInputDetector.js +60 -0
  71. package/dist/pty/textInputDetector.js.map +1 -0
  72. package/dist/pty/textInputDetector.test.js +32 -0
  73. package/dist/pty/textInputDetector.test.js.map +1 -0
  74. package/dist/services/bootstrapService.js +28 -0
  75. package/dist/services/bootstrapService.js.map +1 -0
  76. package/dist/services/claudeAdapterProxy.js +104 -0
  77. package/dist/services/claudeAdapterProxy.js.map +1 -0
  78. package/dist/services/claudeMenuBridge.js +116 -0
  79. package/dist/services/claudeMenuBridge.js.map +1 -0
  80. package/dist/services/claudeMenuBridge.test.js +90 -0
  81. package/dist/services/claudeMenuBridge.test.js.map +1 -0
  82. package/dist/services/claudeService.js +7 -79
  83. package/dist/services/claudeService.js.map +1 -1
  84. package/dist/services/claudeService.test.js +39 -0
  85. package/dist/services/claudeService.test.js.map +1 -0
  86. package/dist/services/cliRuntime.js +49 -0
  87. package/dist/services/cliRuntime.js.map +1 -0
  88. package/dist/services/cliRuntime.test.js +55 -0
  89. package/dist/services/cliRuntime.test.js.map +1 -0
  90. package/dist/services/fileService.js +22 -24
  91. package/dist/services/fileService.js.map +1 -1
  92. package/dist/services/modelAdapter.test.js +47 -0
  93. package/dist/services/modelAdapter.test.js.map +1 -0
  94. package/dist/services/modelArgs.test.js +64 -0
  95. package/dist/services/modelArgs.test.js.map +1 -0
  96. package/dist/services/modelBaseUrl.test.js +32 -0
  97. package/dist/services/modelBaseUrl.test.js.map +1 -0
  98. package/dist/services/modelService.js +54 -12
  99. package/dist/services/modelService.js.map +1 -1
  100. package/dist/services/modelService.test.js +136 -0
  101. package/dist/services/modelService.test.js.map +1 -0
  102. package/dist/services/notificationDedupe.js +23 -0
  103. package/dist/services/notificationDedupe.js.map +1 -0
  104. package/dist/services/notificationDedupe.test.js +20 -0
  105. package/dist/services/notificationDedupe.test.js.map +1 -0
  106. package/dist/services/pairingService.js +17 -8
  107. package/dist/services/pairingService.js.map +1 -1
  108. package/dist/services/sessionListService.js +19 -0
  109. package/dist/services/sessionListService.js.map +1 -0
  110. package/dist/services/sessionListService.test.js +22 -0
  111. package/dist/services/sessionListService.test.js.map +1 -0
  112. package/dist/services/sessionService.js +8 -45
  113. package/dist/services/sessionService.js.map +1 -1
  114. package/dist/services/tunnelHelpers.js +69 -0
  115. package/dist/services/tunnelHelpers.js.map +1 -0
  116. package/dist/services/tunnelHelpers.test.js +42 -0
  117. package/dist/services/tunnelHelpers.test.js.map +1 -0
  118. package/dist/services/tunnelService.js +69 -25
  119. package/dist/services/tunnelService.js.map +1 -1
  120. package/dist/services/tunnelService.test.js +91 -0
  121. package/dist/services/tunnelService.test.js.map +1 -0
  122. package/dist/transport/webrtcTransport.js +145 -0
  123. package/dist/transport/webrtcTransport.js.map +1 -0
  124. package/dist/transport/webrtcTransport.test.js +104 -0
  125. package/dist/transport/webrtcTransport.test.js.map +1 -0
  126. package/dist/transport/webrtcTypes.js +29 -0
  127. package/dist/transport/webrtcTypes.js.map +1 -0
  128. package/dist/transport/webrtcTypes.test.js +20 -0
  129. package/dist/transport/webrtcTypes.test.js.map +1 -0
  130. package/dist/transport/wsClient.js +16 -3
  131. package/dist/transport/wsClient.js.map +1 -1
  132. package/dist/utils/safePath.js +18 -6
  133. package/dist/utils/safePath.js.map +1 -1
  134. package/package.json +8 -2
  135. package/scripts/run-tests.mjs +45 -0
@@ -0,0 +1,64 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { ModelService } from './modelService.js';
4
+ test('apply replaces legacy model args with the selected model flag', async () => {
5
+ const service = new ModelService();
6
+ const args = service.apply({
7
+ model: 'claude-sonnet-4',
8
+ baseURL: 'https://provider.example.com/custom',
9
+ apiKey: 'sk-test',
10
+ }, ['--verbose', '--model', 'legacy-model']);
11
+ const env = await service.buildEnv();
12
+ assert.deepEqual(args, ['--verbose', '--model', 'claude-sonnet-4']);
13
+ assert.equal(env.ANTHROPIC_BASE_URL, 'https://provider.example.com/custom');
14
+ assert.equal(env.ANTHROPIC_API_KEY, 'sk-test');
15
+ await service.dispose();
16
+ });
17
+ test('apply accepts payloads without legacy model name', async () => {
18
+ const service = new ModelService();
19
+ const args = service.apply({
20
+ baseURL: 'https://api.example.com/root/v1/messages',
21
+ apiKey: 'sk-test',
22
+ }, ['-m', 'legacy-model', '--debug']);
23
+ const env = await service.buildEnv();
24
+ assert.deepEqual(args, ['--debug']);
25
+ assert.equal(env.ANTHROPIC_BASE_URL, 'https://api.example.com/root');
26
+ assert.equal(env.ANTHROPIC_API_KEY, 'sk-test');
27
+ await service.dispose();
28
+ });
29
+ test('apply keeps no-set requests free of model flags', async () => {
30
+ const service = new ModelService();
31
+ const args = service.apply(null, ['--model', 'legacy-model', '--debug']);
32
+ const env = await service.buildEnv();
33
+ assert.deepEqual(args, ['--debug']);
34
+ assert.equal(service.getCurrent(), null);
35
+ assert.equal(env.ANTHROPIC_BASE_URL, undefined);
36
+ assert.equal(env.ANTHROPIC_API_KEY, undefined);
37
+ await service.dispose();
38
+ });
39
+ test('ensureBeforeSpawn does not fallback to default after no-set request', async () => {
40
+ const service = new ModelService();
41
+ service.apply(null, ['--debug']);
42
+ service.fetchDefault = async () => ({
43
+ model: 'claude-sonnet-4',
44
+ baseURL: 'https://provider.example.com/custom',
45
+ apiKey: 'sk-test',
46
+ });
47
+ const args = await service.ensureBeforeSpawn(['--debug'], 'device-token');
48
+ assert.deepEqual(args, ['--debug']);
49
+ assert.equal(service.getCurrent(), null);
50
+ await service.dispose();
51
+ });
52
+ test('ensureBeforeSpawn still loads default when no selection was applied', async () => {
53
+ const service = new ModelService();
54
+ service.fetchDefault = async () => ({
55
+ model: 'claude-sonnet-4',
56
+ baseURL: 'https://provider.example.com/custom',
57
+ apiKey: 'sk-test',
58
+ });
59
+ const args = await service.ensureBeforeSpawn(['--debug'], 'device-token');
60
+ assert.deepEqual(args, ['--debug', '--model', 'claude-sonnet-4']);
61
+ assert.equal(service.getCurrent()?.model, 'claude-sonnet-4');
62
+ await service.dispose();
63
+ });
64
+ //# sourceMappingURL=modelArgs.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modelArgs.test.js","sourceRoot":"","sources":["../../src/services/modelArgs.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { ModelService } from './modelService.js';
4
+ test('strips v1 messages suffix for Claude-compatible providers', async () => {
5
+ const service = new ModelService();
6
+ const url = await service.resolveClaudeBaseURL({
7
+ model: 'claude-haiku-4.5',
8
+ baseURL: 'https://api.example.com/root/v1/messages',
9
+ apiKey: 'sk-test',
10
+ });
11
+ assert.equal(url, 'https://api.example.com/root');
12
+ });
13
+ test('uses local adapter proxy for OpenAI chat completions providers', async () => {
14
+ const service = new ModelService();
15
+ const url = await service.resolveClaudeBaseURL({
16
+ model: 'deepseek-v4-flash-free',
17
+ baseURL: 'https://opencode.ai/zen/v1/chat/completions',
18
+ apiKey: 'public',
19
+ });
20
+ assert.match(url, /^http:\/\/127\.0\.0\.1:\d+$/);
21
+ await service.dispose();
22
+ });
23
+ test('keeps unknown provider url unchanged', async () => {
24
+ const service = new ModelService();
25
+ const url = await service.resolveClaudeBaseURL({
26
+ model: 'custom',
27
+ baseURL: 'https://provider.example.com/custom',
28
+ apiKey: 'sk-test',
29
+ });
30
+ assert.equal(url, 'https://provider.example.com/custom');
31
+ });
32
+ //# sourceMappingURL=modelBaseUrl.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modelBaseUrl.test.js","sourceRoot":"","sources":["../../src/services/modelBaseUrl.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,IAAI,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;IAC3E,MAAM,OAAO,GAAG,IAAI,YAAY,EAAE,CAAC;IACnC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,oBAAoB,CAAC;QAC7C,KAAK,EAAE,kBAAkB;QACzB,OAAO,EAAE,0CAA0C;QACnD,MAAM,EAAE,SAAS;KAClB,CAAC,CAAC;IACH,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,8BAA8B,CAAC,CAAC;AACpD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,gEAAgE,EAAE,KAAK,IAAI,EAAE;IAChF,MAAM,OAAO,GAAG,IAAI,YAAY,EAAE,CAAC;IACnC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,oBAAoB,CAAC;QAC7C,KAAK,EAAE,wBAAwB;QAC/B,OAAO,EAAE,6CAA6C;QACtD,MAAM,EAAE,QAAQ;KACjB,CAAC,CAAC;IACH,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,6BAA6B,CAAC,CAAC;IACjD,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;AAC1B,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;IACtD,MAAM,OAAO,GAAG,IAAI,YAAY,EAAE,CAAC;IACnC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,oBAAoB,CAAC;QAC7C,KAAK,EAAE,QAAQ;QACf,OAAO,EAAE,qCAAqC;QAC9C,MAAM,EAAE,SAAS;KAClB,CAAC,CAAC;IACH,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,qCAAqC,CAAC,CAAC;AAC3D,CAAC,CAAC,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { httpRequest } from '../http/httpClient.js';
2
2
  import { SERVER_HTTP } from '../core/config.js';
3
+ import { ClaudeAdapterProxy } from './claudeAdapterProxy.js';
3
4
  // strip any existing --model / --model=... / -m flag pairs
4
5
  export function removeModelArgs(args) {
5
6
  const result = [];
@@ -18,37 +19,72 @@ export function removeModelArgs(args) {
18
19
  // orchestrates the currently-active model pushed from backend via ws
19
20
  export class ModelService {
20
21
  current = null;
22
+ suppressDefaultFallback = false;
23
+ adapterProxy = new ClaudeAdapterProxy();
21
24
  getCurrent() { return this.current; }
22
- // apply an incoming model payload to args and cache it for buildEnv
25
+ // cache incoming connection settings and rebuild the runtime model args
23
26
  apply(payload, args) {
24
27
  const cleaned = removeModelArgs(args);
25
28
  if (payload && typeof payload === 'object') {
26
29
  const p = payload;
27
- const model = typeof p['model'] === 'string' ? p['model'] : '';
28
- if (model) {
29
- this.current = {
30
- model,
31
- baseURL: typeof p['baseURL'] === 'string' ? p['baseURL'] : undefined,
32
- apiKey: typeof p['apiKey'] === 'string' ? p['apiKey'] : undefined,
33
- };
34
- cleaned.push('--model', model);
30
+ const current = {
31
+ model: typeof p['model'] === 'string' && p['model'].trim()
32
+ ? p['model'].trim()
33
+ : this.current?.model ?? '',
34
+ baseURL: typeof p['baseURL'] === 'string' ? p['baseURL'] : undefined,
35
+ apiKey: typeof p['apiKey'] === 'string' ? p['apiKey'] : undefined,
36
+ };
37
+ if (current.model || current.baseURL || current.apiKey) {
38
+ this.current = current;
39
+ this.suppressDefaultFallback = false;
40
+ if (current.model)
41
+ cleaned.push('--model', current.model);
35
42
  return cleaned;
36
43
  }
37
44
  }
38
45
  this.current = null;
46
+ this.suppressDefaultFallback = payload === null;
39
47
  return cleaned;
40
48
  }
41
49
  // build env overrides for claude pty based on current model
42
- buildEnv() {
43
- const env = { ANTHROPIC_BASE_URL: undefined, ANTHROPIC_API_KEY: undefined };
50
+ async buildEnv() {
51
+ const env = {
52
+ ANTHROPIC_BASE_URL: undefined,
53
+ ANTHROPIC_API_KEY: undefined,
54
+ ANTHROPIC_AUTH_TOKEN: undefined,
55
+ };
44
56
  if (this.current?.baseURL) {
45
- env.ANTHROPIC_BASE_URL = this.current.baseURL;
57
+ env.ANTHROPIC_BASE_URL = await this.resolveClaudeBaseURL(this.current);
46
58
  env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
47
59
  }
60
+ else {
61
+ await this.adapterProxy.stop();
62
+ }
48
63
  if (this.current?.apiKey)
49
64
  env.ANTHROPIC_API_KEY = this.current.apiKey;
65
+ if (env.ANTHROPIC_BASE_URL?.startsWith('http://127.0.0.1:'))
66
+ env.ANTHROPIC_AUTH_TOKEN = 'default';
50
67
  return env;
51
68
  }
69
+ // normalize provider endpoints into Claude-compatible base urls
70
+ async resolveClaudeBaseURL(model) {
71
+ const baseURL = (model.baseURL ?? '').trim().replace(/\/+$/, '');
72
+ if (!baseURL)
73
+ return baseURL;
74
+ if (/\/v1\/messages$/i.test(baseURL)) {
75
+ await this.adapterProxy.stop();
76
+ return baseURL.replace(/\/v1\/messages$/i, '');
77
+ }
78
+ if (/\/v1\/chat\/completions$/i.test(baseURL)) {
79
+ return this.adapterProxy.start({
80
+ baseURL,
81
+ apiKey: model.apiKey ?? '',
82
+ model: model.model,
83
+ });
84
+ }
85
+ await this.adapterProxy.stop();
86
+ return baseURL;
87
+ }
52
88
  // fetch the user's current default model from backend using deviceToken
53
89
  async fetchDefault(deviceToken) {
54
90
  if (!deviceToken)
@@ -75,10 +111,16 @@ export class ModelService {
75
111
  async ensureBeforeSpawn(args, deviceToken) {
76
112
  if (this.current)
77
113
  return args;
114
+ if (this.suppressDefaultFallback)
115
+ return args;
78
116
  const def = await this.fetchDefault(deviceToken);
79
117
  if (def)
80
118
  return this.apply({ model: def.model, baseURL: def.baseURL, apiKey: def.apiKey }, args);
81
119
  return args;
82
120
  }
121
+ // release local adapter resources
122
+ async dispose() {
123
+ await this.adapterProxy.stop();
124
+ }
83
125
  }
84
126
  //# sourceMappingURL=modelService.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"modelService.js","sourceRoot":"","sources":["../../src/services/modelService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"modelService.js","sourceRoot":"","sources":["../../src/services/modelService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,136 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { ModelService } from './modelService.js';
4
+ import { convertRequestToOpenAI } from 'claude-adapter';
5
+ test('strips v1 messages suffix for Claude-compatible providers', async () => {
6
+ const service = new ModelService();
7
+ const url = await service.resolveClaudeBaseURL({
8
+ model: 'claude-haiku-4.5',
9
+ baseURL: 'https://api.example.com/root/v1/messages',
10
+ apiKey: 'sk-test',
11
+ });
12
+ assert.equal(url, 'https://api.example.com/root');
13
+ });
14
+ test('uses local adapter proxy for OpenAI chat completions providers', async () => {
15
+ const service = new ModelService();
16
+ const url = await service.resolveClaudeBaseURL({
17
+ model: 'deepseek-v4-flash-free',
18
+ baseURL: 'https://opencode.ai/zen/v1/chat/completions',
19
+ apiKey: 'public',
20
+ });
21
+ assert.match(url, /^http:\/\/127\.0\.0\.1:\d+$/);
22
+ await service.dispose();
23
+ });
24
+ test('keeps unknown provider url unchanged', async () => {
25
+ const service = new ModelService();
26
+ const url = await service.resolveClaudeBaseURL({
27
+ model: 'custom',
28
+ baseURL: 'https://provider.example.com/custom',
29
+ apiKey: 'sk-test',
30
+ });
31
+ assert.equal(url, 'https://provider.example.com/custom');
32
+ });
33
+ test('apply replaces legacy model args with the selected model flag', async () => {
34
+ const service = new ModelService();
35
+ const args = service.apply({
36
+ model: 'claude-sonnet-4',
37
+ baseURL: 'https://provider.example.com/custom',
38
+ apiKey: 'sk-test',
39
+ }, ['--verbose', '--model', 'legacy-model']);
40
+ const env = await service.buildEnv();
41
+ assert.deepEqual(args, ['--verbose', '--model', 'claude-sonnet-4']);
42
+ assert.equal(env.ANTHROPIC_BASE_URL, 'https://provider.example.com/custom');
43
+ assert.equal(env.ANTHROPIC_API_KEY, 'sk-test');
44
+ await service.dispose();
45
+ });
46
+ test('apply accepts payloads without legacy model name', async () => {
47
+ const service = new ModelService();
48
+ const args = service.apply({
49
+ baseURL: 'https://api.example.com/root/v1/messages',
50
+ apiKey: 'sk-test',
51
+ }, ['-m', 'legacy-model', '--debug']);
52
+ const env = await service.buildEnv();
53
+ assert.deepEqual(args, ['--debug']);
54
+ assert.equal(env.ANTHROPIC_BASE_URL, 'https://api.example.com/root');
55
+ assert.equal(env.ANTHROPIC_API_KEY, 'sk-test');
56
+ await service.dispose();
57
+ });
58
+ test('apply keeps no-set requests free of model flags', async () => {
59
+ const service = new ModelService();
60
+ const args = service.apply(null, ['--model', 'legacy-model', '--debug']);
61
+ const env = await service.buildEnv();
62
+ assert.deepEqual(args, ['--debug']);
63
+ assert.equal(service.getCurrent(), null);
64
+ assert.equal(env.ANTHROPIC_BASE_URL, undefined);
65
+ assert.equal(env.ANTHROPIC_API_KEY, undefined);
66
+ await service.dispose();
67
+ });
68
+ test('ensureBeforeSpawn does not fallback to default after no-set request', async () => {
69
+ const service = new ModelService();
70
+ service.apply(null, ['--debug']);
71
+ service.fetchDefault = async () => ({
72
+ model: 'claude-sonnet-4',
73
+ baseURL: 'https://provider.example.com/custom',
74
+ apiKey: 'sk-test',
75
+ });
76
+ const args = await service.ensureBeforeSpawn(['--debug'], 'device-token');
77
+ assert.deepEqual(args, ['--debug']);
78
+ assert.equal(service.getCurrent(), null);
79
+ await service.dispose();
80
+ });
81
+ test('ensureBeforeSpawn still loads default when no selection was applied', async () => {
82
+ const service = new ModelService();
83
+ service.fetchDefault = async () => ({
84
+ model: 'claude-sonnet-4',
85
+ baseURL: 'https://provider.example.com/custom',
86
+ apiKey: 'sk-test',
87
+ });
88
+ const args = await service.ensureBeforeSpawn(['--debug'], 'device-token');
89
+ assert.deepEqual(args, ['--debug', '--model', 'claude-sonnet-4']);
90
+ assert.equal(service.getCurrent()?.model, 'claude-sonnet-4');
91
+ await service.dispose();
92
+ });
93
+ test('xml tool format avoids tool role messages for strict providers', () => {
94
+ const request = {
95
+ model: 'claude-sonnet-4',
96
+ max_tokens: 128,
97
+ messages: [
98
+ { role: 'user', content: 'list files' },
99
+ {
100
+ role: 'assistant',
101
+ content: [
102
+ {
103
+ type: 'tool_use',
104
+ id: 'toolu_1',
105
+ name: 'ls',
106
+ input: { path: '.' },
107
+ },
108
+ ],
109
+ },
110
+ {
111
+ role: 'user',
112
+ content: [
113
+ {
114
+ type: 'tool_result',
115
+ tool_use_id: 'toolu_1',
116
+ content: 'package.json',
117
+ },
118
+ ],
119
+ },
120
+ ],
121
+ tools: [
122
+ {
123
+ name: 'ls',
124
+ description: 'list files',
125
+ input_schema: {
126
+ type: 'object',
127
+ properties: { path: { type: 'string' } },
128
+ },
129
+ },
130
+ ],
131
+ };
132
+ const converted = convertRequestToOpenAI(request, 'deepseek-v4-flash-free', 'xml');
133
+ assert.deepEqual([...new Set(converted.messages.map((message) => message.role))].sort(), ['assistant', 'system', 'user']);
134
+ assert.equal(converted.messages.some((message) => message.role === 'tool'), false);
135
+ });
136
+ //# sourceMappingURL=modelService.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modelService.test.js","sourceRoot":"","sources":["../../src/services/modelService.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ const NOTIFICATION_DEDUPE_WINDOW_MS = 5 * 60 * 1000;
2
+ // keep a short-lived cache of normalized notifications to avoid duplicate ws sends
3
+ export class NotificationDedupe {
4
+ dedupes = new Map();
5
+ // return true only when the notification is outside the dedupe window
6
+ shouldSend(level, message, now = Date.now()) {
7
+ const key = this.buildKey(level, message);
8
+ for (const [existingKey, expiresAt] of this.dedupes) {
9
+ if (expiresAt <= now)
10
+ this.dedupes.delete(existingKey);
11
+ }
12
+ const expiresAt = this.dedupes.get(key);
13
+ if (expiresAt && expiresAt > now)
14
+ return false;
15
+ this.dedupes.set(key, now + NOTIFICATION_DEDUPE_WINDOW_MS);
16
+ return true;
17
+ }
18
+ // normalize notification text so cosmetic whitespace changes do not bypass dedupe
19
+ buildKey(level, message) {
20
+ return `${level.trim().toLowerCase()}::${message.trim().replace(/\s+/g, ' ').toLowerCase()}`;
21
+ }
22
+ }
23
+ //# sourceMappingURL=notificationDedupe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notificationDedupe.js","sourceRoot":"","sources":["../../src/services/notificationDedupe.ts"],"names":[],"mappings":"AAAA,MAAM,6BAA6B,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AAEpD,mFAAmF;AACnF,MAAM,OAAO,kBAAkB;IACrB,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE5C,sEAAsE;IACtE,UAAU,CAAC,KAAa,EAAE,OAAe,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;QACzD,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC1C,KAAK,MAAM,CAAC,WAAW,EAAE,SAAS,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACpD,IAAI,SAAS,IAAI,GAAG;gBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QACzD,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,SAAS,IAAI,SAAS,GAAG,GAAG;YAAE,OAAO,KAAK,CAAC;QAC/C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,6BAA6B,CAAC,CAAC;QAC3D,OAAO,IAAI,CAAC;IACd,CAAC;IAED,kFAAkF;IAC1E,QAAQ,CAAC,KAAa,EAAE,OAAe;QAC7C,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,KAAK,OAAO,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;IAC/F,CAAC;CACF"}
@@ -0,0 +1,20 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { NotificationDedupe } from './notificationDedupe.js';
4
+ test('dedupes same notification within 5 minutes', () => {
5
+ const dedupe = new NotificationDedupe();
6
+ const now = new Date('2026-05-29T10:09:17.389Z').getTime();
7
+ const first = dedupe.shouldSend('error', 'IDE extension install failed (see /status for info)', now);
8
+ const second = dedupe.shouldSend('error', ' IDE extension install failed (see /status for info) ', now);
9
+ assert.equal(first, true);
10
+ assert.equal(second, false);
11
+ });
12
+ test('allows same notification again after 5 minutes', () => {
13
+ const dedupe = new NotificationDedupe();
14
+ const base = new Date('2026-05-29T10:09:17.389Z').getTime();
15
+ const first = dedupe.shouldSend('error', 'IDE extension install failed (see /status for info)', base);
16
+ const second = dedupe.shouldSend('error', 'IDE extension install failed (see /status for info)', base + 5 * 60 * 1000 + 1);
17
+ assert.equal(first, true);
18
+ assert.equal(second, true);
19
+ });
20
+ //# sourceMappingURL=notificationDedupe.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notificationDedupe.test.js","sourceRoot":"","sources":["../../src/services/notificationDedupe.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D,IAAI,CAAC,4CAA4C,EAAE,GAAG,EAAE;IACtD,MAAM,MAAM,GAAG,IAAI,kBAAkB,EAAE,CAAC;IACxC,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,0BAA0B,CAAC,CAAC,OAAO,EAAE,CAAC;IAE3D,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,qDAAqD,EAAE,GAAG,CAAC,CAAC;IACrG,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,yDAAyD,EAAE,GAAG,CAAC,CAAC;IAE1G,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC1B,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAC9B,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,gDAAgD,EAAE,GAAG,EAAE;IAC1D,MAAM,MAAM,GAAG,IAAI,kBAAkB,EAAE,CAAC;IACxC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,0BAA0B,CAAC,CAAC,OAAO,EAAE,CAAC;IAE5D,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,qDAAqD,EAAE,IAAI,CAAC,CAAC;IACtG,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,qDAAqD,EAAE,IAAI,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC;IAE3H,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC1B,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAC7B,CAAC,CAAC,CAAC"}
@@ -1,9 +1,12 @@
1
1
  import os from 'node:os';
2
2
  import readline from 'node:readline';
3
- import { randomUUID } from 'node:crypto';
4
3
  import { httpRequest } from '../http/httpClient.js';
5
4
  import { SERVER_HTTP } from '../core/config.js';
6
- import { saveCredentials, credentialsPath } from '../auth/credentialsStore.js';
5
+ import { ensureDeviceUuid, saveCredentials, saveDeviceIdentity } from '../auth/credentialsStore.js';
6
+ const BANNER_TIPS = [
7
+ 'tip: press --help to see the command guide.',
8
+ 'tip: run `kumo reset claude` to pair the Claude agent again.',
9
+ ];
7
10
  // poll pair status until approved/rejected or timeout (5 min)
8
11
  async function pollPairStatus(requestId) {
9
12
  const deadline = Date.now() + 5 * 60 * 1000;
@@ -49,7 +52,7 @@ export async function verifyCredentials(creds) {
49
52
  return null;
50
53
  }
51
54
  }
52
- function printBanner() {
55
+ export function printBanner() {
53
56
  const cloud = [
54
57
  " \x1b[1;36m_ __ _ _ __ __ ____ \x1b[0m",
55
58
  " \x1b[1;36m| |/ /| | | || \\/ | / __ \\\x1b[0m",
@@ -62,16 +65,21 @@ function printBanner() {
62
65
  console.log(line);
63
66
  console.log('');
64
67
  console.log(' \x1b[2mwhere your code drifts between devices, softly.\x1b[0m');
68
+ const tip = BANNER_TIPS[Math.floor(Math.random() * BANNER_TIPS.length)];
69
+ console.log(` \x1b[2;37m${tip}\x1b[0m`);
65
70
  console.log('');
66
- console.log(' \x1b[36m›\x1b[0m open the \x1b[1mKumo app\x1b[0m, sign in, and tap \x1b[1m"generate"\x1b[0m on the pairing tab.');
67
- console.log(' \x1b[36m›\x1b[0m whisper the 9 digits back to me below — spaces are fine.');
71
+ }
72
+ // print pairing-specific instructions only when the cli needs a fresh code
73
+ function printPairingInstructions() {
74
+ console.log(' \x1b[36m›\x1b[0m open the \x1b[1mKumo app\x1b[0m, sign in, and copy the active 9-digit pairing code.');
75
+ console.log(' \x1b[36m›\x1b[0m if needed, tap \x1b[1m"Regenerate"\x1b[0m for a new code, then enter it below. spaces are fine.');
68
76
  console.log('');
69
77
  }
70
78
  // interactive pairing flow: ask for code, submit to backend, poll until approved
71
79
  export async function pairFlow() {
72
80
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
73
81
  const ask = (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a)));
74
- printBanner();
82
+ printPairingInstructions();
75
83
  try {
76
84
  while (true) {
77
85
  const raw = (await ask('\x1b[1;33m✦ pairing code ›\x1b[0m ')).trim();
@@ -84,7 +92,7 @@ export async function pairFlow() {
84
92
  console.warn('[kumo-cli] pairing code must be exactly 9 digits, please try again');
85
93
  continue;
86
94
  }
87
- const deviceUuid = randomUUID();
95
+ const deviceUuid = ensureDeviceUuid();
88
96
  const deviceName = `${os.userInfo().username}@${os.hostname()}`;
89
97
  let result;
90
98
  try {
@@ -111,6 +119,7 @@ export async function pairFlow() {
111
119
  console.warn('[kumo-cli] please try again with a fresh code');
112
120
  continue;
113
121
  }
122
+ saveDeviceIdentity(approved.device.deviceUuid);
114
123
  const creds = {
115
124
  deviceUuid: approved.device.deviceUuid,
116
125
  deviceToken: approved.deviceToken,
@@ -118,7 +127,7 @@ export async function pairFlow() {
118
127
  user: approved.user ?? undefined,
119
128
  };
120
129
  saveCredentials(creds);
121
- console.log(`[kumo-cli] paired and saved to ${credentialsPath()}`);
130
+ console.log('[kumo-cli] paired successfully');
122
131
  return creds;
123
132
  }
124
133
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pairingService.js","sourceRoot":"","sources":["../../src/services/pairingService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pairingService.js","sourceRoot":"","sources":["../../src/services/pairingService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import * as sessionService from './sessionService.js';
2
+ // merge persisted sessions with the active session ids seen in the current runtime
3
+ export function mergeSessionActivity(sessions, activeIds, currentSessionId) {
4
+ const resolvedActiveIds = new Set(activeIds);
5
+ if (currentSessionId) {
6
+ resolvedActiveIds.add(currentSessionId);
7
+ }
8
+ return sessions.map((session) => ({
9
+ ...session,
10
+ isActive: resolvedActiveIds.has(session.sessionId),
11
+ }));
12
+ }
13
+ // load the session list payload exactly once so callers do not duplicate the same merge logic
14
+ export async function loadSessionList(cwdArg, currentSessionId) {
15
+ const sessions = await sessionService.getSessionListForCwd(cwdArg);
16
+ const activeIds = await sessionService.getActiveSessionIds();
17
+ return mergeSessionActivity(sessions, activeIds, currentSessionId);
18
+ }
19
+ //# sourceMappingURL=sessionListService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionListService.js","sourceRoot":"","sources":["../../src/services/sessionListService.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAC;AAMtD,mFAAmF;AACnF,MAAM,UAAU,oBAAoB,CAClC,QAA4B,EAC5B,SAAsB,EACtB,gBAAgC;IAEhC,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC;IAC7C,IAAI,gBAAgB,EAAE,CAAC;QACrB,iBAAiB,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAChC,GAAG,OAAO;QACV,QAAQ,EAAE,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC;KACnD,CAAC,CAAC,CAAC;AACN,CAAC;AAED,8FAA8F;AAC9F,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAAe,EAAE,gBAAgC;IACrF,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC;IACnE,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,mBAAmB,EAAE,CAAC;IAC7D,OAAO,oBAAoB,CAAC,QAAQ,EAAE,SAAS,EAAE,gBAAgB,CAAC,CAAC;AACrE,CAAC"}
@@ -0,0 +1,22 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mergeSessionActivity } from './sessionListService.js';
4
+ test('mergeSessionActivity marks persisted active sessions', () => {
5
+ const sessions = [
6
+ { sessionId: 's1', firstMessage: 'hello', lastTimestamp: '2026-06-22T10:00:00.000Z' },
7
+ { sessionId: 's2', firstMessage: 'world', lastTimestamp: '2026-06-22T09:00:00.000Z' },
8
+ ];
9
+ const result = mergeSessionActivity(sessions, new Set(['s2']));
10
+ assert.deepEqual(result, [
11
+ { sessionId: 's1', firstMessage: 'hello', lastTimestamp: '2026-06-22T10:00:00.000Z', isActive: false },
12
+ { sessionId: 's2', firstMessage: 'world', lastTimestamp: '2026-06-22T09:00:00.000Z', isActive: true },
13
+ ]);
14
+ });
15
+ test('mergeSessionActivity includes the current runtime session id', () => {
16
+ const sessions = [
17
+ { sessionId: 's1', firstMessage: 'hello', lastTimestamp: '2026-06-22T10:00:00.000Z' },
18
+ ];
19
+ const result = mergeSessionActivity(sessions, new Set(), 's1');
20
+ assert.equal(result[0]?.isActive, true);
21
+ });
22
+ //# sourceMappingURL=sessionListService.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessionListService.test.js","sourceRoot":"","sources":["../../src/services/sessionListService.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAE/D,IAAI,CAAC,sDAAsD,EAAE,GAAG,EAAE;IAChE,MAAM,QAAQ,GAAG;QACf,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,0BAA0B,EAAE;QACrF,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,0BAA0B,EAAE;KACtF,CAAC;IAEF,MAAM,MAAM,GAAG,oBAAoB,CAAC,QAAQ,EAAE,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAE/D,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE;QACvB,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,0BAA0B,EAAE,QAAQ,EAAE,KAAK,EAAE;QACtG,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,0BAA0B,EAAE,QAAQ,EAAE,IAAI,EAAE;KACtG,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,8DAA8D,EAAE,GAAG,EAAE;IACxE,MAAM,QAAQ,GAAG;QACf,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,0BAA0B,EAAE;KACtF,CAAC;IAEF,MAAM,MAAM,GAAG,oBAAoB,CAAC,QAAQ,EAAE,IAAI,GAAG,EAAU,EAAE,IAAI,CAAC,CAAC;IAEvE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;AAC1C,CAAC,CAAC,CAAC"}