akemon 0.3.2 → 0.3.3

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
@@ -14,6 +14,39 @@ const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-
14
14
  const RELAY_WS = "wss://relay.akemon.dev";
15
15
  const RELAY_HTTP = "https://relay.akemon.dev";
16
16
  const program = new Command();
17
+ function parsePortOption(port) {
18
+ const value = typeof port === "number" ? port : parseInt(String(port || "3000"));
19
+ return Number.isInteger(value) && value > 0 ? value : 3000;
20
+ }
21
+ async function callLocalOwnerEndpoint(path, opts, init = {}) {
22
+ const credentials = await getOrCreateRelayCredentials();
23
+ const port = parsePortOption(opts.port);
24
+ const headers = {
25
+ Authorization: `Bearer ${credentials.secretKey}`,
26
+ };
27
+ if (init.body !== undefined)
28
+ headers["Content-Type"] = "application/json";
29
+ const res = await fetch(`http://127.0.0.1:${port}${path}`, {
30
+ ...init,
31
+ headers: {
32
+ ...headers,
33
+ ...init.headers,
34
+ },
35
+ });
36
+ const text = await res.text();
37
+ let data;
38
+ try {
39
+ data = text ? JSON.parse(text) : {};
40
+ }
41
+ catch {
42
+ data = { output: text };
43
+ }
44
+ if (!res.ok || data.success === false) {
45
+ console.error(data.error || text || `Request failed with HTTP ${res.status}`);
46
+ process.exit(1);
47
+ }
48
+ return data;
49
+ }
17
50
  program
18
51
  .name("akemon")
19
52
  .description("Agent work marketplace — train your agent, let it work for others")
@@ -145,8 +178,6 @@ program
145
178
  .option("--deliverable <text>", "Expected output shape")
146
179
  .option("--timeout-ms <ms>", "Task timeout in milliseconds")
147
180
  .action(async (goalParts, opts) => {
148
- const credentials = await getOrCreateRelayCredentials();
149
- const port = parseInt(opts.port) || 3000;
150
181
  const body = {
151
182
  goal: goalParts.join(" "),
152
183
  roleScope: opts.roleScope,
@@ -167,31 +198,35 @@ program
167
198
  }
168
199
  body.timeoutMs = timeoutMs;
169
200
  }
170
- const res = await fetch(`http://127.0.0.1:${port}/self/software-agent/run`, {
201
+ const data = await callLocalOwnerEndpoint("/self/software-agent/run", opts, {
171
202
  method: "POST",
172
- headers: {
173
- Authorization: `Bearer ${credentials.secretKey}`,
174
- "Content-Type": "application/json",
175
- },
176
203
  body: JSON.stringify(body),
177
204
  });
178
- const text = await res.text();
179
- let data;
180
- try {
181
- data = text ? JSON.parse(text) : {};
182
- }
183
- catch {
184
- data = { output: text };
185
- }
186
- if (!res.ok || data.success === false) {
187
- console.error(data.error || text || `Request failed with HTTP ${res.status}`);
188
- process.exit(1);
189
- }
190
205
  if (data.output)
191
206
  console.log(data.output);
192
207
  else
193
208
  console.log(JSON.stringify(data, null, 2));
194
209
  });
210
+ program
211
+ .command("software-agent-status")
212
+ .description("Show the owner-only local software-agent peripheral state")
213
+ .option("-p, --port <port>", "Local akemon serve port", "3000")
214
+ .action(async (opts) => {
215
+ const data = await callLocalOwnerEndpoint("/self/software-agent/status", opts, {
216
+ method: "GET",
217
+ });
218
+ console.log(JSON.stringify(data, null, 2));
219
+ });
220
+ program
221
+ .command("software-agent-reset")
222
+ .description("Reset the owner-only local software-agent peripheral session")
223
+ .option("-p, --port <port>", "Local akemon serve port", "3000")
224
+ .action(async (opts) => {
225
+ const data = await callLocalOwnerEndpoint("/self/software-agent/reset", opts, {
226
+ method: "POST",
227
+ });
228
+ console.log(JSON.stringify(data, null, 2));
229
+ });
195
230
  program
196
231
  .command("dashboard")
197
232
  .description("Open your agent dashboard in the browser")
package/dist/server.js CHANGED
@@ -158,6 +158,41 @@ export async function handleSoftwareAgentRunHttp(req, res, deps) {
158
158
  .end(JSON.stringify({ error: err.message || String(err) }));
159
159
  }
160
160
  }
