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.
Files changed (122) hide show
  1. package/bin/ztc-audio-darwin-arm64 +0 -0
  2. package/dist/utils/dictation_native.d.ts.map +1 -1
  3. package/dist/utils/dictation_native.js +43 -23
  4. package/dist/utils/dictation_native.js.map +1 -1
  5. package/package.json +5 -4
  6. package/packages/ztc-dictation/Cargo.toml +0 -43
  7. package/packages/ztc-dictation/README.md +0 -65
  8. package/packages/ztc-dictation/index.d.ts +0 -16
  9. package/packages/ztc-dictation/index.js +0 -74
  10. package/packages/ztc-dictation/package.json +0 -41
  11. package/packages/ztc-dictation/src/main.rs +0 -430
  12. package/src/App.tsx +0 -910
  13. package/src/agent/agent.ts +0 -534
  14. package/src/agent/backends/anthropic.ts +0 -86
  15. package/src/agent/backends/gemini.ts +0 -119
  16. package/src/agent/backends/inception.ts +0 -23
  17. package/src/agent/backends/index.ts +0 -17
  18. package/src/agent/backends/openai.ts +0 -23
  19. package/src/agent/backends/openai_compatible.ts +0 -143
  20. package/src/agent/backends/types.ts +0 -83
  21. package/src/agent/commands/clipboard.ts +0 -77
  22. package/src/agent/commands/config.ts +0 -204
  23. package/src/agent/commands/debug.ts +0 -23
  24. package/src/agent/commands/dictation.ts +0 -11
  25. package/src/agent/commands/emulation.ts +0 -80
  26. package/src/agent/commands/execution.ts +0 -9
  27. package/src/agent/commands/help.ts +0 -20
  28. package/src/agent/commands/history.ts +0 -13
  29. package/src/agent/commands/index.ts +0 -48
  30. package/src/agent/commands/input_mode.ts +0 -22
  31. package/src/agent/commands/keybindings.ts +0 -40
  32. package/src/agent/commands/model.ts +0 -11
  33. package/src/agent/commands/models.ts +0 -116
  34. package/src/agent/commands/permissions.ts +0 -64
  35. package/src/agent/commands/retry.ts +0 -9
  36. package/src/agent/commands/shell.ts +0 -68
  37. package/src/agent/commands/skills.ts +0 -54
  38. package/src/agent/commands/status.ts +0 -19
  39. package/src/agent/commands/types.ts +0 -88
  40. package/src/agent/commands/update.ts +0 -32
  41. package/src/agent/factory.ts +0 -60
  42. package/src/agent/index.ts +0 -20
  43. package/src/agent/runtime/capabilities.ts +0 -7
  44. package/src/agent/runtime/memory.ts +0 -23
  45. package/src/agent/runtime/policy.ts +0 -48
  46. package/src/agent/runtime/session.ts +0 -18
  47. package/src/agent/runtime/tracing.ts +0 -23
  48. package/src/agent/tools/file.ts +0 -178
  49. package/src/agent/tools/index.ts +0 -52
  50. package/src/agent/tools/screenshot.ts +0 -821
  51. package/src/agent/tools/search.ts +0 -138
  52. package/src/agent/tools/shell.ts +0 -69
  53. package/src/agent/tools/skills.ts +0 -28
  54. package/src/agent/tools/types.ts +0 -14
  55. package/src/agent/tools/zerg.ts +0 -50
  56. package/src/cli.tsx +0 -163
  57. package/src/components/ActivityLine.tsx +0 -23
  58. package/src/components/FullScreen.tsx +0 -79
  59. package/src/components/Header.tsx +0 -27
  60. package/src/components/InputArea.tsx +0 -1660
  61. package/src/components/MessageList.tsx +0 -71
  62. package/src/components/SingleMessage.tsx +0 -298
  63. package/src/components/StatusBar.tsx +0 -55
  64. package/src/components/index.tsx +0 -8
  65. package/src/config/types.ts +0 -19
  66. package/src/config.ts +0 -186
  67. package/src/debug/logger.ts +0 -14
  68. package/src/emulation/README.md +0 -24
  69. package/src/emulation/catalog.ts +0 -82
  70. package/src/emulation/trace_style.ts +0 -8
  71. package/src/emulation/types.ts +0 -7
  72. package/src/skills/index.ts +0 -36
  73. package/src/skills/loader.ts +0 -135
  74. package/src/skills/registry.ts +0 -6
  75. package/src/skills/types.ts +0 -10
  76. package/src/types.ts +0 -84
  77. package/src/ui/README.md +0 -44
  78. package/src/ui/core/factory.ts +0 -9
  79. package/src/ui/core/index.ts +0 -4
  80. package/src/ui/core/input.ts +0 -38
  81. package/src/ui/core/input_segments.ts +0 -410
  82. package/src/ui/core/input_state.ts +0 -17
  83. package/src/ui/core/layout_yoga.ts +0 -122
  84. package/src/ui/core/style.ts +0 -38
  85. package/src/ui/core/types.ts +0 -54
  86. package/src/ui/ink/index.tsx +0 -1
  87. package/src/ui/ink/render.tsx +0 -60
  88. package/src/ui/views/activity_line.ts +0 -33
  89. package/src/ui/views/app.ts +0 -111
  90. package/src/ui/views/header.ts +0 -44
  91. package/src/ui/views/input_area.ts +0 -255
  92. package/src/ui/views/message_list.ts +0 -443
  93. package/src/ui/views/status_bar.ts +0 -114
  94. package/src/ui/vue/index.ts +0 -53
  95. package/src/ui/web/frame_render.tsx +0 -148
  96. package/src/ui/web/index.tsx +0 -1
  97. package/src/ui/web/render.tsx +0 -41
  98. package/src/utils/clipboard.ts +0 -39
  99. package/src/utils/clipboard_image.ts +0 -40
  100. package/src/utils/dictation.ts +0 -467
  101. package/src/utils/dictation_native.ts +0 -258
  102. package/src/utils/diff.ts +0 -52
  103. package/src/utils/image_preview.ts +0 -36
  104. package/src/utils/models.ts +0 -98
  105. package/src/utils/path_complete.ts +0 -173
  106. package/src/utils/path_format.ts +0 -99
  107. package/src/utils/shell.ts +0 -72
  108. package/src/utils/spinner_frames.ts +0 -1
  109. package/src/utils/spinner_verbs.ts +0 -23
  110. package/src/utils/table.ts +0 -171
  111. package/src/utils/tool_summary.ts +0 -56
  112. package/src/utils/tool_trace.ts +0 -346
  113. package/src/utils/update.ts +0 -44
  114. package/src/utils/version.ts +0 -15
  115. package/src/web/index.html +0 -352
  116. package/src/web/mirror-favicon.svg +0 -4
  117. package/src/web/mirror.html +0 -641
  118. package/src/web/mirror_hook.ts +0 -25
  119. package/src/web/mirror_server.ts +0 -204
  120. package/tsconfig.json +0 -22
  121. package/vite.config.ts +0 -363
  122. /package/{packages/ztc-dictation/bin → bin}/.gitkeep +0 -0
