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.
Files changed (151) hide show
  1. package/bin/.gitkeep +0 -0
  2. package/bin/ztc-audio-darwin-arm64 +0 -0
  3. package/dist/App.d.ts.map +1 -1
  4. package/dist/App.js +63 -2
  5. package/dist/App.js.map +1 -1
  6. package/dist/agent/commands/dictation.d.ts +3 -0
  7. package/dist/agent/commands/dictation.d.ts.map +1 -0
  8. package/dist/agent/commands/dictation.js +10 -0
  9. package/dist/agent/commands/dictation.js.map +1 -0
  10. package/dist/agent/commands/index.d.ts.map +1 -1
  11. package/dist/agent/commands/index.js +2 -1
  12. package/dist/agent/commands/index.js.map +1 -1
  13. package/dist/agent/commands/types.d.ts +7 -0
  14. package/dist/agent/commands/types.d.ts.map +1 -1
  15. package/dist/components/InputArea.d.ts +1 -0
  16. package/dist/components/InputArea.d.ts.map +1 -1
  17. package/dist/components/InputArea.js +591 -43
  18. package/dist/components/InputArea.js.map +1 -1
  19. package/dist/components/SingleMessage.d.ts.map +1 -1
  20. package/dist/components/SingleMessage.js +157 -7
  21. package/dist/components/SingleMessage.js.map +1 -1
  22. package/dist/config/types.d.ts +6 -0
  23. package/dist/config/types.d.ts.map +1 -1
  24. package/dist/ui/views/status_bar.js +2 -2
  25. package/dist/ui/views/status_bar.js.map +1 -1
  26. package/dist/utils/dictation.d.ts +46 -0
  27. package/dist/utils/dictation.d.ts.map +1 -0
  28. package/dist/utils/dictation.js +409 -0
  29. package/dist/utils/dictation.js.map +1 -0
  30. package/dist/utils/dictation_native.d.ts +51 -0
  31. package/dist/utils/dictation_native.d.ts.map +1 -0
  32. package/dist/utils/dictation_native.js +236 -0
  33. package/dist/utils/dictation_native.js.map +1 -0
  34. package/dist/utils/path_format.d.ts +20 -0
  35. package/dist/utils/path_format.d.ts.map +1 -0
  36. package/dist/utils/path_format.js +90 -0
  37. package/dist/utils/path_format.js.map +1 -0
  38. package/dist/utils/table.d.ts +38 -0
  39. package/dist/utils/table.d.ts.map +1 -0
  40. package/dist/utils/table.js +133 -0
  41. package/dist/utils/table.js.map +1 -0
  42. package/dist/utils/tool_trace.d.ts +7 -2
  43. package/dist/utils/tool_trace.d.ts.map +1 -1
  44. package/dist/utils/tool_trace.js +156 -51
  45. package/dist/utils/tool_trace.js.map +1 -1
  46. package/package.json +5 -1
  47. package/src/App.tsx +0 -813
  48. package/src/agent/agent.ts +0 -534
  49. package/src/agent/backends/anthropic.ts +0 -86
  50. package/src/agent/backends/gemini.ts +0 -119
  51. package/src/agent/backends/inception.ts +0 -23
  52. package/src/agent/backends/index.ts +0 -17
  53. package/src/agent/backends/openai.ts +0 -23
  54. package/src/agent/backends/openai_compatible.ts +0 -143
  55. package/src/agent/backends/types.ts +0 -83
  56. package/src/agent/commands/clipboard.ts +0 -77
  57. package/src/agent/commands/config.ts +0 -204
  58. package/src/agent/commands/debug.ts +0 -23
  59. package/src/agent/commands/emulation.ts +0 -80
  60. package/src/agent/commands/execution.ts +0 -9
  61. package/src/agent/commands/help.ts +0 -20
  62. package/src/agent/commands/history.ts +0 -13
  63. package/src/agent/commands/index.ts +0 -46
  64. package/src/agent/commands/input_mode.ts +0 -22
  65. package/src/agent/commands/keybindings.ts +0 -40
  66. package/src/agent/commands/model.ts +0 -11
  67. package/src/agent/commands/models.ts +0 -116
  68. package/src/agent/commands/permissions.ts +0 -64
  69. package/src/agent/commands/retry.ts +0 -9
  70. package/src/agent/commands/shell.ts +0 -68
  71. package/src/agent/commands/skills.ts +0 -54
  72. package/src/agent/commands/status.ts +0 -19
  73. package/src/agent/commands/types.ts +0 -80
  74. package/src/agent/commands/update.ts +0 -32
  75. package/src/agent/factory.ts +0 -60
  76. package/src/agent/index.ts +0 -20
  77. package/src/agent/runtime/capabilities.ts +0 -7
  78. package/src/agent/runtime/memory.ts +0 -23
  79. package/src/agent/runtime/policy.ts +0 -48
  80. package/src/agent/runtime/session.ts +0 -18
  81. package/src/agent/runtime/tracing.ts +0 -23
  82. package/src/agent/tools/file.ts +0 -178
  83. package/src/agent/tools/index.ts +0 -52
  84. package/src/agent/tools/screenshot.ts +0 -821
  85. package/src/agent/tools/search.ts +0 -138
  86. package/src/agent/tools/shell.ts +0 -69
  87. package/src/agent/tools/skills.ts +0 -28
  88. package/src/agent/tools/types.ts +0 -14
  89. package/src/agent/tools/zerg.ts +0 -50
  90. package/src/cli.tsx +0 -163
  91. package/src/components/ActivityLine.tsx +0 -23
  92. package/src/components/FullScreen.tsx +0 -79
  93. package/src/components/Header.tsx +0 -27
  94. package/src/components/InputArea.tsx +0 -1096
  95. package/src/components/MessageList.tsx +0 -71
  96. package/src/components/SingleMessage.tsx +0 -59
  97. package/src/components/StatusBar.tsx +0 -55
  98. package/src/components/index.tsx +0 -8
  99. package/src/config/types.ts +0 -12
  100. package/src/config.ts +0 -186
  101. package/src/debug/logger.ts +0 -14
  102. package/src/emulation/README.md +0 -24
  103. package/src/emulation/catalog.ts +0 -82
  104. package/src/emulation/trace_style.ts +0 -8
  105. package/src/emulation/types.ts +0 -7
  106. package/src/skills/index.ts +0 -36
  107. package/src/skills/loader.ts +0 -135
  108. package/src/skills/registry.ts +0 -6
  109. package/src/skills/types.ts +0 -10
  110. package/src/types.ts +0 -84
  111. package/src/ui/README.md +0 -44
  112. package/src/ui/core/factory.ts +0 -9
  113. package/src/ui/core/index.ts +0 -4
  114. package/src/ui/core/input.ts +0 -38
  115. package/src/ui/core/input_segments.ts +0 -410
  116. package/src/ui/core/input_state.ts +0 -17
  117. package/src/ui/core/layout_yoga.ts +0 -122
  118. package/src/ui/core/style.ts +0 -38
  119. package/src/ui/core/types.ts +0 -54
  120. package/src/ui/ink/index.tsx +0 -1
  121. package/src/ui/ink/render.tsx +0 -60
  122. package/src/ui/views/activity_line.ts +0 -33
  123. package/src/ui/views/app.ts +0 -111
  124. package/src/ui/views/header.ts +0 -44
  125. package/src/ui/views/input_area.ts +0 -255
  126. package/src/ui/views/message_list.ts +0 -443
  127. package/src/ui/views/status_bar.ts +0 -114
  128. package/src/ui/vue/index.ts +0 -53
  129. package/src/ui/web/frame_render.tsx +0 -148
  130. package/src/ui/web/index.tsx +0 -1
  131. package/src/ui/web/render.tsx +0 -41
  132. package/src/utils/clipboard.ts +0 -39
  133. package/src/utils/clipboard_image.ts +0 -40
  134. package/src/utils/diff.ts +0 -52
  135. package/src/utils/image_preview.ts +0 -36
  136. package/src/utils/models.ts +0 -98
  137. package/src/utils/path_complete.ts +0 -173
  138. package/src/utils/shell.ts +0 -72
  139. package/src/utils/spinner_frames.ts +0 -1
  140. package/src/utils/spinner_verbs.ts +0 -23
  141. package/src/utils/tool_summary.ts +0 -56
  142. package/src/utils/tool_trace.ts +0 -216
  143. package/src/utils/update.ts +0 -44
  144. package/src/utils/version.ts +0 -15
  145. package/src/web/index.html +0 -352
  146. package/src/web/mirror-favicon.svg +0 -4
  147. package/src/web/mirror.html +0 -641
  148. package/src/web/mirror_hook.ts +0 -25
  149. package/src/web/mirror_server.ts +0 -204
  150. package/tsconfig.json +0 -22
  151. package/vite.config.ts +0 -363
