@heyamiko/amiko-cli 0.4.11 → 0.4.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.
package/dist/cli.js CHANGED
@@ -15,6 +15,11 @@ function parseArgs(argv) {
15
15
  case "--headless":
16
16
  overrides.headless = true;
17
17
  break;
18
+ case "--message":
19
+ case "-m":
20
+ overrides.message = argv[i + 1];
21
+ i += 1;
22
+ break;
18
23
  case "--workspace":
19
24
  case "-w":
20
25
  overrides.workspacePath = argv[i + 1];
@@ -80,9 +85,9 @@ async function main() {
80
85
  const persisted = await loadConfigFile(workspace.root);
81
86
  const mergedConfig = { ...finalConfig, ...persisted, ...overrides };
82
87
  const sessionFile = await createSessionFile(workspace.sessionsDir, finalConfig.sessionId);
83
- // Auto-detect non-TTY or explicit --headless flag
84
- if (overrides.headless || !process.stdout.isTTY) {
85
- await runHeadless(mergedConfig, workspace, sessionFile.path);
88
+ // Auto-detect non-TTY, --headless flag, or -m single-shot
89
+ if (overrides.headless || overrides.message || !process.stdout.isTTY) {
90
+ await runHeadless(mergedConfig, workspace, sessionFile.path, overrides.message);
86
91
  return;
87
92
  }
88
93
  render(_jsx(App, { config: mergedConfig, workspace: workspace, sessionPath: sessionFile.path }));
package/dist/headless.js CHANGED
@@ -7,7 +7,7 @@ import { createPiAgent } from "./agent/piAgent.js";
7
7
  import { qmdSearch, qmdGet, qmdCollectionAdd, qmdEmbed } from "./memory/qmd.js";
8
8
  import { saveConfigFile } from "./configFile.js";
9
9
  import { getModels, getProviders } from "@mariozechner/pi-ai";
10
- export async function runHeadless(config, workspace, sessionPath) {
10
+ export async function runHeadless(config, workspace, sessionPath, singleMessage) {
11
11
  const agent = await createPiAgent(config);
12
12
  let currentModel = config.model
13
13
  ?? (config.modelProvider && config.modelId ? `${config.modelProvider}/${config.modelId}` : "auto");
@@ -33,6 +33,13 @@ export async function runHeadless(config, workspace, sessionPath) {
33
33
  }
34
34
  });
35
35
  }
36
+ // Single-shot mode: process one message and exit
37
+ if (singleMessage) {
38
+ const response = await processMessage(singleMessage, { config, workspace, sessionPath, agent, currentModel });
39
+ if (response !== undefined)
40
+ process.stdout.write(response.endsWith("\n") ? response : response + "\n");
41
+ process.exit(0);
42
+ }
36
43
  console.log(`amiko-cli (headless) | model: ${currentModel} | workspace: ${workspace.root}`);
37
44
  console.log("Type a message or /command. Press Ctrl+D to exit.");
38
45
  const rl = createInterface({
@@ -57,57 +64,59 @@ export async function runHeadless(config, workspace, sessionPath) {
57
64
  rl.prompt();
58
65
  continue;
59
66
  }
60
- console.log(`[user] ${trimmed}`);
61
- await appendSession(sessionPath, "user", trimmed);
62
- const workspaceData = await readWorkspaceFiles(workspace);
63
- let hits = [];
64
- if (config.qmdEnabled) {
65
- hits = await qmdSearch({
66
- qmdBin: config.qmdBin,
67
- query: trimmed,
68
- limit: config.qmdLimit,
69
- collection: config.qmdCollection,
70
- });
71
- }
72
- if (config.qmdFull && hits.length > 0) {
73
- const expanded = [];
74
- for (const hit of hits) {
75
- const docId = hit.path || hit.id;
76
- if (!docId) {
77
- expanded.push(hit);
78
- continue;
79
- }
80
- const text = await qmdGet({ qmdBin: config.qmdBin, docId, full: true });
81
- expanded.push({ ...hit, snippet: text || hit.snippet });
82
- }
83
- hits = expanded;
67
+ const response = await processMessage(trimmed, { config, workspace, sessionPath, agent, currentModel });
68
+ if (response) {
69
+ process.stdout.write(response.endsWith("\n") ? response : response + "\n");
84
70
  }
85
- const prompt = buildPrompt({
86
- identity: workspaceData.identity,
87
- personality: workspaceData.personality,
88
- memory: workspaceData.memory,
89
- hits,
71
+ rl.prompt();
72
+ }
73
+ process.exit(0);
74
+ }
75
+ async function processMessage(message, ctx) {
76
+ await appendSession(ctx.sessionPath, "user", message);
77
+ const workspaceData = await readWorkspaceFiles(ctx.workspace);
78
+ let hits = [];
79
+ if (ctx.config.qmdEnabled) {
80
+ hits = await qmdSearch({
81
+ qmdBin: ctx.config.qmdBin,
82
+ query: message,
83
+ limit: ctx.config.qmdLimit,
84
+ collection: ctx.config.qmdCollection,
90
85
  });
91
- let assistantText = "";
92
- try {
93
- for await (const chunk of agent.streamReply({ prompt, userMessage: trimmed })) {
94
- process.stdout.write(chunk);
95
- assistantText += chunk;
86
+ }
87
+ if (ctx.config.qmdFull && hits.length > 0) {
88
+ const expanded = [];
89
+ for (const hit of hits) {
90
+ const docId = hit.path || hit.id;
91
+ if (!docId) {
92
+ expanded.push(hit);
93
+ continue;
96
94
  }
95
+ const text = await qmdGet({ qmdBin: ctx.config.qmdBin, docId, full: true });
96
+ expanded.push({ ...hit, snippet: text || hit.snippet });
97
97
  }
98
- catch (error) {
99
- const message = error.message || "unknown error";
100
- assistantText += `\n[error] ${message}\n`;
101
- console.error(`[error] ${message}`);
102
- }
103
- if (!assistantText.endsWith("\n"))
104
- process.stdout.write("\n");
105
- if (assistantText) {
106
- await appendSession(sessionPath, "assistant", assistantText);
98
+ hits = expanded;
99
+ }
100
+ const prompt = buildPrompt({
101
+ identity: workspaceData.identity,
102
+ personality: workspaceData.personality,
103
+ memory: workspaceData.memory,
104
+ hits,
105
+ });
106
+ let assistantText = "";
107
+ try {
108
+ for await (const chunk of ctx.agent.streamReply({ prompt, userMessage: message })) {
109
+ assistantText += chunk;
107
110
  }
108
- rl.prompt();
109
111
  }
110
- process.exit(0);
112
+ catch (error) {
113
+ const msg = error.message || "unknown error";
114
+ assistantText += `\n[error] ${msg}\n`;
115
+ }
116
+ if (assistantText) {
117
+ await appendSession(ctx.sessionPath, "assistant", assistantText);
118
+ }
119
+ return assistantText;
111
120
  }
112
121
  async function handleCommand(name, args, ctx) {
113
122
  switch (name) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyamiko/amiko-cli",
3
- "version": "0.4.11",
3
+ "version": "0.4.12",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "amiko": "dist/cli.js"
package/src/cli.tsx CHANGED
@@ -21,6 +21,7 @@ type ArgOverrides = Partial<{
21
21
  modelId?: string;
22
22
  thinkingLevel?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
23
23
  headless?: boolean;
24
+ message?: string;
24
25
  }>;
25
26
 
26
27
  function parseArgs(argv: string[]): ArgOverrides {
@@ -31,6 +32,11 @@ function parseArgs(argv: string[]): ArgOverrides {
31
32
  case "--headless":
32
33
  overrides.headless = true;
33
34
  break;
35
+ case "--message":
36
+ case "-m":
37
+ overrides.message = argv[i + 1];
38
+ i += 1;
39
+ break;
34
40
  case "--workspace":
35
41
  case "-w":
36
42
  overrides.workspacePath = argv[i + 1];
@@ -99,9 +105,9 @@ async function main() {
99
105
  const mergedConfig = { ...finalConfig, ...persisted, ...overrides };
100
106
  const sessionFile = await createSessionFile(workspace.sessionsDir, finalConfig.sessionId);
101
107
 
102
- // Auto-detect non-TTY or explicit --headless flag
103
- if (overrides.headless || !process.stdout.isTTY) {
104
- await runHeadless(mergedConfig, workspace, sessionFile.path);
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);
105
111
  return;
106
112
  }
107
113
 
package/src/headless.ts CHANGED
@@ -13,6 +13,7 @@ export async function runHeadless(
13
13
  config: AppConfig,
14
14
  workspace: WorkspaceFiles,
15
15
  sessionPath: string,
16
+ singleMessage?: string,
16
17
  ) {
17
18
  const agent = await createPiAgent(config);
18
19
  let currentModel = config.model
@@ -40,6 +41,13 @@ export async function runHeadless(
40
41
  });
41
42
  }
42
43
 
44
+ // Single-shot mode: process one message and exit
45
+ if (singleMessage) {
46
+ const response = await processMessage(singleMessage, { config, workspace, sessionPath, agent, currentModel });
47
+ if (response !== undefined) process.stdout.write(response.endsWith("\n") ? response : response + "\n");
48
+ process.exit(0);
49
+ }
50
+
43
51
  console.log(`amiko-cli (headless) | model: ${currentModel} | workspace: ${workspace.root}`);
44
52
  console.log("Type a message or /command. Press Ctrl+D to exit.");
45
53
 
@@ -69,61 +77,74 @@ export async function runHeadless(
69
77
  continue;
70
78
  }
71
79
 
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
- });
80
+ const response = await processMessage(trimmed, { config, workspace, sessionPath, agent, currentModel });
81
+ if (response) {
82
+ process.stdout.write(response.endsWith("\n") ? response : response + "\n");
85
83
  }
86
84
 
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
- }
85
+ rl.prompt();
86
+ }
87
+
88
+ process.exit(0);
89
+ }
97
90
 
98
- const prompt = buildPrompt({
99
- identity: workspaceData.identity,
100
- personality: workspaceData.personality,
101
- memory: workspaceData.memory,
102
- hits,
91
+ async function processMessage(
92
+ message: string,
93
+ ctx: {
94
+ config: AppConfig;
95
+ workspace: WorkspaceFiles;
96
+ sessionPath: string;
97
+ agent: any;
98
+ currentModel: string;
99
+ },
100
+ ): Promise<string> {
101
+ await appendSession(ctx.sessionPath, "user", message);
102
+
103
+ const workspaceData = await readWorkspaceFiles(ctx.workspace);
104
+
105
+ let hits: MemoryHit[] = [];
106
+ if (ctx.config.qmdEnabled) {
107
+ hits = await qmdSearch({
108
+ qmdBin: ctx.config.qmdBin,
109
+ query: message,
110
+ limit: ctx.config.qmdLimit,
111
+ collection: ctx.config.qmdCollection,
103
112
  });
113
+ }
104
114
 
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
+ if (ctx.config.qmdFull && hits.length > 0) {
116
+ const expanded: MemoryHit[] = [];
117
+ for (const hit of hits) {
118
+ const docId = hit.path || hit.id;
119
+ if (!docId) { expanded.push(hit); continue; }
120
+ const text = await qmdGet({ qmdBin: ctx.config.qmdBin, docId, full: true });
121
+ expanded.push({ ...hit, snippet: text || hit.snippet });
115
122
  }
123
+ hits = expanded;
124
+ }
116
125
 
117
- if (!assistantText.endsWith("\n")) process.stdout.write("\n");
126
+ const prompt = buildPrompt({
127
+ identity: workspaceData.identity,
128
+ personality: workspaceData.personality,
129
+ memory: workspaceData.memory,
130
+ hits,
131
+ });
118
132
 
119
- if (assistantText) {
120
- await appendSession(sessionPath, "assistant", assistantText);
133
+ let assistantText = "";
134
+ try {
135
+ for await (const chunk of ctx.agent.streamReply({ prompt, userMessage: message })) {
136
+ assistantText += chunk;
121
137
  }
138
+ } catch (error) {
139
+ const msg = (error as Error).message || "unknown error";
140
+ assistantText += `\n[error] ${msg}\n`;
141
+ }
122
142
 
123
- rl.prompt();
143
+ if (assistantText) {
144
+ await appendSession(ctx.sessionPath, "assistant", assistantText);
124
145
  }
125
146
 
126
- process.exit(0);
147
+ return assistantText;
127
148
  }
128
149
 
129
150
  async function handleCommand(