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/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
|
-
}
|
package/src/utils/path_format.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { homedir } from 'os';
|
|
2
|
-
import { basename, dirname } from 'path';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Shorten a path for display:
|
|
6
|
-
* - Replace home directory with ~
|
|
7
|
-
* - Truncate middle of long paths
|
|
8
|
-
* - Show basename prominently
|
|
9
|
-
*/
|
|
10
|
-
export function shortenPath(path: string, maxLength = 60): string {
|
|
11
|
-
if (!path) return '';
|
|
12
|
-
|
|
13
|
-
// Replace home directory with ~
|
|
14
|
-
const home = homedir();
|
|
15
|
-
let shortened = path;
|
|
16
|
-
if (path.startsWith(home)) {
|
|
17
|
-
shortened = '~' + path.slice(home.length);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// If short enough, return as-is
|
|
21
|
-
if (shortened.length <= maxLength) {
|
|
22
|
-
return shortened;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Truncate middle, keeping start and end
|
|
26
|
-
const base = basename(shortened);
|
|
27
|
-
const dir = dirname(shortened);
|
|
28
|
-
|
|
29
|
-
// Always show at least the basename
|
|
30
|
-
if (base.length >= maxLength - 4) {
|
|
31
|
-
return '…' + base.slice(-(maxLength - 1));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Show as much of the directory as we can
|
|
35
|
-
const availableForDir = maxLength - base.length - 2; // 2 for /…
|
|
36
|
-
if (availableForDir < 10) {
|
|
37
|
-
return '…/' + base;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Split dir and keep start + end
|
|
41
|
-
const startLen = Math.floor(availableForDir * 0.4);
|
|
42
|
-
const endLen = availableForDir - startLen - 1; // 1 for …
|
|
43
|
-
|
|
44
|
-
const dirStart = dir.slice(0, startLen);
|
|
45
|
-
const dirEnd = dir.slice(-endLen);
|
|
46
|
-
|
|
47
|
-
return `${dirStart}…${dirEnd}/${base}`;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Format a path for trace display - very compact
|
|
52
|
-
*/
|
|
53
|
-
export function formatTracePath(path: string): string {
|
|
54
|
-
if (!path) return '""';
|
|
55
|
-
|
|
56
|
-
const home = homedir();
|
|
57
|
-
let formatted = path;
|
|
58
|
-
|
|
59
|
-
// Replace home with ~
|
|
60
|
-
if (path.startsWith(home)) {
|
|
61
|
-
formatted = '~' + path.slice(home.length);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// For very long paths, just show basename with hint
|
|
65
|
-
if (formatted.length > 50) {
|
|
66
|
-
const base = basename(formatted);
|
|
67
|
-
const dir = dirname(formatted);
|
|
68
|
-
// Show abbreviated dir + full basename
|
|
69
|
-
if (dir.length > 30) {
|
|
70
|
-
const dirParts = dir.split('/').filter(Boolean);
|
|
71
|
-
if (dirParts.length > 3) {
|
|
72
|
-
const abbreviated = dirParts.slice(0, 2).join('/') + '/…/' + dirParts.slice(-1).join('/');
|
|
73
|
-
return abbreviated + '/' + base;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return formatted;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Format bytes in human readable form
|
|
83
|
-
*/
|
|
84
|
-
export function formatBytes(bytes: number): string {
|
|
85
|
-
if (bytes < 1024) return `${bytes} bytes`;
|
|
86
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
87
|
-
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Format duration in human readable form
|
|
92
|
-
*/
|
|
93
|
-
export function formatDuration(ms: number): string {
|
|
94
|
-
if (ms < 1000) return `${ms}ms`;
|
|
95
|
-
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
96
|
-
const mins = Math.floor(ms / 60000);
|
|
97
|
-
const secs = Math.round((ms % 60000) / 1000);
|
|
98
|
-
return `${mins}m${secs}s`;
|
|
99
|
-
}
|
package/src/utils/shell.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { exec, ExecException } from 'child_process';
|
|
2
|
-
import { promisify } from 'util';
|
|
3
|
-
import { resolve } from 'path';
|
|
4
|
-
import { stat } from 'fs/promises';
|
|
5
|
-
|
|
6
|
-
import type { ShellResult } from '../agent/commands/types.js';
|
|
7
|
-
|
|
8
|
-
const execAsync = promisify(exec);
|
|
9
|
-
|
|
10
|
-
const MAX_BUFFER = 1024 * 1024;
|
|
11
|
-
const MAX_OUTPUT = 10000;
|
|
12
|
-
const DEFAULT_TIMEOUT_MS = 30000;
|
|
13
|
-
const DANGEROUS = ['rm -rf /', 'mkfs', ':(){:|:&};:'];
|
|
14
|
-
|
|
15
|
-
export async function runShellCommand(command: string, cwd: string): Promise<ShellResult> {
|
|
16
|
-
if (DANGEROUS.some(entry => command.includes(entry))) {
|
|
17
|
-
throw new Error('Command rejected for safety reasons');
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const resolvedCwd = resolve(cwd);
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
const { stdout, stderr } = await execAsync(command, {
|
|
24
|
-
cwd: resolvedCwd,
|
|
25
|
-
timeout: DEFAULT_TIMEOUT_MS,
|
|
26
|
-
maxBuffer: MAX_BUFFER
|
|
27
|
-
});
|
|
28
|
-
const clippedStdout = stdout.slice(0, MAX_OUTPUT);
|
|
29
|
-
const clippedStderr = stderr.slice(0, MAX_OUTPUT);
|
|
30
|
-
return {
|
|
31
|
-
command,
|
|
32
|
-
cwd: resolvedCwd,
|
|
33
|
-
stdout: clippedStdout,
|
|
34
|
-
stderr: clippedStderr,
|
|
35
|
-
exitCode: 0,
|
|
36
|
-
truncated: stdout.length > MAX_OUTPUT || stderr.length > MAX_OUTPUT
|
|
37
|
-
};
|
|
38
|
-
} catch (err) {
|
|
39
|
-
const error = err as ExecException & { stdout?: string; stderr?: string; code?: number };
|
|
40
|
-
const stdout = String(error.stdout || '');
|
|
41
|
-
const stderr = String(error.stderr || error.message || '');
|
|
42
|
-
const clippedStdout = stdout.slice(0, MAX_OUTPUT);
|
|
43
|
-
const clippedStderr = stderr.slice(0, MAX_OUTPUT);
|
|
44
|
-
return {
|
|
45
|
-
command,
|
|
46
|
-
cwd: resolvedCwd,
|
|
47
|
-
stdout: clippedStdout,
|
|
48
|
-
stderr: clippedStderr,
|
|
49
|
-
exitCode: typeof error.code === 'number' ? error.code : 1,
|
|
50
|
-
truncated: stdout.length > MAX_OUTPUT || stderr.length > MAX_OUTPUT
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export async function resolveWorkingDir(current: string, target?: string): Promise<string> {
|
|
56
|
-
const base = current || process.cwd();
|
|
57
|
-
let path = target?.trim() || '';
|
|
58
|
-
|
|
59
|
-
// Expand tilde to home directory
|
|
60
|
-
if (path.startsWith('~/')) {
|
|
61
|
-
path = resolve(process.env.HOME || '', path.slice(2));
|
|
62
|
-
} else if (path === '~') {
|
|
63
|
-
path = process.env.HOME || '';
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const next = path.length > 0 ? resolve(base, path) : base;
|
|
67
|
-
const info = await stat(next);
|
|
68
|
-
if (!info.isDirectory()) {
|
|
69
|
-
throw new Error('Not a directory');
|
|
70
|
-
}
|
|
71
|
-
return next;
|
|
72
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const SPINNER_FRAMES = ['-', '\\', '|', '/'];
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export const DEFAULT_SPINNER_VERBS = [
|
|
2
|
-
'Thinking',
|
|
3
|
-
'Reticulating splines',
|
|
4
|
-
'Allocating time slices',
|
|
5
|
-
'Negotiating with entropy',
|
|
6
|
-
'Consulting the archives',
|
|
7
|
-
'Organizing thoughts',
|
|
8
|
-
'Compiling context',
|
|
9
|
-
'Assembling response',
|
|
10
|
-
'Tracing dependencies',
|
|
11
|
-
'Resolving intent'
|
|
12
|
-
];
|
|
13
|
-
|
|
14
|
-
export function parseSpinnerVerbs(input: string): string[] {
|
|
15
|
-
return input
|
|
16
|
-
.split(/[,|;\n]+/g)
|
|
17
|
-
.map(part => part.trim())
|
|
18
|
-
.filter(Boolean);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function formatSpinnerVerbs(verbs: string[]): string {
|
|
22
|
-
return verbs.length === 0 ? '(none)' : verbs.join(', ');
|
|
23
|
-
}
|