161
+ export async function handleSoftwareAgentStatusHttp(req, res, deps) {
162
+ if (!isOwnerRequest(req, deps.options)) {
163
+ res.writeHead(401, { "Content-Type": "application/json" })
164
+ .end(JSON.stringify({ error: "Owner token required" }));
165
+ return;
166
+ }
167
+ if (!deps.softwareAgent) {
168
+ res.writeHead(503, { "Content-Type": "application/json" })
169
+ .end(JSON.stringify({ error: "Software agent peripheral not ready" }));
170
+ return;
171
+ }
172
+ res.writeHead(200, { "Content-Type": "application/json" })
173
+ .end(JSON.stringify(deps.softwareAgent.getState(), null, 2));
174
+ }
175
+ export async function handleSoftwareAgentResetHttp(req, res, deps) {
176
+ if (!isOwnerRequest(req, deps.options)) {
177
+ res.writeHead(401, { "Content-Type": "application/json" })
178
+ .end(JSON.stringify({ error: "Owner token required" }));
179
+ return;
180
+ }
181
+ if (!deps.softwareAgent) {
182
+ res.writeHead(503, { "Content-Type": "application/json" })
183
+ .end(JSON.stringify({ error: "Software agent peripheral not ready" }));
184
+ return;
185
+ }
186
+ try {
187
+ await deps.softwareAgent.resetSession();
188
+ res.writeHead(200, { "Content-Type": "application/json" })
189
+ .end(JSON.stringify({ ok: true, state: deps.softwareAgent.getState() }, null, 2));
190
+ }
191
+ catch (err) {
192
+ res.writeHead(500, { "Content-Type": "application/json" })
193
+ .end(JSON.stringify({ error: err.message || String(err) }));
194
+ }
195
+ }
161
196
  import { RelayPeripheral } from "./relay-peripheral.js";
162
197
  import { EnginePeripheral, LLM_ENGINES as LLM_ENGINES_SET } from "./engine-peripheral.js";
163
198
  import { EngineQueue } from "./engine-queue.js";
@@ -261,6 +296,20 @@ export async function serve(options) {
261
296
  });
262
297
  return;
263
298
  }
299
+ if (req.url === "/self/software-agent/status" && req.method === "GET") {
300
+ await handleSoftwareAgentStatusHttp(req, res, {
301
+ options,
302
+ softwareAgent: codexSoftwareAgent,
303
+ });
304
+ return;
305
+ }
306
+ if (req.url === "/self/software-agent/reset" && req.method === "POST") {
307
+ await handleSoftwareAgentResetHttp(req, res, {
308
+ options,
309
+ softwareAgent: codexSoftwareAgent,
310
+ });
311
+ return;
312
+ }
264
313
  // Auth check
265
314
  if (options.key) {
266
315
  const token = bearerToken(req);
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "akemon",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Agent work marketplace — train your agent, let it work for others",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/lhead/akemon"
9
+ "url": "git+https://github.com/lhead/akemon.git"
10
10
  },
11
11
  "keywords": [
12
12
  "ai",
@@ -18,7 +18,7 @@
18
18
  "gemini"
19
19
  ],
20
20
  "bin": {
21
- "akemon": "./dist/cli.js"
21
+ "akemon": "dist/cli.js"
22
22
  },
23
23
  "files": [
24
24
  "dist",
@@ -26,7 +26,7 @@
26
26
  "README.md"
27
27
  ],
