zerg-ztc 0.1.11 → 0.1.13
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/bin/ztc-audio-darwin-arm64 +0 -0
- package/dist/utils/dictation_native.d.ts.map +1 -1
- package/dist/utils/dictation_native.js +43 -23
- package/dist/utils/dictation_native.js.map +1 -1
- package/package.json +5 -4
- package/packages/ztc-dictation/Cargo.toml +0 -43
- package/packages/ztc-dictation/README.md +0 -65
- package/packages/ztc-dictation/index.d.ts +0 -16
- package/packages/ztc-dictation/index.js +0 -74
- package/packages/ztc-dictation/package.json +0 -41
- package/packages/ztc-dictation/src/main.rs +0 -430
- package/src/App.tsx +0 -910
- package/src/agent/agent.ts +0 -534
- package/src/agent/backends/anthropic.ts +0 -86
- package/src/agent/backends/gemini.ts +0 -119
- package/src/agent/backends/inception.ts +0 -23
- package/src/agent/backends/index.ts +0 -17
- package/src/agent/backends/openai.ts +0 -23
- package/src/agent/backends/openai_compatible.ts +0 -143
- package/src/agent/backends/types.ts +0 -83
- package/src/agent/commands/clipboard.ts +0 -77
- package/src/agent/commands/config.ts +0 -204
- package/src/agent/commands/debug.ts +0 -23
- package/src/agent/commands/dictation.ts +0 -11
- package/src/agent/commands/emulation.ts +0 -80
- package/src/agent/commands/execution.ts +0 -9
- package/src/agent/commands/help.ts +0 -20
- package/src/agent/commands/history.ts +0 -13
- package/src/agent/commands/index.ts +0 -48
- package/src/agent/commands/input_mode.ts +0 -22
- package/src/agent/commands/keybindings.ts +0 -40
- package/src/agent/commands/model.ts +0 -11
- package/src/agent/commands/models.ts +0 -116
- package/src/agent/commands/permissions.ts +0 -64
- package/src/agent/commands/retry.ts +0 -9
- package/src/agent/commands/shell.ts +0 -68
- package/src/agent/commands/skills.ts +0 -54
- package/src/agent/commands/status.ts +0 -19
- package/src/agent/commands/types.ts +0 -88
- package/src/agent/commands/update.ts +0 -32
- package/src/agent/factory.ts +0 -60
- package/src/agent/index.ts +0 -20
- package/src/agent/runtime/capabilities.ts +0 -7
- package/src/agent/runtime/memory.ts +0 -23
- package/src/agent/runtime/policy.ts +0 -48
- package/src/agent/runtime/session.ts +0 -18
- package/src/agent/runtime/tracing.ts +0 -23
- package/src/agent/tools/file.ts +0 -178
- package/src/agent/tools/index.ts +0 -52
- package/src/agent/tools/screenshot.ts +0 -821
- package/src/agent/tools/search.ts +0 -138
- package/src/agent/tools/shell.ts +0 -69
- package/src/agent/tools/skills.ts +0 -28
- package/src/agent/tools/types.ts +0 -14
- package/src/agent/tools/zerg.ts +0 -50
- package/src/cli.tsx +0 -163
- package/src/components/ActivityLine.tsx +0 -23
- package/src/components/FullScreen.tsx +0 -79
- package/src/components/Header.tsx +0 -27
- package/src/components/InputArea.tsx +0 -1660
- package/src/components/MessageList.tsx +0 -71
- package/src/components/SingleMessage.tsx +0 -298
- package/src/components/StatusBar.tsx +0 -55
- package/src/components/index.tsx +0 -8
- package/src/config/types.ts +0 -19
- package/src/config.ts +0 -186
- package/src/debug/logger.ts +0 -14
- package/src/emulation/README.md +0 -24
- package/src/emulation/catalog.ts +0 -82
- package/src/emulation/trace_style.ts +0 -8
- package/src/emulation/types.ts +0 -7
- package/src/skills/index.ts +0 -36
- package/src/skills/loader.ts +0 -135
- package/src/skills/registry.ts +0 -6
- package/src/skills/types.ts +0 -10
- package/src/types.ts +0 -84
- package/src/ui/README.md +0 -44
- package/src/ui/core/factory.ts +0 -9
- package/src/ui/core/index.ts +0 -4
- package/src/ui/core/input.ts +0 -38
- package/src/ui/core/input_segments.ts +0 -410
- package/src/ui/core/input_state.ts +0 -17
- package/src/ui/core/layout_yoga.ts +0 -122
- package/src/ui/core/style.ts +0 -38
- package/src/ui/core/types.ts +0 -54
- package/src/ui/ink/index.tsx +0 -1
- package/src/ui/ink/render.tsx +0 -60
- package/src/ui/views/activity_line.ts +0 -33
- package/src/ui/views/app.ts +0 -111
- package/src/ui/views/header.ts +0 -44
- package/src/ui/views/input_area.ts +0 -255
- package/src/ui/views/message_list.ts +0 -443
- package/src/ui/views/status_bar.ts +0 -114
- package/src/ui/vue/index.ts +0 -53
- package/src/ui/web/frame_render.tsx +0 -148
- package/src/ui/web/index.tsx +0 -1
- package/src/ui/web/render.tsx +0 -41
- package/src/utils/clipboard.ts +0 -39
- package/src/utils/clipboard_image.ts +0 -40
- package/src/utils/dictation.ts +0 -467
- package/src/utils/dictation_native.ts +0 -258
- package/src/utils/diff.ts +0 -52
- package/src/utils/image_preview.ts +0 -36
- package/src/utils/models.ts +0 -98
- package/src/utils/path_complete.ts +0 -173
- package/src/utils/path_format.ts +0 -99
- package/src/utils/shell.ts +0 -72
- package/src/utils/spinner_frames.ts +0 -1
- package/src/utils/spinner_verbs.ts +0 -23
- package/src/utils/table.ts +0 -171
- package/src/utils/tool_summary.ts +0 -56
- package/src/utils/tool_trace.ts +0 -346
- package/src/utils/update.ts +0 -44
- package/src/utils/version.ts +0 -15
- package/src/web/index.html +0 -352
- package/src/web/mirror-favicon.svg +0 -4
- package/src/web/mirror.html +0 -641
- package/src/web/mirror_hook.ts +0 -25
- package/src/web/mirror_server.ts +0 -204
- package/tsconfig.json +0 -22
- package/vite.config.ts +0 -363
- /package/{packages/ztc-dictation/bin → bin}/.gitkeep +0 -0
package/src/web/mirror_server.ts
DELETED
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
import { createServer, IncomingMessage, ServerResponse } from 'http';
|
|
2
|
-
import { readFile, mkdir, writeFile } from 'fs/promises';
|
|
3
|
-
import { resolve, join, basename } from 'path';
|
|
4
|
-
import { homedir } from 'os';
|
|
5
|
-
import { randomUUID } from 'crypto';
|
|
6
|
-
import { InputBus, InputEvent } from '../ui/core/input.js';
|
|
7
|
-
import { LayoutNode, computeLayout, LayoutFrame } from '../ui/core/index.js';
|
|
8
|
-
|
|
9
|
-
interface Client {
|
|
10
|
-
id: string;
|
|
11
|
-
res: ServerResponse;
|
|
12
|
-
cols: number;
|
|
13
|
-
rows: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export class MirrorServer {
|
|
17
|
-
private clients = new Map<string, Client>();
|
|
18
|
-
private port: number;
|
|
19
|
-
private server = createServer(this.handleRequest.bind(this));
|
|
20
|
-
private lastTree: LayoutNode | null = null;
|
|
21
|
-
private inputBus?: InputBus;
|
|
22
|
-
|
|
23
|
-
constructor(port: number, inputBus?: InputBus) {
|
|
24
|
-
this.port = port;
|
|
25
|
-
this.inputBus = inputBus;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
start(): void {
|
|
29
|
-
this.server.listen(this.port);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
stop(): void {
|
|
33
|
-
this.server.close();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
publish(tree: LayoutNode): void {
|
|
37
|
-
this.lastTree = tree;
|
|
38
|
-
for (const client of this.clients.values()) {
|
|
39
|
-
this.sendLayout(client, tree);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
private async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
|
|
44
|
-
const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
|
|
45
|
-
|
|
46
|
-
if (url.pathname === '/') {
|
|
47
|
-
const htmlPath = new URL('./mirror.html', import.meta.url);
|
|
48
|
-
const html = await readFile(htmlPath, 'utf-8').catch(async () => {
|
|
49
|
-
const fallback = resolve(process.cwd(), 'src/web/mirror.html');
|
|
50
|
-
return readFile(fallback, 'utf-8');
|
|
51
|
-
});
|
|
52
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
53
|
-
res.end(html);
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (url.pathname === '/mirror-favicon.svg' || url.pathname === '/favicon.svg' || url.pathname === '/favicon.ico') {
|
|
58
|
-
const iconPath = new URL('./mirror-favicon.svg', import.meta.url);
|
|
59
|
-
const icon = await readFile(iconPath, 'utf-8').catch(async () => {
|
|
60
|
-
const fallback = resolve(process.cwd(), 'src/web/mirror-favicon.svg');
|
|
61
|
-
return readFile(fallback, 'utf-8');
|
|
62
|
-
});
|
|
63
|
-
const contentType = url.pathname === '/favicon.ico' ? 'image/x-icon' : 'image/svg+xml';
|
|
64
|
-
res.writeHead(200, { 'Content-Type': contentType, 'Cache-Control': 'no-cache' });
|
|
65
|
-
res.end(icon);
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (url.pathname === '/events') {
|
|
70
|
-
const id = url.searchParams.get('id') || `client_${Date.now()}`;
|
|
71
|
-
res.writeHead(200, {
|
|
72
|
-
'Content-Type': 'text/event-stream',
|
|
73
|
-
'Cache-Control': 'no-cache',
|
|
74
|
-
Connection: 'keep-alive'
|
|
75
|
-
});
|
|
76
|
-
res.write('\n');
|
|
77
|
-
|
|
78
|
-
const client: Client = {
|
|
79
|
-
id,
|
|
80
|
-
res,
|
|
81
|
-
cols: 80,
|
|
82
|
-
rows: 24
|
|
83
|
-
};
|
|
84
|
-
this.clients.set(id, client);
|
|
85
|
-
|
|
86
|
-
if (this.lastTree) {
|
|
87
|
-
this.sendLayout(client, this.lastTree);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
req.on('close', () => {
|
|
91
|
-
this.clients.delete(id);
|
|
92
|
-
});
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (url.pathname === '/size' && req.method === 'POST') {
|
|
97
|
-
const id = url.searchParams.get('id');
|
|
98
|
-
if (!id || !this.clients.has(id)) {
|
|
99
|
-
res.writeHead(400);
|
|
100
|
-
res.end();
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const body = await new Promise<string>(resolve => {
|
|
105
|
-
let data = '';
|
|
106
|
-
req.on('data', chunk => { data += chunk; });
|
|
107
|
-
req.on('end', () => resolve(data));
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
try {
|
|
111
|
-
const parsed = JSON.parse(body) as { cols: number; rows: number };
|
|
112
|
-
const client = this.clients.get(id);
|
|
113
|
-
if (client) {
|
|
114
|
-
client.cols = parsed.cols;
|
|
115
|
-
client.rows = parsed.rows;
|
|
116
|
-
if (this.lastTree) {
|
|
117
|
-
this.sendLayout(client, this.lastTree);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
} catch {
|
|
121
|
-
// Ignore invalid payloads
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
res.writeHead(204);
|
|
125
|
-
res.end();
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (url.pathname === '/input' && req.method === 'POST') {
|
|
130
|
-
const body = await new Promise<string>(resolve => {
|
|
131
|
-
let data = '';
|
|
132
|
-
req.on('data', chunk => { data += chunk; });
|
|
133
|
-
req.on('end', () => resolve(data));
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
const parsed = JSON.parse(body) as InputEvent;
|
|
138
|
-
this.inputBus?.emit(parsed);
|
|
139
|
-
} catch {
|
|
140
|
-
// Ignore invalid payloads
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
res.writeHead(204);
|
|
144
|
-
res.end();
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (url.pathname === '/upload' && req.method === 'POST') {
|
|
149
|
-
const buffer = await new Promise<Buffer>(resolve => {
|
|
150
|
-
const chunks: Buffer[] = [];
|
|
151
|
-
req.on('data', chunk => { chunks.push(Buffer.from(chunk)); });
|
|
152
|
-
req.on('end', () => resolve(Buffer.concat(chunks)));
|
|
153
|
-
});
|
|
154
|
-
const headerName = req.headers['x-filename'];
|
|
155
|
-
const rawName = typeof headerName === 'string' && headerName.trim().length > 0 ? headerName : 'upload.bin';
|
|
156
|
-
const safeName = basename(rawName).replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
157
|
-
const dir = join(homedir(), '.ztc', 'uploads');
|
|
158
|
-
await mkdir(dir, { recursive: true });
|
|
159
|
-
const filePath = join(dir, `${Date.now()}-${randomUUID().slice(0, 8)}-${safeName}`);
|
|
160
|
-
await writeFile(filePath, buffer);
|
|
161
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
162
|
-
res.end(JSON.stringify({ path: filePath }));
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (url.pathname === '/file' && req.method === 'GET') {
|
|
167
|
-
const rawPath = url.searchParams.get('path');
|
|
168
|
-
if (!rawPath) {
|
|
169
|
-
res.writeHead(400);
|
|
170
|
-
res.end();
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
const allowedRoots = [
|
|
174
|
-
join(homedir(), '.ztc', 'uploads'),
|
|
175
|
-
join(homedir(), '.ztc', 'clipboard')
|
|
176
|
-
];
|
|
177
|
-
const resolvedPath = resolve(rawPath);
|
|
178
|
-
const allowed = allowedRoots.some(root => resolvedPath.startsWith(resolve(root)));
|
|
179
|
-
if (!allowed) {
|
|
180
|
-
res.writeHead(403);
|
|
181
|
-
res.end();
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
try {
|
|
185
|
-
const data = await readFile(resolvedPath);
|
|
186
|
-
res.writeHead(200, { 'Content-Type': 'application/octet-stream' });
|
|
187
|
-
res.end(data);
|
|
188
|
-
} catch {
|
|
189
|
-
res.writeHead(404);
|
|
190
|
-
res.end();
|
|
191
|
-
}
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
res.writeHead(404);
|
|
196
|
-
res.end();
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
private sendLayout(client: Client, tree: LayoutNode): void {
|
|
200
|
-
const frame = computeLayout(tree, client.cols, client.rows);
|
|
201
|
-
const payload = JSON.stringify(frame);
|
|
202
|
-
client.res.write(`data: ${payload}\n\n`);
|
|
203
|
-
}
|
|
204
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "NodeNext",
|
|
5
|
-
"moduleResolution": "NodeNext",
|
|
6
|
-
"lib": ["ES2022"],
|
|
7
|
-
"outDir": "./dist",
|
|
8
|
-
"rootDir": "./src",
|
|
9
|
-
"strict": true,
|
|
10
|
-
"esModuleInterop": true,
|
|
11
|
-
"skipLibCheck": true,
|
|
12
|
-
"forceConsistentCasingInFileNames": true,
|
|
13
|
-
"resolveJsonModule": true,
|
|
14
|
-
"declaration": true,
|
|
15
|
-
"declarationMap": true,
|
|
16
|
-
"sourceMap": true,
|
|
17
|
-
"jsx": "react-jsx",
|
|
18
|
-
"jsxImportSource": "react"
|
|
19
|
-
},
|
|
20
|
-
"include": ["src/**/*"],
|
|
21
|
-
"exclude": ["node_modules", "dist"]
|
|
22
|
-
}
|
package/vite.config.ts
DELETED
|
@@ -1,363 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'vite';
|
|
2
|
-
import react from '@vitejs/plugin-react';
|
|
3
|
-
import { resolve, join, basename } from 'path';
|
|
4
|
-
import { mkdir, writeFile } from 'fs/promises';
|
|
5
|
-
import { homedir } from 'os';
|
|
6
|
-
import { randomUUID } from 'crypto';
|
|
7
|
-
import { loadEmulationProfiles } from './src/emulation/catalog.js';
|
|
8
|
-
import { configStore } from './src/config.js';
|
|
9
|
-
import { runShellCommand, resolveWorkingDir } from './src/utils/shell.js';
|
|
10
|
-
import { createAgentFromConfig } from './src/agent/factory.js';
|
|
11
|
-
import { listModels } from './src/utils/models.js';
|
|
12
|
-
import { autoActivateSkills, buildSkillPrompt } from './src/skills/index.js';
|
|
13
|
-
import { getSkillRegistry } from './src/skills/registry.js';
|
|
14
|
-
|
|
15
|
-
export default defineConfig({
|
|
16
|
-
root: 'web/dev',
|
|
17
|
-
plugins: [
|
|
18
|
-
react(),
|
|
19
|
-
{
|
|
20
|
-
name: 'ztc-emulation',
|
|
21
|
-
configureServer(server) {
|
|
22
|
-
let skillCache: any[] | null = null;
|
|
23
|
-
const loadSkills = async () => {
|
|
24
|
-
if (skillCache) return skillCache;
|
|
25
|
-
skillCache = await getSkillRegistry();
|
|
26
|
-
return skillCache;
|
|
27
|
-
};
|
|
28
|
-
server.middlewares.use('/__emulation/list', (_req, res) => {
|
|
29
|
-
const payload = JSON.stringify(loadEmulationProfiles().map(profile => ({
|
|
30
|
-
id: profile.id,
|
|
31
|
-
name: profile.name,
|
|
32
|
-
description: profile.description,
|
|
33
|
-
systemPrompt: profile.systemPrompt,
|
|
34
|
-
tools: profile.tools
|
|
35
|
-
})));
|
|
36
|
-
res.statusCode = 200;
|
|
37
|
-
res.setHeader('Content-Type', 'application/json');
|
|
38
|
-
res.end(payload);
|
|
39
|
-
});
|
|
40
|
-
server.middlewares.use('/__config/get', async (_req, res) => {
|
|
41
|
-
await configStore.load(true);
|
|
42
|
-
const payload = JSON.stringify({
|
|
43
|
-
config: configStore.get(),
|
|
44
|
-
locationLabel: configStore.getConfigPath()
|
|
45
|
-
});
|
|
46
|
-
res.statusCode = 200;
|
|
47
|
-
res.setHeader('Content-Type', 'application/json');
|
|
48
|
-
res.end(payload);
|
|
49
|
-
});
|
|
50
|
-
server.middlewares.use('/__config/save', async (req, res) => {
|
|
51
|
-
const body = await new Promise<string>((resolve) => {
|
|
52
|
-
let data = '';
|
|
53
|
-
req.on('data', chunk => { data += chunk; });
|
|
54
|
-
req.on('end', () => resolve(data));
|
|
55
|
-
});
|
|
56
|
-
try {
|
|
57
|
-
const parsed = JSON.parse(body) as { config?: Record<string, unknown> };
|
|
58
|
-
if (parsed.config && typeof parsed.config === 'object') {
|
|
59
|
-
configStore.setAll(parsed.config);
|
|
60
|
-
await configStore.save();
|
|
61
|
-
}
|
|
62
|
-
} catch {
|
|
63
|
-
// ignore invalid payloads
|
|
64
|
-
}
|
|
65
|
-
const payload = JSON.stringify({
|
|
66
|
-
config: configStore.get(),
|
|
67
|
-
locationLabel: configStore.getConfigPath()
|
|
68
|
-
});
|
|
69
|
-
res.statusCode = 200;
|
|
70
|
-
res.setHeader('Content-Type', 'application/json');
|
|
71
|
-
res.end(payload);
|
|
72
|
-
});
|
|
73
|
-
server.middlewares.use('/__shell/cwd', (_req, res) => {
|
|
74
|
-
const payload = JSON.stringify({ cwd: process.cwd() });
|
|
75
|
-
res.statusCode = 200;
|
|
76
|
-
res.setHeader('Content-Type', 'application/json');
|
|
77
|
-
res.end(payload);
|
|
78
|
-
});
|
|
79
|
-
server.middlewares.use('/__shell/resolve', async (req, res) => {
|
|
80
|
-
const body = await new Promise<string>((resolve) => {
|
|
81
|
-
let data = '';
|
|
82
|
-
req.on('data', chunk => { data += chunk; });
|
|
83
|
-
req.on('end', () => resolve(data));
|
|
84
|
-
});
|
|
85
|
-
try {
|
|
86
|
-
const parsed = JSON.parse(body) as { cwd?: string; path?: string };
|
|
87
|
-
const base = parsed.cwd || process.cwd();
|
|
88
|
-
const next = await resolveWorkingDir(base, parsed.path || '');
|
|
89
|
-
const payload = JSON.stringify({ cwd: next });
|
|
90
|
-
res.statusCode = 200;
|
|
91
|
-
res.setHeader('Content-Type', 'application/json');
|
|
92
|
-
res.end(payload);
|
|
93
|
-
return;
|
|
94
|
-
} catch (err) {
|
|
95
|
-
res.statusCode = 400;
|
|
96
|
-
res.setHeader('Content-Type', 'application/json');
|
|
97
|
-
res.end(JSON.stringify({ error: 'Invalid path' }));
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
server.middlewares.use('/__shell/run', async (req, res) => {
|
|
102
|
-
const body = await new Promise<string>((resolve) => {
|
|
103
|
-
let data = '';
|
|
104
|
-
req.on('data', chunk => { data += chunk; });
|
|
105
|
-
req.on('end', () => resolve(data));
|
|
106
|
-
});
|
|
107
|
-
try {
|
|
108
|
-
const parsed = JSON.parse(body) as { command?: string; cwd?: string };
|
|
109
|
-
const command = parsed.command || '';
|
|
110
|
-
const cwd = parsed.cwd || process.cwd();
|
|
111
|
-
const result = await runShellCommand(command, cwd);
|
|
112
|
-
const payload = JSON.stringify(result);
|
|
113
|
-
res.statusCode = 200;
|
|
114
|
-
res.setHeader('Content-Type', 'application/json');
|
|
115
|
-
res.end(payload);
|
|
116
|
-
return;
|
|
117
|
-
} catch (err) {
|
|
118
|
-
res.statusCode = 500;
|
|
119
|
-
res.setHeader('Content-Type', 'application/json');
|
|
120
|
-
res.end(JSON.stringify({ error: 'Command failed' }));
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
server.middlewares.use('/__upload', async (req, res) => {
|
|
125
|
-
if (req.method !== 'POST') {
|
|
126
|
-
res.statusCode = 405;
|
|
127
|
-
res.end();
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
const buffer = await new Promise<Buffer>((resolve) => {
|
|
131
|
-
const chunks: Buffer[] = [];
|
|
132
|
-
req.on('data', chunk => { chunks.push(Buffer.from(chunk)); });
|
|
133
|
-
req.on('end', () => resolve(Buffer.concat(chunks)));
|
|
134
|
-
});
|
|
135
|
-
const headerName = req.headers['x-filename'];
|
|
136
|
-
const rawName = typeof headerName === 'string' && headerName.trim().length > 0 ? headerName : 'upload.bin';
|
|
137
|
-
const safeName = basename(rawName).replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
138
|
-
const dir = join(homedir(), '.ztc', 'uploads');
|
|
139
|
-
await mkdir(dir, { recursive: true });
|
|
140
|
-
const filePath = join(dir, `${Date.now()}-${randomUUID().slice(0, 8)}-${safeName}`);
|
|
141
|
-
await writeFile(filePath, buffer);
|
|
142
|
-
res.statusCode = 200;
|
|
143
|
-
res.setHeader('Content-Type', 'application/json');
|
|
144
|
-
res.end(JSON.stringify({ path: filePath }));
|
|
145
|
-
});
|
|
146
|
-
server.middlewares.use('/__file', async (req, res) => {
|
|
147
|
-
const url = new URL(req.url || '', 'http://localhost');
|
|
148
|
-
const rawPath = url.searchParams.get('path');
|
|
149
|
-
if (!rawPath) {
|
|
150
|
-
res.statusCode = 400;
|
|
151
|
-
res.end();
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
const allowedRoots = [
|
|
155
|
-
join(homedir(), '.ztc', 'uploads'),
|
|
156
|
-
join(homedir(), '.ztc', 'clipboard')
|
|
157
|
-
];
|
|
158
|
-
const resolvedPath = resolve(rawPath);
|
|
159
|
-
const allowed = allowedRoots.some(root => resolvedPath.startsWith(resolve(root)));
|
|
160
|
-
if (!allowed) {
|
|
161
|
-
res.statusCode = 403;
|
|
162
|
-
res.end();
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
try {
|
|
166
|
-
const data = await (await import('fs/promises')).readFile(resolvedPath);
|
|
167
|
-
res.statusCode = 200;
|
|
168
|
-
res.setHeader('Content-Type', 'application/octet-stream');
|
|
169
|
-
res.end(data);
|
|
170
|
-
} catch {
|
|
171
|
-
res.statusCode = 404;
|
|
172
|
-
res.end();
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
server.middlewares.use('/__agent/run', async (req, res) => {
|
|
176
|
-
const body = await new Promise<string>((resolve) => {
|
|
177
|
-
let data = '';
|
|
178
|
-
req.on('data', chunk => { data += chunk; });
|
|
179
|
-
req.on('end', () => resolve(data));
|
|
180
|
-
});
|
|
181
|
-
try {
|
|
182
|
-
const parsed = JSON.parse(body) as { messages?: unknown[] };
|
|
183
|
-
const messages = Array.isArray(parsed.messages) ? parsed.messages : [];
|
|
184
|
-
const lastUser = [...messages].reverse().find(msg => msg && typeof msg === 'object' && (msg as any).role === 'user');
|
|
185
|
-
const text = lastUser && typeof (lastUser as any).content === 'string' ? (lastUser as any).content : '';
|
|
186
|
-
const skills = await loadSkills();
|
|
187
|
-
const activated = autoActivateSkills(text, skills);
|
|
188
|
-
const skillPrompt = buildSkillPrompt(activated);
|
|
189
|
-
await configStore.load();
|
|
190
|
-
const provider = configStore.getProvider();
|
|
191
|
-
const apiKey = configStore.getApiKey(provider);
|
|
192
|
-
const agent = createAgentFromConfig({
|
|
193
|
-
config: configStore.get(),
|
|
194
|
-
provider,
|
|
195
|
-
apiKey,
|
|
196
|
-
openaiCompatibleBaseUrl: configStore.getOpenAICompatibleBaseUrl(),
|
|
197
|
-
emulationId: configStore.getEmulationId(),
|
|
198
|
-
skillPrompt
|
|
199
|
-
});
|
|
200
|
-
if (!agent) {
|
|
201
|
-
res.statusCode = 400;
|
|
202
|
-
res.setHeader('Content-Type', 'application/json');
|
|
203
|
-
res.end(JSON.stringify({ error: 'No API key configured' }));
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
const result = await agent.run(messages as any);
|
|
207
|
-
res.statusCode = 200;
|
|
208
|
-
res.setHeader('Content-Type', 'application/json');
|
|
209
|
-
res.end(JSON.stringify({
|
|
210
|
-
content: result.content,
|
|
211
|
-
toolCalls: result.toolCalls,
|
|
212
|
-
usage: result.usage
|
|
213
|
-
}));
|
|
214
|
-
return;
|
|
215
|
-
} catch (err) {
|
|
216
|
-
const message = err instanceof Error ? err.message : 'Agent error';
|
|
217
|
-
res.statusCode = 500;
|
|
218
|
-
res.setHeader('Content-Type', 'application/json');
|
|
219
|
-
res.end(JSON.stringify({ error: message }));
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
server.middlewares.use('/__agent/stream', async (req, res) => {
|
|
223
|
-
const body = await new Promise<string>((resolve) => {
|
|
224
|
-
let data = '';
|
|
225
|
-
req.on('data', chunk => { data += chunk; });
|
|
226
|
-
req.on('end', () => resolve(data));
|
|
227
|
-
});
|
|
228
|
-
res.writeHead(200, {
|
|
229
|
-
'Content-Type': 'text/event-stream',
|
|
230
|
-
'Cache-Control': 'no-cache',
|
|
231
|
-
Connection: 'keep-alive'
|
|
232
|
-
});
|
|
233
|
-
const send = (event: string, payload: unknown) => {
|
|
234
|
-
res.write(`event: ${event}\n`);
|
|
235
|
-
res.write(`data: ${JSON.stringify(payload)}\n\n`);
|
|
236
|
-
};
|
|
237
|
-
let cleanup: (() => void) | null = null;
|
|
238
|
-
try {
|
|
239
|
-
const parsed = JSON.parse(body) as { messages?: unknown[] };
|
|
240
|
-
const messages = Array.isArray(parsed.messages) ? parsed.messages : [];
|
|
241
|
-
const lastUser = [...messages].reverse().find(msg => msg && typeof msg === 'object' && (msg as any).role === 'user');
|
|
242
|
-
const text = lastUser && typeof (lastUser as any).content === 'string' ? (lastUser as any).content : '';
|
|
243
|
-
const skills = await loadSkills();
|
|
244
|
-
const activated = autoActivateSkills(text, skills);
|
|
245
|
-
const skillPrompt = buildSkillPrompt(activated);
|
|
246
|
-
if (activated.length > 0) {
|
|
247
|
-
send('skill_activated', { skills: activated.map(skill => ({ id: skill.id, name: skill.name })) });
|
|
248
|
-
}
|
|
249
|
-
await configStore.load();
|
|
250
|
-
const provider = configStore.getProvider();
|
|
251
|
-
const apiKey = configStore.getApiKey(provider);
|
|
252
|
-
const agent = createAgentFromConfig({
|
|
253
|
-
config: configStore.get(),
|
|
254
|
-
provider,
|
|
255
|
-
apiKey,
|
|
256
|
-
openaiCompatibleBaseUrl: configStore.getOpenAICompatibleBaseUrl(),
|
|
257
|
-
emulationId: configStore.getEmulationId(),
|
|
258
|
-
skillPrompt
|
|
259
|
-
});
|
|
260
|
-
if (!agent) {
|
|
261
|
-
send('error', { error: 'No API key configured' });
|
|
262
|
-
res.end();
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
cleanup = agent.on((event) => {
|
|
266
|
-
if (event.type === 'stream_start') {
|
|
267
|
-
send('stream_start', {});
|
|
268
|
-
} else if (event.type === 'stream_delta') {
|
|
269
|
-
send('stream_delta', { content: event.content });
|
|
270
|
-
} else if (event.type === 'stream_end') {
|
|
271
|
-
send('stream_end', {});
|
|
272
|
-
} else if (event.type === 'token_usage') {
|
|
273
|
-
send('token_usage', { usage: event.usage });
|
|
274
|
-
} else if (event.type === 'tool_start') {
|
|
275
|
-
send('tool_start', { tool: event.tool, args: event.args });
|
|
276
|
-
} else if (event.type === 'tool_end') {
|
|
277
|
-
send('tool_end', { tool: event.tool, result: event.result });
|
|
278
|
-
} else if (event.type === 'tool_error') {
|
|
279
|
-
send('tool_error', { tool: event.tool, error: event.error });
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
const result = await agent.run(messages as any);
|
|
283
|
-
send('result', {
|
|
284
|
-
content: result.content,
|
|
285
|
-
toolCalls: result.toolCalls,
|
|
286
|
-
usage: result.usage
|
|
287
|
-
});
|
|
288
|
-
res.end();
|
|
289
|
-
} catch (err) {
|
|
290
|
-
const message = err instanceof Error ? err.message : 'Agent error';
|
|
291
|
-
send('error', { error: message });
|
|
292
|
-
res.end();
|
|
293
|
-
} finally {
|
|
294
|
-
cleanup?.();
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
server.middlewares.use('/__models/list', async (req, res) => {
|
|
298
|
-
try {
|
|
299
|
-
await configStore.load();
|
|
300
|
-
const url = new URL(req.url || '', 'http://localhost');
|
|
301
|
-
const provider = url.searchParams.get('provider') || configStore.getProvider();
|
|
302
|
-
const models = await listModels({
|
|
303
|
-
provider,
|
|
304
|
-
apiKey: configStore.getApiKey(provider),
|
|
305
|
-
baseUrl: configStore.getOpenAICompatibleBaseUrl()
|
|
306
|
-
});
|
|
307
|
-
res.statusCode = 200;
|
|
308
|
-
res.setHeader('Content-Type', 'application/json');
|
|
309
|
-
res.end(JSON.stringify({ models }));
|
|
310
|
-
} catch (err) {
|
|
311
|
-
const message = err instanceof Error ? err.message : 'Models error';
|
|
312
|
-
res.statusCode = 500;
|
|
313
|
-
res.setHeader('Content-Type', 'application/json');
|
|
314
|
-
res.end(JSON.stringify({ error: message }));
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
|
-
server.middlewares.use('/__skills/list', async (_req, res) => {
|
|
318
|
-
try {
|
|
319
|
-
const skills = await loadSkills();
|
|
320
|
-
res.statusCode = 200;
|
|
321
|
-
res.setHeader('Content-Type', 'application/json');
|
|
322
|
-
res.end(JSON.stringify({ skills }));
|
|
323
|
-
} catch (err) {
|
|
324
|
-
const message = err instanceof Error ? err.message : 'Skills error';
|
|
325
|
-
res.statusCode = 500;
|
|
326
|
-
res.setHeader('Content-Type', 'application/json');
|
|
327
|
-
res.end(JSON.stringify({ error: message }));
|
|
328
|
-
}
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
],
|
|
333
|
-
esbuild: {
|
|
334
|
-
target: 'esnext'
|
|
335
|
-
},
|
|
336
|
-
resolve: {
|
|
337
|
-
alias: [
|
|
338
|
-
{ find: '@src', replacement: resolve(__dirname, 'src') }
|
|
339
|
-
]
|
|
340
|
-
},
|
|
341
|
-
build: {
|
|
342
|
-
target: 'esnext',
|
|
343
|
-
outDir: '../../dist-web',
|
|
344
|
-
emptyOutDir: true
|
|
345
|
-
},
|
|
346
|
-
optimizeDeps: {
|
|
347
|
-
esbuildOptions: {
|
|
348
|
-
target: 'esnext'
|
|
349
|
-
}
|
|
350
|
-
},
|
|
351
|
-
ssr: {
|
|
352
|
-
target: 'esnext'
|
|
353
|
-
},
|
|
354
|
-
define: {
|
|
355
|
-
'import.meta.env.LEGACY': 'false'
|
|
356
|
-
},
|
|
357
|
-
server: {
|
|
358
|
-
port: 5175,
|
|
359
|
-
fs: {
|
|
360
|
-
allow: ['.']
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
});
|
|
File without changes
|