@heyamiko/amiko-cli 0.4.12 → 0.5.1
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/README.md +225 -0
- package/dist/index.js +75792 -0
- package/package.json +26 -19
- package/PLAN.md +0 -113
- package/bun.lock +0 -743
- package/dist/__tests__/cli-non-tty.test.js +0 -74
- package/dist/agent/piAgent.js +0 -239
- package/dist/agent/tools.js +0 -149
- package/dist/app.js +0 -355
- package/dist/cli.js +0 -99
- package/dist/commands.js +0 -22
- package/dist/components/ModelPicker.js +0 -63
- package/dist/config.js +0 -26
- package/dist/configFile.js +0 -23
- package/dist/headless.js +0 -227
- package/dist/memory/qmd.js +0 -87
- package/dist/prompt.js +0 -43
- package/dist/session.js +0 -26
- package/dist/types.js +0 -1
- package/dist/workspace.js +0 -42
- package/src/__tests__/cli-non-tty.test.ts +0 -86
- package/src/agent/piAgent.ts +0 -266
- package/src/agent/tools.ts +0 -153
- package/src/app.tsx +0 -448
- package/src/cli.tsx +0 -121
- package/src/commands.ts +0 -39
- package/src/components/ModelPicker.tsx +0 -107
- package/src/config.ts +0 -31
- package/src/configFile.ts +0 -33
- package/src/headless.ts +0 -243
- package/src/memory/qmd.ts +0 -111
- package/src/prompt.ts +0 -55
- package/src/session.ts +0 -35
- package/src/types.ts +0 -40
- package/src/workspace.ts +0 -58
- package/tsconfig.json +0 -16
package/src/app.tsx
DELETED
|
@@ -1,448 +0,0 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
|
2
|
-
import { Box, Static, Text, useApp, useInput, useStdout } from "ink";
|
|
3
|
-
import { getEnvApiKey, getModels, getProviders, type KnownProvider } from "@mariozechner/pi-ai";
|
|
4
|
-
import { AppConfig, Message, MemoryHit } from "./types.js";
|
|
5
|
-
import { parseCommand } from "./commands.js";
|
|
6
|
-
import { qmdCollectionAdd, qmdEmbed, qmdGet, qmdSearch } from "./memory/qmd.js";
|
|
7
|
-
import { buildPrompt } from "./prompt.js";
|
|
8
|
-
import { appendSession } from "./session.js";
|
|
9
|
-
import { readWorkspaceFiles, WorkspaceFiles } from "./workspace.js";
|
|
10
|
-
import { createPiAgent } from "./agent/piAgent.js";
|
|
11
|
-
import { ModelPicker } from "./components/ModelPicker.js";
|
|
12
|
-
import { saveConfigFile } from "./configFile.js";
|
|
13
|
-
|
|
14
|
-
export type AppProps = {
|
|
15
|
-
config: AppConfig;
|
|
16
|
-
workspace: WorkspaceFiles;
|
|
17
|
-
sessionPath: string;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
type StyledMessage = {
|
|
21
|
-
role: Message["role"];
|
|
22
|
-
content: string;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
type StaticItem =
|
|
26
|
-
| { type: "banner" }
|
|
27
|
-
| { type: "message"; message: StyledMessage };
|
|
28
|
-
|
|
29
|
-
export function App({ config, workspace, sessionPath }: AppProps) {
|
|
30
|
-
const { exit } = useApp();
|
|
31
|
-
const { stdout } = useStdout();
|
|
32
|
-
const [input, setInput] = useState("");
|
|
33
|
-
const [messages, setMessages] = useState<StyledMessage[]>([]);
|
|
34
|
-
const [streamingAssistant, setStreamingAssistant] = useState("");
|
|
35
|
-
const [status, setStatus] = useState<string>("ready");
|
|
36
|
-
const [agentReady, setAgentReady] = useState(false);
|
|
37
|
-
const [currentModel, setCurrentModel] = useState<string>(() => {
|
|
38
|
-
if (config.model) return config.model;
|
|
39
|
-
if (config.modelProvider && config.modelId) return `${config.modelProvider}/${config.modelId}`;
|
|
40
|
-
return "auto";
|
|
41
|
-
});
|
|
42
|
-
const [showModelPicker, setShowModelPicker] = useState(false);
|
|
43
|
-
const toolMessageIds = useMemo(() => new Set<string>(), []);
|
|
44
|
-
|
|
45
|
-
const agentPromise = useMemo(() => createPiAgent(config), [
|
|
46
|
-
config.model,
|
|
47
|
-
config.modelProvider,
|
|
48
|
-
config.modelId,
|
|
49
|
-
config.thinkingLevel
|
|
50
|
-
]);
|
|
51
|
-
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
let mounted = true;
|
|
54
|
-
agentPromise.then((agent) => {
|
|
55
|
-
if (!mounted) return;
|
|
56
|
-
setAgentReady(true);
|
|
57
|
-
if (currentModel === "auto") {
|
|
58
|
-
const value = agent.getCurrentModel?.();
|
|
59
|
-
if (value) setCurrentModel(value);
|
|
60
|
-
}
|
|
61
|
-
if (agent.onEvent) {
|
|
62
|
-
agent.onEvent((event) => {
|
|
63
|
-
if (event?.type === "tool_execution_start") {
|
|
64
|
-
const key = `${event.toolCallId ?? ""}:${event.toolName ?? ""}:${JSON.stringify(event.args || {})}`;
|
|
65
|
-
if (toolMessageIds.has(key)) return;
|
|
66
|
-
toolMessageIds.add(key);
|
|
67
|
-
|
|
68
|
-
const lines = [`[${event.toolName}] start`];
|
|
69
|
-
if (event.args?.command) lines.push(`command: ${event.args.command}`);
|
|
70
|
-
if (event.args?.cwd) lines.push(`cwd: ${event.args.cwd}`);
|
|
71
|
-
if (event.args?.timeoutMs) lines.push(`timeoutMs: ${event.args.timeoutMs}`);
|
|
72
|
-
pushMessage("tool", lines.join("\n"));
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
if (event?.type === "tool_execution_end") {
|
|
76
|
-
const key = `${event.toolCallId ?? ""}:${event.toolName ?? ""}:${JSON.stringify(event.args || {})}`;
|
|
77
|
-
if (toolMessageIds.has(key + ":end")) return;
|
|
78
|
-
toolMessageIds.add(key + ":end");
|
|
79
|
-
|
|
80
|
-
const resultText = event.result?.content
|
|
81
|
-
? event.result.content.map((c: any) => (c.text ? c.text : "")).join("\\n")
|
|
82
|
-
: "";
|
|
83
|
-
const trimmed = resultText.trim();
|
|
84
|
-
const summary = trimmed.length > 0 ? trimmed : "(no output)";
|
|
85
|
-
pushMessage("tool", `[${event.toolName}] done\n${summary}`);
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
return () => {
|
|
91
|
-
mounted = false;
|
|
92
|
-
};
|
|
93
|
-
}, [agentPromise, currentModel]);
|
|
94
|
-
|
|
95
|
-
const pushMessage = useCallback((role: Message["role"], content: string) => {
|
|
96
|
-
setMessages((prev) => [...prev, { role, content }]);
|
|
97
|
-
}, []);
|
|
98
|
-
|
|
99
|
-
const handleSubmit = useCallback(async () => {
|
|
100
|
-
const trimmed = input.trim();
|
|
101
|
-
if (!trimmed) return;
|
|
102
|
-
|
|
103
|
-
setInput("");
|
|
104
|
-
|
|
105
|
-
const command = parseCommand(trimmed);
|
|
106
|
-
if (command) {
|
|
107
|
-
await handleCommand(command.name, command.args);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
pushMessage("user", trimmed);
|
|
112
|
-
await appendSession(sessionPath, "user", trimmed);
|
|
113
|
-
|
|
114
|
-
setStatus("thinking");
|
|
115
|
-
setStreamingAssistant("");
|
|
116
|
-
|
|
117
|
-
const [workspaceData, agent] = await Promise.all([
|
|
118
|
-
readWorkspaceFiles(workspace),
|
|
119
|
-
agentPromise
|
|
120
|
-
]);
|
|
121
|
-
|
|
122
|
-
let hits: MemoryHit[] = [];
|
|
123
|
-
if (config.qmdEnabled) {
|
|
124
|
-
hits = await qmdSearch({
|
|
125
|
-
qmdBin: config.qmdBin,
|
|
126
|
-
query: trimmed,
|
|
127
|
-
limit: config.qmdLimit,
|
|
128
|
-
collection: config.qmdCollection
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (config.qmdFull && hits.length > 0) {
|
|
133
|
-
const expanded: MemoryHit[] = [];
|
|
134
|
-
for (const hit of hits) {
|
|
135
|
-
const docId = hit.path || hit.id;
|
|
136
|
-
if (!docId) {
|
|
137
|
-
expanded.push(hit);
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
const text = await qmdGet({ qmdBin: config.qmdBin, docId, full: true });
|
|
141
|
-
expanded.push({ ...hit, snippet: text || hit.snippet });
|
|
142
|
-
}
|
|
143
|
-
hits = expanded;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const prompt = buildPrompt({
|
|
147
|
-
identity: workspaceData.identity,
|
|
148
|
-
personality: workspaceData.personality,
|
|
149
|
-
memory: workspaceData.memory,
|
|
150
|
-
hits
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
let assistantText = "";
|
|
154
|
-
try {
|
|
155
|
-
for await (const chunk of agent.streamReply({ prompt, userMessage: trimmed })) {
|
|
156
|
-
assistantText += chunk;
|
|
157
|
-
setStreamingAssistant((prev) => prev + chunk);
|
|
158
|
-
}
|
|
159
|
-
} catch (error) {
|
|
160
|
-
const message = (error as Error).message || "unknown error";
|
|
161
|
-
assistantText += `\n[amiko] ${message}\n`;
|
|
162
|
-
setStreamingAssistant((prev) => prev + `\n[amiko] ${message}\n`);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if (assistantText) {
|
|
166
|
-
await appendSession(sessionPath, "assistant", assistantText);
|
|
167
|
-
pushMessage("assistant", assistantText);
|
|
168
|
-
}
|
|
169
|
-
setStreamingAssistant("");
|
|
170
|
-
|
|
171
|
-
setStatus("ready");
|
|
172
|
-
}, [
|
|
173
|
-
input,
|
|
174
|
-
agentPromise,
|
|
175
|
-
config.qmdBin,
|
|
176
|
-
config.qmdEnabled,
|
|
177
|
-
config.qmdLimit,
|
|
178
|
-
config.qmdFull,
|
|
179
|
-
config.qmdCollection,
|
|
180
|
-
pushMessage,
|
|
181
|
-
sessionPath,
|
|
182
|
-
workspace
|
|
183
|
-
]);
|
|
184
|
-
|
|
185
|
-
const handleCommand = useCallback(
|
|
186
|
-
async (name: string, args: string) => {
|
|
187
|
-
switch (name) {
|
|
188
|
-
case "exit":
|
|
189
|
-
exit();
|
|
190
|
-
return;
|
|
191
|
-
case "help":
|
|
192
|
-
pushMessage(
|
|
193
|
-
"assistant",
|
|
194
|
-
"Commands: /help /exit /search <query> /memory /session /plan /identity /personality /index [collection] /model [provider[/id]]"
|
|
195
|
-
);
|
|
196
|
-
return;
|
|
197
|
-
case "memory": {
|
|
198
|
-
const data = await readWorkspaceFiles(workspace);
|
|
199
|
-
pushMessage("assistant", `MEMORY.md:\n\n${data.memory.trim() || "(empty)"}`);
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
case "session":
|
|
203
|
-
pushMessage("assistant", `Session file: ${sessionPath}`);
|
|
204
|
-
return;
|
|
205
|
-
case "plan":
|
|
206
|
-
pushMessage("assistant", "Plan file: PLAN.md");
|
|
207
|
-
return;
|
|
208
|
-
case "identity": {
|
|
209
|
-
const data = await readWorkspaceFiles(workspace);
|
|
210
|
-
pushMessage("assistant", `IDENTITY.md:\n\n${data.identity.trim() || "(empty)"}`);
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
case "personality": {
|
|
214
|
-
const data = await readWorkspaceFiles(workspace);
|
|
215
|
-
pushMessage("assistant", `PERSONALITY.md:\n\n${data.personality.trim() || "(empty)"}`);
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
case "index": {
|
|
219
|
-
if (!config.qmdEnabled) {
|
|
220
|
-
pushMessage("assistant", "QMD is disabled.");
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
const name = args || config.qmdCollection || "amiko";
|
|
224
|
-
const added = await qmdCollectionAdd({
|
|
225
|
-
qmdBin: config.qmdBin,
|
|
226
|
-
name,
|
|
227
|
-
path: workspace.root
|
|
228
|
-
});
|
|
229
|
-
if (!added) {
|
|
230
|
-
pushMessage("assistant", "Failed to add QMD collection.");
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
const embedded = await qmdEmbed({ qmdBin: config.qmdBin, collection: name });
|
|
234
|
-
pushMessage(
|
|
235
|
-
"assistant",
|
|
236
|
-
embedded ? `Indexed QMD collection "${name}".` : `Collection "${name}" added, but embedding failed.`
|
|
237
|
-
);
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
case "search": {
|
|
241
|
-
if (!args) {
|
|
242
|
-
pushMessage("assistant", "Usage: /search <query>");
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
if (!config.qmdEnabled) {
|
|
246
|
-
pushMessage("assistant", "QMD is disabled.");
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
const hits = await qmdSearch({
|
|
250
|
-
qmdBin: config.qmdBin,
|
|
251
|
-
query: args,
|
|
252
|
-
limit: config.qmdLimit,
|
|
253
|
-
collection: config.qmdCollection
|
|
254
|
-
});
|
|
255
|
-
if (hits.length === 0) {
|
|
256
|
-
pushMessage("assistant", "No memory hits.");
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
const formatted = hits
|
|
260
|
-
.map((hit, index) => {
|
|
261
|
-
const label = hit.title || hit.path || hit.id;
|
|
262
|
-
const score = hit.score ? ` (${hit.score.toFixed(2)})` : "";
|
|
263
|
-
return `${index + 1}. ${label}${score}`;
|
|
264
|
-
})
|
|
265
|
-
.join("\n");
|
|
266
|
-
pushMessage("assistant", formatted);
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
case "model": {
|
|
270
|
-
const agent = await agentPromise;
|
|
271
|
-
if (!args) {
|
|
272
|
-
setShowModelPicker(true);
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const parts = args.split(" ").filter(Boolean);
|
|
277
|
-
const slashParts = args.includes("/") ? args.split("/") : [];
|
|
278
|
-
let provider = parts[0];
|
|
279
|
-
let modelId = parts[1];
|
|
280
|
-
|
|
281
|
-
const providerIndex = Number(provider);
|
|
282
|
-
const providers = getProviders().sort();
|
|
283
|
-
if (Number.isInteger(providerIndex) && providerIndex > 0 && providerIndex <= providers.length) {
|
|
284
|
-
provider = providers[providerIndex - 1];
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (slashParts.length >= 2) {
|
|
288
|
-
provider = slashParts[0]?.trim();
|
|
289
|
-
modelId = slashParts.slice(1).join("/").trim();
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (provider && !modelId) {
|
|
293
|
-
const models = getModels(provider as KnownProvider);
|
|
294
|
-
if (models.length === 0) {
|
|
295
|
-
pushMessage("assistant", `Unknown provider: ${provider}`);
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
const modelList = models.map((m) => m.id).sort();
|
|
299
|
-
pushMessage("assistant", `Models for ${provider}:\n${modelList.join("\n")}`);
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (!provider || !modelId) {
|
|
304
|
-
pushMessage("assistant", "Usage: /model <provider> [modelId] or /model <provider>/<modelId>");
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
if (!agent.setModel) {
|
|
309
|
-
pushMessage("assistant", "Agent does not support model switching.");
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
const result = agent.setModel(provider, modelId);
|
|
314
|
-
pushMessage("assistant", result.message);
|
|
315
|
-
if (result.ok) {
|
|
316
|
-
setCurrentModel(`${provider}/${modelId}`);
|
|
317
|
-
await saveConfigFile(workspace.root, {
|
|
318
|
-
model: `${provider}/${modelId}`,
|
|
319
|
-
modelProvider: provider,
|
|
320
|
-
modelId
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
default:
|
|
326
|
-
pushMessage("assistant", `Unknown command: ${name}`);
|
|
327
|
-
}
|
|
328
|
-
},
|
|
329
|
-
[
|
|
330
|
-
agentPromise,
|
|
331
|
-
config.qmdBin,
|
|
332
|
-
config.qmdLimit,
|
|
333
|
-
config.qmdCollection,
|
|
334
|
-
exit,
|
|
335
|
-
pushMessage,
|
|
336
|
-
sessionPath,
|
|
337
|
-
workspace,
|
|
338
|
-
currentModel,
|
|
339
|
-
config.qmdEnabled
|
|
340
|
-
]
|
|
341
|
-
);
|
|
342
|
-
|
|
343
|
-
useInput((value, key) => {
|
|
344
|
-
if (key.return) {
|
|
345
|
-
void handleSubmit();
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
if (key.backspace || key.delete) {
|
|
349
|
-
setInput((prev) => prev.slice(0, -1));
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
if (key.ctrl && value === "c") {
|
|
353
|
-
exit();
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
setInput((prev) => prev + value);
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
const terminalWidth = stdout?.columns ?? 80;
|
|
360
|
-
const shortWorkspace = terminalWidth < 90 ? workspace.root.split("/").slice(-2).join("/") : workspace.root;
|
|
361
|
-
const shortModel =
|
|
362
|
-
terminalWidth < 90 && currentModel.includes("/")
|
|
363
|
-
? currentModel.split("/").slice(-2).join("/")
|
|
364
|
-
: currentModel;
|
|
365
|
-
|
|
366
|
-
const maxVisibleMessages = Math.max(4, (stdout?.rows ?? 24) - 12);
|
|
367
|
-
const visibleMessages =
|
|
368
|
-
messages.length > maxVisibleMessages ? messages.slice(messages.length - maxVisibleMessages) : messages;
|
|
369
|
-
|
|
370
|
-
const staticItems: StaticItem[] = [
|
|
371
|
-
{ type: "banner" },
|
|
372
|
-
...visibleMessages.map((message): StaticItem => ({ type: "message", message }))
|
|
373
|
-
];
|
|
374
|
-
|
|
375
|
-
return (
|
|
376
|
-
<Box flexDirection="column" padding={1}>
|
|
377
|
-
<Box flexDirection="column" marginBottom={1}>
|
|
378
|
-
<Static items={staticItems}>
|
|
379
|
-
{(item, index) => {
|
|
380
|
-
if (item.type === "banner") {
|
|
381
|
-
return (
|
|
382
|
-
<Box key={`banner-${index}`} borderStyle="round" borderColor="cyan" paddingX={1} marginBottom={1} flexDirection="column">
|
|
383
|
-
<Box>
|
|
384
|
-
<Text>amiko-cli</Text>
|
|
385
|
-
<Text color="gray"> {shortWorkspace}</Text>
|
|
386
|
-
</Box>
|
|
387
|
-
<Box>
|
|
388
|
-
<Text color="blue">model: {shortModel}</Text>
|
|
389
|
-
<Text> </Text>
|
|
390
|
-
<Text color={agentReady ? "green" : "yellow"}>agent: {agentReady ? "ready" : "loading"}</Text>
|
|
391
|
-
<Text> </Text>
|
|
392
|
-
<Text color="magenta">status: {status}</Text>
|
|
393
|
-
</Box>
|
|
394
|
-
</Box>
|
|
395
|
-
);
|
|
396
|
-
}
|
|
397
|
-
const message = item.message;
|
|
398
|
-
return (
|
|
399
|
-
<Box key={`message-${index}`} flexDirection="column" marginBottom={1}>
|
|
400
|
-
<Text color={message.role === "user" ? "cyan" : message.role === "tool" ? "yellow" : "green"}>
|
|
401
|
-
[{message.role}]
|
|
402
|
-
</Text>
|
|
403
|
-
<Text>{message.content}</Text>
|
|
404
|
-
</Box>
|
|
405
|
-
);
|
|
406
|
-
}}
|
|
407
|
-
</Static>
|
|
408
|
-
{streamingAssistant ? (
|
|
409
|
-
<Box flexDirection="column" marginBottom={1}>
|
|
410
|
-
<Text color="green">[assistant]</Text>
|
|
411
|
-
<Text>{streamingAssistant}</Text>
|
|
412
|
-
</Box>
|
|
413
|
-
) : null}
|
|
414
|
-
</Box>
|
|
415
|
-
|
|
416
|
-
{showModelPicker ? (
|
|
417
|
-
<ModelPicker
|
|
418
|
-
currentModel={currentModel}
|
|
419
|
-
onCancel={() => setShowModelPicker(false)}
|
|
420
|
-
onSelect={async (provider, modelId) => {
|
|
421
|
-
const agent = await agentPromise;
|
|
422
|
-
if (!agent.setModel) {
|
|
423
|
-
pushMessage("assistant", "Agent does not support model switching.");
|
|
424
|
-
setShowModelPicker(false);
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
const result = agent.setModel(provider, modelId);
|
|
428
|
-
pushMessage("assistant", result.message);
|
|
429
|
-
if (result.ok) setCurrentModel(`${provider}/${modelId}`);
|
|
430
|
-
if (result.ok) {
|
|
431
|
-
await saveConfigFile(workspace.root, {
|
|
432
|
-
model: `${provider}/${modelId}`,
|
|
433
|
-
modelProvider: provider,
|
|
434
|
-
modelId
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
setShowModelPicker(false);
|
|
438
|
-
}}
|
|
439
|
-
/>
|
|
440
|
-
) : null}
|
|
441
|
-
|
|
442
|
-
<Box>
|
|
443
|
-
<Text color="yellow">> </Text>
|
|
444
|
-
<Text>{input}</Text>
|
|
445
|
-
</Box>
|
|
446
|
-
</Box>
|
|
447
|
-
);
|
|
448
|
-
}
|
package/src/cli.tsx
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import React from "react";
|
|
3
|
-
import { render } from "ink";
|
|
4
|
-
import { loadConfig } from "./config.js";
|
|
5
|
-
import { initWorkspace } from "./workspace.js";
|
|
6
|
-
import { createSessionFile } from "./session.js";
|
|
7
|
-
import { App } from "./app.js";
|
|
8
|
-
import { loadConfigFile } from "./configFile.js";
|
|
9
|
-
import { runHeadless } from "./headless.js";
|
|
10
|
-
|
|
11
|
-
type ArgOverrides = Partial<{
|
|
12
|
-
workspacePath: string;
|
|
13
|
-
sessionId: string;
|
|
14
|
-
qmdBin: string;
|
|
15
|
-
qmdLimit: number;
|
|
16
|
-
qmdEnabled: boolean;
|
|
17
|
-
qmdCollection?: string;
|
|
18
|
-
qmdFull: boolean;
|
|
19
|
-
model?: string;
|
|
20
|
-
modelProvider?: string;
|
|
21
|
-
modelId?: string;
|
|
22
|
-
thinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
23
|
-
headless?: boolean;
|
|
24
|
-
message?: string;
|
|
25
|
-
}>;
|
|
26
|
-
|
|
27
|
-
function parseArgs(argv: string[]): ArgOverrides {
|
|
28
|
-
const overrides: ArgOverrides = {};
|
|
29
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
30
|
-
const arg = argv[i];
|
|
31
|
-
switch (arg) {
|
|
32
|
-
case "--headless":
|
|
33
|
-
overrides.headless = true;
|
|
34
|
-
break;
|
|
35
|
-
case "--message":
|
|
36
|
-
case "-m":
|
|
37
|
-
overrides.message = argv[i + 1];
|
|
38
|
-
i += 1;
|
|
39
|
-
break;
|
|
40
|
-
case "--workspace":
|
|
41
|
-
case "-w":
|
|
42
|
-
overrides.workspacePath = argv[i + 1];
|
|
43
|
-
i += 1;
|
|
44
|
-
break;
|
|
45
|
-
case "--session":
|
|
46
|
-
overrides.sessionId = argv[i + 1];
|
|
47
|
-
i += 1;
|
|
48
|
-
break;
|
|
49
|
-
case "--qmd-bin":
|
|
50
|
-
overrides.qmdBin = argv[i + 1];
|
|
51
|
-
i += 1;
|
|
52
|
-
break;
|
|
53
|
-
case "--qmd-limit":
|
|
54
|
-
overrides.qmdLimit = Number(argv[i + 1]);
|
|
55
|
-
i += 1;
|
|
56
|
-
break;
|
|
57
|
-
case "--qmd-collection":
|
|
58
|
-
overrides.qmdCollection = argv[i + 1];
|
|
59
|
-
i += 1;
|
|
60
|
-
break;
|
|
61
|
-
case "--qmd-full":
|
|
62
|
-
overrides.qmdFull = true;
|
|
63
|
-
break;
|
|
64
|
-
case "--no-qmd":
|
|
65
|
-
overrides.qmdEnabled = false;
|
|
66
|
-
break;
|
|
67
|
-
case "--model":
|
|
68
|
-
overrides.model = argv[i + 1];
|
|
69
|
-
i += 1;
|
|
70
|
-
break;
|
|
71
|
-
case "--model-provider":
|
|
72
|
-
overrides.modelProvider = argv[i + 1];
|
|
73
|
-
i += 1;
|
|
74
|
-
break;
|
|
75
|
-
case "--model-id":
|
|
76
|
-
overrides.modelId = argv[i + 1];
|
|
77
|
-
i += 1;
|
|
78
|
-
break;
|
|
79
|
-
case "--thinking":
|
|
80
|
-
overrides.thinkingLevel = argv[i + 1] as ArgOverrides["thinkingLevel"];
|
|
81
|
-
i += 1;
|
|
82
|
-
break;
|
|
83
|
-
default:
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return overrides;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async function main() {
|
|
91
|
-
// Ink's render-border.js calls box.top.repeat(contentWidth) where
|
|
92
|
-
// contentWidth = width - 2. When yoga computes a zero-width node (common
|
|
93
|
-
// in non-TTY / piped environments), contentWidth goes negative and
|
|
94
|
-
// String.repeat() throws. Patch String.repeat to clamp to 0.
|
|
95
|
-
const origRepeat = String.prototype.repeat;
|
|
96
|
-
String.prototype.repeat = function (count: number) {
|
|
97
|
-
return origRepeat.call(this, Math.max(0, Math.floor(count)));
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
const config = loadConfig();
|
|
101
|
-
const overrides = parseArgs(process.argv.slice(2));
|
|
102
|
-
const finalConfig = { ...config, ...overrides };
|
|
103
|
-
const workspace = await initWorkspace(finalConfig.workspacePath);
|
|
104
|
-
const persisted = await loadConfigFile(workspace.root);
|
|
105
|
-
const mergedConfig = { ...finalConfig, ...persisted, ...overrides };
|
|
106
|
-
const sessionFile = await createSessionFile(workspace.sessionsDir, finalConfig.sessionId);
|
|
107
|
-
|
|
108
|
-
// Auto-detect non-TTY, --headless flag, or -m single-shot
|
|
109
|
-
if (overrides.headless || overrides.message || !process.stdout.isTTY) {
|
|
110
|
-
await runHeadless(mergedConfig, workspace, sessionFile.path, overrides.message);
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
render(<App config={mergedConfig} workspace={workspace} sessionPath={sessionFile.path} />);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
main().catch((error) => {
|
|
118
|
-
// eslint-disable-next-line no-console
|
|
119
|
-
console.error("amiko-cli failed:", error);
|
|
120
|
-
process.exit(1);
|
|
121
|
-
});
|
package/src/commands.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
export type CommandName =
|
|
2
|
-
| "exit"
|
|
3
|
-
| "help"
|
|
4
|
-
| "memory"
|
|
5
|
-
| "session"
|
|
6
|
-
| "search"
|
|
7
|
-
| "plan"
|
|
8
|
-
| "index"
|
|
9
|
-
| "identity"
|
|
10
|
-
| "personality"
|
|
11
|
-
| "model";
|
|
12
|
-
|
|
13
|
-
export type ParsedCommand = {
|
|
14
|
-
name: CommandName;
|
|
15
|
-
args: string;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export function parseCommand(input: string): ParsedCommand | null {
|
|
19
|
-
if (!input.startsWith("/")) return null;
|
|
20
|
-
const [command, ...rest] = input.slice(1).split(" ");
|
|
21
|
-
const name = command.toLowerCase();
|
|
22
|
-
const args = rest.join(" ").trim();
|
|
23
|
-
|
|
24
|
-
switch (name) {
|
|
25
|
-
case "exit":
|
|
26
|
-
case "help":
|
|
27
|
-
case "memory":
|
|
28
|
-
case "session":
|
|
29
|
-
case "search":
|
|
30
|
-
case "plan":
|
|
31
|
-
case "index":
|
|
32
|
-
case "identity":
|
|
33
|
-
case "personality":
|
|
34
|
-
case "model":
|
|
35
|
-
return { name, args };
|
|
36
|
-
default:
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
}
|