@heyamiko/amiko-cli 0.4.11 → 0.5.0
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/dist/index.js +75768 -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 -94
- 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 -218
- 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 -115
- 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 -222
- 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/config.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import os from "node:os";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { randomUUID } from "node:crypto";
|
|
4
|
-
import { AppConfig } from "./types.js";
|
|
5
|
-
|
|
6
|
-
function defaultWorkspacePath() {
|
|
7
|
-
return path.join(os.homedir(), ".amiko");
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function newSessionId() {
|
|
11
|
-
return randomUUID().split("-")[0];
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function loadConfig(): AppConfig {
|
|
15
|
-
const workspacePath = process.env.AMIKO_WORKSPACE || defaultWorkspacePath();
|
|
16
|
-
const sessionId = process.env.AMIKO_SESSION_ID || newSessionId();
|
|
17
|
-
|
|
18
|
-
return {
|
|
19
|
-
workspacePath,
|
|
20
|
-
sessionId,
|
|
21
|
-
qmdBin: process.env.AMIKO_QMD_BIN || "qmd",
|
|
22
|
-
qmdLimit: Number(process.env.AMIKO_QMD_LIMIT || 5),
|
|
23
|
-
qmdEnabled: process.env.AMIKO_QMD_ENABLED !== "0",
|
|
24
|
-
qmdCollection: process.env.AMIKO_QMD_COLLECTION || undefined,
|
|
25
|
-
qmdFull: process.env.AMIKO_QMD_FULL === "1",
|
|
26
|
-
model: process.env.AMIKO_MODEL || undefined,
|
|
27
|
-
modelProvider: process.env.AMIKO_MODEL_PROVIDER || undefined,
|
|
28
|
-
modelId: process.env.AMIKO_MODEL_ID || undefined,
|
|
29
|
-
thinkingLevel: (process.env.AMIKO_THINKING_LEVEL as any) || undefined
|
|
30
|
-
};
|
|
31
|
-
}
|
package/src/configFile.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import { AppConfig } from "./types.js";
|
|
5
|
-
|
|
6
|
-
export type PersistedConfig = {
|
|
7
|
-
model?: string;
|
|
8
|
-
modelProvider?: string;
|
|
9
|
-
modelId?: string;
|
|
10
|
-
thinkingLevel?: AppConfig["thinkingLevel"];
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export function getConfigFilePath(workspacePath: string) {
|
|
14
|
-
const base = workspacePath || path.join(os.homedir(), ".amiko");
|
|
15
|
-
return path.join(base, "config.json");
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export async function loadConfigFile(workspacePath: string): Promise<PersistedConfig> {
|
|
19
|
-
const filePath = getConfigFilePath(workspacePath);
|
|
20
|
-
try {
|
|
21
|
-
const raw = await fs.readFile(filePath, "utf8");
|
|
22
|
-
const parsed = JSON.parse(raw) as PersistedConfig;
|
|
23
|
-
return parsed || {};
|
|
24
|
-
} catch {
|
|
25
|
-
return {};
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export async function saveConfigFile(workspacePath: string, config: PersistedConfig): Promise<void> {
|
|
30
|
-
const filePath = getConfigFilePath(workspacePath);
|
|
31
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
32
|
-
await fs.writeFile(filePath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
33
|
-
}
|
package/src/headless.ts
DELETED
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
import { createInterface } from "node:readline";
|
|
2
|
-
import { AppConfig, Message, MemoryHit } from "./types.js";
|
|
3
|
-
import { parseCommand } from "./commands.js";
|
|
4
|
-
import { buildPrompt } from "./prompt.js";
|
|
5
|
-
import { appendSession } from "./session.js";
|
|
6
|
-
import { readWorkspaceFiles, WorkspaceFiles } from "./workspace.js";
|
|
7
|
-
import { createPiAgent } from "./agent/piAgent.js";
|
|
8
|
-
import { qmdSearch, qmdGet, qmdCollectionAdd, qmdEmbed } from "./memory/qmd.js";
|
|
9
|
-
import { saveConfigFile } from "./configFile.js";
|
|
10
|
-
import { getModels, getProviders, type KnownProvider } from "@mariozechner/pi-ai";
|
|
11
|
-
|
|
12
|
-
export async function runHeadless(
|
|
13
|
-
config: AppConfig,
|
|
14
|
-
workspace: WorkspaceFiles,
|
|
15
|
-
sessionPath: string,
|
|
16
|
-
) {
|
|
17
|
-
const agent = await createPiAgent(config);
|
|
18
|
-
let currentModel = config.model
|
|
19
|
-
?? (config.modelProvider && config.modelId ? `${config.modelProvider}/${config.modelId}` : "auto");
|
|
20
|
-
|
|
21
|
-
if (currentModel === "auto") {
|
|
22
|
-
const value = agent.getCurrentModel?.();
|
|
23
|
-
if (value) currentModel = value;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (agent.onEvent) {
|
|
27
|
-
agent.onEvent((event) => {
|
|
28
|
-
if (event?.type === "tool_execution_start") {
|
|
29
|
-
const lines = [`[tool] ${event.toolName} start`];
|
|
30
|
-
if (event.args?.command) lines.push(` command: ${event.args.command}`);
|
|
31
|
-
console.log(lines.join("\n"));
|
|
32
|
-
}
|
|
33
|
-
if (event?.type === "tool_execution_end") {
|
|
34
|
-
const resultText = event.result?.content
|
|
35
|
-
? event.result.content.map((c: any) => (c.text ? c.text : "")).join("\n")
|
|
36
|
-
: "";
|
|
37
|
-
const summary = resultText.trim() || "(no output)";
|
|
38
|
-
console.log(`[tool] ${event.toolName} done\n${summary}`);
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
console.log(`amiko-cli (headless) | model: ${currentModel} | workspace: ${workspace.root}`);
|
|
44
|
-
console.log("Type a message or /command. Press Ctrl+D to exit.");
|
|
45
|
-
|
|
46
|
-
const rl = createInterface({
|
|
47
|
-
input: process.stdin,
|
|
48
|
-
output: process.stdout,
|
|
49
|
-
prompt: "> ",
|
|
50
|
-
terminal: false,
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
rl.prompt();
|
|
54
|
-
|
|
55
|
-
for await (const line of rl) {
|
|
56
|
-
const trimmed = line.trim();
|
|
57
|
-
if (!trimmed) {
|
|
58
|
-
rl.prompt();
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const command = parseCommand(trimmed);
|
|
63
|
-
if (command) {
|
|
64
|
-
await handleCommand(command.name, command.args, {
|
|
65
|
-
config, workspace, sessionPath, agent, currentModel,
|
|
66
|
-
setModel: (m: string) => { currentModel = m; },
|
|
67
|
-
});
|
|
68
|
-
rl.prompt();
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
console.log(`[user] ${trimmed}`);
|
|
73
|
-
await appendSession(sessionPath, "user", trimmed);
|
|
74
|
-
|
|
75
|
-
const workspaceData = await readWorkspaceFiles(workspace);
|
|
76
|
-
|
|
77
|
-
let hits: MemoryHit[] = [];
|
|
78
|
-
if (config.qmdEnabled) {
|
|
79
|
-
hits = await qmdSearch({
|
|
80
|
-
qmdBin: config.qmdBin,
|
|
81
|
-
query: trimmed,
|
|
82
|
-
limit: config.qmdLimit,
|
|
83
|
-
collection: config.qmdCollection,
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (config.qmdFull && hits.length > 0) {
|
|
88
|
-
const expanded: MemoryHit[] = [];
|
|
89
|
-
for (const hit of hits) {
|
|
90
|
-
const docId = hit.path || hit.id;
|
|
91
|
-
if (!docId) { expanded.push(hit); continue; }
|
|
92
|
-
const text = await qmdGet({ qmdBin: config.qmdBin, docId, full: true });
|
|
93
|
-
expanded.push({ ...hit, snippet: text || hit.snippet });
|
|
94
|
-
}
|
|
95
|
-
hits = expanded;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const prompt = buildPrompt({
|
|
99
|
-
identity: workspaceData.identity,
|
|
100
|
-
personality: workspaceData.personality,
|
|
101
|
-
memory: workspaceData.memory,
|
|
102
|
-
hits,
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
let assistantText = "";
|
|
106
|
-
try {
|
|
107
|
-
for await (const chunk of agent.streamReply({ prompt, userMessage: trimmed })) {
|
|
108
|
-
process.stdout.write(chunk);
|
|
109
|
-
assistantText += chunk;
|
|
110
|
-
}
|
|
111
|
-
} catch (error) {
|
|
112
|
-
const message = (error as Error).message || "unknown error";
|
|
113
|
-
assistantText += `\n[error] ${message}\n`;
|
|
114
|
-
console.error(`[error] ${message}`);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (!assistantText.endsWith("\n")) process.stdout.write("\n");
|
|
118
|
-
|
|
119
|
-
if (assistantText) {
|
|
120
|
-
await appendSession(sessionPath, "assistant", assistantText);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
rl.prompt();
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
process.exit(0);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
async function handleCommand(
|
|
130
|
-
name: string,
|
|
131
|
-
args: string,
|
|
132
|
-
ctx: {
|
|
133
|
-
config: AppConfig;
|
|
134
|
-
workspace: WorkspaceFiles;
|
|
135
|
-
sessionPath: string;
|
|
136
|
-
agent: any;
|
|
137
|
-
currentModel: string;
|
|
138
|
-
setModel: (m: string) => void;
|
|
139
|
-
},
|
|
140
|
-
) {
|
|
141
|
-
switch (name) {
|
|
142
|
-
case "exit":
|
|
143
|
-
process.exit(0);
|
|
144
|
-
case "help":
|
|
145
|
-
console.log("Commands: /help /exit /search <query> /memory /session /plan /identity /personality /index [collection] /model [provider[/id]]");
|
|
146
|
-
return;
|
|
147
|
-
case "memory": {
|
|
148
|
-
const data = await readWorkspaceFiles(ctx.workspace);
|
|
149
|
-
console.log(`MEMORY.md:\n\n${data.memory.trim() || "(empty)"}`);
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
case "session":
|
|
153
|
-
console.log(`Session file: ${ctx.sessionPath}`);
|
|
154
|
-
return;
|
|
155
|
-
case "plan":
|
|
156
|
-
console.log("Plan file: PLAN.md");
|
|
157
|
-
return;
|
|
158
|
-
case "identity": {
|
|
159
|
-
const data = await readWorkspaceFiles(ctx.workspace);
|
|
160
|
-
console.log(`IDENTITY.md:\n\n${data.identity.trim() || "(empty)"}`);
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
case "personality": {
|
|
164
|
-
const data = await readWorkspaceFiles(ctx.workspace);
|
|
165
|
-
console.log(`PERSONALITY.md:\n\n${data.personality.trim() || "(empty)"}`);
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
case "index": {
|
|
169
|
-
if (!ctx.config.qmdEnabled) { console.log("QMD is disabled."); return; }
|
|
170
|
-
const collName = args || ctx.config.qmdCollection || "amiko";
|
|
171
|
-
const added = await qmdCollectionAdd({ qmdBin: ctx.config.qmdBin, name: collName, path: ctx.workspace.root });
|
|
172
|
-
if (!added) { console.log("Failed to add QMD collection."); return; }
|
|
173
|
-
const embedded = await qmdEmbed({ qmdBin: ctx.config.qmdBin, collection: collName });
|
|
174
|
-
console.log(embedded ? `Indexed QMD collection "${collName}".` : `Collection "${collName}" added, but embedding failed.`);
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
case "search": {
|
|
178
|
-
if (!args) { console.log("Usage: /search <query>"); return; }
|
|
179
|
-
if (!ctx.config.qmdEnabled) { console.log("QMD is disabled."); return; }
|
|
180
|
-
const hits = await qmdSearch({ qmdBin: ctx.config.qmdBin, query: args, limit: ctx.config.qmdLimit, collection: ctx.config.qmdCollection });
|
|
181
|
-
if (hits.length === 0) { console.log("No memory hits."); return; }
|
|
182
|
-
const formatted = hits.map((hit, i) => {
|
|
183
|
-
const label = hit.title || hit.path || hit.id;
|
|
184
|
-
const score = hit.score ? ` (${hit.score.toFixed(2)})` : "";
|
|
185
|
-
return `${i + 1}. ${label}${score}`;
|
|
186
|
-
}).join("\n");
|
|
187
|
-
console.log(formatted);
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
case "model": {
|
|
191
|
-
if (!args) {
|
|
192
|
-
const providers = getProviders().sort();
|
|
193
|
-
console.log(`Current model: ${ctx.currentModel}\nProviders:\n${providers.map((p, i) => ` ${i + 1}. ${p}`).join("\n")}`);
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
const slashParts = args.includes("/") ? args.split("/") : [];
|
|
197
|
-
let provider = args.split(" ")[0];
|
|
198
|
-
let modelId = args.split(" ")[1];
|
|
199
|
-
if (slashParts.length >= 2) {
|
|
200
|
-
provider = slashParts[0]?.trim();
|
|
201
|
-
modelId = slashParts.slice(1).join("/").trim();
|
|
202
|
-
}
|
|
203
|
-
if (provider && !modelId) {
|
|
204
|
-
const models = getModels(provider as KnownProvider);
|
|
205
|
-
if (models.length === 0) { console.log(`Unknown provider: ${provider}`); return; }
|
|
206
|
-
console.log(`Models for ${provider}:\n${models.map((m) => m.id).sort().join("\n")}`);
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
if (!provider || !modelId) { console.log("Usage: /model <provider>/<modelId>"); return; }
|
|
210
|
-
if (!ctx.agent.setModel) { console.log("Agent does not support model switching."); return; }
|
|
211
|
-
const result = ctx.agent.setModel(provider, modelId);
|
|
212
|
-
console.log(result.message);
|
|
213
|
-
if (result.ok) {
|
|
214
|
-
ctx.setModel(`${provider}/${modelId}`);
|
|
215
|
-
await saveConfigFile(ctx.workspace.root, { model: `${provider}/${modelId}`, modelProvider: provider, modelId });
|
|
216
|
-
}
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
default:
|
|
220
|
-
console.log(`Unknown command: ${name}`);
|
|
221
|
-
}
|
|
222
|
-
}
|
package/src/memory/qmd.ts
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
|
-
import { promisify } from "node:util";
|
|
3
|
-
import { MemoryHit } from "../types.js";
|
|
4
|
-
|
|
5
|
-
const execFileAsync = promisify(execFile);
|
|
6
|
-
|
|
7
|
-
function parseJsonLines(text: string) {
|
|
8
|
-
const lines = text.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
9
|
-
const results: any[] = [];
|
|
10
|
-
for (const line of lines) {
|
|
11
|
-
try {
|
|
12
|
-
results.push(JSON.parse(line));
|
|
13
|
-
} catch {
|
|
14
|
-
// ignore
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
return results;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export async function qmdSearch(options: {
|
|
21
|
-
qmdBin: string;
|
|
22
|
-
query: string;
|
|
23
|
-
limit: number;
|
|
24
|
-
collection?: string;
|
|
25
|
-
}): Promise<MemoryHit[]> {
|
|
26
|
-
const { qmdBin, query, limit, collection } = options;
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
const args = ["query", "--json", "-n", String(limit)];
|
|
30
|
-
if (collection) {
|
|
31
|
-
args.push("-c", collection);
|
|
32
|
-
}
|
|
33
|
-
args.push(query);
|
|
34
|
-
|
|
35
|
-
const { stdout } = await execFileAsync(qmdBin, [
|
|
36
|
-
...args
|
|
37
|
-
]);
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
const parsed = JSON.parse(stdout);
|
|
41
|
-
if (Array.isArray(parsed)) {
|
|
42
|
-
return parsed.map(normalizeHit);
|
|
43
|
-
}
|
|
44
|
-
} catch {
|
|
45
|
-
// fall through to JSONL parsing
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return parseJsonLines(stdout).map(normalizeHit);
|
|
49
|
-
} catch {
|
|
50
|
-
return [];
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export async function qmdGet(options: {
|
|
55
|
-
qmdBin: string;
|
|
56
|
-
docId: string;
|
|
57
|
-
full?: boolean;
|
|
58
|
-
}): Promise<string | null> {
|
|
59
|
-
const { qmdBin, docId, full } = options;
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
const args = ["get", docId];
|
|
63
|
-
if (full) args.push("--full");
|
|
64
|
-
|
|
65
|
-
const { stdout } = await execFileAsync(qmdBin, args);
|
|
66
|
-
return stdout.trim();
|
|
67
|
-
} catch {
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export async function qmdCollectionAdd(options: {
|
|
73
|
-
qmdBin: string;
|
|
74
|
-
name: string;
|
|
75
|
-
path: string;
|
|
76
|
-
}): Promise<boolean> {
|
|
77
|
-
const { qmdBin, name, path } = options;
|
|
78
|
-
|
|
79
|
-
try {
|
|
80
|
-
await execFileAsync(qmdBin, ["collection", "add", path, "--name", name]);
|
|
81
|
-
return true;
|
|
82
|
-
} catch {
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export async function qmdEmbed(options: {
|
|
88
|
-
qmdBin: string;
|
|
89
|
-
collection?: string;
|
|
90
|
-
}): Promise<boolean> {
|
|
91
|
-
const { qmdBin, collection } = options;
|
|
92
|
-
|
|
93
|
-
try {
|
|
94
|
-
const args = ["embed"];
|
|
95
|
-
if (collection) args.push("-c", collection);
|
|
96
|
-
await execFileAsync(qmdBin, args);
|
|
97
|
-
return true;
|
|
98
|
-
} catch {
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function normalizeHit(hit: any): MemoryHit {
|
|
104
|
-
return {
|
|
105
|
-
id: String(hit.id ?? hit.docId ?? hit.path ?? "unknown"),
|
|
106
|
-
score: typeof hit.score === "number" ? hit.score : undefined,
|
|
107
|
-
title: hit.title ?? hit.name,
|
|
108
|
-
snippet: hit.snippet ?? hit.text,
|
|
109
|
-
path: hit.path
|
|
110
|
-
};
|
|
111
|
-
}
|
package/src/prompt.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import os from "node:os";
|
|
2
|
-
import { MemoryHit } from "./types.js";
|
|
3
|
-
|
|
4
|
-
export function buildPrompt(options: {
|
|
5
|
-
identity: string;
|
|
6
|
-
personality: string;
|
|
7
|
-
memory: string;
|
|
8
|
-
hits: MemoryHit[];
|
|
9
|
-
}) {
|
|
10
|
-
const { identity, personality, memory, hits } = options;
|
|
11
|
-
const hitText = hits
|
|
12
|
-
.map((hit, index) => {
|
|
13
|
-
const title = hit.title || hit.path || hit.id;
|
|
14
|
-
const snippet = hit.snippet || "";
|
|
15
|
-
return `(${index + 1}) ${title}\n${snippet}`.trim();
|
|
16
|
-
})
|
|
17
|
-
.filter(Boolean)
|
|
18
|
-
.join("\n\n");
|
|
19
|
-
|
|
20
|
-
const systemInfo = [
|
|
21
|
-
`platform: ${process.platform}`,
|
|
22
|
-
`arch: ${process.arch}`,
|
|
23
|
-
`cwd: ${process.cwd()}`,
|
|
24
|
-
`home: ${os.homedir()}`
|
|
25
|
-
].join("\n");
|
|
26
|
-
|
|
27
|
-
const toolSpec = [
|
|
28
|
-
"Available tools:",
|
|
29
|
-
"- read: { path: string, maxBytes?: number }",
|
|
30
|
-
"- bash: { command: string, cwd?: string, timeoutMs?: number }",
|
|
31
|
-
"",
|
|
32
|
-
"Tool call rules:",
|
|
33
|
-
"1) Always include the tool name.",
|
|
34
|
-
"2) Always include required fields (read.path, bash.command).",
|
|
35
|
-
"3) Do not call tools with empty arguments.",
|
|
36
|
-
"4) Use bash for shell commands; do not invent other tool names.",
|
|
37
|
-
].join("\n");
|
|
38
|
-
|
|
39
|
-
const sections = [
|
|
40
|
-
"# Identity\n" + identity.trim(),
|
|
41
|
-
"# Personality\n" + personality.trim(),
|
|
42
|
-
"# System Info\n" + systemInfo,
|
|
43
|
-
"# Tooling Policy\n" +
|
|
44
|
-
"You can use tools (read, bash) to answer questions. Prefer tools over asking the user to run commands. " +
|
|
45
|
-
"Only ask for clarification if needed to choose a safe tool or path.",
|
|
46
|
-
"# Tool Schemas\n" + toolSpec,
|
|
47
|
-
"# Long-term Memory\n" + memory.trim()
|
|
48
|
-
];
|
|
49
|
-
|
|
50
|
-
if (hitText.length > 0) {
|
|
51
|
-
sections.push("# Retrieved Memory\n" + hitText);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return sections.join("\n\n").trim();
|
|
55
|
-
}
|
package/src/session.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
export type SessionFile = {
|
|
5
|
-
id: string;
|
|
6
|
-
path: string;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
function formatDate(date: Date) {
|
|
10
|
-
const yyyy = date.getFullYear();
|
|
11
|
-
const mm = String(date.getMonth() + 1).padStart(2, "0");
|
|
12
|
-
const dd = String(date.getDate()).padStart(2, "0");
|
|
13
|
-
return `${yyyy}-${mm}-${dd}`;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function createSessionFile(sessionsDir: string, sessionId: string): Promise<SessionFile> {
|
|
17
|
-
const date = formatDate(new Date());
|
|
18
|
-
const fileName = `${date}-${sessionId}.md`;
|
|
19
|
-
const sessionPath = path.join(sessionsDir, fileName);
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
await fs.access(sessionPath);
|
|
23
|
-
} catch {
|
|
24
|
-
const header = `# Session ${fileName}\n\n`;
|
|
25
|
-
await fs.writeFile(sessionPath, header, "utf8");
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return { id: sessionId, path: sessionPath };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export async function appendSession(sessionPath: string, role: "user" | "assistant", content: string) {
|
|
32
|
-
const timestamp = new Date().toISOString();
|
|
33
|
-
const block = `## ${role} @ ${timestamp}\n\n${content}\n\n`;
|
|
34
|
-
await fs.appendFile(sessionPath, block, "utf8");
|
|
35
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
export type Role = "user" | "assistant" | "system" | "tool";
|
|
2
|
-
|
|
3
|
-
export type Message = {
|
|
4
|
-
role: Role;
|
|
5
|
-
content: string;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export type MemoryHit = {
|
|
9
|
-
id: string;
|
|
10
|
-
score?: number;
|
|
11
|
-
title?: string;
|
|
12
|
-
snippet?: string;
|
|
13
|
-
path?: string;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export type AgentInput = {
|
|
17
|
-
prompt: string;
|
|
18
|
-
userMessage: string;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export type Agent = {
|
|
22
|
-
streamReply: (input: AgentInput) => AsyncGenerator<string>;
|
|
23
|
-
setModel?: (provider: string, modelId: string) => { ok: boolean; message: string };
|
|
24
|
-
getCurrentModel?: () => string | null;
|
|
25
|
-
onEvent?: (handler: (event: any) => void) => void;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export type AppConfig = {
|
|
29
|
-
workspacePath: string;
|
|
30
|
-
sessionId: string;
|
|
31
|
-
qmdBin: string;
|
|
32
|
-
qmdLimit: number;
|
|
33
|
-
qmdEnabled: boolean;
|
|
34
|
-
qmdCollection?: string;
|
|
35
|
-
qmdFull: boolean;
|
|
36
|
-
model?: string;
|
|
37
|
-
modelProvider?: string;
|
|
38
|
-
modelId?: string;
|
|
39
|
-
thinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
|
|
40
|
-
};
|
package/src/workspace.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
const DEFAULT_IDENTITY = "# Identity\n\nYou are Amiko, a helpful CLI agent.\n";
|
|
5
|
-
const DEFAULT_PERSONALITY = "# Personality\n\n- Clear, concise, friendly.\n- Ask for clarification when unsure.\n";
|
|
6
|
-
const DEFAULT_MEMORY = "# Memory\n\n";
|
|
7
|
-
|
|
8
|
-
export type WorkspaceFiles = {
|
|
9
|
-
root: string;
|
|
10
|
-
identityPath: string;
|
|
11
|
-
personalityPath: string;
|
|
12
|
-
memoryPath: string;
|
|
13
|
-
sessionsDir: string;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
async function ensureDir(dirPath: string) {
|
|
17
|
-
await fs.mkdir(dirPath, { recursive: true });
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async function ensureFile(filePath: string, content: string) {
|
|
21
|
-
try {
|
|
22
|
-
await fs.access(filePath);
|
|
23
|
-
} catch {
|
|
24
|
-
await fs.writeFile(filePath, content, "utf8");
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export async function initWorkspace(root: string): Promise<WorkspaceFiles> {
|
|
29
|
-
const identityPath = path.join(root, "IDENTITY.md");
|
|
30
|
-
const personalityPath = path.join(root, "PERSONALITY.md");
|
|
31
|
-
const memoryPath = path.join(root, "MEMORY.md");
|
|
32
|
-
const sessionsDir = path.join(root, "sessions");
|
|
33
|
-
|
|
34
|
-
await ensureDir(root);
|
|
35
|
-
await ensureDir(sessionsDir);
|
|
36
|
-
|
|
37
|
-
await ensureFile(identityPath, DEFAULT_IDENTITY);
|
|
38
|
-
await ensureFile(personalityPath, DEFAULT_PERSONALITY);
|
|
39
|
-
await ensureFile(memoryPath, DEFAULT_MEMORY);
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
root,
|
|
43
|
-
identityPath,
|
|
44
|
-
personalityPath,
|
|
45
|
-
memoryPath,
|
|
46
|
-
sessionsDir
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export async function readWorkspaceFiles(files: WorkspaceFiles) {
|
|
51
|
-
const [identity, personality, memory] = await Promise.all([
|
|
52
|
-
fs.readFile(files.identityPath, "utf8"),
|
|
53
|
-
fs.readFile(files.personalityPath, "utf8"),
|
|
54
|
-
fs.readFile(files.memoryPath, "utf8")
|
|
55
|
-
]);
|
|
56
|
-
|
|
57
|
-
return { identity, personality, memory };
|
|
58
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "NodeNext",
|
|
5
|
-
"moduleResolution": "NodeNext",
|
|
6
|
-
"outDir": "dist",
|
|
7
|
-
"rootDir": "src",
|
|
8
|
-
"jsx": "react-jsx",
|
|
9
|
-
"strict": true,
|
|
10
|
-
"esModuleInterop": true,
|
|
11
|
-
"forceConsistentCasingInFileNames": true,
|
|
12
|
-
"skipLibCheck": true,
|
|
13
|
-
"types": ["node"]
|
|
14
|
-
},
|
|
15
|
-
"include": ["src"]
|
|
16
|
-
}
|