@deepseekcode/cli 3.0.1 → 3.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 +76 -55
- package/backend/jwcode-web.jar +0 -0
- package/dist/cli.js +16147 -31
- package/package.json +8 -6
- package/scripts/download-jar.js +93 -0
- package/dist/__tests__/store.test.d.ts +0 -1
- package/dist/__tests__/store.test.js +0 -43
- package/dist/__tests__/theme.test.d.ts +0 -1
- package/dist/__tests__/theme.test.js +0 -40
- package/dist/__tests__/tokenEstimate.test.d.ts +0 -1
- package/dist/__tests__/tokenEstimate.test.js +0 -48
- package/dist/commands/index.d.ts +0 -18
- package/dist/commands/index.js +0 -99
- package/dist/components/ApprovalModal.d.ts +0 -8
- package/dist/components/ApprovalModal.js +0 -47
- package/dist/components/ChatArea.d.ts +0 -10
- package/dist/components/ChatArea.js +0 -49
- package/dist/components/CommandPalette.d.ts +0 -6
- package/dist/components/CommandPalette.js +0 -72
- package/dist/components/StatusLine.d.ts +0 -1
- package/dist/components/StatusLine.js +0 -25
- package/dist/components/TextInput.d.ts +0 -10
- package/dist/components/TextInput.js +0 -118
- package/dist/hooks/useAppState.d.ts +0 -18
- package/dist/hooks/useAppState.js +0 -42
- package/dist/hooks/useWebSocket.d.ts +0 -3
- package/dist/hooks/useWebSocket.js +0 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseekcode/cli",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4",
|
|
4
4
|
"description": "JWCode — Java AI Coding Tool TypeScript CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/cli.js",
|
|
@@ -8,24 +8,26 @@
|
|
|
8
8
|
"deepseekcode": "./dist/cli.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"postinstall": "node scripts/download-jre.js"
|
|
11
|
+
"postinstall": "node scripts/download-jar.js && node scripts/download-jre.js"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"
|
|
15
|
-
"
|
|
14
|
+
"@opentui/core": "^0.4.0",
|
|
15
|
+
"@opentui/solid": "^0.4.0",
|
|
16
|
+
"@solid-primitives/i18n": "2.2.1",
|
|
16
17
|
"marked": "^14.1.0",
|
|
17
|
-
"
|
|
18
|
+
"solid-js": "1.9.12",
|
|
18
19
|
"ws": "^8.18.0"
|
|
19
20
|
},
|
|
20
21
|
"files": [
|
|
21
22
|
"dist/",
|
|
22
23
|
"backend/",
|
|
23
24
|
"scripts/download-jre.js",
|
|
25
|
+
"scripts/download-jar.js",
|
|
24
26
|
"README.md",
|
|
25
27
|
"LICENSE"
|
|
26
28
|
],
|
|
27
29
|
"engines": {
|
|
28
|
-
"
|
|
30
|
+
"bun": ">=1.3.0"
|
|
29
31
|
},
|
|
30
32
|
"publishConfig": {
|
|
31
33
|
"access": "public"
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* postinstall fallback — download backend JAR if not bundled.
|
|
4
|
+
*
|
|
5
|
+
* The backend JAR (jwcode-web.jar) should be bundled in the npm package
|
|
6
|
+
* at backend/jwcode-web.jar. If it's missing (e.g. broken publish),
|
|
7
|
+
* this script downloads it from GitHub Releases as a fallback.
|
|
8
|
+
*
|
|
9
|
+
* Environment variables:
|
|
10
|
+
* JWCODE_JAR_BASE_URL Base URL for JAR download (default: GitHub Releases)
|
|
11
|
+
* JWCODE_SKIP_JAR Set to "1" to skip JAR download
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, mkdirSync, createWriteStream } from 'node:fs';
|
|
14
|
+
import { join, dirname } from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { pipeline } from 'node:stream/promises';
|
|
17
|
+
|
|
18
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const PACKAGE_DIR = join(__dirname, '..');
|
|
20
|
+
const JAR_PATH = join(PACKAGE_DIR, 'backend', 'jwcode-web.jar');
|
|
21
|
+
const BASE_URL = process.env.JWCODE_JAR_BASE_URL || 'https://github.com/jwcode-project/jwcode/releases/download';
|
|
22
|
+
|
|
23
|
+
let PACKAGE_VERSION = '0.0.0';
|
|
24
|
+
try {
|
|
25
|
+
const pkg = JSON.parse(
|
|
26
|
+
await import('node:fs').then(fs =>
|
|
27
|
+
fs.promises.readFile(join(PACKAGE_DIR, 'package.json'), 'utf-8')
|
|
28
|
+
)
|
|
29
|
+
);
|
|
30
|
+
PACKAGE_VERSION = pkg.version || PACKAGE_VERSION;
|
|
31
|
+
} catch { /* fallback */ }
|
|
32
|
+
|
|
33
|
+
async function downloadFile(url, destPath) {
|
|
34
|
+
console.log(`[jar] Downloading: ${url}`);
|
|
35
|
+
const response = await fetch(url);
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
throw new Error(`Download failed: ${response.status} ${response.statusText}`);
|
|
38
|
+
}
|
|
39
|
+
const total = parseInt(response.headers.get('content-length') || '0', 10);
|
|
40
|
+
let downloaded = 0;
|
|
41
|
+
let lastLog = 0;
|
|
42
|
+
|
|
43
|
+
mkdirSync(dirname(destPath), { recursive: true });
|
|
44
|
+
const writer = createWriteStream(destPath);
|
|
45
|
+
const reader = response.body;
|
|
46
|
+
|
|
47
|
+
reader.on('data', (chunk) => {
|
|
48
|
+
downloaded += chunk.length;
|
|
49
|
+
if (total && Date.now() - lastLog > 2000) {
|
|
50
|
+
const pct = Math.round((downloaded / total) * 100);
|
|
51
|
+
console.log(`[jar] Progress: ${pct}% (${(downloaded / 1024 / 1024).toFixed(1)} MB)`);
|
|
52
|
+
lastLog = Date.now();
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
await pipeline(reader, writer);
|
|
57
|
+
console.log(`[jar] Downloaded: ${(downloaded / 1024 / 1024).toFixed(1)} MB`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function main() {
|
|
61
|
+
if (process.env.JWCODE_SKIP_JAR === '1') {
|
|
62
|
+
console.log('[jar] JWCODE_SKIP_JAR=1, skipping.');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check if JAR already bundled
|
|
67
|
+
if (existsSync(JAR_PATH) && JAR_PATH.endsWith('.jar')) {
|
|
68
|
+
const stats = await import('node:fs').then(fs => fs.promises.stat(JAR_PATH));
|
|
69
|
+
if (stats.size > 1000000) { // > 1MB = real JAR, not a placeholder
|
|
70
|
+
console.log('[jar] Backend JAR already exists, skipping download.');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const url = `${BASE_URL}/v${PACKAGE_VERSION}/jwcode-web.jar`;
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
console.log(`[jar] Backend JAR not bundled, downloading from GitHub Releases...`);
|
|
79
|
+
await downloadFile(url, JAR_PATH);
|
|
80
|
+
console.log('[jar] Backend JAR ready at backend/jwcode-web.jar');
|
|
81
|
+
} catch (err) {
|
|
82
|
+
console.error(`[jar] Download failed: ${err.message}`);
|
|
83
|
+
console.log('[jar] System Java and Maven build will be required at runtime.');
|
|
84
|
+
console.log('[jar] You can also manually build the backend:');
|
|
85
|
+
console.log(` cd jwcode && mvn package -pl jwcode-web -am -DskipTests`);
|
|
86
|
+
console.log(` cp jwcode-web/target/jwcode-web.jar ts-cli/backend/`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
main().catch(err => {
|
|
91
|
+
console.error('[jar] Unexpected error:', err.message);
|
|
92
|
+
process.exit(0); // Don't fail — fall back to Maven build
|
|
93
|
+
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { createStore } from '../store.js';
|
|
3
|
-
describe('store', () => {
|
|
4
|
-
it('creates store with initial state', () => {
|
|
5
|
-
const store = createStore({ count: 0, name: 'test' });
|
|
6
|
-
expect(store.getState().count).toBe(0);
|
|
7
|
-
expect(store.getState().name).toBe('test');
|
|
8
|
-
});
|
|
9
|
-
it('setState updates state immutably', () => {
|
|
10
|
-
const store = createStore({ count: 0, name: 'test' });
|
|
11
|
-
const prev = store.getState();
|
|
12
|
-
store.setState(s => ({ ...s, count: 5 }));
|
|
13
|
-
expect(store.getState().count).toBe(5);
|
|
14
|
-
expect(prev.count).toBe(0); // original unchanged
|
|
15
|
-
});
|
|
16
|
-
it('subscribe receives updates', () => {
|
|
17
|
-
const store = createStore({ count: 0, name: 'test' });
|
|
18
|
-
let called = false;
|
|
19
|
-
store.subscribe(() => { called = true; });
|
|
20
|
-
store.setState(s => ({ ...s, count: 10 }));
|
|
21
|
-
expect(called).toBe(true);
|
|
22
|
-
expect(store.getState().count).toBe(10);
|
|
23
|
-
});
|
|
24
|
-
it('subscribe returns unsubscribe function', () => {
|
|
25
|
-
const store = createStore({ count: 0, name: 'test' });
|
|
26
|
-
let callCount = 0;
|
|
27
|
-
const unsub = store.subscribe(() => { callCount++; });
|
|
28
|
-
store.setState(s => ({ ...s, count: 1 }));
|
|
29
|
-
expect(callCount).toBe(1);
|
|
30
|
-
unsub();
|
|
31
|
-
store.setState(s => ({ ...s, count: 2 }));
|
|
32
|
-
expect(callCount).toBe(1); // not called again
|
|
33
|
-
});
|
|
34
|
-
it('multiple subscribers all notified', () => {
|
|
35
|
-
const store = createStore({ count: 0, name: 'test' });
|
|
36
|
-
let calls = 0;
|
|
37
|
-
store.subscribe(() => { calls++; });
|
|
38
|
-
store.subscribe(() => { calls++; });
|
|
39
|
-
store.setState(s => ({ ...s, count: 42 }));
|
|
40
|
-
expect(calls).toBe(2);
|
|
41
|
-
expect(store.getState().count).toBe(42);
|
|
42
|
-
});
|
|
43
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
-
import { getTheme, setTheme, t } from '../theme.js';
|
|
3
|
-
describe('theme', () => {
|
|
4
|
-
afterEach(() => {
|
|
5
|
-
setTheme('dark');
|
|
6
|
-
});
|
|
7
|
-
it('default theme is dark', () => {
|
|
8
|
-
const theme = getTheme();
|
|
9
|
-
expect(theme.primary).toBe('cyan');
|
|
10
|
-
expect(theme.success).toBe('green');
|
|
11
|
-
expect(theme.error).toBe('red');
|
|
12
|
-
});
|
|
13
|
-
it('setTheme changes current theme', () => {
|
|
14
|
-
setTheme('light');
|
|
15
|
-
const theme = getTheme();
|
|
16
|
-
expect(theme.muted).toBe('blackBright');
|
|
17
|
-
});
|
|
18
|
-
it('all required color keys exist', () => {
|
|
19
|
-
const required = [
|
|
20
|
-
'bg', 'text', 'muted', 'border',
|
|
21
|
-
'primary', 'success', 'warning', 'error', 'info',
|
|
22
|
-
'user', 'assistant', 'system', 'tool', 'thinking',
|
|
23
|
-
'plan', 'auto', 'connected', 'disconnected',
|
|
24
|
-
];
|
|
25
|
-
const theme = getTheme();
|
|
26
|
-
for (const key of required) {
|
|
27
|
-
expect(theme).toHaveProperty(key);
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
it('module-level t exports dark theme initially', () => {
|
|
31
|
-
expect(t.primary).toBe('cyan');
|
|
32
|
-
});
|
|
33
|
-
it('theme switches preserve all keys', () => {
|
|
34
|
-
setTheme('light');
|
|
35
|
-
const light = getTheme();
|
|
36
|
-
setTheme('dark');
|
|
37
|
-
const dark = getTheme();
|
|
38
|
-
expect(Object.keys(light)).toEqual(Object.keys(dark));
|
|
39
|
-
});
|
|
40
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
// Replicate the token estimation logic from TextInput.tsx
|
|
3
|
-
function estimateTokens(text) {
|
|
4
|
-
let cjk = 0;
|
|
5
|
-
let other = 0;
|
|
6
|
-
for (const ch of text) {
|
|
7
|
-
if (/[一-鿿㐀-䶿豈- -〿-]/.test(ch)) {
|
|
8
|
-
cjk++;
|
|
9
|
-
}
|
|
10
|
-
else {
|
|
11
|
-
other++;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
return Math.ceil(cjk / 1.5 + other / 4);
|
|
15
|
-
}
|
|
16
|
-
describe('tokenEstimate', () => {
|
|
17
|
-
it('empty string is 0', () => {
|
|
18
|
-
expect(estimateTokens('')).toBe(0);
|
|
19
|
-
});
|
|
20
|
-
it('English text: ~4 chars per token', () => {
|
|
21
|
-
// 40 English chars → ~10 tokens
|
|
22
|
-
const tokens = estimateTokens('Hello world this is a test message here');
|
|
23
|
-
expect(tokens).toBeGreaterThan(6);
|
|
24
|
-
expect(tokens).toBeLessThan(20);
|
|
25
|
-
});
|
|
26
|
-
it('Chinese text: ~1.5 chars per token', () => {
|
|
27
|
-
// 15 Chinese chars → ~10 tokens
|
|
28
|
-
const tokens = estimateTokens('这是一段中文测试文字用于验证分词估算');
|
|
29
|
-
expect(tokens).toBeGreaterThan(8);
|
|
30
|
-
expect(tokens).toBeLessThan(15);
|
|
31
|
-
});
|
|
32
|
-
it('mixed text combines both ratios', () => {
|
|
33
|
-
const tokens = estimateTokens('Hello 世界 this 测试 works');
|
|
34
|
-
expect(tokens).toBeGreaterThan(0);
|
|
35
|
-
});
|
|
36
|
-
it('code block is mostly other chars', () => {
|
|
37
|
-
const code = 'function hello() { return 42; }';
|
|
38
|
-
const tokens = estimateTokens(code);
|
|
39
|
-
expect(tokens).toBeGreaterThan(2);
|
|
40
|
-
expect(tokens).toBeLessThan(15);
|
|
41
|
-
});
|
|
42
|
-
it('100K token threshold is detectable', () => {
|
|
43
|
-
// Very rough: 400K chars ≈ 100K tokens
|
|
44
|
-
const long = 'x'.repeat(400_000);
|
|
45
|
-
const tokens = estimateTokens(long);
|
|
46
|
-
expect(tokens).toBeGreaterThanOrEqual(100_000);
|
|
47
|
-
});
|
|
48
|
-
});
|
package/dist/commands/index.d.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Command definitions — Chinese descriptions.
|
|
3
|
-
* Local commands handled by TUI; WS commands sent to backend.
|
|
4
|
-
*/
|
|
5
|
-
export interface CmdEntry {
|
|
6
|
-
cmd: string;
|
|
7
|
-
desc: string;
|
|
8
|
-
via: 'local' | 'ws';
|
|
9
|
-
action: string | null;
|
|
10
|
-
}
|
|
11
|
-
export declare const LOCAL_COMMANDS: CmdEntry[];
|
|
12
|
-
export declare const WS_COMMANDS: CmdEntry[];
|
|
13
|
-
export declare const ALL_COMMANDS: CmdEntry[];
|
|
14
|
-
export declare const SLASH_COMMANDS: Record<string, {
|
|
15
|
-
action: string;
|
|
16
|
-
needsArg?: boolean;
|
|
17
|
-
} | null>;
|
|
18
|
-
export declare const HELP_TEXT = "\n\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\n\u2551 JWCode \u547D\u4EE4\u5E2E\u52A9 \u2551\n\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\n\u2551 \u672C\u5730\u547D\u4EE4: \u2551\n\u2551 /help \u663E\u793A\u6B64\u5E2E\u52A9\u4FE1\u606F \u2551\n\u2551 /plan \u5207\u6362\u89C4\u5212\u6A21\u5F0F \u2551\n\u2551 /auto \u5207\u6362\u81EA\u52A8\u6A21\u5F0F \u2551\n\u2551 /context \u663E\u793A\u5F53\u524D\u4F1A\u8BDD\u72B6\u6001 \u2551\n\u2551 /exit \u9000\u51FA JWCode \u2551\n\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\n\u2551 \u540E\u7AEF\u547D\u4EE4: \u2551\n\u2551 /confirm \u786E\u8BA4\u6267\u884C\u5F53\u524D\u89C4\u5212 \u2551\n\u2551 /cancel \u53D6\u6D88\u5F53\u524D\u89C4\u5212 \u2551\n\u2551 /stop \u505C\u6B62\u5F53\u524D AI \u751F\u6210 \u2551\n\u2551 /pause \u6682\u505C\u5F53\u524D AI \u751F\u6210 \u2551\n\u2551 /resume \u6062\u590D\u6682\u505C\u7684\u751F\u6210 \u2551\n\u2551 /clear \u6E05\u9664\u5F53\u524D\u4F1A\u8BDD\u6D88\u606F \u2551\n\u2551 /model <\u540D> \u5207\u6362 AI \u6A21\u578B \u2551\n\u2551 /compact \u538B\u7F29\u4F1A\u8BDD\u4E0A\u4E0B\u6587 \u2551\n\u2551 /doctor \u7CFB\u7EDF\u81EA\u8BCA\u65AD \u2551\n\u2551 /rewind \u56DE\u6EDA\u5230\u6700\u8FD1\u68C0\u67E5\u70B9 \u2551\n\u2551 /init \u751F\u6210\u9879\u76EE JWCODE.md \u2551\n\u2551 /effort <\u7EA7> \u8BBE\u7F6E\u52AA\u529B\u7EA7\u522B low/med/high \u2551\n\u2551 /branch <\u540D> \u521B\u5EFA\u5206\u652F\u4F1A\u8BDD \u2551\n\u2551 /mcp <\u64CD\u4F5C> MCP \u670D\u52A1\u5668\u7BA1\u7406 \u2551\n\u2551 /skills \u67E5\u770B Skills \u5217\u8868 \u2551\n\u2551 /agents \u5217\u51FA Agent \u4EE3\u7406 \u2551\n\u2551 /config <\u64CD> \u7BA1\u7406\u914D\u7F6E (get/set/list) \u2551\n\u2551 /plugin <\u64CD> \u63D2\u4EF6\u7BA1\u7406 \u2551\n\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\n\u2551 \u5FEB\u6377\u952E: \u2551\n\u2551 \u2191\u2193 \u6D4F\u89C8\u8F93\u5165\u5386\u53F2 (\u6700\u8FD130\u6761) \u2551\n\u2551 PgUp/PgDn \u7FFB\u9875\u6D4F\u89C8\u6D88\u606F \u2551\n\u2551 Home/End \u8DF3\u5230\u6700\u65E9/\u6700\u65B0\u6D88\u606F \u2551\n\u2551 Tab \u5207\u6362 Plan/Act \u6A21\u5F0F \u2551\n\u2551 / \u6253\u5F00\u547D\u4EE4\u9762\u677F (\u53EF\u7FFB\u9875) \u2551\n\u2551 Esc \u5173\u95ED\u9762\u677F/\u53D6\u6D88\u5BA1\u6279 \u2551\n\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563\n\u2551 \u666E\u901A\u8F93\u5165\u5373\u53D1\u9001\u804A\u5929\u6D88\u606F \u2551\n\u2551 \u8F93\u5165\u6846\u663E\u793A\u5B57\u7B26\u6570+token\u4F30\u7B97 \u2551\n\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D";
|
package/dist/commands/index.js
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
// Local TUI commands
|
|
2
|
-
export const LOCAL_COMMANDS = [
|
|
3
|
-
{ cmd: '/help', desc: '显示所有命令', via: 'local', action: null },
|
|
4
|
-
{ cmd: '/plan', desc: '切换规划模式 (先规划再执行)', via: 'local', action: 'plan_mode' },
|
|
5
|
-
{ cmd: '/auto', desc: '切换自动模式 (自动批准工具执行)', via: 'local', action: 'auto_mode' },
|
|
6
|
-
{ cmd: '/context', desc: '显示当前会话状态', via: 'local', action: 'show_context' },
|
|
7
|
-
{ cmd: '/exit', desc: '退出 JWCode', via: 'local', action: '__exit__' },
|
|
8
|
-
];
|
|
9
|
-
// WebSocket commands sent to backend
|
|
10
|
-
export const WS_COMMANDS = [
|
|
11
|
-
{ cmd: '/confirm', desc: '确认当前规划并开始执行', via: 'ws', action: '__confirm_plan' },
|
|
12
|
-
{ cmd: '/cancel', desc: '取消当前规划', via: 'ws', action: '__cancel_plan' },
|
|
13
|
-
{ cmd: '/stop', desc: '停止当前 AI 生成', via: 'ws', action: 'stop' },
|
|
14
|
-
{ cmd: '/pause', desc: '暂停当前 AI 生成', via: 'ws', action: 'pause' },
|
|
15
|
-
{ cmd: '/resume', desc: '恢复暂停的 AI 生成', via: 'ws', action: 'resume' },
|
|
16
|
-
{ cmd: '/clear', desc: '清除当前会话消息', via: 'ws', action: 'clear' },
|
|
17
|
-
{ cmd: '/doctor', desc: '运行系统自诊断', via: 'ws', action: 'doctor' },
|
|
18
|
-
{ cmd: '/rewind', desc: '回滚到最近的检查点', via: 'ws', action: 'rewind' },
|
|
19
|
-
{ cmd: '/compact', desc: '压缩会话上下文 (释放 token)', via: 'ws', action: 'compact' },
|
|
20
|
-
{ cmd: '/model', desc: '切换 AI 模型 (用法: /model <模型名>)', via: 'ws', action: 'model_change' },
|
|
21
|
-
{ cmd: '/init', desc: '分析项目并生成 JWCODE.md 项目记忆文件', via: 'ws', action: 'init' },
|
|
22
|
-
{ cmd: '/effort', desc: '设置任务努力级别 (low/medium/high)', via: 'ws', action: 'effort' },
|
|
23
|
-
{ cmd: '/branch', desc: '创建分支会话 (用法: /branch <名称>)', via: 'ws', action: 'branch' },
|
|
24
|
-
{ cmd: '/mcp', desc: 'MCP 服务器管理 (list/add/remove)', via: 'ws', action: 'mcp' },
|
|
25
|
-
{ cmd: '/skills', desc: '查看可用 Skills 列表', via: 'ws', action: 'skills' },
|
|
26
|
-
{ cmd: '/agents', desc: '列出配置的 Agent 代理', via: 'ws', action: 'agents' },
|
|
27
|
-
{ cmd: '/config', desc: '管理配置 (get/set/list)', via: 'ws', action: 'config' },
|
|
28
|
-
{ cmd: '/plugin', desc: '插件管理 (install/list/remove)', via: 'ws', action: 'plugin' },
|
|
29
|
-
];
|
|
30
|
-
export const ALL_COMMANDS = [...LOCAL_COMMANDS, ...WS_COMMANDS];
|
|
31
|
-
// Action map: slash command -> { action, needsArg? }
|
|
32
|
-
export const SLASH_COMMANDS = {
|
|
33
|
-
'/help': null,
|
|
34
|
-
'/plan': { action: 'plan_mode' },
|
|
35
|
-
'/auto': { action: 'auto_mode' },
|
|
36
|
-
'/context': { action: 'show_context' },
|
|
37
|
-
'/exit': { action: '__exit__' },
|
|
38
|
-
'/quit': { action: '__exit__' },
|
|
39
|
-
'/confirm': { action: '__confirm_plan' },
|
|
40
|
-
'/cancel': { action: '__cancel_plan' },
|
|
41
|
-
'/stop': { action: 'stop' },
|
|
42
|
-
'/pause': { action: 'pause' },
|
|
43
|
-
'/resume': { action: 'resume' },
|
|
44
|
-
'/clear': { action: 'clear' },
|
|
45
|
-
'/doctor': { action: 'doctor' },
|
|
46
|
-
'/rewind': { action: 'rewind' },
|
|
47
|
-
'/compact': { action: 'compact' },
|
|
48
|
-
'/model': { action: 'model_change', needsArg: true },
|
|
49
|
-
'/init': { action: 'init' },
|
|
50
|
-
'/effort': { action: 'effort', needsArg: true },
|
|
51
|
-
'/branch': { action: 'branch', needsArg: true },
|
|
52
|
-
'/mcp': { action: 'mcp', needsArg: true },
|
|
53
|
-
'/skills': { action: 'skills' },
|
|
54
|
-
'/agents': { action: 'agents' },
|
|
55
|
-
'/config': { action: 'config', needsArg: true },
|
|
56
|
-
'/plugin': { action: 'plugin', needsArg: true },
|
|
57
|
-
};
|
|
58
|
-
export const HELP_TEXT = `
|
|
59
|
-
╔══════════════════════════════════════════╗
|
|
60
|
-
║ JWCode 命令帮助 ║
|
|
61
|
-
╠══════════════════════════════════════════╣
|
|
62
|
-
║ 本地命令: ║
|
|
63
|
-
║ /help 显示此帮助信息 ║
|
|
64
|
-
║ /plan 切换规划模式 ║
|
|
65
|
-
║ /auto 切换自动模式 ║
|
|
66
|
-
║ /context 显示当前会话状态 ║
|
|
67
|
-
║ /exit 退出 JWCode ║
|
|
68
|
-
╠══════════════════════════════════════════╣
|
|
69
|
-
║ 后端命令: ║
|
|
70
|
-
║ /confirm 确认执行当前规划 ║
|
|
71
|
-
║ /cancel 取消当前规划 ║
|
|
72
|
-
║ /stop 停止当前 AI 生成 ║
|
|
73
|
-
║ /pause 暂停当前 AI 生成 ║
|
|
74
|
-
║ /resume 恢复暂停的生成 ║
|
|
75
|
-
║ /clear 清除当前会话消息 ║
|
|
76
|
-
║ /model <名> 切换 AI 模型 ║
|
|
77
|
-
║ /compact 压缩会话上下文 ║
|
|
78
|
-
║ /doctor 系统自诊断 ║
|
|
79
|
-
║ /rewind 回滚到最近检查点 ║
|
|
80
|
-
║ /init 生成项目 JWCODE.md ║
|
|
81
|
-
║ /effort <级> 设置努力级别 low/med/high ║
|
|
82
|
-
║ /branch <名> 创建分支会话 ║
|
|
83
|
-
║ /mcp <操作> MCP 服务器管理 ║
|
|
84
|
-
║ /skills 查看 Skills 列表 ║
|
|
85
|
-
║ /agents 列出 Agent 代理 ║
|
|
86
|
-
║ /config <操> 管理配置 (get/set/list) ║
|
|
87
|
-
║ /plugin <操> 插件管理 ║
|
|
88
|
-
╠══════════════════════════════════════════╣
|
|
89
|
-
║ 快捷键: ║
|
|
90
|
-
║ ↑↓ 浏览输入历史 (最近30条) ║
|
|
91
|
-
║ PgUp/PgDn 翻页浏览消息 ║
|
|
92
|
-
║ Home/End 跳到最早/最新消息 ║
|
|
93
|
-
║ Tab 切换 Plan/Act 模式 ║
|
|
94
|
-
║ / 打开命令面板 (可翻页) ║
|
|
95
|
-
║ Esc 关闭面板/取消审批 ║
|
|
96
|
-
╠══════════════════════════════════════════╣
|
|
97
|
-
║ 普通输入即发送聊天消息 ║
|
|
98
|
-
║ 输入框显示字符数+token估算 ║
|
|
99
|
-
╚══════════════════════════════════════════╝`;
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* ApprovalModal — permission prompt in Claude Code style.
|
|
4
|
-
* Arrow keys to select, Enter to confirm, Esc to cancel.
|
|
5
|
-
*/
|
|
6
|
-
import { useState } from 'react';
|
|
7
|
-
import { Box, Text, useInput } from 'ink';
|
|
8
|
-
export function ApprovalModal({ toolName, payload, onAllow, onDeny }) {
|
|
9
|
-
const [selected, setSelected] = useState(0); // 0=allow, 1=deny
|
|
10
|
-
useInput((_input, key) => {
|
|
11
|
-
if (key.escape || key.tab) {
|
|
12
|
-
onDeny();
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
if (key.upArrow || key.downArrow) {
|
|
16
|
-
setSelected(prev => prev === 0 ? 1 : 0);
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
if (key.return) {
|
|
20
|
-
if (selected === 0)
|
|
21
|
-
onAllow();
|
|
22
|
-
else
|
|
23
|
-
onDeny();
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
if (_input === '1') {
|
|
27
|
-
onAllow();
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
if (_input === '2') {
|
|
31
|
-
onDeny();
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
if (_input === 'y' || _input === 'Y') {
|
|
35
|
-
onAllow();
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
if (_input === 'n' || _input === 'N') {
|
|
39
|
-
onDeny();
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
const desc = payload
|
|
44
|
-
? (payload.length > 200 ? payload.slice(0, 200) + '...' : payload)
|
|
45
|
-
: '';
|
|
46
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 2, paddingY: 1, marginTop: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Do you want to proceed?" }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [_jsx(Box, { children: _jsxs(Text, { color: selected === 0 ? 'green' : undefined, children: [selected === 0 ? ' ❯' : ' ', " 1. Allow"] }) }), _jsx(Box, { children: _jsxs(Text, { color: selected === 1 ? 'red' : undefined, children: [selected === 1 ? ' ❯' : ' ', " 2. Deny"] }) })] }), _jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { dimColor: true, children: "Tool: " }), _jsx(Text, { color: "cyan", children: toolName }), desc ? _jsxs(Text, { dimColor: true, children: [" ", desc] }) : null] }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: " Esc to cancel \u00B7 " }), _jsx(Text, { dimColor: true, children: "\u2191\u2193 to select \u00B7 " }), _jsx(Text, { dimColor: true, children: "Enter to confirm" })] })] }));
|
|
47
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { type Message } from '../protocol.js';
|
|
2
|
-
interface Props {
|
|
3
|
-
messages: Message[];
|
|
4
|
-
currentMessage: Message | null;
|
|
5
|
-
scrollOffset: number;
|
|
6
|
-
terminalRows: number;
|
|
7
|
-
reservedRows: number;
|
|
8
|
-
}
|
|
9
|
-
export declare function ChatArea({ messages, currentMessage, scrollOffset, terminalRows, reservedRows }: Props): import("react/jsx-runtime").JSX.Element;
|
|
10
|
-
export {};
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* ChatArea — renders message list with markdown, tool calls, steps, thinking.
|
|
4
|
-
* Shows newest messages at the bottom; uses scrollOffset to page through history.
|
|
5
|
-
*/
|
|
6
|
-
import { Box, Text } from 'ink';
|
|
7
|
-
const SEP = '─'.repeat(60);
|
|
8
|
-
export function ChatArea({ messages, currentMessage, scrollOffset, terminalRows, reservedRows }) {
|
|
9
|
-
const allMessages = currentMessage
|
|
10
|
-
? [...messages.filter(m => m.id !== currentMessage.id)]
|
|
11
|
-
: messages;
|
|
12
|
-
const availableRows = Math.max(10, terminalRows - reservedRows);
|
|
13
|
-
const maxVisible = Math.max(5, Math.floor(availableRows / 4));
|
|
14
|
-
const total = allMessages.length;
|
|
15
|
-
// scrollOffset = how many messages scrolled above the bottom
|
|
16
|
-
// 0 = at bottom (show newest), N = N messages scrolled up
|
|
17
|
-
const clampedOffset = Math.min(scrollOffset, total - 1);
|
|
18
|
-
const end = total - clampedOffset;
|
|
19
|
-
const start = Math.max(0, end - maxVisible);
|
|
20
|
-
const visibleMessages = allMessages.slice(start, end);
|
|
21
|
-
const isScrolledUp = clampedOffset > 0;
|
|
22
|
-
const hiddenAbove = start;
|
|
23
|
-
const hiddenBelow = clampedOffset;
|
|
24
|
-
return (_jsxs(Box, { flexDirection: "column", width: "100%", children: [isScrolledUp && (_jsx(Box, { children: _jsxs(Text, { color: "yellow", dimColor: true, children: ["\u25B2 [", start + 1, "-", end, "/", total, "] \u4E0A\u7FFB\u4E2D (PgUp/PgDn \u7FFB\u9875, Home \u5F00\u5934, End \u6700\u65B0)"] }) })), !isScrolledUp && total > maxVisible && (_jsx(Box, { children: _jsxs(Text, { color: "grey", dimColor: true, children: ["[", start + 1, "-", end, "/", total, "] \u2191/PgUp \u67E5\u770B\u66F4\u65E9\u6D88\u606F"] }) })), visibleMessages.map(msg => (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [msg.type === 'user' && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: SEP }), _jsxs(Text, { color: "green", bold: true, children: ["> ", msg.content] })] })), msg.type === 'assistant' && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: ' ' }), msg.steps.map((step, i) => (_jsx(StepDisplay, { step: step }, step.id || i))), msg.thinking && (_jsx(Text, { dimColor: true, italic: true, children: truncate(msg.thinking, 200) })), msg.toolCalls.map((tc, i) => (_jsx(ToolCallDisplay, { tc: tc }, tc.id || i))), msg.content && _jsx(Text, { children: msg.content }), _jsx(Text, { dimColor: true, children: SEP })] })), msg.type === 'system' && (_jsx(Box, { children: _jsxs(Text, { color: "red", children: ["Error: ", msg.content] }) }))] }, msg.id))), currentMessage && (_jsxs(Box, { flexDirection: "column", children: [currentMessage.thinking && (_jsx(Text, { dimColor: true, italic: true, children: truncate(currentMessage.thinking, 200) })), currentMessage.toolCalls.map((tc, i) => (_jsx(ToolCallDisplay, { tc: tc }, tc.id || i))), currentMessage.content && _jsx(Text, { children: currentMessage.content })] }, currentMessage.id))] }));
|
|
25
|
-
}
|
|
26
|
-
function StepDisplay({ step }) {
|
|
27
|
-
const icon = step.status === 'success' ? '✓' : step.status === 'error' ? '✗' : '▶';
|
|
28
|
-
const color = step.status === 'success' ? 'green' : step.status === 'error' ? 'red' : 'cyan';
|
|
29
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: color, children: [" ", icon, " ", step.title] }), step.thought && _jsxs(Text, { color: "blue", dimColor: true, children: [" ", truncate(step.thought, 200)] }), step.action && _jsxs(Text, { color: "yellow", children: [" ", truncate(step.action, 200)] }), step.result && _jsxs(Text, { color: "green", children: [" ", truncate(step.result, 300)] })] }));
|
|
30
|
-
}
|
|
31
|
-
function ToolCallDisplay({ tc }) {
|
|
32
|
-
const argsStr = tc.args ? truncate(formatJson(tc.args), 200) : '';
|
|
33
|
-
const statusIcon = tc.status === 'complete' ? '✓' : tc.status === 'running' ? '◷' : '✗';
|
|
34
|
-
const statusColor = tc.status === 'complete' ? 'green' : tc.status === 'running' ? 'yellow' : 'red';
|
|
35
|
-
return (_jsxs(Box, { flexDirection: "column", paddingLeft: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: statusColor, children: [" ", statusIcon, " "] }), _jsx(Text, { bold: true, color: "magenta", children: tc.name }), argsStr && _jsxs(Text, { dimColor: true, children: [" ", argsStr] })] }), tc.result && (_jsx(Box, { paddingLeft: 4, children: _jsx(Text, { color: "green", dimColor: true, children: truncate(tc.result, 200) }) }))] }));
|
|
36
|
-
}
|
|
37
|
-
function formatJson(s) {
|
|
38
|
-
try {
|
|
39
|
-
return JSON.stringify(JSON.parse(s), null, 2);
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
return s;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
function truncate(s, max) {
|
|
46
|
-
if (s.length <= max)
|
|
47
|
-
return s;
|
|
48
|
-
return s.slice(0, max) + '...';
|
|
49
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* CommandPalette — / filterable popup.
|
|
4
|
-
* Character input is handled by TextInput; this only handles navigation.
|
|
5
|
-
*/
|
|
6
|
-
import { useState, useMemo, useEffect } from 'react';
|
|
7
|
-
import { Box, Text, useInput, useStdout } from 'ink';
|
|
8
|
-
import { ALL_COMMANDS } from '../commands/index.js';
|
|
9
|
-
export function CommandPalette({ filter, onSelect }) {
|
|
10
|
-
const [selected, setSelected] = useState(0);
|
|
11
|
-
const [scrollOffset, setScrollOffset] = useState(0);
|
|
12
|
-
const { stdout } = useStdout();
|
|
13
|
-
const terminalRows = stdout?.rows || 24;
|
|
14
|
-
const visible = useMemo(() => {
|
|
15
|
-
const f = filter.replace(/^\//, '').toLowerCase();
|
|
16
|
-
if (!f)
|
|
17
|
-
return ALL_COMMANDS;
|
|
18
|
-
return ALL_COMMANDS.filter(c => c.cmd.toLowerCase().includes(f) || c.desc.includes(f));
|
|
19
|
-
}, [filter]);
|
|
20
|
-
useEffect(() => { setSelected(0); setScrollOffset(0); }, [filter]);
|
|
21
|
-
const maxShow = Math.max(5, terminalRows - 13);
|
|
22
|
-
// Keep selected row in view
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
setScrollOffset(prev => {
|
|
25
|
-
if (selected < prev)
|
|
26
|
-
return selected;
|
|
27
|
-
if (selected >= prev + maxShow)
|
|
28
|
-
return selected - maxShow + 1;
|
|
29
|
-
return prev;
|
|
30
|
-
});
|
|
31
|
-
}, [selected, maxShow]);
|
|
32
|
-
const sliced = visible.slice(scrollOffset, scrollOffset + maxShow);
|
|
33
|
-
useInput((_input, key) => {
|
|
34
|
-
if (key.escape) {
|
|
35
|
-
onSelect(null);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
if (key.downArrow) {
|
|
39
|
-
setSelected(prev => Math.min(prev + 1, visible.length - 1));
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
if (key.upArrow) {
|
|
43
|
-
setSelected(prev => Math.max(prev - 1, 0));
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
if (key.pageDown) {
|
|
47
|
-
setSelected(prev => Math.min(prev + maxShow, visible.length - 1));
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
if (key.pageUp) {
|
|
51
|
-
setSelected(prev => Math.max(prev - maxShow, 0));
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
if (key.home) {
|
|
55
|
-
setSelected(0);
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
if (key.end) {
|
|
59
|
-
setSelected(visible.length - 1);
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
if (key.return) {
|
|
63
|
-
if (visible.length > 0 && selected >= 0 && selected < visible.length) {
|
|
64
|
-
onSelect(visible[selected].cmd);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "cyan", paddingX: 1, width: 52, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "cyan", children: "\u547D\u4EE4\u5217\u8868" }), _jsx(Text, { dimColor: true, children: " \u2191\u2193\u9009\u62E9 / PgUp/PgDn\u7FFB\u9875 / \u56DE\u8F66\u786E\u8BA4 / Esc\u53D6\u6D88" })] }), sliced.map((cmd, i) => {
|
|
69
|
-
const idx = scrollOffset + i;
|
|
70
|
-
return (_jsxs(Box, { paddingLeft: 1, children: [_jsx(Text, { color: idx === selected ? 'cyan' : undefined, bold: idx === selected, children: idx === selected ? '> ' : ' ' }), _jsx(Text, { color: "green", children: cmd.cmd }), _jsxs(Text, { dimColor: true, children: [" ", cmd.desc] }), _jsxs(Text, { color: cmd.via === 'ws' ? 'yellow' : 'blue', dimColor: idx !== selected, children: ["(", cmd.via === 'ws' ? '后端' : '本地', ")"] })] }, cmd.cmd));
|
|
71
|
-
}), visible.length > maxShow && (_jsx(Box, { children: _jsxs(Text, { dimColor: true, children: [" ", scrollOffset + 1, "-", Math.min(scrollOffset + maxShow, visible.length), " / ", visible.length] }) }))] }));
|
|
72
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function StatusLine(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from 'ink';
|
|
3
|
-
import { useAppState } from '../hooks/useAppState.js';
|
|
4
|
-
function formatTokens(n) {
|
|
5
|
-
if (n >= 1_000_000)
|
|
6
|
-
return `${(n / 1_000_000).toFixed(1)}M`;
|
|
7
|
-
if (n >= 1_000)
|
|
8
|
-
return `${Math.round(n / 1_000)}K`;
|
|
9
|
-
return String(n);
|
|
10
|
-
}
|
|
11
|
-
export function StatusLine() {
|
|
12
|
-
const state = useAppState();
|
|
13
|
-
const { usage, modelName, planMode, autoMode, connected, statusText, messages } = state;
|
|
14
|
-
const msgCount = messages.length;
|
|
15
|
-
const pct = Math.min(100, Math.round(usage.usageRatio * 100));
|
|
16
|
-
const filled = Math.round(pct / 10);
|
|
17
|
-
const bar = '='.repeat(filled) + '-'.repeat(10 - filled);
|
|
18
|
-
const model = modelName || (connected ? 'ready' : 'connecting...');
|
|
19
|
-
const modeLabel = planMode ? ' Plan ' : ' Act ';
|
|
20
|
-
const modeColor = planMode ? 'cyan' : 'green';
|
|
21
|
-
const connIcon = connected ? '●' : '○';
|
|
22
|
-
const connColor = connected ? 'green' : 'red';
|
|
23
|
-
const isError = statusText.startsWith('Error:');
|
|
24
|
-
return (_jsxs(Box, { flexDirection: "column", width: "100%", paddingRight: 1, children: [_jsxs(Box, { height: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "jwcode" }), _jsx(Text, { children: " " }), _jsxs(Text, { backgroundColor: modeColor, color: "black", children: [" ", modeLabel, " "] }), _jsx(Text, { children: " " }), autoMode && (_jsxs(_Fragment, { children: [_jsx(Text, { backgroundColor: "magenta", color: "black", children: " AUTO " }), _jsx(Text, { children: " " })] })), _jsxs(Text, { color: connColor, children: [connIcon, " "] }), _jsx(Text, { color: "green", children: model }), _jsx(Text, { children: " " }), _jsxs(Text, { dimColor: true, children: [msgCount, "msgs"] }), _jsx(Text, { children: " t: " }), _jsx(Text, { color: "yellow", children: formatTokens(usage.totalTokens) }), _jsx(Text, { children: " " }), _jsxs(Text, { color: pct > 90 ? 'red' : 'white', children: [bar, " ", pct, "%"] })] }), statusText && statusText !== 'connecting...' && (_jsx(Box, { height: 1, children: _jsx(Text, { color: isError ? 'red' : 'grey', dimColor: !isError, children: statusText.slice(0, 100) }) }))] }));
|
|
25
|
-
}
|