@@ -1,119 +0,0 @@
1
- import { AgentBackend, BackendRequest, BackendResponse, ContentBlock } from './types.js';
2
-
3
- interface GeminiConfig {
4
- apiKey: string;
5
- apiEndpoint?: string;
6
- }
7
-
8
- interface GeminiCandidate {
9
- content?: {
10
- parts?: Array<{
11
- text?: string;
12
- functionCall?: {
13
- name?: string;
14
- args?: Record<string, unknown>;
15
- };
16
- }>;
17
- };
18
- finishReason?: string;
19
- }
20
-
21
- interface GeminiResponse {
22
- candidates?: GeminiCandidate[];
23
- usageMetadata?: {
24
- promptTokenCount?: number;
25
- candidatesTokenCount?: number;
26
- totalTokenCount?: number;
27
- };
28
- }
29
-
30
- export class GeminiBackend implements AgentBackend {
31
- private apiKey: string;
32
- private apiEndpoint: string;
33
-
34
- constructor(config: GeminiConfig) {
35
- this.apiKey = config.apiKey;
36
- this.apiEndpoint = config.apiEndpoint || 'https://generativelanguage.googleapis.com/v1beta/models';
37
- }
38
-
39
- async generate(request: BackendRequest): Promise<BackendResponse> {
40
- const url = `${this.apiEndpoint}/${encodeURIComponent(request.model)}:generateContent?key=${this.apiKey}`;
41
- const mapParts = (content: BackendRequest['messages'][number]['content']) => {
42
- if (typeof content === 'string') {
43
- return [{ text: content }];
44
- }
45
- return content.map(block => {
46
- if (block.type === 'text') {
47
- return { text: block.text };
48
- }
49
- if (block.type === 'tool_result') {
50
- // Gemini handles function responses differently - convert to text for now
51
- const resultText = typeof block.content === 'string'
52
- ? block.content
53
- : block.content.map(b => b.type === 'text' ? b.text : '[image]').join('\n');
54
- return { text: `Function result: ${resultText}` };
55
- }
56
- if (block.type === 'tool_use') {
57
- // Convert tool_use to function call format for Gemini
58
- return { functionCall: { name: block.name, args: block.input } };
59
- }
60
- // Image block
61
- return { inlineData: { mimeType: block.mediaType, data: block.data } };
62
- });
63
- };
64
- const body = {
65
- systemInstruction: { parts: [{ text: request.system }] },
66
- contents: request.messages.map(m => ({
67
- role: m.role === 'assistant' ? 'model' : 'user',
68
- parts: mapParts(m.content)
69
- })),
70
- tools: [{
71
- functionDeclarations: request.tools.map(t => ({
72
- name: t.name,
73
- description: t.description,
74
- parameters: t.parameters
75
- }))
76
- }]
77
- };
78
-
79
- const response = await fetch(url, {
80
- method: 'POST',
81
- headers: { 'Content-Type': 'application/json' },
82
- body: JSON.stringify(body)
83
- });
84
-
85
- if (!response.ok) {
86
- const errorText = await response.text();
87
- throw new Error(`API error (${response.status}): ${errorText}`);
88
- }
89
-
90
- const data = await response.json() as GeminiResponse;
91
- const candidate = data.candidates?.[0];
92
- const blocks: ContentBlock[] = [];
93
- const parts = candidate?.content?.parts || [];
94
-
95
- for (const part of parts) {
96
- if (part.text) {
97
- blocks.push({ type: 'text', text: part.text });
98
- }
99
- if (part.functionCall?.name) {
100
- blocks.push({
101
- type: 'tool_use',
102
- id: `${part.functionCall.name}_${Date.now()}`,
103
- name: part.functionCall.name,
104
- input: part.functionCall.args || {}
105
- });
106
- }
107
- }
108
-
109
- const inputTokens = data.usageMetadata?.promptTokenCount ?? 0;
110
- const outputTokens = data.usageMetadata?.candidatesTokenCount ?? 0;
111
- const totalTokens = data.usageMetadata?.totalTokenCount ?? (inputTokens + outputTokens);
112
-
113
- return {
114
- content: blocks,
115
- stopReason: candidate?.finishReason || 'unknown',
116
- usage: data.usageMetadata ? { inputTokens, outputTokens, totalTokens } : undefined
117
- };
118
- }
119
- }
@@ -1,23 +0,0 @@
1
- import { OpenAICompatibleBackend } from './openai_compatible.js';
2
- import { AgentBackend, BackendRequest, BackendResponse } from './types.js';
3
-
4
- interface InceptionConfig {
5
- apiKey: string;
6
- apiEndpoint?: string;
7
- }
8
-
9
- export class InceptionBackend implements AgentBackend {
10
- private backend: OpenAICompatibleBackend;
11
-
12
- constructor(config: InceptionConfig) {
13
- this.backend = new OpenAICompatibleBackend({
14
- apiKey: config.apiKey,
15
- baseUrl: config.apiEndpoint || 'https://api.inceptionlabs.ai/v1',
16
- apiPath: '/chat/completions'
17
- });
18
- }
19
-
20
- async generate(request: BackendRequest): Promise<BackendResponse> {
21
- return this.backend.generate(request);
22
- }
23
- }
@@ -1,17 +0,0 @@
1
- export { AnthropicBackend } from './anthropic.js';
2
- export { OpenAIBackend } from './openai.js';
3
- export { OpenAICompatibleBackend } from './openai_compatible.js';
4
- export { InceptionBackend } from './inception.js';
5
- export { GeminiBackend } from './gemini.js';
6
- export type {
7
- AgentBackend,
8
- BackendRequest,
9
- BackendResponse,
10
- ContentBlock,
11
- TextBlock,
12
- ToolUseBlock,
13
- LlmMessage,
14
- RequestContentBlock,
15
- ToolResultBlock,
16
- TokenUsage
17
- } from './types.js';
@@ -1,23 +0,0 @@
1
- import { OpenAICompatibleBackend } from './openai_compatible.js';
2
- import { AgentBackend, BackendRequest, BackendResponse } from './types.js';
3
-
4
- interface OpenAIConfig {
5
- apiKey: string;
6
- apiEndpoint?: string;
7
- }
8
-
9
- export class OpenAIBackend implements AgentBackend {
10
- private backend: OpenAICompatibleBackend;
11
-
12
- constructor(config: OpenAIConfig) {
13
- this.backend = new OpenAICompatibleBackend({
14
- apiKey: config.apiKey,
15
- baseUrl: config.apiEndpoint || 'https://api.openai.com/v1',
16
- apiPath: '/chat/completions'
17
- });
18
- }
19
-
20
- async generate(request: BackendRequest): Promise<BackendResponse> {
21
- return this.backend.generate(request);
22
- }
23
- }
@@ -1,143 +0,0 @@
1
- import { AgentBackend, BackendRequest, BackendResponse, ContentBlock } from './types.js';
2
-
3
- interface OpenAICompatibleConfig {
4
- apiKey: string;
5
- baseUrl: string;
6
- apiPath?: string;
7
- headers?: Record<string, string>;
8
- }
9
-
10
- interface OpenAICompatibleResponse {
11
- choices?: Array<{
12
- message?: {
13
- content?: string | null;
14
- tool_calls?: Array<{
15
- id?: string;
16
- function?: {
17
- name?: string;
18
- arguments?: string;
19
- };
20
- }>;
21
- };
22
- finish_reason?: string;
23
- }>;
24
- usage?: {
25
- prompt_tokens?: number;
26
- completion_tokens?: number;
27
- total_tokens?: number;
28
- };
29
- }
30
-
31
- export class OpenAICompatibleBackend implements AgentBackend {
32
- private apiKey: string;
33
- private baseUrl: string;
34
- private apiPath: string;
35
- private headers: Record<string, string>;
36
-
37
- constructor(config: OpenAICompatibleConfig) {
38
- this.apiKey = config.apiKey;
39
- this.baseUrl = config.baseUrl.replace(/\/+$/, '');
40
- this.apiPath = config.apiPath || '/chat/completions';
41
- this.headers = config.headers || {};
42
- }
43
-
44
- async generate(request: BackendRequest): Promise<BackendResponse> {
45
- const url = `${this.baseUrl}${this.apiPath}`;
46
- const mapContent = (content: BackendRequest['messages'][number]['content']) => {
47
- if (typeof content === 'string') return content;
48
- return content.map(block => {
49
- if (block.type === 'text') {
50
- return { type: 'text', text: block.text };
51
- }
52
- if (block.type === 'tool_result') {
53
- // OpenAI format: convert tool result to text
54
- const resultText = typeof block.content === 'string'
55
- ? block.content
56
- : block.content.map(b => b.type === 'text' ? b.text : '[image]').join('\n');
57
- return { type: 'text', text: `Tool result (${block.tool_use_id}): ${resultText}` };
58
- }
59
- if (block.type === 'tool_use') {
60
- // OpenAI handles tool calls differently - convert to text representation
61
- return { type: 'text', text: `[Tool call: ${block.name}(${JSON.stringify(block.input)})]` };
62
- }
63
- // Image block
64
- return {
65
- type: 'image_url',
66
- image_url: { url: `data:${block.mediaType};base64,${block.data}` }
67
- };
68
- });
69
- };
70
- const body = {
71
- model: request.model,
72
- max_tokens: request.maxTokens,
73
- messages: [
74
- { role: 'system', content: request.system },
75
- ...request.messages.map(message => ({
76
- role: message.role,
77
- content: mapContent(message.content)
78
- }))
79
- ],
80
- tools: request.tools.map(t => ({
81
- type: 'function',
82
- function: {
83
- name: t.name,
84
- description: t.description,
85
- parameters: t.parameters
86
- }
87
- }))
88
- };
89
-
90
- const response = await fetch(url, {
91
- method: 'POST',
92
- headers: {
93
- 'Content-Type': 'application/json',
94
- Authorization: `Bearer ${this.apiKey}`,
95
- ...this.headers
96
- },
97
- body: JSON.stringify(body)
98
- });
99
-
100
- if (!response.ok) {
101
- const errorText = await response.text();
102
- throw new Error(`API error (${response.status}): ${errorText}`);
103
- }
104
-
105
- const data = await response.json() as OpenAICompatibleResponse;
106
- const choice = data.choices?.[0];
107
- const message = choice?.message;
108
- const blocks: ContentBlock[] = [];
109
-
110
- if (message?.content) {
111
- blocks.push({ type: 'text', text: message.content });
112
- }
113
-
114
- if (message?.tool_calls) {
115
- for (const call of message.tool_calls) {
116
- const name = call.function?.name || 'unknown_tool';
117
- const rawArgs = call.function?.arguments || '{}';
118
- let args: Record<string, unknown> = {};
119
- try {
120
- args = JSON.parse(rawArgs);
121
- } catch {
122
- args = { _raw: rawArgs };
123
- }
124
- blocks.push({
125
- type: 'tool_use',
126
- id: call.id || `${name}_${Date.now()}`,
127
- name,
128
- input: args
129
- });
130
- }
131
- }
132
-
133
- const inputTokens = data.usage?.prompt_tokens ?? 0;
134
- const outputTokens = data.usage?.completion_tokens ?? 0;
135
- const totalTokens = data.usage?.total_tokens ?? (inputTokens + outputTokens);
136
-
137
- return {
138
- content: blocks,
139
- stopReason: choice?.finish_reason || 'unknown',
140
- usage: data.usage ? { inputTokens, outputTokens, totalTokens } : undefined
141
- };
142
- }
143
- }
@@ -1,83 +0,0 @@
1
- import { ToolDefinition } from '../../types.js';
2
-
3
- export interface RequestTextBlock {
4
- type: 'text';
5
- text: string;
6
- }
7
-
8
- export interface RequestImageBlock {
9
- type: 'image';
10
- mediaType: string;
11
- data: string;
12
- path?: string;
13
- }
14
-
15
- export interface ToolResultImageSource {
16
- type: 'base64';
17
- media_type: string;
18
- data: string;
19
- }
20
-
21
- export interface ToolResultImageBlock {
22
- type: 'image';
23
- source: ToolResultImageSource;
24
- }
25
-
26
- export interface ToolResultBlock {
27
- type: 'tool_result';
28
- tool_use_id: string;
29
- content: string | Array<RequestTextBlock | ToolResultImageBlock>;
30
- }
31
-
32
- export interface ToolUseRequestBlock {
33
- type: 'tool_use';
34
- id: string;
35
- name: string;
36
- input: Record<string, unknown>;
37
- }
38
-
39
- export type RequestContentBlock = RequestTextBlock | RequestImageBlock | ToolResultBlock | ToolUseRequestBlock;
40
-
41
- export interface LlmMessage {
42
- role: 'user' | 'assistant';
43
- content: string | RequestContentBlock[];
44
- }
45
-
46
- export interface TextBlock {
47
- type: 'text';
48
- text: string;
49
- }
50
-
51
- export interface ToolUseBlock {
52
- type: 'tool_use';
53
- id: string;
54
- name: string;
55
- input: Record<string, unknown>;
56
- }
57
-
58
- export type ContentBlock = TextBlock | ToolUseBlock;
59
-
60
- export interface BackendRequest {
61
- model: string;
62
- maxTokens: number;
63
- system: string;
64
- messages: LlmMessage[];
65
- tools: ToolDefinition[];
66
- }
67
-
68
- export interface BackendResponse {
69
- content: ContentBlock[];
70
- stopReason: string;
71
- usage?: TokenUsage;
72
- }
73
-
74
- export interface TokenUsage {
75
- inputTokens: number;
76
- outputTokens: number;
77
- totalTokens: number;
78
- estimated?: boolean;
79
- }
80
-
81
- export interface AgentBackend {
82
- generate(request: BackendRequest): Promise<BackendResponse>;
83
- }
@@ -1,77 +0,0 @@
1
- import { Command } from './types.js';
2
-
3
- type Role = 'user' | 'assistant' | 'system';
4
-
5
- function formatMessages(messages: { role: string; content: string }[]): string {
6
- return messages
7
- .map(msg => `[${msg.role}] ${msg.content}`)
8
- .join('\n\n')
9
- .trim();
10
- }
11
-
12
- function lastByRole(messages: { role: string; content: string }[], role: Role): { role: string; content: string } | undefined {
13
- for (let i = messages.length - 1; i >= 0; i -= 1) {
14
- if (messages[i].role === role) return messages[i];
15
- }
16
- return undefined;
17
- }
18
-
19
- export const clipboardCommand: Command = {
20
- name: 'clipboard',
21
- description: 'Copy conversation text to the clipboard',
22
- usage: '<all|last|assistant|user|system> [role]',
23
- handler: async (args, ctx) => {
24
- const messages = ctx.getMessages();
25
- if (messages.length === 0) {
26
- ctx.addMessage({ role: 'system', content: 'Clipboard: no messages to copy.' });
27
- return;
28
- }
29
-
30
- const target = (args[0] || 'last').toLowerCase();
31
- const roleArg = (args[1] || '').toLowerCase();
32
- const role = (roleArg === 'assistant' || roleArg === 'user' || roleArg === 'system')
33
- ? roleArg as Role
34
- : undefined;
35
-
36
- let text = '';
37
- let label = target;
38
-
39
- if (target === 'all') {
40
- text = formatMessages(messages);
41
- } else if (target === 'last') {
42
- const msg = role ? lastByRole(messages, role) : messages[messages.length - 1];
43
- if (!msg) {
44
- ctx.addMessage({ role: 'system', content: `Clipboard: no ${role} messages found.` });
45
- return;
46
- }
47
- text = formatMessages([msg]);
48
- label = role ? `last ${role}` : 'last';
49
- } else if (target === 'assistant' || target === 'user' || target === 'system') {
50
- const msg = lastByRole(messages, target as Role);
51
- if (!msg) {
52
- ctx.addMessage({ role: 'system', content: `Clipboard: no ${target} messages found.` });
53
- return;
54
- }
55
- text = formatMessages([msg]);
56
- } else {
57
- ctx.addMessage({
58
- role: 'system',
59
- content: 'Usage: /clipboard <all|last|assistant|user|system> [role]\n\nExamples:\n /clipboard all\n /clipboard last\n /clipboard last assistant\n /clipboard assistant'
60
- });
61
- return;
62
- }
63
-
64
- if (!text) {
65
- ctx.addMessage({ role: 'system', content: 'Clipboard: nothing to copy.' });
66
- return;
67
- }
68
-
69
- try {
70
- await ctx.clipboard.writeText(text);
71
- ctx.addMessage({ role: 'system', content: `✓ Copied ${text.length} chars (${label})` });
72
- } catch (err) {
73
- const message = err instanceof Error ? err.message : 'Failed to copy';
74
- ctx.addMessage({ role: 'system', content: `Clipboard error: ${message}` });
75
- }
76
- }
77
- };
@@ -1,204 +0,0 @@
1
- import { Command } from './types.js';
2
- import { DEFAULT_SPINNER_VERBS, formatSpinnerVerbs, parseSpinnerVerbs } from '../../utils/spinner_verbs.js';
3
-
4
- export const configCommand: Command = {
5
- name: 'config',
6
- description: 'Manage configuration',
7
- usage: '<show|key|provider|endpoint|model|spinner> [value]',
8
- handler: async (args, ctx) => {
9
- const [subCmd, ...rest] = args;
10
- const value = rest.join(' ');
11
-
12
- switch (subCmd) {
13
- case 'show':
14
- if (ctx.config.refresh) {
15
- await ctx.config.refresh();
16
- }
17
- const config = ctx.config.get();
18
- ctx.addMessage({
19
- role: 'system',
20
- content: [
21
- 'Current configuration:',
22
- ` API Key: ${ctx.config.getMaskedApiKey()}`,
23
- ` Provider: ${ctx.config.getProvider()}`,
24
- ` Model: ${config.model}`,
25
- ` Max Tokens: ${config.maxTokens}`,
26
- ` Zerg Endpoint: ${config.zergEndpoint || '(not set)'}`,
27
- ` Emulation: ${ctx.config.getEmulationId() || '(none)'}`,
28
- ` Spinner verbs: ${formatSpinnerVerbs(config.spinnerVerbs || DEFAULT_SPINNER_VERBS)}`,
29
- '',
30
- `Config storage: ${ctx.config.locationLabel || '~/.ztc/config.json'}`
31
- ].join('\n')
32
- });
33
- break;
34
-
35
- case 'key':
36
- if (!value) {
37
- ctx.addMessage({
38
- role: 'system',
39
- content: `Current API key: ${ctx.config.getMaskedApiKey()}\n\nUsage: /config key <your-api-key>`
40
- });
41
- return;
42
- }
43
-
44
- let provider = ctx.config.getProvider();
45
- let apiKey = value;
46
- if (args[0] && ['anthropic', 'openai', 'gemini', 'inception', 'openai_compatible'].includes(args[0])) {
47
- provider = args[0];
48
- apiKey = args.slice(1).join(' ');
49
- }
50
- if (!apiKey) {
51
- ctx.addMessage({
52
- role: 'system',
53
- content: 'Usage: /config key <provider> <api-key>'
54
- });
55
- return;
56
- }
57
-
58
- if (provider === 'anthropic' && !apiKey.startsWith('sk-ant-')) {
59
- ctx.addMessage({
60
- role: 'system',
61
- content: '⚠️ Warning: API key doesn\'t look like an Anthropic key (should start with sk-ant-). Setting anyway...'
62
- });
63
- }
64
-
65
- ctx.config.setApiKey(apiKey, provider);
66
- ctx.config.save();
67
- ctx.reloadAgent();
68
- ctx.addMessage({
69
- role: 'system',
70
- content: `✓ API key updated (${provider}): ${ctx.config.getMaskedApiKey()}\n Saved to ${ctx.config.locationLabel || '~/.ztc/config.json'}`
71
- });
72
- break;
73
-
74
- case 'provider':
75
- if (!value) {
76
- ctx.addMessage({
77
- role: 'system',
78
- content: `Current provider: ${ctx.config.getProvider()}\n\nUsage: /config provider <anthropic|openai|gemini|inception|openai_compatible>`
79
- });
80
- return;
81
- }
82
- ctx.config.setProvider(value);
83
- ctx.config.save();
84
- ctx.reloadAgent();
85
- ctx.addMessage({
86
- role: 'system',
87
- content: `✓ Provider updated: ${value}`
88
- });
89
- break;
90
-
91
- case 'endpoint':
92
- if (!value) {
93
- ctx.addMessage({
94
- role: 'system',
95
- content: `Current OpenAI-compatible base URL: ${ctx.config.getOpenAICompatibleBaseUrl() || '(not set)'}\n\nUsage: /config endpoint <url>`
96
- });
97
- return;
98
- }
99
- ctx.config.setOpenAICompatibleBaseUrl(value);
100
- ctx.config.save();
101
- ctx.reloadAgent();
102
- ctx.addMessage({
103
- role: 'system',
104
- content: `✓ OpenAI-compatible base URL updated: ${value}`
105
- });
106
- break;
107
-
108
- case 'model':
109
- if (!value) {
110
- ctx.addMessage({
111
- role: 'system',
112
- content: `Current model: ${ctx.config.get().model}\n\nUsage: /config model <model-name>\n\nAvailable models:\n claude-opus-4-20250514\n claude-sonnet-4-20250514\n claude-haiku-3-20240307`
113
- });
114
- return;
115
- }
116
- ctx.config.set('model', value);
117
- ctx.config.save();
118
- ctx.reloadAgent();
119
- ctx.addMessage({
120
- role: 'system',
121
- content: `✓ Model updated: ${value}`
122
- });
123
- break;
124
-
125
- case 'spinner': {
126
- const [action, ...restParts] = rest;
127
- const actionValue = restParts.join(' ');
128
- const current = ctx.config.get().spinnerVerbs || DEFAULT_SPINNER_VERBS;
129
-
130
- if (!action || action === 'show') {
131
- ctx.addMessage({
132
- role: 'system',
133
- content: `Spinner verbs:\n ${formatSpinnerVerbs(current)}\n\nUsage:\n /config spinner show\n /config spinner set <verb1, verb2, ...>\n /config spinner add <verb>\n /config spinner remove <verb>\n /config spinner reset\n /config spinner off`
134
- });
135
- return;
136
- }
137
-
138
- if (action === 'reset') {
139
- ctx.config.set('spinnerVerbs', DEFAULT_SPINNER_VERBS);
140
- ctx.config.save();
141
- ctx.addMessage({ role: 'system', content: '✓ Spinner verbs reset to defaults.' });
142
- return;
143
- }
144
-
145
- if (action === 'off') {
146
- ctx.config.set('spinnerVerbs', []);
147
- ctx.config.save();
148
- ctx.addMessage({ role: 'system', content: '✓ Spinner verbs disabled.' });
149
- return;
150
- }
151
-
152
- if (action === 'set') {
153
- const verbs = parseSpinnerVerbs(actionValue);
154
- if (verbs.length === 0) {
155
- ctx.addMessage({ role: 'system', content: 'Usage: /config spinner set <verb1, verb2, ...>' });
156
- return;
157
- }
158
- ctx.config.set('spinnerVerbs', verbs);
159
- ctx.config.save();
160
- ctx.addMessage({ role: 'system', content: `✓ Spinner verbs updated: ${formatSpinnerVerbs(verbs)}` });
161
- return;
162
- }
163
-
164
- if (action === 'add') {
165
- const verbs = parseSpinnerVerbs(actionValue);
166
- if (verbs.length === 0) {
167
- ctx.addMessage({ role: 'system', content: 'Usage: /config spinner add <verb>' });
168
- return;
169
- }
170
- const next = [...current, ...verbs];
171
- ctx.config.set('spinnerVerbs', next);
172
- ctx.config.save();
173
- ctx.addMessage({ role: 'system', content: `✓ Added spinner verbs: ${formatSpinnerVerbs(verbs)}` });
174
- return;
175
- }
176
-
177
- if (action === 'remove') {
178
- const verbs = parseSpinnerVerbs(actionValue).map(v => v.toLowerCase());
179
- if (verbs.length === 0) {
180
- ctx.addMessage({ role: 'system', content: 'Usage: /config spinner remove <verb>' });
181
- return;
182
- }
183
- const next = current.filter(v => !verbs.includes(v.toLowerCase()));
184
- ctx.config.set('spinnerVerbs', next);
185
- ctx.config.save();
186
- ctx.addMessage({ role: 'system', content: `✓ Removed spinner verbs: ${formatSpinnerVerbs(verbs)}` });
187
- return;
188
- }
189
-
190
- ctx.addMessage({
191
- role: 'system',
192
- content: 'Usage: /config spinner <show|set|add|remove|reset|off> [value]'
193
- });
194
- break;
195
- }
196
-
197
- default:
198
- ctx.addMessage({
199
- role: 'system',
200
- content: 'Usage: /config <show|key|provider|endpoint|model|spinner> [value]\n\nExamples:\n /config show Show current config\n /config key sk-ant-... Set API key (current provider)\n /config key openai sk-... Set API key for provider\n /config provider openai Set provider\n /config endpoint https://api.example.com/v1 Set OpenAI-compatible base URL\n /config model claude-opus-4-20250514 Set model\n /config spinner set Reticulating splines, Organizing thoughts'
201
- });
202
- }
203
- }
204
- };