28
28
  "scripts": {
29
- "build": "tsc && cp src/live.html dist/live.html",
29
+ "build": "rm -rf dist && tsc && cp src/live.html dist/live.html",
30
30
  "dev": "tsc --watch",
31
31
  "start": "node dist/cli.js",
32
32
  "postinstall": "node scripts/fix-node-pty.cjs",
@@ -1,90 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { describe, it, before, after } from "node:test";
3
- import { mkdtemp, rm } from "node:fs/promises";
4
- import { tmpdir } from "node:os";
5
- import { join } from "node:path";
6
- // We import the functions we need to test. appendMessage and loadConversation
7
- // need a workdir on disk; parseConversation is internal — tested via loadConversation.
8
- import { appendMessage, loadConversation } from "./context.js";
9
- const agentName = "test-agent";
10
- // ---------------------------------------------------------------------------
11
- // appendMessage + loadConversation round-trip
12
- // ---------------------------------------------------------------------------
13
- describe("appendMessage / loadConversation", () => {
14
- let workdir = "";
15
- before(async () => {
16
- workdir = await mkdtemp(join(tmpdir(), "ctx-test-"));
17
- });
18
- after(async () => {
19
- await rm(workdir, { recursive: true, force: true });
20
- });
21
- it("writes a chat message and reads it back with kind='chat'", async () => {
22
- const convId = "test-chat";
23
- await appendMessage(workdir, agentName, convId, "User", "hello", "chat");
24
- const conv = await loadConversation(workdir, agentName, convId);
25
- assert.equal(conv.rounds.length, 1);
26
- assert.equal(conv.rounds[0].role, "user");
27
- assert.equal(conv.rounds[0].content, "hello");
28
- assert.equal(conv.rounds[0].kind, "chat");
29
- });
30
- it("writes an order message with [order] tag and reads kind='order'", async () => {
31
- const convId = "test-order";
32
- await appendMessage(workdir, agentName, convId, "User", "order request", "order");
33
- await appendMessage(workdir, agentName, convId, "Agent", "order delivered", "order");
34
- const conv = await loadConversation(workdir, agentName, convId);
35
- assert.equal(conv.rounds.length, 2);
36
- assert.equal(conv.rounds[0].kind, "order");
37
- assert.equal(conv.rounds[0].role, "user");
38
- assert.equal(conv.rounds[1].kind, "order");
39
- assert.equal(conv.rounds[1].role, "agent");
40
- });
41
- it("defaults to kind='chat' when kind arg is omitted", async () => {
42
- const convId = "test-default-kind";
43
- await appendMessage(workdir, agentName, convId, "Agent", "hi there");
44
- const conv = await loadConversation(workdir, agentName, convId);
45
- assert.equal(conv.rounds[0].kind, "chat");
46
- });
47
- it("parses mixed chat + order rounds correctly", async () => {
48
- const convId = "test-mixed";
49
- await appendMessage(workdir, agentName, convId, "User", "chat message", "chat");
50
- await appendMessage(workdir, agentName, convId, "User", "order task", "order");
51
- await appendMessage(workdir, agentName, convId, "Agent", "chat reply", "chat");
52
- await appendMessage(workdir, agentName, convId, "Agent", "order result", "order");
53
- const conv = await loadConversation(workdir, agentName, convId);
54
- assert.equal(conv.rounds.length, 4);
55
- assert.equal(conv.rounds[0].kind, "chat");
56
- assert.equal(conv.rounds[1].kind, "order");
57
- assert.equal(conv.rounds[2].kind, "chat");
58
- assert.equal(conv.rounds[3].kind, "order");
59
- });
60
- it("parses legacy file (no [kind] tag) as kind='chat' for backward-compat", async () => {
61
- const convId = "test-legacy";
62
- // Write a legacy-style file manually (no [order] tag)
63
- const { mkdir, writeFile } = await import("node:fs/promises");
64
- const dir = join(workdir, ".akemon", "agents", agentName, "conversations");
65
- await mkdir(dir, { recursive: true });
66
- const legacyContent = "## Summary\n\n\n## Recent\n[2026-04-23 10:00] User: old message\n[2026-04-23 10:01] Agent: old reply\n";
67
- await writeFile(join(dir, `${convId}.md`), legacyContent);
68
- const conv = await loadConversation(workdir, agentName, convId);
69
- assert.equal(conv.rounds.length, 2);
70
- assert.equal(conv.rounds[0].kind, "chat");
71
- assert.equal(conv.rounds[1].kind, "chat");
72
- });
73
- it("[order] tag appears in the raw file content", async () => {
74
- const convId = "test-tag-on-disk";
75
- await appendMessage(workdir, agentName, convId, "User", "buy something", "order");
76
- const { readFile: rf } = await import("node:fs/promises");
77
- const dir = join(workdir, ".akemon", "agents", agentName, "conversations");
78
- const raw = await rf(join(dir, `${convId}.md`), "utf-8");
79
- assert.ok(raw.includes("[order] User: buy something"), `raw file should contain [order] tag: ${raw}`);
80
- });
81
- it("chat message does NOT have [order] tag in raw file", async () => {
82
- const convId = "test-no-tag-on-disk";
83
- await appendMessage(workdir, agentName, convId, "User", "just chatting", "chat");
84
- const { readFile: rf } = await import("node:fs/promises");
85
- const dir = join(workdir, ".akemon", "agents", agentName, "conversations");
86
- const raw = await rf(join(dir, `${convId}.md`), "utf-8");
87
- assert.ok(!raw.includes("[order]"), `chat line should NOT contain [order] tag: ${raw}`);
88
- assert.ok(raw.includes("User: just chatting"));
89
- });
90
- });