@@ -1,71 +0,0 @@
1
- import React, { useRef, useMemo } from 'react';
2
- import { Message } from '../types.js';
3
- import { InkNode } from '../ui/ink/index.js';
4
- import { buildMessageListView } from '../ui/views/message_list.js';
5
-
6
- interface MessageListProps {
7
- messages: Message[];
8
- height?: number;
9
- maxMessages?: number;
10
- debug?: boolean;
11
- expandToolOutputs?: boolean;
12
- scrollback?: boolean;
13
- }
14
-
15
- export const MessageList: React.FC<MessageListProps> = ({
16
- messages,
17
- height,
18
- maxMessages = 50,
19
- debug = false,
20
- expandToolOutputs = false,
21
- scrollback = false
22
- }) => {
23
- // In scrollback mode, track which messages have been rendered to avoid duplication
24
- const renderedIdsRef = useRef<Set<string>>(new Set());
25
-
26
- // Filter to only new, complete messages in scrollback mode
27
- const messagesToRender = useMemo(() => {
28
- if (!scrollback) {
29
- return messages;
30
- }
31
-
32
- // In scrollback mode, only render messages that:
33
- // 1. Are NOT currently streaming (wait for completion)
34
- // 2. Haven't been rendered yet
35
- const newMessages: Message[] = [];
36
- for (const msg of messages) {
37
- // Skip streaming messages - we'll render them when complete
38
- if (msg.isStreaming) {
39
- continue;
40
- }
41
-
42
- // Skip already rendered messages
43
- if (renderedIdsRef.current.has(msg.id)) {
44
- continue;
45
- }
46
-
47
- // This is a new, complete message - render it
48
- newMessages.push(msg);
49
- // Mark as rendered immediately
50
- renderedIdsRef.current.add(msg.id);
51
- }
52
- return newMessages;
53
- }, [messages, scrollback]);
54
-
55
- // Don't render anything if there are no new messages in scrollback mode
56
- if (scrollback && messagesToRender.length === 0) {
57
- return null;
58
- }
59
-
60
- const node = buildMessageListView({
61
- messages: messagesToRender,
62
- height,
63
- maxMessages,
64
- debug,
65
- expandToolOutputs,
66
- scrollback
67
- });
68
- return <InkNode node={node} />;
69
- };
70
-
71
- export default MessageList;
@@ -1,298 +0,0 @@
1
- import React from 'react';
2
- import { Box, Text } from 'ink';
3
- import { Message } from '../types.js';
4
-
5
- // Box-drawing characters for tables
6
- const BOX = {
7
- topLeft: '╭',
8
- topRight: '╮',
9
- bottomLeft: '╰',
10
- bottomRight: '╯',
11
- horizontal: '─',
12
- vertical: '│',
13
- leftT: '├',
14
- rightT: '┤',
15
- topT: '┬',
16
- bottomT: '┴',
17
- cross: '┼'
18
- };
19
-
20
- interface ParsedTable {
21
- headers: string[];
22
- rows: string[][];
23
- }
24
-
25
- function parseMarkdownTable(lines: string[]): ParsedTable | null {
26
- // Need at least header row and separator row
27
- if (lines.length < 2) return null;
28
-
29
- const headerLine = lines[0];
30
- const separatorLine = lines[1];
31
-
32
- // Check if it looks like a markdown table
33
- if (!headerLine.includes('|') || !separatorLine.match(/^\|?[\s\-:|]+\|?$/)) {
34
- return null;
35
- }
36
-
37
- const parseRow = (line: string): string[] => {
38
- return line
39
- .split('|')
40
- .map(cell => cell.trim())
41
- .filter((_, i, arr) => i > 0 && i < arr.length - 1 || (arr.length === 2 && i === 0));
42
- };
43
-
44
- const headers = parseRow(headerLine);
45
- if (headers.length === 0) return null;
46
-
47
- const rows: string[][] = [];
48
- for (let i = 2; i < lines.length; i++) {
49
- if (!lines[i].includes('|')) break;
50
- rows.push(parseRow(lines[i]));
51
- }
52
-
53
- return { headers, rows };
54
- }
55
-
56
- function renderBoxTable(table: ParsedTable): string[] {
57
- const { headers, rows } = table;
58
-
59
- // Calculate column widths
60
- const widths = headers.map((h, i) => {
61
- const dataMax = rows.reduce((max, row) => {
62
- const cell = row[i] || '';
63
- return Math.max(max, cell.length);
64
- }, 0);
65
- return Math.max(h.length, dataMax);
66
- });
67
-
68
- const output: string[] = [];
69
-
70
- // Top border
71
- output.push(
72
- BOX.topLeft +
73
- widths.map(w => BOX.horizontal.repeat(w + 2)).join(BOX.topT) +
74
- BOX.topRight
75
- );
76
-
77
- // Header row
78
- output.push(
79
- BOX.vertical +
80
- headers.map((h, i) => ' ' + h.padEnd(widths[i]) + ' ').join(BOX.vertical) +
81
- BOX.vertical
82
- );
83
-
84
- // Header separator
85
- output.push(
86
- BOX.leftT +
87
- widths.map(w => BOX.horizontal.repeat(w + 2)).join(BOX.cross) +
88
- BOX.rightT
89
- );
90
-
91
- // Data rows
92
- for (const row of rows) {
93
- output.push(
94
- BOX.vertical +
95
- widths.map((w, i) => ' ' + (row[i] || '').padEnd(w) + ' ').join(BOX.vertical) +
96
- BOX.vertical
97
- );
98
- }
99
-
100
- // Bottom border
101
- output.push(
102
- BOX.bottomLeft +
103
- widths.map(w => BOX.horizontal.repeat(w + 2)).join(BOX.bottomT) +
104
- BOX.bottomRight
105
- );
106
-
107
- return output;
108
- }
109
-
110
- interface SingleMessageProps {
111
- message: Message;
112
- expandToolOutputs?: boolean;
113
- }
114
-
115
- interface RoleConfig {
116
- icon: string;
117
- color: string;
118
- label: string;
119
- }
120
-
121
- const roleConfigs: Record<string, RoleConfig> = {
122
- user: { icon: '❯', color: 'blue', label: 'You' },
123
- assistant: { icon: '◆', color: 'magenta', label: 'Zerg' },
124
- tool: { icon: '⏺', color: 'yellow', label: 'Trace' },
125
- system: { icon: '●', color: 'gray', label: 'System' }
126
- };
127
-
128
- function formatTime(date: Date): string {
129
- return date.toLocaleTimeString('en-US', {
130
- hour: '2-digit',
131
- minute: '2-digit',
132
- hour12: false
133
- });
134
- }
135
-
136
- interface DiffLine {
137
- type: 'add' | 'remove' | 'context';
138
- text: string;
139
- }
140
-
141
- interface ToolOutput {
142
- preview: string;
143
- full: string;
144
- truncated: boolean;
145
- diffLines?: DiffLine[];
146
- }
147
-
148
- // Render a line with diff coloring
149
- const DiffLineComponent: React.FC<{ line: DiffLine; lineNum?: number }> = ({ line, lineNum }) => {
150
- const prefix = line.type === 'add' ? '+' : line.type === 'remove' ? '-' : ' ';
151
- const color = line.type === 'add' ? 'green' : line.type === 'remove' ? 'red' : undefined;
152
- const lineNumStr = lineNum !== undefined ? `${String(lineNum).padStart(4)} ` : '';
153
-
154
- return (
155
- <Text color={color}>
156
- {' '}{lineNumStr}{prefix} {line.text}
157
- </Text>
158
- );
159
- };
160
-
161
- // Render content with diff awareness and markdown table support
162
- const ContentRenderer: React.FC<{
163
- content: string;
164
- diffLines?: DiffLine[];
165
- isToolTrace: boolean;
166
- }> = ({ content, diffLines, isToolTrace }) => {
167
- // If we have structured diff lines, render them with colors
168
- if (diffLines && diffLines.length > 0 && isToolTrace) {
169
- const lines = content.split('\n');
170
- const summaryLine = lines[0] || '';
171
-
172
- return (
173
- <Box flexDirection="column">
174
- <Text>{summaryLine}</Text>
175
- {diffLines.map((line, i) => (
176
- <DiffLineComponent key={i} line={line} />
177
- ))}
178
- </Box>
179
- );
180
- }
181
-
182
- // Parse content into segments (text and tables)
183
- const lines = content.split('\n');
184
- const segments: Array<{ type: 'text' | 'table'; lines: string[] }> = [];
185
- let i = 0;
186
-
187
- while (i < lines.length) {
188
- const line = lines[i];
189
-
190
- // Check if this looks like the start of a markdown table
191
- if (line.includes('|') && i + 1 < lines.length && lines[i + 1].match(/^\|?[\s\-:|]+\|?$/)) {
192
- // Collect all table lines
193
- const tableLines: string[] = [line];
194
- let j = i + 1;
195
- while (j < lines.length && lines[j].includes('|')) {
196
- tableLines.push(lines[j]);
197
- j++;
198
- }
199
-
200
- const parsed = parseMarkdownTable(tableLines);
201
- if (parsed && parsed.rows.length > 0) {
202
- const boxLines = renderBoxTable(parsed);
203
- segments.push({ type: 'table', lines: boxLines });
204
- i = j;
205
- continue;
206
- }
207
- }
208
-
209
- // Regular text line
210
- if (segments.length === 0 || segments[segments.length - 1].type !== 'text') {
211
- segments.push({ type: 'text', lines: [] });
212
- }
213
- segments[segments.length - 1].lines.push(line);
214
- i++;
215
- }
216
-
217
- return (
218
- <Box flexDirection="column">
219
- {segments.map((segment, segIdx) => (
220
- <Box key={segIdx} flexDirection="column">
221
- {segment.lines.map((line, lineIdx) => {
222
- // Color diff lines in output
223
- if (isToolTrace) {
224
- const trimmed = line.trimStart();
225
- if (trimmed.startsWith('+ ') && !trimmed.startsWith('+ …')) {
226
- return <Text key={lineIdx} color="green">{line}</Text>;
227
- }
228
- if (trimmed.startsWith('- ')) {
229
- return <Text key={lineIdx} color="red">{line}</Text>;
230
- }
231
- }
232
- // Table lines get a subtle color
233
- if (segment.type === 'table') {
234
- return <Text key={lineIdx} color="cyan">{line}</Text>;
235
- }
236
- return <Text key={lineIdx}>{line}</Text>;
237
- })}
238
- </Box>
239
- ))}
240
- </Box>
241
- );
242
- };
243
-
244
- export const SingleMessage: React.FC<SingleMessageProps> = ({
245
- message,
246
- expandToolOutputs = false
247
- }) => {
248
- const config = roleConfigs[message.role] || roleConfigs.system;
249
- const metadata = message.metadata as Record<string, unknown> | undefined;
250
- const toolOutput = metadata?.toolOutput as ToolOutput | undefined;
251
-
252
- // Determine content to show
253
- let content: string;
254
- let diffLines: DiffLine[] | undefined;
255
-
256
- if (toolOutput) {
257
- content = expandToolOutputs && toolOutput.full ? toolOutput.full : toolOutput.preview;
258
- diffLines = toolOutput.diffLines;
259
- } else {
260
- content = message.content;
261
- }
262
-
263
- const isToolTrace = message.role === 'tool';
264
-
265
- // For tool traces, use more compact display
266
- if (isToolTrace) {
267
- return (
268
- <Box flexDirection="column" marginY={0} paddingX={1}>
269
- <Box flexDirection="column" marginLeft={1}>
270
- <ContentRenderer
271
- content={content}
272
- diffLines={diffLines}
273
- isToolTrace={true}
274
- />
275
- </Box>
276
- </Box>
277
- );
278
- }
279
-
280
- return (
281
- <Box flexDirection="column" marginY={1} paddingX={1}>
282
- <Box flexDirection="row">
283
- <Text color={config.color} bold>{config.icon} {config.label}</Text>
284
- <Text color="gray" dimColor> • {formatTime(message.timestamp)}</Text>
285
- {message.isStreaming && <Text color="yellow" bold> ▌</Text>}
286
- </Box>
287
- <Box flexDirection="column" marginLeft={2}>
288
- <ContentRenderer
289
- content={content}
290
- diffLines={diffLines}
291
- isToolTrace={false}
292
- />
293
- </Box>
294
- </Box>
295
- );
296
- };
297
-
298
- export default SingleMessage;
@@ -1,55 +0,0 @@
1
- import React from 'react';
2
- import { AgentState } from '../types.js';
3
- import { InkNode } from '../ui/ink/index.js';
4
- import { buildStatusBarView } from '../ui/views/status_bar.js';
5
-
6
- interface StatusBarProps {
7
- state: AgentState;
8
- sessionId?: string;
9
- version?: string;
10
- connectionStatus?: 'connected' | 'disconnected' | 'connecting';
11
- contextLength?: number;
12
- contextEstimated?: boolean;
13
- provider?: string;
14
- model?: string;
15
- emulationId?: string;
16
- inputMode?: 'queue' | 'interrupt';
17
- toast?: string | null;
18
- spinnerLabel?: string | null;
19
- debug?: boolean;
20
- }
21
-
22
- export const StatusBar: React.FC<StatusBarProps> = ({
23
- state,
24
- sessionId,
25
- version = '0.1.0',
26
- connectionStatus = 'connected',
27
- contextLength,
28
- contextEstimated,
29
- provider,
30
- model,
31
- emulationId,
32
- inputMode,
33
- toast,
34
- spinnerLabel,
35
- debug = false
36
- }) => {
37
- const node = buildStatusBarView({
38
- state,
39
- sessionId,
40
- version,
41
- connectionStatus,
42
- contextLength,
43
- contextEstimated,
44
- provider,
45
- model,
46
- emulationId,
47
- inputMode,
48
- toast,
49
- spinnerLabel,
50
- debug
51
- });
52
- return <InkNode node={node} />;
53
- };
54
-
55
- export default StatusBar;
@@ -1,8 +0,0 @@
1
- // Component exports
2
- export { Header } from './Header.js';
3
- export { MessageList } from './MessageList.js';
4
- export { SingleMessage } from './SingleMessage.js';
5
- export { InputArea } from './InputArea.js';
6
- export { StatusBar } from './StatusBar.js';
7
- export { FullScreen, useScreenSize } from './FullScreen.js';
8
- export { ActivityLine } from './ActivityLine.js';
@@ -1,19 +0,0 @@
1
- export interface DictationSettings {
2
- provider?: 'openai' | 'local' | 'macos';
3
- language?: string; // e.g., 'en', 'es', 'fr'
4
- whisperModel?: string; // For local whisper: 'tiny', 'base', 'small', 'medium', 'large'
5
- }
6
-
7
- export interface ZTCConfig {
8
- apiKey?: string;
9
- apiKeys?: Record<string, string>;
10
- provider?: string;
11
- openaiCompatibleBaseUrl?: string;
12
- model: string;
13
- maxTokens: number;
14
- zergEndpoint?: string;
15
- emulationId?: string;
16
- spinnerVerbs?: string[];
17
- toolPermissions?: Partial<Record<'file_read' | 'file_write' | 'shell_exec' | 'network', boolean>>;
18
- dictation?: DictationSettings;
19
- }
package/src/config.ts DELETED
@@ -1,186 +0,0 @@
1
- import { homedir } from 'os';
2
- import { join } from 'path';
3
- import { readFile, writeFile, mkdir } from 'fs/promises';
4
-
5
- import { ZTCConfig } from './config/types.js';
6
- import { DEFAULT_SPINNER_VERBS } from './utils/spinner_verbs.js';
7
-
8
- // --- Configuration Store ---
9
-
10
- const DEFAULT_CONFIG: ZTCConfig = {
11
- apiKey: process.env.ANTHROPIC_API_KEY,
12
- apiKeys: undefined,
13
- provider: 'anthropic',
14
- openaiCompatibleBaseUrl: undefined,
15
- model: 'claude-opus-4-20250514',
16
- maxTokens: 4096,
17
- zergEndpoint: undefined,
18
- emulationId: undefined,
19
- spinnerVerbs: DEFAULT_SPINNER_VERBS,
20
- toolPermissions: {
21
- file_read: true,
22
- file_write: true,
23
- shell_exec: true,
24
- network: true
25
- }
26
- };
27
-
28
- const CONFIG_DIR = join(homedir(), '.ztc');
29
- const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
30
-
31
- class ConfigStore {
32
- private config: ZTCConfig = { ...DEFAULT_CONFIG };
33
- private loaded = false;
34
-
35
- private applyEnvOverrides(): void {
36
- if (process.env.ANTHROPIC_API_KEY) {
37
- this.config.apiKey = process.env.ANTHROPIC_API_KEY;
38
- }
39
- }
40
-
41
- async load(force = false): Promise<ZTCConfig> {
42
- if (this.loaded && !force) return this.config;
43
-
44
- try {
45
- const data = await readFile(CONFIG_FILE, 'utf-8');
46
- const saved = JSON.parse(data) as Partial<ZTCConfig>;
47
- this.config = { ...DEFAULT_CONFIG, ...saved };
48
- if (this.config.model === 'claude-opus-4-5-20250514') {
49
- this.config.model = 'claude-opus-4-20250514';
50
- }
51
- if (!this.config.spinnerVerbs) {
52
- this.config.spinnerVerbs = DEFAULT_SPINNER_VERBS;
53
- }
54
- if (!this.config.toolPermissions) {
55
- this.config.toolPermissions = { ...DEFAULT_CONFIG.toolPermissions };
56
- }
57
- this.applyEnvOverrides();
58
- } catch {
59
- // File doesn't exist or is invalid, use defaults
60
- this.config = { ...DEFAULT_CONFIG };
61
- }
62
-
63
- this.loaded = true;
64
- return this.config;
65
- }
66
-
67
- async refresh(): Promise<void> {
68
- await this.load(true);
69
- }
70
-
71
- async save(): Promise<void> {
72
- try {
73
- await mkdir(CONFIG_DIR, { recursive: true });
74
-
75
- // Don't persist API key if it came from env
76
- const toSave: Partial<ZTCConfig> = { ...this.config };
77
- if (process.env.ANTHROPIC_API_KEY === this.config.apiKey) {
78
- delete toSave.apiKey;
79
- }
80
-
81
- await writeFile(CONFIG_FILE, JSON.stringify(toSave, null, 2), 'utf-8');
82
- } catch (err) {
83
- console.error('Failed to save config:', err);
84
- }
85
- }
86
-
87
- get(): ZTCConfig {
88
- return this.config;
89
- }
90
-
91
- set<K extends keyof ZTCConfig>(key: K, value: ZTCConfig[K]): void {
92
- this.config[key] = value;
93
- }
94
-
95
- setAll(next: Partial<ZTCConfig>): void {
96
- this.config = { ...DEFAULT_CONFIG, ...this.config, ...next };
97
- if (this.config.model === 'claude-opus-4-5-20250514') {
98
- this.config.model = 'claude-opus-4-20250514';
99
- }
100
- if (!this.config.spinnerVerbs) {
101
- this.config.spinnerVerbs = DEFAULT_SPINNER_VERBS;
102
- }
103
- if (!this.config.toolPermissions) {
104
- this.config.toolPermissions = { ...DEFAULT_CONFIG.toolPermissions };
105
- }
106
- this.applyEnvOverrides();
107
- this.loaded = true;
108
- }
109
-
110
- hasApiKey(): boolean {
111
- const key = this.getApiKey();
112
- return !!key && key.length > 0;
113
- }
114
-
115
- getApiKey(provider?: string): string | undefined {
116
- const chosen = provider || this.getProvider();
117
- const envKey = this.getEnvKey(chosen);
118
- if (envKey) return envKey;
119
- if (this.config.apiKeys && this.config.apiKeys[chosen]) return this.config.apiKeys[chosen];
120
- return this.config.apiKey;
121
- }
122
-
123
- setApiKey(key: string, provider?: string): void {
124
- const chosen = provider || this.getProvider();
125
- if (!this.config.apiKeys) {
126
- this.config.apiKeys = {};
127
- }
128
- this.config.apiKeys[chosen] = key;
129
- if (chosen === 'anthropic') {
130
- this.config.apiKey = key;
131
- }
132
- }
133
-
134
- getEmulationId(): string | undefined {
135
- return this.config.emulationId;
136
- }
137
-
138
- setEmulationId(id: string | undefined): void {
139
- this.config.emulationId = id;
140
- }
141
-
142
- getMaskedApiKey(): string {
143
- const key = this.getApiKey();
144
- if (!key) return '(not set)';
145
- if (key.length < 12) return '****';
146
- return `${key.slice(0, 7)}...${key.slice(-4)}`;
147
- }
148
-
149
- getProvider(): string {
150
- return this.config.provider || 'anthropic';
151
- }
152
-
153
- setProvider(provider: string): void {
154
- this.config.provider = provider;
155
- }
156
-
157
- getOpenAICompatibleBaseUrl(): string | undefined {
158
- return this.config.openaiCompatibleBaseUrl;
159
- }
160
-
161
- setOpenAICompatibleBaseUrl(url: string | undefined): void {
162
- this.config.openaiCompatibleBaseUrl = url;
163
- }
164
-
165
- private getEnvKey(provider: string): string | undefined {
166
- const map: Record<string, string | undefined> = {
167
- anthropic: process.env.ANTHROPIC_API_KEY,
168
- openai: process.env.OPENAI_API_KEY,
169
- gemini: process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY,
170
- inception: process.env.INCEPTION_API_KEY,
171
- openai_compatible: process.env.OPENAI_COMPAT_API_KEY
172
- };
173
- return map[provider];
174
- }
175
-
176
- getConfigPath(): string {
177
- return CONFIG_FILE;
178
- }
179
-
180
- get locationLabel(): string {
181
- return CONFIG_FILE;
182
- }
183
- }
184
-
185
- export const configStore = new ConfigStore();
186
- export default configStore;
@@ -1,14 +0,0 @@
1
- import { appendFileSync } from 'fs';
2
-
3
- const enabled = process.env.ZTC_DEBUG_RENDER === '1';
4
- const logPath = process.env.ZTC_DEBUG_RENDER_LOG || '/tmp/ztc_render.log';
5
-
6
- export function debugLog(message: string): void {
7
- if (!enabled) return;
8
- const line = `[${new Date().toISOString()}] ${message}\n`;
9
- try {
10
- appendFileSync(logPath, line, 'utf-8');
11
- } catch {
12
- // Ignore logging failures
13
- }
14
- }
@@ -1,24 +0,0 @@
1
- ## Emulation Layer
2
-
3
- This module loads third-party tool prompts (e.g., Claude Code, Codex CLI) and exposes them as selectable emulation profiles.
4
-
5
- ### Data Source
6
- The content is extracted from the `x1xhlol/system-prompts-and-models-of-ai-tools` repository and stored locally in:
7
- `~/.ztc/emulations/x1xhlol/`
8
-
9
- You can override the base path with `ZTC_EMULATION_DIR` (useful for tests or custom fixtures).
10
-
11
- ### Fixture Setup
12
- For tests, point `ZTC_EMULATION_DIR` at `tests/fixtures/emulations/x1xhlol` to use the bundled fixture data.
13
-
14
- To populate your local emulation cache:
15
- - `npm run emulation:init`
16
-
17
- ### Usage
18
- - `/emulation list` to see available profiles
19
- - `/emulation set <id>` to activate
20
- - `/emulation show` to see the current profile
21
-
22
- ### Notes
23
- - The emulation layer currently wires the **system prompt** into the agent.
24
- - Tool schemas are loaded and stored for future use, but not yet mapped to runtime tools.