akemon 0.3.5 → 0.3.7
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/DATA_POLICY.md +128 -0
- package/README.md +156 -19
- package/TRADEMARK.md +74 -0
- package/dist/akemon-home.js +56 -0
- package/dist/akemon-message.js +107 -0
- package/dist/best-effort.js +8 -0
- package/dist/cli.js +1411 -132
- package/dist/cognitive-artifact-store.js +101 -0
- package/dist/cognitive-event-log.js +47 -0
- package/dist/config.js +45 -9
- package/dist/context.js +27 -6
- package/dist/core/contracts/layers.js +1 -0
- package/dist/core/contracts/permission.js +1 -0
- package/dist/core/contracts/workspace.js +1 -0
- package/dist/core-cognitive-module.js +768 -0
- package/dist/engine-peripheral.js +127 -26
- package/dist/engine-routing.js +58 -17
- package/dist/interactive-session.js +361 -0
- package/dist/local-interconnect.js +156 -0
- package/dist/local-registry.js +178 -0
- package/dist/mcp-server.js +4 -1
- package/dist/memory-proposal.js +379 -0
- package/dist/memory-recorder.js +368 -0
- package/dist/orphan-scan.js +36 -24
- package/dist/passive-reflection-cognitive-module.js +172 -0
- package/dist/peripheral-registry.js +235 -0
- package/dist/permission-audit.js +132 -0
- package/dist/relay-client.js +68 -9
- package/dist/relay-mode.js +34 -0
- package/dist/relay-peripheral.js +139 -49
- package/dist/runtime-platform.js +122 -0
- package/dist/secretariat/client.js +87 -0
- package/dist/self.js +15 -6
- package/dist/server.js +3695 -439
- package/dist/social-discovery.js +231 -0
- package/dist/software-agent-peripheral.js +314 -235
- package/dist/software-agent-result-cli.js +69 -0
- package/dist/software-agent-stream-cli.js +23 -0
- package/dist/software-agent-transport.js +177 -0
- package/dist/task-module.js +243 -0
- package/dist/task-registry.js +756 -0
- package/dist/vendor/xterm/addon-fit.js +2 -0
- package/dist/vendor/xterm/addon-search.js +2 -0
- package/dist/vendor/xterm/addon-web-links.js +2 -0
- package/dist/vendor/xterm/xterm.css +285 -0
- package/dist/vendor/xterm/xterm.js +2 -0
- package/dist/work-memory.js +339 -0
- package/dist/workbench-peripheral-guide.js +79 -0
- package/dist/workbench-session.js +1074 -0
- package/dist/workbench.html +4011 -0
- package/package.json +11 -4
- package/scripts/build.cjs +24 -0
- package/scripts/check-architecture-baseline.cjs +68 -0
- package/scripts/test.cjs +38 -0
package/dist/cli.js
CHANGED
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Command } from "commander";
|
|
2
|
+
import { Command, Option } from "commander";
|
|
3
3
|
import { serve, onOrderNotify } from "./server.js";
|
|
4
4
|
import { addAgent } from "./add.js";
|
|
5
|
-
import { getOrCreateRelayCredentials } from "./config.js";
|
|
5
|
+
import { getLocalManagerName, getOrCreateLocalOwnerSecret, getOrCreateRelayCredentials, setLocalManagerName, } from "./config.js";
|
|
6
6
|
import { connectRelay } from "./relay-client.js";
|
|
7
7
|
import { listAgents } from "./list.js";
|
|
8
8
|
import { connect } from "./connect.js";
|
|
9
9
|
import { PrivacyFilterUnavailableError, sanitizeText, } from "./privacy-filter.js";
|
|
10
10
|
import { SoftwareAgentStreamCliRenderer } from "./software-agent-stream-cli.js";
|
|
11
|
+
import { appendWorkMemoryNote, buildWorkMemoryContext, } from "./work-memory.js";
|
|
12
|
+
import { renderSoftwareAgentRunResult } from "./software-agent-result-cli.js";
|
|
13
|
+
import { DEFAULT_RELAY_HTTP, resolveServeRelayMode, } from "./relay-mode.js";
|
|
14
|
+
import { LocalInstanceLookupError, listRunningLocalInstances, resolveDefaultLocalInstance, resolveLocalInstanceByName, } from "./local-registry.js";
|
|
15
|
+
import { appendLocalPeerContact, createLocalAkemonMessage, } from "./local-interconnect.js";
|
|
16
|
+
import { discoverPublicAkemonProfiles, } from "./social-discovery.js";
|
|
17
|
+
import { getMemoryRecorder, listMemoryRecorders, } from "./memory-recorder.js";
|
|
18
|
+
import { listPermissionAuditRecords, } from "./permission-audit.js";
|
|
19
|
+
import { buildPeripheralExploreBriefing, loadPeripheralRecords, upsertPeripheralRecord, } from "./peripheral-registry.js";
|
|
20
|
+
import { openUrl } from "./runtime-platform.js";
|
|
21
|
+
import { createOwnerChatMessage, SecretariatClient, taskIdFromOwnerChatMessage, taskIsTerminal, } from "./secretariat/client.js";
|
|
11
22
|
import { readFileSync } from "fs";
|
|
12
23
|
import { fileURLToPath } from "url";
|
|
13
24
|
import { dirname, join } from "path";
|
|
14
25
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
26
|
const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
16
|
-
const RELAY_WS = "wss://relay.akemon.dev";
|
|
17
|
-
const RELAY_HTTP = "https://relay.akemon.dev";
|
|
18
27
|
const program = new Command();
|
|
28
|
+
const ALL_MODULES = ["biostate", "memory", "task", "social", "longterm", "reflection", "script"];
|
|
19
29
|
function parsePortOption(port) {
|
|
20
30
|
const value = typeof port === "number" ? port : parseInt(String(port || "3000"));
|
|
21
31
|
return Number.isInteger(value) && value > 0 ? value : 3000;
|
|
@@ -47,6 +57,57 @@ function parseSoftwareAgentEnvPolicy(value) {
|
|
|
47
57
|
console.error("--software-agent-env must be one of: inherit, allowlist");
|
|
48
58
|
process.exit(1);
|
|
49
59
|
}
|
|
60
|
+
function parseAkemonMemoryScope(value) {
|
|
61
|
+
const normalized = (value || "task").trim().toLowerCase();
|
|
62
|
+
if (normalized === "none" || normalized === "public" || normalized === "task" || normalized === "owner") {
|
|
63
|
+
return normalized;
|
|
64
|
+
}
|
|
65
|
+
console.error("--memory-scope must be one of: none, public, task, owner");
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
function parseMemoryRecorderSource(value) {
|
|
69
|
+
if (value === undefined)
|
|
70
|
+
return undefined;
|
|
71
|
+
const normalized = value.trim().toLowerCase();
|
|
72
|
+
if (normalized === "builtin" || normalized === "module" || normalized === "skill") {
|
|
73
|
+
return normalized;
|
|
74
|
+
}
|
|
75
|
+
console.error("--source must be one of: builtin, module, skill");
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
function parseMemoryRecorderScope(value) {
|
|
79
|
+
if (value === undefined)
|
|
80
|
+
return undefined;
|
|
81
|
+
const normalized = value.trim().toLowerCase();
|
|
82
|
+
if (normalized === "self"
|
|
83
|
+
|| normalized === "work"
|
|
84
|
+
|| normalized === "contacts"
|
|
85
|
+
|| normalized === "inbox"
|
|
86
|
+
|| normalized === "events"
|
|
87
|
+
|| normalized === "conversation"
|
|
88
|
+
|| normalized === "product"
|
|
89
|
+
|| normalized === "software-agent"
|
|
90
|
+
|| normalized === "runtime"
|
|
91
|
+
|| normalized === "audit"
|
|
92
|
+
|| normalized === "config") {
|
|
93
|
+
return normalized;
|
|
94
|
+
}
|
|
95
|
+
console.error("--scope must be one of: self, work, contacts, inbox, events, conversation, product, software-agent, runtime, audit, config");
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
function parsePermissionAuditActionKind(value) {
|
|
99
|
+
if (value === undefined)
|
|
100
|
+
return undefined;
|
|
101
|
+
const normalized = value.trim().toLowerCase();
|
|
102
|
+
if (normalized === "software-agent-task"
|
|
103
|
+
|| normalized === "akemon-message"
|
|
104
|
+
|| normalized === "relay-publication"
|
|
105
|
+
|| normalized === "memory-write") {
|
|
106
|
+
return normalized;
|
|
107
|
+
}
|
|
108
|
+
console.error("--kind must be one of: software-agent-task, akemon-message, relay-publication, memory-write");
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
50
111
|
function parseCommaSeparatedCliOption(value) {
|
|
51
112
|
if (!value)
|
|
52
113
|
return undefined;
|
|
@@ -76,9 +137,254 @@ function printSoftwareAgentTaskList(tasks) {
|
|
|
76
137
|
: "no-git";
|
|
77
138
|
const goal = truncateOneLine(task.envelope?.goal || "", 90);
|
|
78
139
|
console.log(`${task.taskId} ${task.status}/${result} ${duration} ${git} ${task.updatedAt || task.startedAt}`);
|
|
140
|
+
if (task.contextSession?.sessionId)
|
|
141
|
+
console.log(` session: ${task.contextSession.sessionId}`);
|
|
142
|
+
const workMemoryDir = task.envelope?.workMemoryDir || task.result?.workMemoryDir;
|
|
143
|
+
if (workMemoryDir)
|
|
144
|
+
console.log(` work memory: ${workMemoryDir}`);
|
|
145
|
+
if (goal)
|
|
146
|
+
console.log(` ${goal}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function printSoftwareAgentSessionList(sessions) {
|
|
150
|
+
if (!sessions.length) {
|
|
151
|
+
console.log("No software-agent context sessions found.");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
for (const session of sessions) {
|
|
155
|
+
const result = session.lastResult?.success === true ? "ok" : session.lastResult?.success === false ? "error" : "pending";
|
|
156
|
+
const duration = typeof session.lastResult?.durationMs === "number" ? `${session.lastResult.durationMs}ms` : "-";
|
|
157
|
+
const updatedAt = session.updatedAt || "-";
|
|
158
|
+
const goal = truncateOneLine(session.lastGoal || "", 90);
|
|
159
|
+
console.log(`${session.sessionId} ${result} ${duration} ${updatedAt}`);
|
|
160
|
+
if (session.lastTaskId)
|
|
161
|
+
console.log(` last task: ${session.lastTaskId}`);
|
|
79
162
|
if (goal)
|
|
80
163
|
console.log(` ${goal}`);
|
|
164
|
+
if (session.packetPath)
|
|
165
|
+
console.log(` context: ${session.packetPath}`);
|
|
166
|
+
if (session.workMemoryDir)
|
|
167
|
+
console.log(` work memory: ${session.workMemoryDir}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function printProfileDiscoveryResults(results, query) {
|
|
171
|
+
if (!results.length) {
|
|
172
|
+
console.log(`No public Akemon profiles matched: ${query}`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
for (const [index, result] of results.entries()) {
|
|
176
|
+
const profile = result.profile;
|
|
177
|
+
const tags = [...profile.tags, ...profile.interests].slice(0, 8).join(", ");
|
|
178
|
+
const details = [
|
|
179
|
+
profile.status && profile.status !== "unknown" ? profile.status : "",
|
|
180
|
+
profile.engine ? `engine=${profile.engine}` : "",
|
|
181
|
+
tags ? `tags=${tags}` : "",
|
|
182
|
+
`score=${result.score}`,
|
|
183
|
+
].filter(Boolean).join(" ");
|
|
184
|
+
console.log(`${index + 1}. ${profile.name}${details ? ` ${details}` : ""}`);
|
|
185
|
+
if (profile.description)
|
|
186
|
+
console.log(` ${truncateOneLine(profile.description, 120)}`);
|
|
187
|
+
if (result.reasons.length)
|
|
188
|
+
console.log(` reasons: ${result.reasons.join("; ")}`);
|
|
189
|
+
if (profile.profileUrl)
|
|
190
|
+
console.log(` profile: ${profile.profileUrl}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function printMemoryRecorderList(recorders) {
|
|
194
|
+
if (!recorders.length) {
|
|
195
|
+
console.log("No memory recorders found.");
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const rows = recorders.map((recorder) => ({
|
|
199
|
+
id: recorder.id,
|
|
200
|
+
source: recorder.source,
|
|
201
|
+
owner: recorder.owner,
|
|
202
|
+
trigger: recorder.trigger.event,
|
|
203
|
+
destinations: summarizeDestinations(recorder),
|
|
204
|
+
approval: recorder.privacy.approvalRequired ? "yes" : "no",
|
|
205
|
+
}));
|
|
206
|
+
const idW = Math.max(2, ...rows.map((row) => row.id.length)) + 2;
|
|
207
|
+
const sourceW = Math.max(6, ...rows.map((row) => row.source.length)) + 2;
|
|
208
|
+
const ownerW = Math.max(5, ...rows.map((row) => row.owner.length)) + 2;
|
|
209
|
+
const triggerW = Math.max(7, ...rows.map((row) => row.trigger.length)) + 2;
|
|
210
|
+
const approvalW = 10;
|
|
211
|
+
console.log(padCli("ID", idW)
|
|
212
|
+
+ padCli("SOURCE", sourceW)
|
|
213
|
+
+ padCli("OWNER", ownerW)
|
|
214
|
+
+ padCli("TRIGGER", triggerW)
|
|
215
|
+
+ padCli("APPROVAL", approvalW)
|
|
216
|
+
+ "DESTINATIONS");
|
|
217
|
+
for (const row of rows) {
|
|
218
|
+
console.log(padCli(row.id, idW)
|
|
219
|
+
+ padCli(row.source, sourceW)
|
|
220
|
+
+ padCli(row.owner, ownerW)
|
|
221
|
+
+ padCli(row.trigger, triggerW)
|
|
222
|
+
+ padCli(row.approval, approvalW)
|
|
223
|
+
+ row.destinations);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function printMemoryRecorderDetail(recorder) {
|
|
227
|
+
console.log(`${recorder.id} - ${recorder.title}`);
|
|
228
|
+
console.log(`Source: ${recorder.source}`);
|
|
229
|
+
console.log(`Owner: ${recorder.owner}`);
|
|
230
|
+
console.log(`Enabled: ${recorder.enabled ? "yes" : "no"}`);
|
|
231
|
+
console.log(`Trigger: ${recorder.trigger.event}`);
|
|
232
|
+
console.log(`Trigger detail: ${recorder.trigger.description}`);
|
|
233
|
+
console.log(`Approval required: ${recorder.privacy.approvalRequired ? "yes" : "no"}`);
|
|
234
|
+
console.log(`Redacts secrets: ${recorder.privacy.redactsSecrets ? "yes" : "no"}`);
|
|
235
|
+
console.log(`Includes owner memory/content: ${recorder.privacy.includesOwnerMemory ? "yes" : "no"}`);
|
|
236
|
+
console.log("Destinations:");
|
|
237
|
+
for (const dest of recorder.destinations) {
|
|
238
|
+
console.log(`- ${dest.scope} ${dest.path}`);
|
|
239
|
+
console.log(` format=${dest.format} mode=${dest.writeMode}`);
|
|
240
|
+
console.log(` ${dest.description}`);
|
|
241
|
+
}
|
|
242
|
+
if (recorder.sourceFiles.length) {
|
|
243
|
+
console.log("Source files:");
|
|
244
|
+
for (const file of recorder.sourceFiles)
|
|
245
|
+
console.log(`- ${file}`);
|
|
246
|
+
}
|
|
247
|
+
if (recorder.notes)
|
|
248
|
+
console.log(`Notes: ${recorder.notes}`);
|
|
249
|
+
}
|
|
250
|
+
function printPermissionAuditRecords(records) {
|
|
251
|
+
if (!records.length) {
|
|
252
|
+
console.log("No permission audit records found.");
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
for (const record of records) {
|
|
256
|
+
const decision = `${record.decision.result}/${record.decision.mode}`;
|
|
257
|
+
const requestedBy = `${record.requestedBy.kind}:${record.requestedBy.id}`;
|
|
258
|
+
const performedBy = `${record.performedBy.kind}:${record.performedBy.id}`;
|
|
259
|
+
const scope = [
|
|
260
|
+
record.riskLevel ? `risk=${record.riskLevel}` : "",
|
|
261
|
+
record.memoryScope ? `memory=${record.memoryScope}` : "",
|
|
262
|
+
record.transport ? `transport=${record.transport}` : "",
|
|
263
|
+
].filter(Boolean).join(" ");
|
|
264
|
+
console.log(`${record.ts} ${record.actionKind} ${record.action} ${decision}`);
|
|
265
|
+
console.log(` requested: ${requestedBy} performed: ${performedBy}`);
|
|
266
|
+
if (scope)
|
|
267
|
+
console.log(` ${scope}`);
|
|
268
|
+
if (record.references?.taskId)
|
|
269
|
+
console.log(` task: ${record.references.taskId}`);
|
|
270
|
+
if (record.references?.messageId)
|
|
271
|
+
console.log(` message: ${record.references.messageId}`);
|
|
272
|
+
if (record.references?.noteId)
|
|
273
|
+
console.log(` note: ${record.references.noteId}`);
|
|
274
|
+
if (record.workdir)
|
|
275
|
+
console.log(` workdir: ${truncateOneLine(record.workdir, 120)}`);
|
|
276
|
+
if (record.decision.reason)
|
|
277
|
+
console.log(` ${truncateOneLine(record.decision.reason, 140)}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function printPeripheralRecords(records) {
|
|
281
|
+
if (!records.length) {
|
|
282
|
+
console.log("No peripheral records found.");
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const idWidth = Math.min(Math.max(...records.map((record) => record.id.length), 2), 32);
|
|
286
|
+
const typeWidth = Math.min(Math.max(...records.map((record) => record.type.length), 4), 16);
|
|
287
|
+
console.log(`${padCli("ID", idWidth)} ${padCli("TYPE", typeWidth)} RISK STATUS CAPABILITIES`);
|
|
288
|
+
for (const record of records) {
|
|
289
|
+
console.log([
|
|
290
|
+
padCli(truncateOneLine(record.id, idWidth), idWidth),
|
|
291
|
+
padCli(record.type, typeWidth),
|
|
292
|
+
padCli(record.riskLevel, 7),
|
|
293
|
+
padCli(record.status, 12),
|
|
294
|
+
truncateOneLine(record.capabilities.join(", ") || "-", 80),
|
|
295
|
+
].join(" "));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function parsePeripheralRecordType(value) {
|
|
299
|
+
const normalized = (value || "custom").trim().toLowerCase();
|
|
300
|
+
if (normalized === "engine"
|
|
301
|
+
|| normalized === "relay"
|
|
302
|
+
|| normalized === "software-agent"
|
|
303
|
+
|| normalized === "mcp"
|
|
304
|
+
|| normalized === "service"
|
|
305
|
+
|| normalized === "hardware"
|
|
306
|
+
|| normalized === "custom") {
|
|
307
|
+
return normalized;
|
|
308
|
+
}
|
|
309
|
+
console.error("--type must be one of: engine, relay, software-agent, mcp, service, hardware, custom");
|
|
310
|
+
process.exit(1);
|
|
311
|
+
}
|
|
312
|
+
function parsePeripheralRiskLevel(value) {
|
|
313
|
+
const normalized = (value || "medium").trim().toLowerCase();
|
|
314
|
+
if (normalized === "low" || normalized === "medium" || normalized === "high")
|
|
315
|
+
return normalized;
|
|
316
|
+
console.error("--risk must be one of: low, medium, high");
|
|
317
|
+
process.exit(1);
|
|
318
|
+
}
|
|
319
|
+
function parsePeripheralExploreMode(value) {
|
|
320
|
+
const normalized = (value || "none").trim().toLowerCase();
|
|
321
|
+
if (normalized === "none" || normalized === "plain-text")
|
|
322
|
+
return normalized;
|
|
323
|
+
console.error("--explore must be one of: none, plain-text");
|
|
324
|
+
process.exit(1);
|
|
325
|
+
}
|
|
326
|
+
async function resolvePermissionAuditAgentName(value) {
|
|
327
|
+
const explicit = value?.trim();
|
|
328
|
+
if (explicit)
|
|
329
|
+
return explicit;
|
|
330
|
+
const manager = await getLocalManagerName();
|
|
331
|
+
if (manager)
|
|
332
|
+
return manager;
|
|
333
|
+
console.error("No Akemon name specified for audit. Use --name <name> or set a local manager.");
|
|
334
|
+
process.exit(1);
|
|
335
|
+
}
|
|
336
|
+
async function resolvePeripheralRegistryAgentName(value) {
|
|
337
|
+
const explicit = value?.trim();
|
|
338
|
+
if (explicit)
|
|
339
|
+
return explicit;
|
|
340
|
+
const manager = await getLocalManagerName();
|
|
341
|
+
if (manager)
|
|
342
|
+
return manager;
|
|
343
|
+
console.error("No Akemon name specified for peripherals. Use --name <name> or set a local manager.");
|
|
344
|
+
process.exit(1);
|
|
345
|
+
}
|
|
346
|
+
async function runPermissionAuditListCli(opts) {
|
|
347
|
+
const decision = opts.decision === undefined ? undefined : String(opts.decision).trim().toLowerCase();
|
|
348
|
+
if (decision !== undefined && decision !== "allowed" && decision !== "denied") {
|
|
349
|
+
console.error("--decision must be one of: allowed, denied");
|
|
350
|
+
process.exit(1);
|
|
81
351
|
}
|
|
352
|
+
const agentName = await resolvePermissionAuditAgentName(opts.name);
|
|
353
|
+
const records = await listPermissionAuditRecords(agentName, {
|
|
354
|
+
limit: clampPositiveInt(opts.limit, 20, 500),
|
|
355
|
+
actionKind: parsePermissionAuditActionKind(opts.kind),
|
|
356
|
+
decision: decision,
|
|
357
|
+
});
|
|
358
|
+
if (opts.json) {
|
|
359
|
+
console.log(JSON.stringify(records, null, 2));
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
printPermissionAuditRecords(records);
|
|
363
|
+
}
|
|
364
|
+
function summarizeDestinations(recorder) {
|
|
365
|
+
return recorder.destinations
|
|
366
|
+
.map((dest) => `${dest.scope}:${dest.path}`)
|
|
367
|
+
.join("; ");
|
|
368
|
+
}
|
|
369
|
+
function padCli(value, width) {
|
|
370
|
+
return value.padEnd(width);
|
|
371
|
+
}
|
|
372
|
+
async function fetchPublicRelayAgentsForDiscovery(relayUrl) {
|
|
373
|
+
const baseUrl = relayUrl.replace(/\/+$/, "");
|
|
374
|
+
const url = `${baseUrl}/v1/agents?online=true&public=true`;
|
|
375
|
+
let res;
|
|
376
|
+
try {
|
|
377
|
+
res = await fetch(url);
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
throw new Error(`Failed to connect to relay: ${error instanceof Error ? error.message : String(error)}`);
|
|
381
|
+
}
|
|
382
|
+
if (!res.ok)
|
|
383
|
+
throw new Error(`Failed to fetch public agents: HTTP ${res.status}`);
|
|
384
|
+
const data = await res.json();
|
|
385
|
+
if (!Array.isArray(data))
|
|
386
|
+
throw new Error("Relay returned an invalid agent list");
|
|
387
|
+
return data;
|
|
82
388
|
}
|
|
83
389
|
function truncateOneLine(value, max) {
|
|
84
390
|
const oneLine = value.replace(/\s+/g, " ").trim();
|
|
@@ -86,6 +392,23 @@ function truncateOneLine(value, max) {
|
|
|
86
392
|
return oneLine;
|
|
87
393
|
return `${oneLine.slice(0, Math.max(0, max - 3))}...`;
|
|
88
394
|
}
|
|
395
|
+
function formatLocalInstanceRecord(record) {
|
|
396
|
+
return `${record.name} port=${record.port} pid=${record.pid} mode=${record.mode} workdir=${record.workdir}`;
|
|
397
|
+
}
|
|
398
|
+
function printLocalInstanceCandidates(candidates) {
|
|
399
|
+
for (const candidate of candidates) {
|
|
400
|
+
console.error(` ${formatLocalInstanceRecord(candidate)}`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
function printLocalInstanceList(instances) {
|
|
404
|
+
if (instances.length === 0) {
|
|
405
|
+
console.log("No running local Akemon instances.");
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
for (const instance of instances) {
|
|
409
|
+
console.log(formatLocalInstanceRecord(instance));
|
|
410
|
+
}
|
|
411
|
+
}
|
|
89
412
|
async function callLocalOwnerEndpoint(path, opts, init = {}) {
|
|
90
413
|
const res = await fetchLocalOwnerEndpoint(path, opts, init);
|
|
91
414
|
const text = await res.text();
|
|
@@ -103,10 +426,10 @@ async function callLocalOwnerEndpoint(path, opts, init = {}) {
|
|
|
103
426
|
return data;
|
|
104
427
|
}
|
|
105
428
|
async function fetchLocalOwnerEndpoint(path, opts, init = {}) {
|
|
106
|
-
const
|
|
107
|
-
const port =
|
|
429
|
+
const secretKey = await getOrCreateLocalOwnerSecret();
|
|
430
|
+
const port = await resolveLocalOwnerPort(opts);
|
|
108
431
|
const headers = {
|
|
109
|
-
Authorization: `Bearer ${
|
|
432
|
+
Authorization: `Bearer ${secretKey}`,
|
|
110
433
|
};
|
|
111
434
|
if (init.body !== undefined)
|
|
112
435
|
headers["Content-Type"] = "application/json";
|
|
@@ -127,13 +450,51 @@ async function fetchLocalOwnerEndpoint(path, opts, init = {}) {
|
|
|
127
450
|
process.exit(1);
|
|
128
451
|
}
|
|
129
452
|
if (error instanceof TypeError && error.message === "fetch failed") {
|
|
130
|
-
console.error(`Cannot connect to local
|
|
453
|
+
console.error(`Cannot connect to local Akemon on port ${port}. Start it with: akemon run --port ${port}`);
|
|
131
454
|
process.exit(1);
|
|
132
455
|
}
|
|
133
456
|
throw error;
|
|
134
457
|
}
|
|
135
458
|
return res;
|
|
136
459
|
}
|
|
460
|
+
async function resolveLocalOwnerPort(opts) {
|
|
461
|
+
if (opts.port)
|
|
462
|
+
return parsePortOption(opts.port);
|
|
463
|
+
if (opts.name) {
|
|
464
|
+
try {
|
|
465
|
+
return (await resolveLocalInstanceByName(opts.name)).port;
|
|
466
|
+
}
|
|
467
|
+
catch (error) {
|
|
468
|
+
if (error instanceof LocalInstanceLookupError) {
|
|
469
|
+
console.error(error.message);
|
|
470
|
+
for (const candidate of error.candidates) {
|
|
471
|
+
console.error(` ${candidate.name}: port=${candidate.port} pid=${candidate.pid} workdir=${candidate.workdir}`);
|
|
472
|
+
}
|
|
473
|
+
process.exit(1);
|
|
474
|
+
}
|
|
475
|
+
throw error;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
try {
|
|
479
|
+
return (await resolveDefaultLocalInstance()).port;
|
|
480
|
+
}
|
|
481
|
+
catch (error) {
|
|
482
|
+
if (error instanceof LocalInstanceLookupError) {
|
|
483
|
+
if (error.code === "ambiguous") {
|
|
484
|
+
console.error(error.message);
|
|
485
|
+
printLocalInstanceCandidates(error.candidates);
|
|
486
|
+
process.exit(1);
|
|
487
|
+
}
|
|
488
|
+
return parsePortOption(undefined);
|
|
489
|
+
}
|
|
490
|
+
throw error;
|
|
491
|
+
}
|
|
492
|
+
return parsePortOption(undefined);
|
|
493
|
+
}
|
|
494
|
+
async function resolveLocalOwnerEndpointOptions(opts) {
|
|
495
|
+
const port = await resolveLocalOwnerPort(opts);
|
|
496
|
+
return { ...opts, port: String(port) };
|
|
497
|
+
}
|
|
137
498
|
async function streamLocalOwnerEndpoint(path, opts, body) {
|
|
138
499
|
const res = await fetchLocalOwnerEndpoint(path, opts, {
|
|
139
500
|
method: "POST",
|
|
@@ -176,55 +537,425 @@ async function streamLocalOwnerEndpoint(path, opts, body) {
|
|
|
176
537
|
if (failed)
|
|
177
538
|
process.exit(1);
|
|
178
539
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
.
|
|
187
|
-
|
|
188
|
-
.
|
|
189
|
-
|
|
190
|
-
.
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
540
|
+
async function runSoftwareAgentCli(goalParts, opts, forcedSessionId) {
|
|
541
|
+
const body = {
|
|
542
|
+
goal: goalParts.join(" "),
|
|
543
|
+
roleScope: opts.roleScope,
|
|
544
|
+
memoryScope: opts.memoryScope,
|
|
545
|
+
riskLevel: opts.risk,
|
|
546
|
+
};
|
|
547
|
+
if (opts.workdir)
|
|
548
|
+
body.workdir = opts.workdir;
|
|
549
|
+
if (opts.allowOutsideWorkdir)
|
|
550
|
+
body.allowOutsideWorkdir = true;
|
|
551
|
+
if (opts.memorySummary)
|
|
552
|
+
body.memorySummary = opts.memorySummary;
|
|
553
|
+
const workContextBudget = parsePositiveIntCliOption(opts.workContextBudget, "--work-context-budget");
|
|
554
|
+
if (opts.workContext || workContextBudget !== undefined)
|
|
555
|
+
body.includeWorkMemoryContext = true;
|
|
556
|
+
if (workContextBudget !== undefined)
|
|
557
|
+
body.workMemoryContextBudget = workContextBudget;
|
|
558
|
+
const sessionId = forcedSessionId || opts.session;
|
|
559
|
+
if (sessionId)
|
|
560
|
+
body.contextSessionId = sessionId;
|
|
561
|
+
if (opts.deliverable)
|
|
562
|
+
body.deliverable = opts.deliverable;
|
|
563
|
+
if (opts.timeoutMs) {
|
|
564
|
+
const timeoutMs = Number(opts.timeoutMs);
|
|
565
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
|
|
566
|
+
console.error("--timeout-ms must be a positive integer");
|
|
567
|
+
process.exit(1);
|
|
568
|
+
}
|
|
569
|
+
body.timeoutMs = timeoutMs;
|
|
570
|
+
}
|
|
571
|
+
if (opts.stream !== false) {
|
|
572
|
+
await streamLocalOwnerEndpoint("/self/software-agent/run-stream", opts, body);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
const res = await fetchLocalOwnerEndpoint("/self/software-agent/run", opts, {
|
|
576
|
+
method: "POST",
|
|
577
|
+
body: JSON.stringify(body),
|
|
578
|
+
});
|
|
579
|
+
const text = await res.text();
|
|
580
|
+
let data;
|
|
581
|
+
try {
|
|
582
|
+
data = text ? JSON.parse(text) : {};
|
|
583
|
+
}
|
|
584
|
+
catch {
|
|
585
|
+
data = { output: text };
|
|
586
|
+
}
|
|
587
|
+
if (!res.ok) {
|
|
588
|
+
console.error(data.error || text || `Request failed with HTTP ${res.status}`);
|
|
589
|
+
process.exit(1);
|
|
590
|
+
}
|
|
591
|
+
const failed = renderSoftwareAgentRunResult(data);
|
|
592
|
+
if (failed)
|
|
593
|
+
process.exit(1);
|
|
594
|
+
}
|
|
595
|
+
async function runWorkbenchStartCli(tool, args, opts) {
|
|
596
|
+
const body = { tool };
|
|
597
|
+
if (opts.command)
|
|
598
|
+
body.command = opts.command;
|
|
599
|
+
if (args.length > 0)
|
|
600
|
+
body.args = args;
|
|
601
|
+
if (opts.workdir)
|
|
602
|
+
body.workdir = opts.workdir;
|
|
603
|
+
if (opts.allowOutsideWorkdir)
|
|
604
|
+
body.allowOutsideWorkdir = true;
|
|
605
|
+
if (opts.label)
|
|
606
|
+
body.label = opts.label;
|
|
607
|
+
if (opts.input)
|
|
608
|
+
body.initialInput = opts.input;
|
|
609
|
+
const cols = parsePositiveIntCliOption(opts.cols, "--cols");
|
|
610
|
+
const rows = parsePositiveIntCliOption(opts.rows, "--rows");
|
|
611
|
+
if (cols !== undefined)
|
|
612
|
+
body.cols = cols;
|
|
613
|
+
if (rows !== undefined)
|
|
614
|
+
body.rows = rows;
|
|
615
|
+
const data = await callLocalOwnerEndpoint("/self/workbench/sessions", opts, {
|
|
616
|
+
method: "POST",
|
|
617
|
+
body: JSON.stringify(body),
|
|
618
|
+
});
|
|
619
|
+
if (opts.json) {
|
|
620
|
+
console.log(JSON.stringify(data.session, null, 2));
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
printWorkbenchSession(data.session);
|
|
624
|
+
console.error(`[workbench] next: akemon workbench-tail ${data.session.sessionId} | akemon workbench-input ${data.session.sessionId} "..." | akemon workbench-stop ${data.session.sessionId}`);
|
|
625
|
+
}
|
|
626
|
+
function printWorkbenchSession(session) {
|
|
627
|
+
const status = session.status || "unknown";
|
|
628
|
+
const command = session.commandLineDisplay || "";
|
|
629
|
+
const started = session.startedAt || "-";
|
|
630
|
+
const workdir = session.workdir || "-";
|
|
631
|
+
console.log(`${session.sessionId} ${status} ${session.tool || "custom"} ${started}`);
|
|
632
|
+
if (command)
|
|
633
|
+
console.log(` command: ${command}`);
|
|
634
|
+
console.log(` workdir: ${workdir}`);
|
|
635
|
+
if (session.logPath)
|
|
636
|
+
console.log(` log: ${session.logPath}`);
|
|
637
|
+
if (typeof session.ownerDirectInputCount === "number") {
|
|
638
|
+
console.log(` owner-direct inputs: ${session.ownerDirectInputCount}`);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
async function listWorkbenchSessionsCli(opts) {
|
|
642
|
+
const data = await callLocalOwnerEndpoint("/self/workbench/sessions", opts, { method: "GET" });
|
|
643
|
+
const sessions = Array.isArray(data.sessions) ? data.sessions : [];
|
|
644
|
+
if (opts.json) {
|
|
645
|
+
console.log(JSON.stringify(sessions, null, 2));
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
if (!sessions.length) {
|
|
649
|
+
console.log("No Workbench sessions found.");
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
for (const session of sessions)
|
|
653
|
+
printWorkbenchSession(session);
|
|
654
|
+
}
|
|
655
|
+
async function showWorkbenchTailCli(sessionId, opts) {
|
|
656
|
+
const data = await callLocalOwnerEndpoint(`/self/workbench/sessions/${encodeURIComponent(sessionId)}/tail`, opts, { method: "GET" });
|
|
657
|
+
if (opts.json) {
|
|
658
|
+
console.log(JSON.stringify(data, null, 2));
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
process.stdout.write(String(data.tail || ""));
|
|
662
|
+
}
|
|
663
|
+
async function sendWorkbenchInputCli(sessionId, inputParts, opts) {
|
|
664
|
+
const inputText = inputParts.join(" ");
|
|
665
|
+
if (!inputText) {
|
|
666
|
+
console.error("Missing input text");
|
|
667
|
+
process.exit(1);
|
|
668
|
+
}
|
|
669
|
+
const input = opts.enter === false || inputText.endsWith("\n") ? inputText : `${inputText}\n`;
|
|
670
|
+
const data = await callLocalOwnerEndpoint(`/self/workbench/sessions/${encodeURIComponent(sessionId)}/input`, opts, {
|
|
671
|
+
method: "POST",
|
|
672
|
+
body: JSON.stringify({ input }),
|
|
673
|
+
});
|
|
674
|
+
if (opts.json) {
|
|
675
|
+
console.log(JSON.stringify(data.session, null, 2));
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
printWorkbenchSession(data.session);
|
|
679
|
+
}
|
|
680
|
+
async function stopWorkbenchSessionCli(sessionId, opts) {
|
|
681
|
+
const data = await callLocalOwnerEndpoint(`/self/workbench/sessions/${encodeURIComponent(sessionId)}/stop`, opts, {
|
|
682
|
+
method: "POST",
|
|
683
|
+
});
|
|
684
|
+
if (opts.json) {
|
|
685
|
+
console.log(JSON.stringify(data.session, null, 2));
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
printWorkbenchSession(data.session);
|
|
689
|
+
}
|
|
690
|
+
async function resizeWorkbenchSessionCli(sessionId, opts) {
|
|
691
|
+
const cols = parsePositiveIntCliOption(opts.cols, "--cols");
|
|
692
|
+
const rows = parsePositiveIntCliOption(opts.rows, "--rows");
|
|
693
|
+
if (cols === undefined || rows === undefined) {
|
|
694
|
+
console.error("--cols and --rows are required");
|
|
695
|
+
process.exit(1);
|
|
696
|
+
}
|
|
697
|
+
const data = await callLocalOwnerEndpoint(`/self/workbench/sessions/${encodeURIComponent(sessionId)}/resize`, opts, {
|
|
698
|
+
method: "POST",
|
|
699
|
+
body: JSON.stringify({ cols, rows }),
|
|
700
|
+
});
|
|
701
|
+
if (opts.json) {
|
|
702
|
+
console.log(JSON.stringify(data.session, null, 2));
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
printWorkbenchSession(data.session);
|
|
706
|
+
}
|
|
707
|
+
async function openWorkbenchUiCli(opts) {
|
|
708
|
+
const secretKey = await getOrCreateLocalOwnerSecret();
|
|
709
|
+
const port = await resolveLocalOwnerPort(opts);
|
|
710
|
+
const url = `http://127.0.0.1:${port}/workbench#token=${encodeURIComponent(secretKey)}`;
|
|
711
|
+
console.log(`Workbench: ${url}`);
|
|
712
|
+
if (opts.open === false || opts.printUrl)
|
|
713
|
+
return;
|
|
714
|
+
try {
|
|
715
|
+
await openUrl(url);
|
|
716
|
+
}
|
|
717
|
+
catch (error) {
|
|
718
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
719
|
+
process.exit(1);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
function createSecretariatClient(opts) {
|
|
723
|
+
return new SecretariatClient({
|
|
724
|
+
requestJson: (path, init) => callLocalOwnerEndpoint(path, opts, init),
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
async function resolveSecretariatAgentName(opts) {
|
|
728
|
+
if (opts.name && !opts.port)
|
|
729
|
+
return String(opts.name);
|
|
730
|
+
const data = await callLocalOwnerEndpoint("/self/state", opts, { method: "GET" });
|
|
731
|
+
const agent = typeof data.agent === "string" ? data.agent.trim() : "";
|
|
732
|
+
if (!agent) {
|
|
733
|
+
console.error("Could not resolve the target Akemon name. Pass --name when using --port.");
|
|
734
|
+
process.exit(1);
|
|
735
|
+
}
|
|
736
|
+
return agent;
|
|
737
|
+
}
|
|
738
|
+
function defaultChatConversationId(opts) {
|
|
739
|
+
const value = typeof opts.conversation === "string" ? opts.conversation.trim() : "";
|
|
740
|
+
return value || "akemon_cli";
|
|
741
|
+
}
|
|
742
|
+
function printSecretariatMessageResponse(data, opts) {
|
|
743
|
+
if (opts.json) {
|
|
744
|
+
console.log(JSON.stringify(data, null, 2));
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
const output = data.output || data.response?.payload?.text || "";
|
|
748
|
+
if (output)
|
|
749
|
+
console.log(output);
|
|
750
|
+
if (data.task?.taskId) {
|
|
751
|
+
console.error(`[akemon-chat] task: ${data.task.taskId}`);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
function printSecretariatTaskStatus(task) {
|
|
755
|
+
const view = task.view;
|
|
756
|
+
const stage = view?.stage?.label || task.stage;
|
|
757
|
+
const status = [task.status, task.stage].filter(Boolean).join("/");
|
|
758
|
+
console.log(`${task.taskId} ${status} ${stage}`);
|
|
759
|
+
if (task.title)
|
|
760
|
+
console.log(` ${truncateOneLine(task.title, 120)}`);
|
|
761
|
+
if (task.summary)
|
|
762
|
+
console.log(` ${truncateOneLine(task.summary, 160)}`);
|
|
763
|
+
if (task.conversationId)
|
|
764
|
+
console.log(` conversation: ${task.conversationId}`);
|
|
765
|
+
if (task.updatedAt)
|
|
766
|
+
console.log(` updated: ${task.updatedAt}`);
|
|
767
|
+
}
|
|
768
|
+
function taskReportText(response) {
|
|
769
|
+
const task = response.task;
|
|
770
|
+
const view = response.taskView || task.view;
|
|
771
|
+
const latestReview = Array.isArray(view?.reviews) && view.reviews.length
|
|
772
|
+
? view.reviews[view.reviews.length - 1]
|
|
773
|
+
: null;
|
|
774
|
+
return view?.report?.text
|
|
775
|
+
|| latestReview?.reportText
|
|
776
|
+
|| (typeof task.data?.reportText === "string" ? task.data.reportText : "")
|
|
777
|
+
|| task.summary
|
|
778
|
+
|| task.nextAction
|
|
779
|
+
|| "";
|
|
780
|
+
}
|
|
781
|
+
function printSecretariatTaskReport(response, opts) {
|
|
782
|
+
if (opts.json) {
|
|
783
|
+
console.log(JSON.stringify(response, null, 2));
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
const text = taskReportText(response);
|
|
787
|
+
if (text) {
|
|
788
|
+
console.log(text);
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
printSecretariatTaskStatus(response.task);
|
|
792
|
+
}
|
|
793
|
+
async function runChatSendCli(textParts, opts) {
|
|
794
|
+
const text = textParts.join(" ").trim();
|
|
795
|
+
if (!text) {
|
|
796
|
+
console.error("Message text is required");
|
|
797
|
+
process.exit(1);
|
|
798
|
+
}
|
|
799
|
+
const targetOpts = await resolveLocalOwnerEndpointOptions(opts);
|
|
800
|
+
const targetAgent = await resolveSecretariatAgentName(targetOpts);
|
|
801
|
+
const conversationId = defaultChatConversationId(opts);
|
|
802
|
+
const createdAt = opts.createdAt || new Date().toISOString();
|
|
803
|
+
const message = createOwnerChatMessage({
|
|
804
|
+
targetAgent,
|
|
805
|
+
text,
|
|
806
|
+
conversationId,
|
|
807
|
+
memoryScope: parseAkemonMemoryScope(opts.memoryScope),
|
|
808
|
+
id: opts.messageId,
|
|
809
|
+
createdAt,
|
|
810
|
+
});
|
|
811
|
+
const taskId = taskIdFromOwnerChatMessage(message);
|
|
812
|
+
const client = createSecretariatClient(targetOpts);
|
|
813
|
+
if (opts.wait === false) {
|
|
814
|
+
const data = await client.submitOwnerIntent({ message, wait: false });
|
|
815
|
+
const acceptedTaskId = data.task?.taskId || taskId;
|
|
816
|
+
if (opts.json) {
|
|
817
|
+
console.log(JSON.stringify({
|
|
818
|
+
...data,
|
|
819
|
+
submitted: true,
|
|
820
|
+
wait: false,
|
|
821
|
+
taskId: acceptedTaskId,
|
|
822
|
+
messageId: message.id,
|
|
823
|
+
conversationId,
|
|
824
|
+
}, null, 2));
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
console.log(`Submitted: ${acceptedTaskId}`);
|
|
828
|
+
console.log(`Status: akemon task status ${acceptedTaskId} --port ${targetOpts.port}`);
|
|
829
|
+
console.log(`Report: akemon task report ${acceptedTaskId} --port ${targetOpts.port}`);
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
const data = await client.submitOwnerIntent({ message });
|
|
833
|
+
printSecretariatMessageResponse(data, opts);
|
|
834
|
+
}
|
|
835
|
+
async function runChatFollowCli(taskId, textParts, opts) {
|
|
836
|
+
const text = textParts.join(" ").trim();
|
|
837
|
+
if (!taskId || !text) {
|
|
838
|
+
console.error("Usage: akemon chat follow <taskId> <message...>");
|
|
839
|
+
process.exit(1);
|
|
840
|
+
}
|
|
841
|
+
const targetOpts = await resolveLocalOwnerEndpointOptions(opts);
|
|
842
|
+
const client = createSecretariatClient(targetOpts);
|
|
843
|
+
const [targetAgent, taskResponse] = await Promise.all([
|
|
844
|
+
resolveSecretariatAgentName(targetOpts),
|
|
845
|
+
client.getTaskStatus(taskId).catch(() => null),
|
|
846
|
+
]);
|
|
847
|
+
const conversationId = opts.conversation || taskResponse?.task.conversationId || defaultChatConversationId(opts);
|
|
848
|
+
const message = createOwnerChatMessage({
|
|
849
|
+
targetAgent,
|
|
850
|
+
text,
|
|
851
|
+
conversationId,
|
|
852
|
+
memoryScope: parseAkemonMemoryScope(opts.memoryScope),
|
|
853
|
+
});
|
|
854
|
+
const data = await client.queueOwnerFollowUp({ taskId, message });
|
|
855
|
+
printSecretariatMessageResponse(data, opts);
|
|
856
|
+
}
|
|
857
|
+
async function runTaskStatusCli(taskId, opts) {
|
|
858
|
+
const targetOpts = await resolveLocalOwnerEndpointOptions(opts);
|
|
859
|
+
const data = await createSecretariatClient(targetOpts).getTaskStatus(taskId);
|
|
860
|
+
if (opts.json) {
|
|
861
|
+
console.log(JSON.stringify(data, null, 2));
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
printSecretariatTaskStatus(data.task);
|
|
865
|
+
}
|
|
866
|
+
async function runTaskReportCli(taskId, opts) {
|
|
867
|
+
const targetOpts = await resolveLocalOwnerEndpointOptions(opts);
|
|
868
|
+
const data = await createSecretariatClient(targetOpts).getTaskReport(taskId);
|
|
869
|
+
printSecretariatTaskReport(data, opts);
|
|
870
|
+
}
|
|
871
|
+
async function runTaskWatchCli(taskId, opts) {
|
|
872
|
+
const targetOpts = await resolveLocalOwnerEndpointOptions(opts);
|
|
873
|
+
const client = createSecretariatClient(targetOpts);
|
|
874
|
+
const intervalMs = parsePositiveIntCliOption(opts.intervalMs, "--interval-ms") || 1000;
|
|
875
|
+
let lastKey = "";
|
|
876
|
+
for (;;) {
|
|
877
|
+
const data = await client.getTaskStatus(taskId);
|
|
878
|
+
const task = data.task;
|
|
879
|
+
const key = [task.status, task.stage, task.updatedAt, task.summary || ""].join("|");
|
|
880
|
+
if (key !== lastKey) {
|
|
881
|
+
lastKey = key;
|
|
882
|
+
if (opts.jsonl) {
|
|
883
|
+
console.log(JSON.stringify({
|
|
884
|
+
taskId: task.taskId,
|
|
885
|
+
status: task.status,
|
|
886
|
+
stage: task.stage,
|
|
887
|
+
updatedAt: task.updatedAt,
|
|
888
|
+
summary: task.summary,
|
|
889
|
+
report: taskReportText(data) || undefined,
|
|
890
|
+
}));
|
|
891
|
+
}
|
|
892
|
+
else {
|
|
893
|
+
printSecretariatTaskStatus(task);
|
|
894
|
+
if (taskIsTerminal(task)) {
|
|
895
|
+
const report = taskReportText(data);
|
|
896
|
+
if (report)
|
|
897
|
+
console.log(` report: ${truncateOneLine(report, 180)}`);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
if (taskIsTerminal(task) || opts.once)
|
|
902
|
+
break;
|
|
903
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
function addLocalRuntimeOptions(command) {
|
|
907
|
+
return command
|
|
908
|
+
.option("-p, --port <port>", "Local port for MCP loopback", "3000")
|
|
909
|
+
.option("-w, --workdir <path>", "Working directory for the engine (default: cwd)")
|
|
910
|
+
.option("-n, --name <name>", "Agent name", "my-agent")
|
|
911
|
+
.option("-m, --model <model>", "Model to use (e.g. claude-sonnet-4-6, gpt-4o)")
|
|
912
|
+
.option("--engine <engine>", "Engine: claude, codex, opencode, gemini, raw, human, or any CLI", "claude")
|
|
913
|
+
.option("--approve", "Review every task before execution")
|
|
914
|
+
.option("--mock", "Use mock responses (for demo/testing)")
|
|
915
|
+
.option("--allow-all", "Skip all permission prompts (for self-use)")
|
|
916
|
+
.option("--mcp-server <command>", "Wrap a community MCP server (stdio) and expose its tools")
|
|
917
|
+
.option("--notify <url>", "ntfy.sh topic URL for push notifications (e.g. https://ntfy.sh/my-agent)")
|
|
918
|
+
.option("--interval <minutes>", "Consciousness cycle interval in minutes (default: 1440 = 24h)")
|
|
919
|
+
.option("--with <modules>", "Enable specific modules (comma-separated: biostate,memory)")
|
|
920
|
+
.option("--without <modules>", "Disable specific modules (comma-separated: biostate,memory)")
|
|
921
|
+
.option("--script <name>", "Script to load for ScriptModule (default: daily-life)", "daily-life")
|
|
922
|
+
.option("--software-agent-env <policy>", "Software-agent child environment policy: inherit or allowlist", process.env.AKEMON_SOFTWARE_AGENT_ENV_POLICY || "inherit")
|
|
923
|
+
.option("--software-agent-env-allow <vars>", "Comma-separated extra env vars for software-agent allowlist");
|
|
924
|
+
}
|
|
925
|
+
function addLocalAkemonSelectionOptions(command) {
|
|
926
|
+
return command
|
|
927
|
+
.option("-n, --name <name>", "Running local Akemon name")
|
|
928
|
+
.option("-p, --port <port>", "Local Akemon port (overrides --name)");
|
|
929
|
+
}
|
|
930
|
+
function parseEnabledModules(opts) {
|
|
221
931
|
if (opts.with) {
|
|
222
|
-
|
|
932
|
+
return opts.with.split(",").map((m) => m.trim()).filter(Boolean);
|
|
223
933
|
}
|
|
224
|
-
|
|
225
|
-
const disabled = opts.without.split(",").map((m) => m.trim());
|
|
226
|
-
|
|
934
|
+
if (opts.without) {
|
|
935
|
+
const disabled = opts.without.split(",").map((m) => m.trim()).filter(Boolean);
|
|
936
|
+
return ALL_MODULES.filter((m) => !disabled.includes(m));
|
|
937
|
+
}
|
|
938
|
+
return undefined;
|
|
939
|
+
}
|
|
940
|
+
async function startServeCli(opts, config = {}) {
|
|
941
|
+
const port = parsePortOption(opts.port);
|
|
942
|
+
const engine = opts.engine || "claude";
|
|
943
|
+
let relayMode;
|
|
944
|
+
try {
|
|
945
|
+
relayMode = resolveServeRelayMode({
|
|
946
|
+
localOnly: config.forceLocalOnly || opts.localOnly,
|
|
947
|
+
public: opts.public,
|
|
948
|
+
relay: opts.relay,
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
catch (error) {
|
|
952
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
953
|
+
process.exit(1);
|
|
227
954
|
}
|
|
955
|
+
let relayCredentials = null;
|
|
956
|
+
const secretKey = relayMode.enabled
|
|
957
|
+
? (relayCredentials = await getOrCreateRelayCredentials()).secretKey
|
|
958
|
+
: await getOrCreateLocalOwnerSecret();
|
|
228
959
|
serve({
|
|
229
960
|
port,
|
|
230
961
|
workdir: opts.workdir,
|
|
@@ -234,37 +965,65 @@ program
|
|
|
234
965
|
approve: opts.approve,
|
|
235
966
|
allowAll: opts.allowAll,
|
|
236
967
|
engine,
|
|
237
|
-
relayHttp,
|
|
238
|
-
secretKey
|
|
968
|
+
relayHttp: relayMode.relayHttp,
|
|
969
|
+
secretKey,
|
|
239
970
|
mcpServer: opts.mcpServer,
|
|
240
971
|
cycleInterval: opts.interval ? parseInt(opts.interval) : undefined,
|
|
241
972
|
notifyUrl: opts.notify,
|
|
242
|
-
enabledModules,
|
|
973
|
+
enabledModules: parseEnabledModules(opts),
|
|
243
974
|
scriptName: opts.script,
|
|
244
975
|
softwareAgentEnvPolicy: parseSoftwareAgentEnvPolicy(opts.softwareAgentEnv),
|
|
245
976
|
softwareAgentEnvAllowlist: parseCommaSeparatedCliOption(opts.softwareAgentEnvAllow),
|
|
246
977
|
});
|
|
247
978
|
console.log(`\nakemon v${pkg.version}`);
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
979
|
+
console.log(`Mode: ${relayMode.enabled ? "relay" : "local-only"}`);
|
|
980
|
+
if (relayMode.enabled && relayCredentials && !opts.public) {
|
|
981
|
+
console.log(`Access key: ${relayCredentials.accessKey} (share with publishers)`);
|
|
982
|
+
}
|
|
983
|
+
console.log(`Relay: ${relayMode.relayWs || "disabled"}\n`);
|
|
984
|
+
if (relayMode.enabled && relayMode.relayWs && relayCredentials) {
|
|
985
|
+
// Default avatar: DiceBear bottts-neutral (deterministic from name)
|
|
986
|
+
const avatar = opts.avatar || `https://api.dicebear.com/9.x/bottts-neutral/svg?seed=${encodeURIComponent(opts.name)}`;
|
|
987
|
+
connectRelay({
|
|
988
|
+
relayUrl: relayMode.relayWs,
|
|
989
|
+
agentName: opts.name,
|
|
990
|
+
credentials: relayCredentials,
|
|
991
|
+
localPort: port,
|
|
992
|
+
description: opts.desc,
|
|
993
|
+
isPublic: opts.public,
|
|
994
|
+
engine,
|
|
995
|
+
tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : undefined,
|
|
996
|
+
price: parseInt(opts.price) || 1,
|
|
997
|
+
avatar,
|
|
998
|
+
onOrderNotify,
|
|
999
|
+
enableTerminal: opts.terminal,
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
program
|
|
1004
|
+
.name("akemon")
|
|
1005
|
+
.description("Local AI companion runtime with memory, modules, relay sync, and software-agent control")
|
|
1006
|
+
.version(pkg.version);
|
|
1007
|
+
addLocalRuntimeOptions(program
|
|
1008
|
+
.command("run")
|
|
1009
|
+
.description("Run a named local Akemon without relay"))
|
|
1010
|
+
.action(async (opts) => {
|
|
1011
|
+
await startServeCli(opts, { forceLocalOnly: true });
|
|
1012
|
+
});
|
|
1013
|
+
addLocalRuntimeOptions(program
|
|
1014
|
+
.command("serve")
|
|
1015
|
+
.description("Run local Akemon; optionally connect or publish to relay"))
|
|
1016
|
+
.option("--desc <description>", "Agent description (for discovery)")
|
|
1017
|
+
.option("--tags <tags>", "Comma-separated tags (e.g. vue,frontend,review)")
|
|
1018
|
+
.option("--public", "Allow anyone to call this agent without a key")
|
|
1019
|
+
.option("--max-tasks <n>", "Maximum tasks per day (PP)")
|
|
1020
|
+
.option("--price <n>", "Price in credits per call (default: 1)", "1")
|
|
1021
|
+
.option("--avatar <url>", "Custom avatar URL (default: auto-generated from name)")
|
|
1022
|
+
.option("--terminal", "Enable remote terminal access (PTY)")
|
|
1023
|
+
.option("--local-only", "Force local-only mode and disable relay")
|
|
1024
|
+
.option("--relay <url>", "Relay WebSocket URL (enables relay; --public uses the default relay)")
|
|
1025
|
+
.action(async (opts) => {
|
|
1026
|
+
await startServeCli(opts);
|
|
268
1027
|
});
|
|
269
1028
|
program
|
|
270
1029
|
.command("add")
|
|
@@ -279,7 +1038,7 @@ program
|
|
|
279
1038
|
await addAgent(name, endpoint, opts.key, platform);
|
|
280
1039
|
}
|
|
281
1040
|
else {
|
|
282
|
-
const relayEndpoint = `${
|
|
1041
|
+
const relayEndpoint = `${DEFAULT_RELAY_HTTP}/v1/agent/${name}/mcp`;
|
|
283
1042
|
await addAgent(name, relayEndpoint, opts.key, platform);
|
|
284
1043
|
}
|
|
285
1044
|
});
|
|
@@ -288,109 +1047,558 @@ program
|
|
|
288
1047
|
.description("List available agents on the relay")
|
|
289
1048
|
.option("--search <query>", "Filter by name or description")
|
|
290
1049
|
.action(async (opts) => {
|
|
291
|
-
await listAgents(
|
|
1050
|
+
await listAgents(DEFAULT_RELAY_HTTP, opts.search);
|
|
1051
|
+
});
|
|
1052
|
+
program
|
|
1053
|
+
.command("discover")
|
|
1054
|
+
.description("Discover public Akemon profiles by public relay interests")
|
|
1055
|
+
.argument("<query...>", "Interest or profile query")
|
|
1056
|
+
.option("--relay <url>", "Relay HTTP URL", DEFAULT_RELAY_HTTP)
|
|
1057
|
+
.option("-l, --limit <n>", "Maximum candidates to print", "5")
|
|
1058
|
+
.option("--exclude <name>", "Exclude one Akemon name from results")
|
|
1059
|
+
.option("--json", "Print raw JSON")
|
|
1060
|
+
.action(async (queryParts, opts) => {
|
|
1061
|
+
const query = queryParts.join(" ").trim();
|
|
1062
|
+
if (!query) {
|
|
1063
|
+
console.error("Discovery query is required");
|
|
1064
|
+
process.exit(1);
|
|
1065
|
+
}
|
|
1066
|
+
try {
|
|
1067
|
+
const rawProfiles = await fetchPublicRelayAgentsForDiscovery(opts.relay);
|
|
1068
|
+
const results = discoverPublicAkemonProfiles(rawProfiles, query, {
|
|
1069
|
+
assumePublic: true,
|
|
1070
|
+
source: "relay",
|
|
1071
|
+
limit: clampPositiveInt(opts.limit, 5, 50),
|
|
1072
|
+
exclude: opts.exclude,
|
|
1073
|
+
});
|
|
1074
|
+
if (opts.json) {
|
|
1075
|
+
console.log(JSON.stringify({ query, results }, null, 2));
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
printProfileDiscoveryResults(results, query);
|
|
1079
|
+
}
|
|
1080
|
+
catch (error) {
|
|
1081
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1082
|
+
process.exit(1);
|
|
1083
|
+
}
|
|
292
1084
|
});
|
|
293
1085
|
program
|
|
294
1086
|
.command("connect")
|
|
295
1087
|
.description("Connect to the akemon network as a client (stdio MCP server for OpenClaw, Claude, etc.)")
|
|
296
|
-
.option("--relay <url>", "Relay HTTP URL",
|
|
1088
|
+
.option("--relay <url>", "Relay HTTP URL", DEFAULT_RELAY_HTTP)
|
|
297
1089
|
.option("--key <key>", "Access key for calling private agents")
|
|
298
1090
|
.action(async (opts) => {
|
|
299
1091
|
await connect({ relay: opts.relay, key: opts.key });
|
|
300
1092
|
});
|
|
1093
|
+
program
|
|
1094
|
+
.command("manager")
|
|
1095
|
+
.description("Show or set the default local manager Akemon")
|
|
1096
|
+
.argument("[name]", "Local Akemon name to use as manager")
|
|
1097
|
+
.action(async (name) => {
|
|
1098
|
+
try {
|
|
1099
|
+
if (name) {
|
|
1100
|
+
const manager = await setLocalManagerName(name);
|
|
1101
|
+
console.log(`Local manager: ${manager}`);
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
const manager = await getLocalManagerName();
|
|
1105
|
+
console.log(manager ? `Local manager: ${manager}` : "No local manager set.");
|
|
1106
|
+
}
|
|
1107
|
+
catch (error) {
|
|
1108
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1109
|
+
process.exit(1);
|
|
1110
|
+
}
|
|
1111
|
+
});
|
|
1112
|
+
program
|
|
1113
|
+
.command("instances")
|
|
1114
|
+
.description("List running local Akemon instances")
|
|
1115
|
+
.option("--json", "Print raw JSON")
|
|
1116
|
+
.action(async (opts) => {
|
|
1117
|
+
try {
|
|
1118
|
+
const instances = await listRunningLocalInstances();
|
|
1119
|
+
if (opts.json) {
|
|
1120
|
+
console.log(JSON.stringify({ instances }, null, 2));
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
printLocalInstanceList(instances);
|
|
1124
|
+
}
|
|
1125
|
+
catch (error) {
|
|
1126
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1127
|
+
process.exit(1);
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
1130
|
+
program
|
|
1131
|
+
.command("message")
|
|
1132
|
+
.description("Send a structured local Akemon message to another running Akemon")
|
|
1133
|
+
.argument("<text...>", "Message text")
|
|
1134
|
+
.requiredOption("--to <name>", "Target running local Akemon name")
|
|
1135
|
+
.option("--from <name>", "Source Akemon name (defaults to configured local manager)")
|
|
1136
|
+
.option("-p, --port <port>", "Target local Akemon port (overrides --to lookup)")
|
|
1137
|
+
.option("--conversation <id>", "Conversation id for this local interconnect message")
|
|
1138
|
+
.option("--memory-scope <scope>", "Memory scope: none|public|task|owner", "task")
|
|
1139
|
+
.option("--require-owner-approval", "Ask the target Akemon owner to approve/respond before engine execution")
|
|
1140
|
+
.option("--json", "Print raw JSON")
|
|
1141
|
+
.action(async (textParts, opts) => {
|
|
1142
|
+
const text = textParts.join(" ").trim();
|
|
1143
|
+
if (!text) {
|
|
1144
|
+
console.error("Message text is required");
|
|
1145
|
+
process.exit(1);
|
|
1146
|
+
}
|
|
1147
|
+
const from = opts.from || await getLocalManagerName();
|
|
1148
|
+
if (!from) {
|
|
1149
|
+
console.error("Missing --from. Set a default manager with: akemon manager <name>");
|
|
1150
|
+
process.exit(1);
|
|
1151
|
+
}
|
|
1152
|
+
const message = createLocalAkemonMessage({
|
|
1153
|
+
sourceAgent: from,
|
|
1154
|
+
targetAgent: opts.to,
|
|
1155
|
+
text,
|
|
1156
|
+
conversationId: opts.conversation,
|
|
1157
|
+
memoryScope: parseAkemonMemoryScope(opts.memoryScope),
|
|
1158
|
+
requiresOwnerApproval: opts.requireOwnerApproval === true,
|
|
1159
|
+
});
|
|
1160
|
+
const data = await callLocalOwnerEndpoint("/self/message", { name: opts.to, port: opts.port }, {
|
|
1161
|
+
method: "POST",
|
|
1162
|
+
body: JSON.stringify({ message }),
|
|
1163
|
+
});
|
|
1164
|
+
await appendLocalPeerContact({
|
|
1165
|
+
schemaVersion: 1,
|
|
1166
|
+
ts: new Date().toISOString(),
|
|
1167
|
+
ownerAgent: from,
|
|
1168
|
+
peerAgent: opts.to,
|
|
1169
|
+
direction: "sent",
|
|
1170
|
+
messageId: message.id,
|
|
1171
|
+
conversationId: message.conversationId,
|
|
1172
|
+
});
|
|
1173
|
+
if (opts.json) {
|
|
1174
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
console.log(data.output || data.response?.payload?.text || "");
|
|
1178
|
+
if (data.response?.id)
|
|
1179
|
+
console.error(`[akemon-message] response: ${data.response.id}`);
|
|
1180
|
+
});
|
|
1181
|
+
const chatCommand = addLocalAkemonSelectionOptions(program
|
|
1182
|
+
.command("chat")
|
|
1183
|
+
.description("Send an owner chat intent through the local Secretariat Runtime")
|
|
1184
|
+
.argument("[text...]", "Message text"))
|
|
1185
|
+
.option("--conversation <id>", "Conversation id for this chat", "akemon_cli")
|
|
1186
|
+
.option("--memory-scope <scope>", "Memory scope: none|public|task|owner", "owner")
|
|
1187
|
+
.option("--no-wait", "Submit and return after the local task is accepted")
|
|
1188
|
+
.option("--json", "Print raw JSON")
|
|
1189
|
+
.addOption(new Option("--message-id <id>", "Internal: fixed message id for detached submission").hideHelp())
|
|
1190
|
+
.addOption(new Option("--created-at <iso>", "Internal: fixed createdAt for detached submission").hideHelp())
|
|
1191
|
+
.action(async (textParts, opts) => {
|
|
1192
|
+
await runChatSendCli(textParts || [], opts);
|
|
1193
|
+
});
|
|
1194
|
+
addLocalAkemonSelectionOptions(chatCommand
|
|
1195
|
+
.command("follow")
|
|
1196
|
+
.description("Queue a follow-up for a running Akemon task")
|
|
1197
|
+
.argument("<taskId>", "Task id")
|
|
1198
|
+
.argument("<text...>", "Follow-up text"))
|
|
1199
|
+
.option("--conversation <id>", "Conversation id for this follow-up; defaults to the task conversation")
|
|
1200
|
+
.option("--memory-scope <scope>", "Memory scope: none|public|task|owner", "owner")
|
|
1201
|
+
.option("--json", "Print raw JSON")
|
|
1202
|
+
.action(async (taskId, textParts, opts) => {
|
|
1203
|
+
await runChatFollowCli(taskId, textParts || [], opts);
|
|
1204
|
+
});
|
|
1205
|
+
const taskCommand = addLocalAkemonSelectionOptions(program
|
|
1206
|
+
.command("task")
|
|
1207
|
+
.description("Inspect Secretariat task lifecycle records"));
|
|
1208
|
+
addLocalAkemonSelectionOptions(taskCommand
|
|
1209
|
+
.command("status")
|
|
1210
|
+
.description("Show a Secretariat task status")
|
|
1211
|
+
.argument("<taskId>", "Task id"))
|
|
1212
|
+
.option("--json", "Print raw JSON")
|
|
1213
|
+
.action(async (taskId, opts) => {
|
|
1214
|
+
await runTaskStatusCli(taskId, opts);
|
|
1215
|
+
});
|
|
1216
|
+
addLocalAkemonSelectionOptions(taskCommand
|
|
1217
|
+
.command("report")
|
|
1218
|
+
.description("Show a Secretariat task report")
|
|
1219
|
+
.argument("<taskId>", "Task id"))
|
|
1220
|
+
.option("--json", "Print raw JSON")
|
|
1221
|
+
.action(async (taskId, opts) => {
|
|
1222
|
+
await runTaskReportCli(taskId, opts);
|
|
1223
|
+
});
|
|
1224
|
+
addLocalAkemonSelectionOptions(taskCommand
|
|
1225
|
+
.command("watch")
|
|
1226
|
+
.description("Poll a Secretariat task until it completes")
|
|
1227
|
+
.argument("<taskId>", "Task id"))
|
|
1228
|
+
.option("--interval-ms <ms>", "Polling interval in milliseconds", "1000")
|
|
1229
|
+
.option("--jsonl", "Print status updates as JSON Lines")
|
|
1230
|
+
.option("--once", "Print one status update and exit")
|
|
1231
|
+
.action(async (taskId, opts) => {
|
|
1232
|
+
await runTaskWatchCli(taskId, opts);
|
|
1233
|
+
});
|
|
301
1234
|
program
|
|
302
1235
|
.command("software-agent")
|
|
303
|
-
.description("Run an owner-only
|
|
1236
|
+
.description("Run an owner-only software-agent task via a running local Akemon")
|
|
304
1237
|
.argument("<goal...>", "Task goal to send to the software agent")
|
|
305
|
-
.option("-
|
|
1238
|
+
.option("-n, --name <name>", "Running local Akemon name")
|
|
1239
|
+
.option("-p, --port <port>", "Local Akemon port (overrides --name)")
|
|
306
1240
|
.option("-w, --workdir <path>", "Workdir for the software agent (default: serve workdir)")
|
|
307
1241
|
.option("--allow-outside-workdir", "Allow the software agent workdir to be outside the serve workdir")
|
|
308
1242
|
.option("--role-scope <scope>", "Role scope: owner|public|order|agent|system", "owner")
|
|
309
1243
|
.option("--memory-scope <scope>", "Memory scope: none|public|task|owner", "owner")
|
|
310
1244
|
.option("--risk <level>", "Risk level: low|medium|high", "medium")
|
|
311
1245
|
.option("--memory-summary <text>", "Pre-filtered memory/context text to include")
|
|
1246
|
+
.option("--work-context", "Embed a bounded work-memory context packet in the task envelope")
|
|
1247
|
+
.option("--work-context-budget <chars>", "Maximum embedded work-memory context size; also enables --work-context")
|
|
312
1248
|
.option("--session <id>", "Akemon-side context session id for explicit software-agent continuity")
|
|
313
1249
|
.option("--deliverable <text>", "Expected output shape")
|
|
314
1250
|
.option("--timeout-ms <ms>", "Task timeout in milliseconds")
|
|
315
1251
|
.option("--no-stream", "Disable local streaming and wait for the final response")
|
|
316
1252
|
.action(async (goalParts, opts) => {
|
|
317
|
-
|
|
318
|
-
goal: goalParts.join(" "),
|
|
319
|
-
roleScope: opts.roleScope,
|
|
320
|
-
memoryScope: opts.memoryScope,
|
|
321
|
-
riskLevel: opts.risk,
|
|
322
|
-
};
|
|
323
|
-
if (opts.workdir)
|
|
324
|
-
body.workdir = opts.workdir;
|
|
325
|
-
if (opts.allowOutsideWorkdir)
|
|
326
|
-
body.allowOutsideWorkdir = true;
|
|
327
|
-
if (opts.memorySummary)
|
|
328
|
-
body.memorySummary = opts.memorySummary;
|
|
329
|
-
if (opts.session)
|
|
330
|
-
body.contextSessionId = opts.session;
|
|
331
|
-
if (opts.deliverable)
|
|
332
|
-
body.deliverable = opts.deliverable;
|
|
333
|
-
if (opts.timeoutMs) {
|
|
334
|
-
const timeoutMs = Number(opts.timeoutMs);
|
|
335
|
-
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
|
|
336
|
-
console.error("--timeout-ms must be a positive integer");
|
|
337
|
-
process.exit(1);
|
|
338
|
-
}
|
|
339
|
-
body.timeoutMs = timeoutMs;
|
|
340
|
-
}
|
|
341
|
-
if (opts.stream !== false) {
|
|
342
|
-
await streamLocalOwnerEndpoint("/self/software-agent/run-stream", opts, body);
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
const data = await callLocalOwnerEndpoint("/self/software-agent/run", opts, {
|
|
346
|
-
method: "POST",
|
|
347
|
-
body: JSON.stringify(body),
|
|
348
|
-
});
|
|
349
|
-
if (data.output)
|
|
350
|
-
console.log(data.output);
|
|
351
|
-
else
|
|
352
|
-
console.log(JSON.stringify(data, null, 2));
|
|
1253
|
+
await runSoftwareAgentCli(goalParts, opts);
|
|
353
1254
|
});
|
|
354
1255
|
program
|
|
1256
|
+
.command("software-agent-continue")
|
|
1257
|
+
.description("Continue an Akemon-side software-agent context session via a running local Akemon")
|
|
1258
|
+
.argument("<sessionId>", "Akemon-side context session id to continue")
|
|
1259
|
+
.argument("<goal...>", "Task goal to send to the software agent")
|
|
1260
|
+
.option("-n, --name <name>", "Running local Akemon name")
|
|
1261
|
+
.option("-p, --port <port>", "Local Akemon port (overrides --name)")
|
|
1262
|
+
.option("-w, --workdir <path>", "Workdir for the software agent (default: serve workdir)")
|
|
1263
|
+
.option("--allow-outside-workdir", "Allow the software agent workdir to be outside the serve workdir")
|
|
1264
|
+
.option("--role-scope <scope>", "Role scope: owner|public|order|agent|system", "owner")
|
|
1265
|
+
.option("--memory-scope <scope>", "Memory scope: none|public|task|owner", "owner")
|
|
1266
|
+
.option("--risk <level>", "Risk level: low|medium|high", "medium")
|
|
1267
|
+
.option("--memory-summary <text>", "Pre-filtered memory/context text to include")
|
|
1268
|
+
.option("--work-context", "Embed a bounded work-memory context packet in the task envelope")
|
|
1269
|
+
.option("--work-context-budget <chars>", "Maximum embedded work-memory context size; also enables --work-context")
|
|
1270
|
+
.option("--deliverable <text>", "Expected output shape")
|
|
1271
|
+
.option("--timeout-ms <ms>", "Task timeout in milliseconds")
|
|
1272
|
+
.option("--no-stream", "Disable local streaming and wait for the final response")
|
|
1273
|
+
.action(async (sessionId, goalParts, opts) => {
|
|
1274
|
+
await runSoftwareAgentCli(goalParts, opts, sessionId);
|
|
1275
|
+
});
|
|
1276
|
+
addLocalAkemonSelectionOptions(program
|
|
355
1277
|
.command("software-agent-status")
|
|
356
|
-
.description("Show the owner-only local software-agent peripheral state")
|
|
357
|
-
.option("-p, --port <port>", "Local akemon serve port", "3000")
|
|
1278
|
+
.description("Show the owner-only local software-agent peripheral state"))
|
|
358
1279
|
.action(async (opts) => {
|
|
359
1280
|
const data = await callLocalOwnerEndpoint("/self/software-agent/status", opts, {
|
|
360
1281
|
method: "GET",
|
|
361
1282
|
});
|
|
362
1283
|
console.log(JSON.stringify(data, null, 2));
|
|
363
1284
|
});
|
|
364
|
-
program
|
|
1285
|
+
addLocalAkemonSelectionOptions(program
|
|
365
1286
|
.command("software-agent-tasks")
|
|
366
1287
|
.description("List recent owner-only software-agent task ledger records")
|
|
367
|
-
.argument("[taskId]", "Task id to inspect")
|
|
368
|
-
.option("-p, --port <port>", "Local akemon serve port", "3000")
|
|
1288
|
+
.argument("[taskId]", "Task id to inspect"))
|
|
369
1289
|
.option("-l, --limit <n>", "Maximum recent tasks to list", "20")
|
|
1290
|
+
.option("--session <id>", "Filter listed tasks by Akemon-side context session id")
|
|
1291
|
+
.option("--context", "Print the task's Akemon TASK_CONTEXT.md content when inspecting one task")
|
|
370
1292
|
.option("--json", "Print raw JSON")
|
|
371
1293
|
.action(async (taskId, opts) => {
|
|
1294
|
+
if (!taskId && opts.context) {
|
|
1295
|
+
console.error("--context requires a taskId");
|
|
1296
|
+
process.exit(1);
|
|
1297
|
+
}
|
|
1298
|
+
if (taskId && opts.session) {
|
|
1299
|
+
console.error("--session cannot be used when inspecting a single taskId");
|
|
1300
|
+
process.exit(1);
|
|
1301
|
+
}
|
|
372
1302
|
const path = taskId
|
|
373
|
-
? `/self/software-agent/tasks/${encodeURIComponent(taskId)}`
|
|
374
|
-
: `/self/software-agent/tasks?limit=${clampPositiveInt(opts.limit, 20, 100)}`;
|
|
1303
|
+
? `/self/software-agent/tasks/${encodeURIComponent(taskId)}${opts.context ? "?includeContext=1" : ""}`
|
|
1304
|
+
: `/self/software-agent/tasks?limit=${clampPositiveInt(opts.limit, 20, 100)}${opts.session ? `&session=${encodeURIComponent(opts.session)}` : ""}`;
|
|
375
1305
|
const data = await callLocalOwnerEndpoint(path, opts, {
|
|
376
1306
|
method: "GET",
|
|
377
1307
|
});
|
|
1308
|
+
if (taskId && opts.context) {
|
|
1309
|
+
const contextPacket = data.contextSession?.contextPacket;
|
|
1310
|
+
if (typeof contextPacket === "string" && contextPacket.length > 0) {
|
|
1311
|
+
process.stdout.write(contextPacket);
|
|
1312
|
+
if (!contextPacket.endsWith("\n"))
|
|
1313
|
+
process.stdout.write("\n");
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
console.error("No TASK_CONTEXT.md content found for this task.");
|
|
1317
|
+
process.exit(1);
|
|
1318
|
+
}
|
|
378
1319
|
if (opts.json || taskId) {
|
|
379
1320
|
console.log(JSON.stringify(taskId ? data.task : data, null, 2));
|
|
380
1321
|
return;
|
|
381
1322
|
}
|
|
382
1323
|
printSoftwareAgentTaskList(Array.isArray(data.tasks) ? data.tasks : []);
|
|
383
1324
|
});
|
|
384
|
-
program
|
|
1325
|
+
addLocalAkemonSelectionOptions(program
|
|
1326
|
+
.command("software-agent-sessions")
|
|
1327
|
+
.description("List or inspect owner-only Akemon-side software-agent context sessions")
|
|
1328
|
+
.argument("[sessionId]", "Context session id to inspect"))
|
|
1329
|
+
.option("-l, --limit <n>", "Maximum recent sessions to list", "20")
|
|
1330
|
+
.option("--context", "Print the session TASK_CONTEXT.md content")
|
|
1331
|
+
.option("--json", "Print raw JSON")
|
|
1332
|
+
.action(async (sessionId, opts) => {
|
|
1333
|
+
const query = sessionId && opts.context ? "?includeContext=1" : "";
|
|
1334
|
+
const path = sessionId
|
|
1335
|
+
? `/self/software-agent/sessions/${encodeURIComponent(sessionId)}${query}`
|
|
1336
|
+
: `/self/software-agent/sessions?limit=${clampPositiveInt(opts.limit, 20, 100)}`;
|
|
1337
|
+
const data = await callLocalOwnerEndpoint(path, opts, {
|
|
1338
|
+
method: "GET",
|
|
1339
|
+
});
|
|
1340
|
+
if (sessionId) {
|
|
1341
|
+
if (opts.context) {
|
|
1342
|
+
const contextPacket = data.session?.contextPacket;
|
|
1343
|
+
if (typeof contextPacket === "string" && contextPacket.length > 0) {
|
|
1344
|
+
process.stdout.write(contextPacket);
|
|
1345
|
+
if (!contextPacket.endsWith("\n"))
|
|
1346
|
+
process.stdout.write("\n");
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
console.error("No TASK_CONTEXT.md content found for this session.");
|
|
1350
|
+
process.exit(1);
|
|
1351
|
+
}
|
|
1352
|
+
console.log(JSON.stringify(data.session, null, 2));
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
if (opts.json) {
|
|
1356
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
printSoftwareAgentSessionList(Array.isArray(data.sessions) ? data.sessions : []);
|
|
1360
|
+
});
|
|
1361
|
+
addLocalAkemonSelectionOptions(program
|
|
385
1362
|
.command("software-agent-reset")
|
|
386
|
-
.description("Reset the owner-only local software-agent peripheral session")
|
|
387
|
-
.option("-p, --port <port>", "Local akemon serve port", "3000")
|
|
1363
|
+
.description("Reset the owner-only local software-agent peripheral session"))
|
|
388
1364
|
.action(async (opts) => {
|
|
389
1365
|
const data = await callLocalOwnerEndpoint("/self/software-agent/reset", opts, {
|
|
390
1366
|
method: "POST",
|
|
391
1367
|
});
|
|
392
1368
|
console.log(JSON.stringify(data, null, 2));
|
|
393
1369
|
});
|
|
1370
|
+
addLocalAkemonSelectionOptions(program
|
|
1371
|
+
.command("workbench")
|
|
1372
|
+
.description("Open the local owner-only Workbench UI"))
|
|
1373
|
+
.option("--no-open", "Print the Workbench URL without opening a browser")
|
|
1374
|
+
.option("--print-url", "Print the Workbench URL without opening a browser")
|
|
1375
|
+
.action(async (opts) => {
|
|
1376
|
+
await openWorkbenchUiCli(opts);
|
|
1377
|
+
});
|
|
1378
|
+
addLocalAkemonSelectionOptions(program
|
|
1379
|
+
.command("workbench-start")
|
|
1380
|
+
.description("Start an owner-only interactive Workbench PTY session")
|
|
1381
|
+
.argument("<tool>", "codex|claude|cursor|shell|custom")
|
|
1382
|
+
.argument("[args...]", "Arguments passed to the Workbench command")
|
|
1383
|
+
.allowUnknownOption(true))
|
|
1384
|
+
.option("--command <cmd>", "Override the command to spawn")
|
|
1385
|
+
.option("-w, --workdir <path>", "Workbench session workdir (default: serve workdir)")
|
|
1386
|
+
.option("--allow-outside-workdir", "Allow the Workbench workdir to be outside the serve workdir")
|
|
1387
|
+
.option("--cols <n>", "Terminal columns")
|
|
1388
|
+
.option("--rows <n>", "Terminal rows")
|
|
1389
|
+
.option("--input <text>", "Initial owner-direct input to write after startup")
|
|
1390
|
+
.option("--label <text>", "Human-readable session label")
|
|
1391
|
+
.option("--json", "Print raw JSON")
|
|
1392
|
+
.action(async (tool, args, opts) => {
|
|
1393
|
+
await runWorkbenchStartCli(tool, args || [], opts);
|
|
1394
|
+
});
|
|
1395
|
+
addLocalAkemonSelectionOptions(program
|
|
1396
|
+
.command("workbench-sessions")
|
|
1397
|
+
.description("List owner-only Workbench PTY sessions"))
|
|
1398
|
+
.option("--json", "Print raw JSON")
|
|
1399
|
+
.action(async (opts) => {
|
|
1400
|
+
await listWorkbenchSessionsCli(opts);
|
|
1401
|
+
});
|
|
1402
|
+
addLocalAkemonSelectionOptions(program
|
|
1403
|
+
.command("workbench-tail")
|
|
1404
|
+
.description("Print the rolling output buffer for a Workbench session")
|
|
1405
|
+
.argument("<sessionId>", "Workbench session id"))
|
|
1406
|
+
.option("--json", "Print raw JSON")
|
|
1407
|
+
.action(async (sessionId, opts) => {
|
|
1408
|
+
await showWorkbenchTailCli(sessionId, opts);
|
|
1409
|
+
});
|
|
1410
|
+
addLocalAkemonSelectionOptions(program
|
|
1411
|
+
.command("workbench-input")
|
|
1412
|
+
.description("Send owner-direct input to a running Workbench session")
|
|
1413
|
+
.argument("<sessionId>", "Workbench session id")
|
|
1414
|
+
.argument("<input...>", "Text to send"))
|
|
1415
|
+
.option("--no-enter", "Do not append a newline to the input")
|
|
1416
|
+
.option("--json", "Print raw JSON")
|
|
1417
|
+
.action(async (sessionId, inputParts, opts) => {
|
|
1418
|
+
await sendWorkbenchInputCli(sessionId, inputParts || [], opts);
|
|
1419
|
+
});
|
|
1420
|
+
addLocalAkemonSelectionOptions(program
|
|
1421
|
+
.command("workbench-stop")
|
|
1422
|
+
.description("Stop a Workbench PTY session")
|
|
1423
|
+
.argument("<sessionId>", "Workbench session id"))
|
|
1424
|
+
.option("--json", "Print raw JSON")
|
|
1425
|
+
.action(async (sessionId, opts) => {
|
|
1426
|
+
await stopWorkbenchSessionCli(sessionId, opts);
|
|
1427
|
+
});
|
|
1428
|
+
addLocalAkemonSelectionOptions(program
|
|
1429
|
+
.command("workbench-resize")
|
|
1430
|
+
.description("Resize a running Workbench PTY session")
|
|
1431
|
+
.argument("<sessionId>", "Workbench session id"))
|
|
1432
|
+
.option("--cols <n>", "Terminal columns")
|
|
1433
|
+
.option("--rows <n>", "Terminal rows")
|
|
1434
|
+
.option("--json", "Print raw JSON")
|
|
1435
|
+
.action(async (sessionId, opts) => {
|
|
1436
|
+
await resizeWorkbenchSessionCli(sessionId, opts);
|
|
1437
|
+
});
|
|
1438
|
+
const memoryRecordersCommand = program
|
|
1439
|
+
.command("memory-recorders")
|
|
1440
|
+
.description("List and inspect Akemon memory-writing situations")
|
|
1441
|
+
.action(() => {
|
|
1442
|
+
printMemoryRecorderList(listMemoryRecorders({ enabled: true }));
|
|
1443
|
+
});
|
|
1444
|
+
memoryRecordersCommand
|
|
1445
|
+
.command("list")
|
|
1446
|
+
.description("List registered built-in memory recorders")
|
|
1447
|
+
.option("--scope <scope>", "Filter by destination scope")
|
|
1448
|
+
.option("--source <source>", "Filter by source: builtin, module, or skill")
|
|
1449
|
+
.option("--json", "Print raw JSON")
|
|
1450
|
+
.action((opts) => {
|
|
1451
|
+
const recorders = listMemoryRecorders({
|
|
1452
|
+
scope: parseMemoryRecorderScope(opts.scope),
|
|
1453
|
+
source: parseMemoryRecorderSource(opts.source),
|
|
1454
|
+
enabled: true,
|
|
1455
|
+
});
|
|
1456
|
+
if (opts.json) {
|
|
1457
|
+
console.log(JSON.stringify(recorders, null, 2));
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
1460
|
+
printMemoryRecorderList(recorders);
|
|
1461
|
+
});
|
|
1462
|
+
memoryRecordersCommand
|
|
1463
|
+
.command("show")
|
|
1464
|
+
.description("Show one memory recorder's trigger, destinations, and privacy boundary")
|
|
1465
|
+
.argument("<id>", "Memory recorder id")
|
|
1466
|
+
.option("--json", "Print raw JSON")
|
|
1467
|
+
.action((id, opts) => {
|
|
1468
|
+
const recorder = getMemoryRecorder(id);
|
|
1469
|
+
if (!recorder) {
|
|
1470
|
+
console.error(`Unknown memory recorder: ${id}`);
|
|
1471
|
+
process.exit(1);
|
|
1472
|
+
}
|
|
1473
|
+
if (opts.json) {
|
|
1474
|
+
console.log(JSON.stringify(recorder, null, 2));
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
printMemoryRecorderDetail(recorder);
|
|
1478
|
+
});
|
|
1479
|
+
const peripheralsCommand = program
|
|
1480
|
+
.command("peripherals")
|
|
1481
|
+
.description("List, register, and explore Akemon peripheral records")
|
|
1482
|
+
.action(() => {
|
|
1483
|
+
peripheralsCommand.help();
|
|
1484
|
+
});
|
|
1485
|
+
peripheralsCommand
|
|
1486
|
+
.command("list")
|
|
1487
|
+
.description("List configured peripheral records")
|
|
1488
|
+
.option("-n, --name <name>", "Akemon name (defaults to the configured local manager)")
|
|
1489
|
+
.option("--json", "Print raw JSON")
|
|
1490
|
+
.action(async (opts) => {
|
|
1491
|
+
try {
|
|
1492
|
+
const agentName = await resolvePeripheralRegistryAgentName(opts.name);
|
|
1493
|
+
const records = await loadPeripheralRecords(agentName);
|
|
1494
|
+
if (opts.json) {
|
|
1495
|
+
console.log(JSON.stringify(records, null, 2));
|
|
1496
|
+
return;
|
|
1497
|
+
}
|
|
1498
|
+
printPeripheralRecords(records);
|
|
1499
|
+
}
|
|
1500
|
+
catch (error) {
|
|
1501
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1502
|
+
process.exit(1);
|
|
1503
|
+
}
|
|
1504
|
+
});
|
|
1505
|
+
peripheralsCommand
|
|
1506
|
+
.command("register")
|
|
1507
|
+
.description("Register or update a configured peripheral record")
|
|
1508
|
+
.requiredOption("--id <id>", "Stable peripheral id")
|
|
1509
|
+
.requiredOption("--label <name>", "Human-readable peripheral name")
|
|
1510
|
+
.option("-n, --name <name>", "Akemon name (defaults to the configured local manager)")
|
|
1511
|
+
.option("--type <type>", "Peripheral type: engine|relay|software-agent|mcp|service|hardware|custom", "custom")
|
|
1512
|
+
.option("--capabilities <items>", "Comma-separated capabilities")
|
|
1513
|
+
.option("--tags <items>", "Comma-separated tags")
|
|
1514
|
+
.option("--risk <level>", "Risk level: low|medium|high", "medium")
|
|
1515
|
+
.option("--allowed-actions <items>", "Comma-separated allowed action labels")
|
|
1516
|
+
.option("--command <command>", "Optional start command, recorded but not executed")
|
|
1517
|
+
.option("--url <url>", "Optional URL for this peripheral")
|
|
1518
|
+
.option("--explore <mode>", "Explore mode: none|plain-text", "none")
|
|
1519
|
+
.option("--explore-description <text>", "Plain-text explore behavior description")
|
|
1520
|
+
.option("--json", "Print raw JSON")
|
|
1521
|
+
.action(async (opts) => {
|
|
1522
|
+
try {
|
|
1523
|
+
const agentName = await resolvePeripheralRegistryAgentName(opts.name);
|
|
1524
|
+
const record = await upsertPeripheralRecord(agentName, {
|
|
1525
|
+
id: opts.id,
|
|
1526
|
+
name: opts.label,
|
|
1527
|
+
type: parsePeripheralRecordType(opts.type),
|
|
1528
|
+
capabilities: parseCommaSeparatedCliOption(opts.capabilities) || [],
|
|
1529
|
+
tags: parseCommaSeparatedCliOption(opts.tags) || [],
|
|
1530
|
+
riskLevel: parsePeripheralRiskLevel(opts.risk),
|
|
1531
|
+
allowedActions: parseCommaSeparatedCliOption(opts.allowedActions) || [],
|
|
1532
|
+
startCommand: opts.command,
|
|
1533
|
+
url: opts.url,
|
|
1534
|
+
explore: {
|
|
1535
|
+
mode: parsePeripheralExploreMode(opts.explore),
|
|
1536
|
+
description: opts.exploreDescription,
|
|
1537
|
+
},
|
|
1538
|
+
source: "owner",
|
|
1539
|
+
status: "configured",
|
|
1540
|
+
updatedAt: new Date().toISOString(),
|
|
1541
|
+
});
|
|
1542
|
+
if (opts.json) {
|
|
1543
|
+
console.log(JSON.stringify(record, null, 2));
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1546
|
+
console.log(`Registered peripheral ${record.id} for ${agentName}.`);
|
|
1547
|
+
}
|
|
1548
|
+
catch (error) {
|
|
1549
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1550
|
+
process.exit(1);
|
|
1551
|
+
}
|
|
1552
|
+
});
|
|
1553
|
+
peripheralsCommand
|
|
1554
|
+
.command("explore")
|
|
1555
|
+
.description("Print a plain-text peripheral environment briefing")
|
|
1556
|
+
.argument("[id]", "Optional peripheral id")
|
|
1557
|
+
.option("-n, --name <name>", "Akemon name (defaults to the configured local manager)")
|
|
1558
|
+
.option("-p, --port <port>", "Running local Akemon port for --live")
|
|
1559
|
+
.option("--live", "Ask the running local Akemon for live peripheral exploration")
|
|
1560
|
+
.option("--json", "Print raw JSON")
|
|
1561
|
+
.action(async (id, opts) => {
|
|
1562
|
+
try {
|
|
1563
|
+
let briefing;
|
|
1564
|
+
if (opts.live) {
|
|
1565
|
+
const query = id ? `?id=${encodeURIComponent(id)}` : "";
|
|
1566
|
+
briefing = await callLocalOwnerEndpoint(`/self/peripherals/explore${query}`, opts, { method: "GET" });
|
|
1567
|
+
}
|
|
1568
|
+
else {
|
|
1569
|
+
const agentName = await resolvePeripheralRegistryAgentName(opts.name);
|
|
1570
|
+
const records = await loadPeripheralRecords(agentName);
|
|
1571
|
+
briefing = await buildPeripheralExploreBriefing({ records, id });
|
|
1572
|
+
}
|
|
1573
|
+
if (opts.json) {
|
|
1574
|
+
console.log(JSON.stringify(briefing, null, 2));
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
process.stdout.write(briefing.text);
|
|
1578
|
+
if (!briefing.text.endsWith("\n"))
|
|
1579
|
+
process.stdout.write("\n");
|
|
1580
|
+
}
|
|
1581
|
+
catch (error) {
|
|
1582
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1583
|
+
process.exit(1);
|
|
1584
|
+
}
|
|
1585
|
+
});
|
|
1586
|
+
program
|
|
1587
|
+
.command("audit")
|
|
1588
|
+
.argument("[command]", "Optional subcommand: list")
|
|
1589
|
+
.description("List Akemon permission and action audit records")
|
|
1590
|
+
.option("-n, --name <name>", "Agent name (defaults to the configured local manager)")
|
|
1591
|
+
.option("-l, --limit <n>", "Maximum records to list", "20")
|
|
1592
|
+
.option("--kind <kind>", "Filter by kind: software-agent-task|akemon-message|relay-publication|memory-write")
|
|
1593
|
+
.option("--decision <decision>", "Filter by decision: allowed|denied")
|
|
1594
|
+
.option("--json", "Print raw JSON")
|
|
1595
|
+
.action(async (command, opts) => {
|
|
1596
|
+
if (command !== undefined && command !== "list") {
|
|
1597
|
+
console.error("Unknown audit command. Use: akemon audit [list]");
|
|
1598
|
+
process.exit(1);
|
|
1599
|
+
}
|
|
1600
|
+
await runPermissionAuditListCli(opts);
|
|
1601
|
+
});
|
|
394
1602
|
program
|
|
395
1603
|
.command("privacy-filter")
|
|
396
1604
|
.description("Sanitize text with built-in redaction and optional OpenAI Privacy Filter")
|
|
@@ -431,15 +1639,86 @@ program
|
|
|
431
1639
|
throw error;
|
|
432
1640
|
}
|
|
433
1641
|
});
|
|
1642
|
+
program
|
|
1643
|
+
.command("work-context")
|
|
1644
|
+
.description("Print a work-memory context packet for external software agents")
|
|
1645
|
+
.option("-w, --workdir <path>", "Akemon workdir (default: cwd)")
|
|
1646
|
+
.option("-n, --name <name>", "Agent name", "my-agent")
|
|
1647
|
+
.option("--global", "Use global Akemon work memory under the agent home")
|
|
1648
|
+
.option("--purpose <text>", "Purpose of this context packet", "external software-agent work context")
|
|
1649
|
+
.option("--budget <chars>", "Maximum packet size in characters", "12000")
|
|
1650
|
+
.option("--json", "Print raw JSON")
|
|
1651
|
+
.action(async (opts) => {
|
|
1652
|
+
try {
|
|
1653
|
+
const packet = await buildWorkMemoryContext({
|
|
1654
|
+
workdir: opts.workdir || process.cwd(),
|
|
1655
|
+
agentName: opts.name,
|
|
1656
|
+
purpose: opts.purpose,
|
|
1657
|
+
budget: parsePositiveIntCliOption(opts.budget, "--budget"),
|
|
1658
|
+
global: opts.global === true,
|
|
1659
|
+
});
|
|
1660
|
+
if (opts.json) {
|
|
1661
|
+
console.log(JSON.stringify(packet, null, 2));
|
|
1662
|
+
return;
|
|
1663
|
+
}
|
|
1664
|
+
process.stdout.write(packet.text);
|
|
1665
|
+
if (!packet.text.endsWith("\n"))
|
|
1666
|
+
process.stdout.write("\n");
|
|
1667
|
+
}
|
|
1668
|
+
catch (error) {
|
|
1669
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1670
|
+
process.exit(1);
|
|
1671
|
+
}
|
|
1672
|
+
});
|
|
1673
|
+
program
|
|
1674
|
+
.command("work-note")
|
|
1675
|
+
.description("Append a note to Akemon work memory")
|
|
1676
|
+
.argument("<text...>", "Durable work-memory note")
|
|
1677
|
+
.option("-w, --workdir <path>", "Akemon workdir (default: cwd)")
|
|
1678
|
+
.option("-n, --name <name>", "Agent name", "my-agent")
|
|
1679
|
+
.option("--global", "Use global Akemon work memory under the agent home")
|
|
1680
|
+
.option("--source <source>", "Note source, e.g. user, codex, or claude-code", "user")
|
|
1681
|
+
.option("--session <id>", "External or Akemon-side session id")
|
|
1682
|
+
.option("--kind <kind>", "Work-memory kind, e.g. note, decision, command, project", "note")
|
|
1683
|
+
.option("--target <path>", "Optional target file under the work memory directory")
|
|
1684
|
+
.option("--json", "Print raw JSON")
|
|
1685
|
+
.action(async (textParts, opts) => {
|
|
1686
|
+
try {
|
|
1687
|
+
const result = await appendWorkMemoryNote({
|
|
1688
|
+
workdir: opts.workdir || process.cwd(),
|
|
1689
|
+
agentName: opts.name,
|
|
1690
|
+
text: textParts.join(" "),
|
|
1691
|
+
source: opts.source,
|
|
1692
|
+
sessionId: opts.session,
|
|
1693
|
+
kind: opts.kind,
|
|
1694
|
+
target: opts.target,
|
|
1695
|
+
global: opts.global === true,
|
|
1696
|
+
});
|
|
1697
|
+
if (opts.json) {
|
|
1698
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1699
|
+
return;
|
|
1700
|
+
}
|
|
1701
|
+
console.log(`Work memory note appended: ${result.note.id}`);
|
|
1702
|
+
console.log(`Path: ${result.path}`);
|
|
1703
|
+
}
|
|
1704
|
+
catch (error) {
|
|
1705
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1706
|
+
process.exit(1);
|
|
1707
|
+
}
|
|
1708
|
+
});
|
|
434
1709
|
program
|
|
435
1710
|
.command("dashboard")
|
|
436
1711
|
.description("Open your agent dashboard in the browser")
|
|
437
1712
|
.action(async () => {
|
|
438
1713
|
const credentials = await getOrCreateRelayCredentials();
|
|
439
|
-
const url = `${
|
|
1714
|
+
const url = `${DEFAULT_RELAY_HTTP}/owner?account=${credentials.accountId}`;
|
|
440
1715
|
console.log(`Opening dashboard: ${url}`);
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
1716
|
+
try {
|
|
1717
|
+
await openUrl(url);
|
|
1718
|
+
}
|
|
1719
|
+
catch (error) {
|
|
1720
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
1721
|
+
process.exit(1);
|
|
1722
|
+
}
|
|
444
1723
|
});
|
|
445
1724
|
program.parse();
|