zerg-ztc 0.1.10 → 0.1.12
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/.gitkeep +0 -0
- package/bin/ztc-audio-darwin-arm64 +0 -0
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +63 -2
- package/dist/App.js.map +1 -1
- package/dist/agent/commands/dictation.d.ts +3 -0
- package/dist/agent/commands/dictation.d.ts.map +1 -0
- package/dist/agent/commands/dictation.js +10 -0
- package/dist/agent/commands/dictation.js.map +1 -0
- package/dist/agent/commands/index.d.ts.map +1 -1
- package/dist/agent/commands/index.js +2 -1
- package/dist/agent/commands/index.js.map +1 -1
- package/dist/agent/commands/types.d.ts +7 -0
- package/dist/agent/commands/types.d.ts.map +1 -1
- package/dist/components/InputArea.d.ts +1 -0
- package/dist/components/InputArea.d.ts.map +1 -1
- package/dist/components/InputArea.js +591 -43
- package/dist/components/InputArea.js.map +1 -1
- package/dist/components/SingleMessage.d.ts.map +1 -1
- package/dist/components/SingleMessage.js +157 -7
- package/dist/components/SingleMessage.js.map +1 -1
- package/dist/config/types.d.ts +6 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/ui/views/status_bar.js +2 -2
- package/dist/ui/views/status_bar.js.map +1 -1
- package/dist/utils/dictation.d.ts +46 -0
- package/dist/utils/dictation.d.ts.map +1 -0
- package/dist/utils/dictation.js +409 -0
- package/dist/utils/dictation.js.map +1 -0
- package/dist/utils/dictation_native.d.ts +51 -0
- package/dist/utils/dictation_native.d.ts.map +1 -0
- package/dist/utils/dictation_native.js +236 -0
- package/dist/utils/dictation_native.js.map +1 -0
- package/dist/utils/path_format.d.ts +20 -0
- package/dist/utils/path_format.d.ts.map +1 -0
- package/dist/utils/path_format.js +90 -0
- package/dist/utils/path_format.js.map +1 -0
- package/dist/utils/table.d.ts +38 -0
- package/dist/utils/table.d.ts.map +1 -0
- package/dist/utils/table.js +133 -0
- package/dist/utils/table.js.map +1 -0
- package/dist/utils/tool_trace.d.ts +7 -2
- package/dist/utils/tool_trace.d.ts.map +1 -1
- package/dist/utils/tool_trace.js +156 -51
- package/dist/utils/tool_trace.js.map +1 -1
- package/package.json +5 -1
- package/src/App.tsx +0 -813
- 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/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 -46
- 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 -80
- 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 -1096
- package/src/components/MessageList.tsx +0 -71
- package/src/components/SingleMessage.tsx +0 -59
- package/src/components/StatusBar.tsx +0 -55
- package/src/components/index.tsx +0 -8
- package/src/config/types.ts +0 -12
- 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/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/shell.ts +0 -72
- package/src/utils/spinner_frames.ts +0 -1
- package/src/utils/spinner_verbs.ts +0 -23
- package/src/utils/tool_summary.ts +0 -56
- package/src/utils/tool_trace.ts +0 -216
- 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
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import React, { useMemo } from 'react';
|
|
2
|
-
import { LayoutNode, LayoutFrame, computeLayout } from '../core/index.js';
|
|
3
|
-
|
|
4
|
-
type BadgeInfo = {
|
|
5
|
-
type: 'paste' | 'file' | 'image';
|
|
6
|
-
preview: string;
|
|
7
|
-
full: string;
|
|
8
|
-
path?: string;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
interface FrameRendererProps {
|
|
12
|
-
node: LayoutNode;
|
|
13
|
-
cols: number;
|
|
14
|
-
rows: number;
|
|
15
|
-
cellWidth: number;
|
|
16
|
-
cellHeight: number;
|
|
17
|
-
debugBorders?: boolean;
|
|
18
|
-
onBadgeHover?: (badge: BadgeInfo, x: number, y: number) => void;
|
|
19
|
-
onBadgeLeave?: () => void;
|
|
20
|
-
onBadgeClick?: (badge: BadgeInfo, x: number, y: number) => void;
|
|
21
|
-
onBadgeDblClick?: (badge: BadgeInfo, x: number, y: number) => void;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function colorMap(color?: string): string {
|
|
25
|
-
const map: Record<string, string> = {
|
|
26
|
-
gray: '#9a9a9a',
|
|
27
|
-
magenta: '#c084fc',
|
|
28
|
-
yellow: '#facc15',
|
|
29
|
-
green: '#34d399',
|
|
30
|
-
red: '#f87171',
|
|
31
|
-
cyan: '#67e8f9',
|
|
32
|
-
blue: '#60a5fa',
|
|
33
|
-
white: '#e5e7eb'
|
|
34
|
-
};
|
|
35
|
-
return map[color || ''] || color || '#e6e6e6';
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function renderFrame(
|
|
39
|
-
frame: LayoutFrame,
|
|
40
|
-
keyPrefix: string,
|
|
41
|
-
cellWidth: number,
|
|
42
|
-
cellHeight: number,
|
|
43
|
-
debugBorders: boolean,
|
|
44
|
-
onBadgeHover?: (badge: BadgeInfo, x: number, y: number) => void,
|
|
45
|
-
onBadgeLeave?: () => void,
|
|
46
|
-
onBadgeClick?: (badge: BadgeInfo, x: number, y: number) => void,
|
|
47
|
-
onBadgeDblClick?: (badge: BadgeInfo, x: number, y: number) => void,
|
|
48
|
-
offsetX = 0,
|
|
49
|
-
offsetY = 0,
|
|
50
|
-
out: React.ReactElement[] = []
|
|
51
|
-
): React.ReactElement[] {
|
|
52
|
-
const x = offsetX + frame.x;
|
|
53
|
-
const y = offsetY + frame.y;
|
|
54
|
-
const keyBase = `${keyPrefix}-${x}-${y}`;
|
|
55
|
-
|
|
56
|
-
if (frame.node.type === 'text') {
|
|
57
|
-
const style = frame.node.style || {};
|
|
58
|
-
if (debugBorders) {
|
|
59
|
-
out.push(
|
|
60
|
-
<div
|
|
61
|
-
key={`${keyBase}-debug`}
|
|
62
|
-
style={{
|
|
63
|
-
position: 'absolute',
|
|
64
|
-
left: x * cellWidth,
|
|
65
|
-
top: y * cellHeight,
|
|
66
|
-
width: frame.width * cellWidth,
|
|
67
|
-
height: frame.height * cellHeight,
|
|
68
|
-
borderStyle: 'dashed',
|
|
69
|
-
borderWidth: 1,
|
|
70
|
-
borderColor: '#3a3a3a',
|
|
71
|
-
pointerEvents: 'none'
|
|
72
|
-
}}
|
|
73
|
-
/>
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
const badge = style.badge;
|
|
77
|
-
out.push(
|
|
78
|
-
<span
|
|
79
|
-
key={`${keyBase}-text`}
|
|
80
|
-
style={{
|
|
81
|
-
position: 'absolute',
|
|
82
|
-
left: x * cellWidth,
|
|
83
|
-
top: y * cellHeight,
|
|
84
|
-
color: style.inverse ? '#111' : colorMap(style.color),
|
|
85
|
-
fontWeight: style.bold ? 600 : undefined,
|
|
86
|
-
opacity: style.dimColor ? 0.6 : undefined,
|
|
87
|
-
background: style.inverse ? '#e6e6e6' : undefined,
|
|
88
|
-
colorScheme: 'only light',
|
|
89
|
-
cursor: badge ? 'pointer' : undefined
|
|
90
|
-
}}
|
|
91
|
-
title={badge?.preview || undefined}
|
|
92
|
-
onMouseEnter={badge ? (evt) => onBadgeHover?.(badge, evt.clientX, evt.clientY) : undefined}
|
|
93
|
-
onMouseLeave={badge ? () => onBadgeLeave?.() : undefined}
|
|
94
|
-
onClick={badge ? (evt) => onBadgeClick?.(badge, evt.clientX, evt.clientY) : undefined}
|
|
95
|
-
onDoubleClick={badge ? (evt) => onBadgeDblClick?.(badge, evt.clientX, evt.clientY) : undefined}
|
|
96
|
-
>
|
|
97
|
-
{frame.node.text}
|
|
98
|
-
</span>
|
|
99
|
-
);
|
|
100
|
-
return out;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (frame.node.type === 'box' && (frame.node.style?.borderStyle || debugBorders)) {
|
|
104
|
-
out.push(
|
|
105
|
-
<div
|
|
106
|
-
key={`${keyBase}-border`}
|
|
107
|
-
style={{
|
|
108
|
-
position: 'absolute',
|
|
109
|
-
left: x * cellWidth,
|
|
110
|
-
top: y * cellHeight,
|
|
111
|
-
width: frame.width * cellWidth,
|
|
112
|
-
height: frame.height * cellHeight,
|
|
113
|
-
borderStyle: frame.node.style?.borderStyle ? 'solid' : 'dashed',
|
|
114
|
-
borderWidth: 1,
|
|
115
|
-
borderColor: colorMap(frame.node.style?.borderColor || '#3a3a3a'),
|
|
116
|
-
pointerEvents: 'none'
|
|
117
|
-
}}
|
|
118
|
-
/>
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
frame.children.forEach((child, idx) => {
|
|
123
|
-
renderFrame(child, `${keyBase}-${idx}`, cellWidth, cellHeight, debugBorders, onBadgeHover, onBadgeLeave, onBadgeClick, onBadgeDblClick, x, y, out);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
return out;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export const WebFrameRenderer: React.FC<FrameRendererProps> = ({
|
|
130
|
-
node,
|
|
131
|
-
cols,
|
|
132
|
-
rows,
|
|
133
|
-
cellWidth,
|
|
134
|
-
cellHeight,
|
|
135
|
-
debugBorders = false,
|
|
136
|
-
onBadgeHover,
|
|
137
|
-
onBadgeLeave,
|
|
138
|
-
onBadgeClick,
|
|
139
|
-
onBadgeDblClick
|
|
140
|
-
}) => {
|
|
141
|
-
const frame = useMemo(() => computeLayout(node, cols, rows), [node, cols, rows]);
|
|
142
|
-
const elements = useMemo(
|
|
143
|
-
() => renderFrame(frame, 'root', cellWidth, cellHeight, debugBorders, onBadgeHover, onBadgeLeave, onBadgeClick, onBadgeDblClick),
|
|
144
|
-
[frame, cellWidth, cellHeight, debugBorders, onBadgeHover, onBadgeLeave, onBadgeClick, onBadgeDblClick]
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
return <>{elements}</>;
|
|
148
|
-
};
|
package/src/ui/web/index.tsx
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { WebNode } from './render.js';
|
package/src/ui/web/render.tsx
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { LayoutNode, TextNode } from '../core/types.js';
|
|
3
|
-
import { toCssStyle } from '../core/style.js';
|
|
4
|
-
|
|
5
|
-
const monoStyle: React.CSSProperties = {
|
|
6
|
-
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
|
7
|
-
fontSize: 14,
|
|
8
|
-
lineHeight: '16px',
|
|
9
|
-
whiteSpace: 'pre'
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
function renderText(node: TextNode): React.ReactElement {
|
|
13
|
-
const style: React.CSSProperties = {
|
|
14
|
-
...monoStyle,
|
|
15
|
-
color: node.style?.color,
|
|
16
|
-
fontWeight: node.style?.bold ? 600 : undefined,
|
|
17
|
-
opacity: node.style?.dimColor ? 0.6 : undefined,
|
|
18
|
-
background: node.style?.inverse ? '#111' : undefined,
|
|
19
|
-
colorScheme: 'only light'
|
|
20
|
-
};
|
|
21
|
-
return <span style={style}>{node.text}</span>;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function WebNode({ node }: { node: LayoutNode }): React.ReactElement {
|
|
25
|
-
if (node.type === 'text') {
|
|
26
|
-
return renderText(node);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const style = {
|
|
30
|
-
...monoStyle,
|
|
31
|
-
...toCssStyle(node.style)
|
|
32
|
-
} as React.CSSProperties;
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<div style={style}>
|
|
36
|
-
{node.children.map((child, index) => (
|
|
37
|
-
<WebNode key={`${child.type}-${index}`} node={child} />
|
|
38
|
-
))}
|
|
39
|
-
</div>
|
|
40
|
-
);
|
|
41
|
-
}
|
package/src/utils/clipboard.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'child_process';
|
|
2
|
-
|
|
3
|
-
function runPipe(command: string, args: string[], input: string): Promise<void> {
|
|
4
|
-
return new Promise((resolve, reject) => {
|
|
5
|
-
const child = spawn(command, args, { stdio: ['pipe', 'ignore', 'pipe'] });
|
|
6
|
-
let err = '';
|
|
7
|
-
child.stderr.on('data', chunk => {
|
|
8
|
-
err += chunk.toString();
|
|
9
|
-
});
|
|
10
|
-
child.on('error', reject);
|
|
11
|
-
child.on('close', (code) => {
|
|
12
|
-
if (code === 0) resolve();
|
|
13
|
-
else reject(new Error(err || `Clipboard command failed (${command})`));
|
|
14
|
-
});
|
|
15
|
-
child.stdin.write(input);
|
|
16
|
-
child.stdin.end();
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export async function writeClipboard(text: string): Promise<void> {
|
|
21
|
-
const platform = process.platform;
|
|
22
|
-
if (platform === 'darwin') {
|
|
23
|
-
await runPipe('pbcopy', [], text);
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
if (platform === 'win32') {
|
|
27
|
-
await runPipe('clip', [], text);
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
await runPipe('wl-copy', [], text);
|
|
33
|
-
return;
|
|
34
|
-
} catch {
|
|
35
|
-
// fall through
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
await runPipe('xclip', ['-selection', 'clipboard'], text);
|
|
39
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { execFile } from 'child_process';
|
|
2
|
-
import { promisify } from 'util';
|
|
3
|
-
import { mkdir } from 'fs/promises';
|
|
4
|
-
import { homedir } from 'os';
|
|
5
|
-
import { join } from 'path';
|
|
6
|
-
import { randomUUID } from 'crypto';
|
|
7
|
-
|
|
8
|
-
const execFileAsync = promisify(execFile);
|
|
9
|
-
|
|
10
|
-
async function hasCommand(command: string): Promise<boolean> {
|
|
11
|
-
try {
|
|
12
|
-
await execFileAsync('which', [command]);
|
|
13
|
-
return true;
|
|
14
|
-
} catch {
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function saveClipboardImage(): Promise<string | null> {
|
|
20
|
-
if (process.platform !== 'darwin') {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const hasPngPaste = await hasCommand('pngpaste');
|
|
25
|
-
if (!hasPngPaste) {
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const targetDir = join(homedir(), '.ztc', 'clipboard');
|
|
30
|
-
await mkdir(targetDir, { recursive: true });
|
|
31
|
-
const filename = `clipboard-${Date.now()}-${randomUUID().slice(0, 8)}.png`;
|
|
32
|
-
const targetPath = join(targetDir, filename);
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
await execFileAsync('pngpaste', [targetPath]);
|
|
36
|
-
return targetPath;
|
|
37
|
-
} catch {
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
}
|
package/src/utils/diff.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
interface DiffLine {
|
|
2
|
-
type: 'context' | 'add' | 'remove';
|
|
3
|
-
text: string;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export function buildSimpleDiff(before: string, after: string, maxLines = 120, context = 2): string {
|
|
7
|
-
const beforeLines = before.split('\n');
|
|
8
|
-
const afterLines = after.split('\n');
|
|
9
|
-
const maxLen = Math.max(beforeLines.length, afterLines.length);
|
|
10
|
-
|
|
11
|
-
const changes = new Set<number>();
|
|
12
|
-
for (let i = 0; i < maxLen; i += 1) {
|
|
13
|
-
const b = beforeLines[i];
|
|
14
|
-
const a = afterLines[i];
|
|
15
|
-
if (b !== a) {
|
|
16
|
-
changes.add(i);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const include = new Set<number>();
|
|
21
|
-
for (const idx of changes) {
|
|
22
|
-
for (let i = Math.max(0, idx - context); i <= idx + context; i += 1) {
|
|
23
|
-
include.add(i);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const lines: DiffLine[] = [];
|
|
28
|
-
for (let i = 0; i < maxLen; i += 1) {
|
|
29
|
-
if (!include.has(i)) continue;
|
|
30
|
-
const b = beforeLines[i];
|
|
31
|
-
const a = afterLines[i];
|
|
32
|
-
if (b === a) {
|
|
33
|
-
lines.push({ type: 'context', text: b || '' });
|
|
34
|
-
} else {
|
|
35
|
-
if (b !== undefined) lines.push({ type: 'remove', text: b || '' });
|
|
36
|
-
if (a !== undefined) lines.push({ type: 'add', text: a || '' });
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const clipped = lines.slice(0, maxLines);
|
|
41
|
-
if (lines.length > maxLines) {
|
|
42
|
-
clipped.push({ type: 'context', text: '... diff truncated ...' });
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return clipped
|
|
46
|
-
.map(line => {
|
|
47
|
-
if (line.type === 'add') return `+ ${line.text}`;
|
|
48
|
-
if (line.type === 'remove') return `- ${line.text}`;
|
|
49
|
-
return ` ${line.text}`;
|
|
50
|
-
})
|
|
51
|
-
.join('\n');
|
|
52
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { execFile } from 'child_process';
|
|
2
|
-
import { promisify } from 'util';
|
|
3
|
-
|
|
4
|
-
const execFileAsync = promisify(execFile);
|
|
5
|
-
const cache = new Map<string, string[]>();
|
|
6
|
-
|
|
7
|
-
async function hasCommand(command: string): Promise<boolean> {
|
|
8
|
-
try {
|
|
9
|
-
await execFileAsync('which', [command]);
|
|
10
|
-
return true;
|
|
11
|
-
} catch {
|
|
12
|
-
return false;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function renderImagePreview(path: string, width = 40, height = 20): Promise<string[] | null> {
|
|
17
|
-
const key = `${path}:${width}x${height}`;
|
|
18
|
-
const cached = cache.get(key);
|
|
19
|
-
if (cached) return cached;
|
|
20
|
-
if (!(await hasCommand('chafa'))) {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
try {
|
|
24
|
-
const { stdout } = await execFileAsync('chafa', [
|
|
25
|
-
'--colors=none',
|
|
26
|
-
'--symbols=block',
|
|
27
|
-
`--size=${width}x${height}`,
|
|
28
|
-
path
|
|
29
|
-
]);
|
|
30
|
-
const lines = stdout.split('\n').filter(line => line.length > 0);
|
|
31
|
-
cache.set(key, lines);
|
|
32
|
-
return lines;
|
|
33
|
-
} catch {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
}
|
package/src/utils/models.ts
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
interface ListModelsOptions {
|
|
2
|
-
provider: string;
|
|
3
|
-
apiKey?: string;
|
|
4
|
-
baseUrl?: string;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
async function parseList(response: Response): Promise<string[]> {
|
|
8
|
-
const data = await response.json() as Record<string, unknown>;
|
|
9
|
-
const array =
|
|
10
|
-
(Array.isArray(data.data) && data.data) ||
|
|
11
|
-
(Array.isArray(data.models) && data.models) ||
|
|
12
|
-
(Array.isArray(data.items) && data.items) ||
|
|
13
|
-
[];
|
|
14
|
-
|
|
15
|
-
const models = array
|
|
16
|
-
.map(item => {
|
|
17
|
-
if (typeof item === 'string') return item;
|
|
18
|
-
if (item && typeof item === 'object') {
|
|
19
|
-
const obj = item as Record<string, unknown>;
|
|
20
|
-
if (typeof obj.id === 'string') return obj.id;
|
|
21
|
-
if (typeof obj.name === 'string') return obj.name;
|
|
22
|
-
}
|
|
23
|
-
return null;
|
|
24
|
-
})
|
|
25
|
-
.filter((value): value is string => typeof value === 'string');
|
|
26
|
-
|
|
27
|
-
return models;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export async function listModels(options: ListModelsOptions): Promise<string[]> {
|
|
31
|
-
const provider = options.provider;
|
|
32
|
-
const apiKey = options.apiKey || '';
|
|
33
|
-
|
|
34
|
-
if (!apiKey) {
|
|
35
|
-
throw new Error(`No API key configured for ${provider}`);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (provider === 'anthropic') {
|
|
39
|
-
const res = await fetch('https://api.anthropic.com/v1/models', {
|
|
40
|
-
headers: {
|
|
41
|
-
'x-api-key': apiKey,
|
|
42
|
-
'anthropic-version': '2023-06-01'
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
if (!res.ok) {
|
|
46
|
-
throw new Error(`Anthropic error (${res.status})`);
|
|
47
|
-
}
|
|
48
|
-
return parseList(res);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (provider === 'openai') {
|
|
52
|
-
const res = await fetch('https://api.openai.com/v1/models', {
|
|
53
|
-
headers: {
|
|
54
|
-
Authorization: `Bearer ${apiKey}`
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
if (!res.ok) {
|
|
58
|
-
throw new Error(`OpenAI error (${res.status})`);
|
|
59
|
-
}
|
|
60
|
-
return parseList(res);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (provider === 'openai_compatible') {
|
|
64
|
-
const baseUrl = options.baseUrl || 'https://api.openai.com/v1';
|
|
65
|
-
const res = await fetch(`${baseUrl.replace(/\/$/, '')}/models`, {
|
|
66
|
-
headers: {
|
|
67
|
-
Authorization: `Bearer ${apiKey}`
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
if (!res.ok) {
|
|
71
|
-
throw new Error(`OpenAI-compatible error (${res.status})`);
|
|
72
|
-
}
|
|
73
|
-
return parseList(res);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (provider === 'gemini') {
|
|
77
|
-
const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models?key=${apiKey}`);
|
|
78
|
-
if (!res.ok) {
|
|
79
|
-
throw new Error(`Gemini error (${res.status})`);
|
|
80
|
-
}
|
|
81
|
-
return parseList(res);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (provider === 'inception') {
|
|
85
|
-
const baseUrl = options.baseUrl || 'https://api.inception.ai/v1';
|
|
86
|
-
const res = await fetch(`${baseUrl.replace(/\/$/, '')}/models`, {
|
|
87
|
-
headers: {
|
|
88
|
-
Authorization: `Bearer ${apiKey}`
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
if (!res.ok) {
|
|
92
|
-
throw new Error(`Inception error (${res.status})`);
|
|
93
|
-
}
|
|
94
|
-
return parseList(res);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
throw new Error(`Unknown provider: ${provider}`);
|
|
98
|
-
}
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import { readdir } from 'fs/promises';
|
|
2
|
-
import { resolve, dirname, basename } from 'path';
|
|
3
|
-
import { homedir } from 'os';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Get path completions for tab autocomplete
|
|
7
|
-
*/
|
|
8
|
-
export async function getPathCompletions(
|
|
9
|
-
partial: string,
|
|
10
|
-
cwd: string
|
|
11
|
-
): Promise<string[]> {
|
|
12
|
-
// Expand tilde
|
|
13
|
-
let expandedPartial = partial;
|
|
14
|
-
if (partial.startsWith('~/')) {
|
|
15
|
-
expandedPartial = resolve(homedir(), partial.slice(2));
|
|
16
|
-
} else if (partial === '~') {
|
|
17
|
-
expandedPartial = homedir();
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Resolve the path relative to cwd
|
|
21
|
-
const fullPath = resolve(cwd, expandedPartial);
|
|
22
|
-
|
|
23
|
-
// Determine the directory to list and the prefix to match
|
|
24
|
-
let dirToList: string;
|
|
25
|
-
let prefix: string;
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
const { stat } = await import('fs/promises');
|
|
29
|
-
const stats = await stat(fullPath);
|
|
30
|
-
if (stats.isDirectory()) {
|
|
31
|
-
// If it's a directory, list its contents
|
|
32
|
-
dirToList = fullPath;
|
|
33
|
-
prefix = '';
|
|
34
|
-
} else {
|
|
35
|
-
// It's a file, no completions
|
|
36
|
-
return [];
|
|
37
|
-
}
|
|
38
|
-
} catch {
|
|
39
|
-
// Path doesn't exist, so complete the last component
|
|
40
|
-
dirToList = dirname(fullPath);
|
|
41
|
-
prefix = basename(fullPath).toLowerCase();
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
try {
|
|
45
|
-
const entries = await readdir(dirToList, { withFileTypes: true });
|
|
46
|
-
const matches = entries
|
|
47
|
-
.filter(entry => entry.name.toLowerCase().startsWith(prefix))
|
|
48
|
-
.map(entry => {
|
|
49
|
-
const name = entry.name;
|
|
50
|
-
const suffix = entry.isDirectory() ? '/' : '';
|
|
51
|
-
return name + suffix;
|
|
52
|
-
})
|
|
53
|
-
.sort();
|
|
54
|
-
|
|
55
|
-
return matches;
|
|
56
|
-
} catch {
|
|
57
|
-
return [];
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Complete a partial path, returning the completed path or null if no unique completion
|
|
63
|
-
*/
|
|
64
|
-
export async function completePath(
|
|
65
|
-
partial: string,
|
|
66
|
-
cwd: string
|
|
67
|
-
): Promise<{ completed: string; isDirectory: boolean; alternatives: string[] } | null> {
|
|
68
|
-
// Handle empty partial - list cwd contents
|
|
69
|
-
if (!partial || partial.trim() === '') {
|
|
70
|
-
try {
|
|
71
|
-
const entries = await readdir(cwd, { withFileTypes: true });
|
|
72
|
-
const matches = entries.filter(e => !e.name.startsWith('.')).sort();
|
|
73
|
-
if (matches.length === 0) return null;
|
|
74
|
-
if (matches.length === 1) {
|
|
75
|
-
const entry = matches[0];
|
|
76
|
-
const suffix = entry.isDirectory() ? '/' : '';
|
|
77
|
-
return { completed: entry.name + suffix, isDirectory: entry.isDirectory(), alternatives: [] };
|
|
78
|
-
}
|
|
79
|
-
const alternatives = matches.map(e => e.name + (e.isDirectory() ? '/' : ''));
|
|
80
|
-
return { completed: '', isDirectory: false, alternatives };
|
|
81
|
-
} catch {
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Expand tilde for internal processing
|
|
87
|
-
let expandedPartial = partial;
|
|
88
|
-
let tildePrefix = '';
|
|
89
|
-
if (partial.startsWith('~/')) {
|
|
90
|
-
expandedPartial = resolve(homedir(), partial.slice(2));
|
|
91
|
-
tildePrefix = '~/';
|
|
92
|
-
} else if (partial === '~') {
|
|
93
|
-
return { completed: '~/', isDirectory: true, alternatives: [] };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const fullPath = resolve(cwd, expandedPartial);
|
|
97
|
-
let dirToList: string;
|
|
98
|
-
let prefix: string;
|
|
99
|
-
let basePath: string;
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
const { stat } = await import('fs/promises');
|
|
103
|
-
const stats = await stat(fullPath);
|
|
104
|
-
if (stats.isDirectory()) {
|
|
105
|
-
// Already a complete directory, append / if not present
|
|
106
|
-
if (!partial.endsWith('/')) {
|
|
107
|
-
return { completed: partial + '/', isDirectory: true, alternatives: [] };
|
|
108
|
-
}
|
|
109
|
-
// List directory contents for alternatives
|
|
110
|
-
dirToList = fullPath;
|
|
111
|
-
prefix = '';
|
|
112
|
-
basePath = partial;
|
|
113
|
-
} else {
|
|
114
|
-
// Complete file path
|
|
115
|
-
return { completed: partial, isDirectory: false, alternatives: [] };
|
|
116
|
-
}
|
|
117
|
-
} catch {
|
|
118
|
-
// Path doesn't exist, complete the last component
|
|
119
|
-
dirToList = dirname(fullPath);
|
|
120
|
-
prefix = basename(fullPath).toLowerCase();
|
|
121
|
-
|
|
122
|
-
// Calculate basePath - the directory portion to preserve
|
|
123
|
-
if (tildePrefix) {
|
|
124
|
-
const afterTilde = expandedPartial.slice(homedir().length + 1);
|
|
125
|
-
const dirPart = dirname(afterTilde);
|
|
126
|
-
basePath = dirPart === '.' ? tildePrefix : tildePrefix + dirPart + '/';
|
|
127
|
-
} else {
|
|
128
|
-
const dirPart = dirname(partial);
|
|
129
|
-
// Don't add ./ prefix for simple filenames
|
|
130
|
-
basePath = (dirPart === '.' || dirPart === '') ? '' : dirPart + '/';
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
const entries = await readdir(dirToList, { withFileTypes: true });
|
|
136
|
-
const matches = entries
|
|
137
|
-
.filter(entry => entry.name.toLowerCase().startsWith(prefix))
|
|
138
|
-
.sort();
|
|
139
|
-
|
|
140
|
-
if (matches.length === 0) {
|
|
141
|
-
return null;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (matches.length === 1) {
|
|
145
|
-
const entry = matches[0];
|
|
146
|
-
const suffix = entry.isDirectory() ? '/' : '';
|
|
147
|
-
const completed = basePath + entry.name + suffix;
|
|
148
|
-
return { completed, isDirectory: entry.isDirectory(), alternatives: [] };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Find common prefix among all matches
|
|
152
|
-
const names = matches.map(e => e.name);
|
|
153
|
-
let commonPrefix = names[0];
|
|
154
|
-
for (const name of names.slice(1)) {
|
|
155
|
-
let i = 0;
|
|
156
|
-
while (i < commonPrefix.length && i < name.length && commonPrefix[i] === name[i]) {
|
|
157
|
-
i++;
|
|
158
|
-
}
|
|
159
|
-
commonPrefix = commonPrefix.slice(0, i);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const alternatives = matches.map(e => e.name + (e.isDirectory() ? '/' : ''));
|
|
163
|
-
const completed = basePath + commonPrefix;
|
|
164
|
-
|
|
165
|
-
return {
|
|
166
|
-
completed: completed.length > partial.length ? completed : partial,
|
|
167
|
-
isDirectory: false,
|
|
168
|
-
alternatives
|
|
169
|
-
};
|
|
170
|
-
} catch {
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
}
|