@sofer_agent/cli 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { agentPaths } from "@sofer_agent/core";
3
3
  import { chatCommand } from "./chat.js";
4
+ import { daemonStart, daemonStop } from "./daemon.js";
4
5
  import { infoCommand } from "./info.js";
5
6
  import { initCommand } from "./init.js";
6
7
  import { telegramCommand } from "./telegram-start.js";
@@ -11,6 +12,8 @@ function printHelp() {
11
12
  sofer --resume <id> resume a previous session
12
13
  sofer init interactive setup — mint agent + provision memory
13
14
  sofer info print on-chain agent state
15
+ sofer daemon start start persistent background agent
16
+ sofer daemon stop stop the background agent
14
17
  sofer telegram start the Telegram bot
15
18
  sofer help show this help
16
19
 
@@ -37,6 +40,16 @@ async function main() {
37
40
  case "telegram":
38
41
  await telegramCommand();
39
42
  break;
43
+ case "daemon": {
44
+ const sub = args[1];
45
+ if (sub === "start")
46
+ await daemonStart();
47
+ else if (sub === "stop")
48
+ await daemonStop();
49
+ else
50
+ console.error("usage: sofer daemon <start|stop>");
51
+ break;
52
+ }
40
53
  case "help":
41
54
  case "-h":
42
55
  case "--help":
package/dist/bin.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,SAAS,SAAS;IAChB,OAAO,CAAC,GAAG,CACT;;;;;;;;;;6BAUyB,UAAU,CAAC,IAAI;gDACI,CAC7C,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAChE,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC;IAE9B,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,MAAM,CAAC;QACZ,KAAK,UAAU;YACb,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;YACnD,MAAM;QACR,KAAK,MAAM;YACT,MAAM,WAAW,EAAE,CAAC;YACpB,MAAM;QACR,KAAK,MAAM;YACT,MAAM,WAAW,EAAE,CAAC;YACpB,MAAM;QACR,KAAK,UAAU;YACb,MAAM,eAAe,EAAE,CAAC;YACxB,MAAM;QACR,KAAK,MAAM,CAAC;QACZ,KAAK,IAAI,CAAC;QACV,KAAK,QAAQ;YACX,SAAS,EAAE,CAAC;YACZ,MAAM;QACR;YACE,OAAO,CAAC,KAAK,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;YAC3C,SAAS,EAAE,CAAC;YACZ,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;IACjB,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACvD,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,IACE,GAAG,CAAC,QAAQ,CAAC,sBAAsB,CAAC;QACpC,GAAG,CAAC,QAAQ,CAAC,mBAAmB,CAAC;QACjC,GAAG,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAChC,CAAC;QACD,OAAO,CAAC,KAAK,CACX,qEAAqE,UAAU,CAAC,IAAI,KAAK,CAC1F,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAEtD,SAAS,SAAS;IAChB,OAAO,CAAC,GAAG,CACT;;;;;;;;;;;;6BAYyB,UAAU,CAAC,IAAI;gDACI,CAC7C,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAChE,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC;IAE9B,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,MAAM,CAAC;QACZ,KAAK,UAAU;YACb,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;YACnD,MAAM;QACR,KAAK,MAAM;YACT,MAAM,WAAW,EAAE,CAAC;YACpB,MAAM;QACR,KAAK,MAAM;YACT,MAAM,WAAW,EAAE,CAAC;YACpB,MAAM;QACR,KAAK,UAAU;YACb,MAAM,eAAe,EAAE,CAAC;YACxB,MAAM;QACR,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YACpB,IAAI,GAAG,KAAK,OAAO;gBAAE,MAAM,WAAW,EAAE,CAAC;iBACpC,IAAI,GAAG,KAAK,MAAM;gBAAE,MAAM,UAAU,EAAE,CAAC;;gBACvC,OAAO,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;YACvD,MAAM;QACR,CAAC;QACD,KAAK,MAAM,CAAC;QACZ,KAAK,IAAI,CAAC;QACV,KAAK,QAAQ;YACX,SAAS,EAAE,CAAC;YACZ,MAAM;QACR;YACE,OAAO,CAAC,KAAK,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;YAC3C,SAAS,EAAE,CAAC;YACZ,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;IACjB,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACvD,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,IACE,GAAG,CAAC,QAAQ,CAAC,sBAAsB,CAAC;QACpC,GAAG,CAAC,QAAQ,CAAC,mBAAmB,CAAC;QACjC,GAAG,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAChC,CAAC;QACD,OAAO,CAAC,KAAK,CACX,qEAAqE,UAAU,CAAC,IAAI,KAAK,CAC1F,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function daemonStop(): Promise<void>;
2
+ export declare function daemonStart(): Promise<void>;
3
+ //# sourceMappingURL=daemon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AA6BA,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAKhD;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAoIjD"}
package/dist/daemon.js ADDED
@@ -0,0 +1,171 @@
1
+ import { createServer } from "node:http";
2
+ import { readFileSync, writeFileSync, unlinkSync, existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { agentPaths, decryptWithKey, loadConfig, readSecret, SoferAgent, } from "@sofer_agent/core";
5
+ import { TelegramListener } from "@sofer_agent/plugin-telegram";
6
+ import { loadSecrets } from "./env.js";
7
+ const PID_FILE = join(agentPaths.root, "daemon.pid");
8
+ function writePid(pid) {
9
+ writeFileSync(PID_FILE, String(pid), "utf8");
10
+ }
11
+ function readPid() {
12
+ try {
13
+ return Number(readFileSync(PID_FILE, "utf8").trim());
14
+ }
15
+ catch {
16
+ return null;
17
+ }
18
+ }
19
+ export async function daemonStop() {
20
+ const pid = readPid();
21
+ if (!pid) {
22
+ console.log("No daemon running.");
23
+ return;
24
+ }
25
+ try {
26
+ process.kill(pid, "SIGTERM");
27
+ console.log(`Sent stop signal to PID ${pid}.`);
28
+ }
29
+ catch {
30
+ console.log("Daemon not running (stale PID).");
31
+ unlinkSync(PID_FILE);
32
+ }
33
+ }
34
+ export async function daemonStart() {
35
+ const secrets = loadSecrets();
36
+ if (!secrets.suiSecretKey || !secrets.anthropicApiKey) {
37
+ console.error("Missing secrets. Run `sofer init` first.");
38
+ process.exit(1);
39
+ }
40
+ const config = loadConfig();
41
+ const agent = await SoferAgent.create(config, {
42
+ suiSecretKey: secrets.suiSecretKey,
43
+ anthropicApiKey: secrets.anthropicApiKey,
44
+ });
45
+ // SSE clients
46
+ let sseId = 0;
47
+ const sseClients = new Set();
48
+ function broadcast(event, data) {
49
+ for (const c of sseClients) {
50
+ c.res.write(`event: ${event}\ndata: ${data}\n\n`);
51
+ }
52
+ }
53
+ // HTTP server
54
+ const server = createServer(async (req, res) => {
55
+ res.setHeader("Access-Control-Allow-Origin", "*");
56
+ if (req.method === "OPTIONS") {
57
+ res.writeHead(204).end();
58
+ return;
59
+ }
60
+ // GET /healthz
61
+ if (req.url === "/healthz" || req.url === "/") {
62
+ res.writeHead(200, { "content-type": "application/json" });
63
+ res.end(JSON.stringify({ status: "ok", agent: agent.address.slice(0, 10), network: config.network }));
64
+ return;
65
+ }
66
+ // GET /events (SSE)
67
+ if (req.url === "/events") {
68
+ res.writeHead(200, {
69
+ "content-type": "text/event-stream",
70
+ "cache-control": "no-cache",
71
+ "connection": "keep-alive",
72
+ });
73
+ const client = { id: ++sseId, res };
74
+ sseClients.add(client);
75
+ res.write(`data: ${JSON.stringify({ event: "connected", agent: agent.address })}\n\n`);
76
+ req.on("close", () => sseClients.delete(client));
77
+ return;
78
+ }
79
+ // POST /chat
80
+ if (req.method === "POST" && req.url === "/chat") {
81
+ const body = await readBody(req);
82
+ try {
83
+ const { message } = JSON.parse(body);
84
+ if (!message) {
85
+ res.writeHead(400).end("missing message");
86
+ return;
87
+ }
88
+ broadcast("thinking", JSON.stringify({ status: "thinking" }));
89
+ const { text } = await agent.chat(message);
90
+ broadcast("response", JSON.stringify({ text }));
91
+ broadcast("idle", JSON.stringify({ status: "idle" }));
92
+ res.writeHead(200, { "content-type": "application/json" });
93
+ res.end(JSON.stringify({ text }));
94
+ }
95
+ catch (e) {
96
+ broadcast("error", JSON.stringify({ error: e.message }));
97
+ res.writeHead(500).end(e.message);
98
+ }
99
+ return;
100
+ }
101
+ // GET /status
102
+ if (req.url === "/status") {
103
+ res.writeHead(200, { "content-type": "application/json" });
104
+ res.end(JSON.stringify({
105
+ agent: agent.address,
106
+ name: agent.name,
107
+ network: config.network,
108
+ tokens: agent.usage.tokens,
109
+ cost: agent.usage.costUsd.toFixed(6),
110
+ }));
111
+ return;
112
+ }
113
+ res.writeHead(404).end("not found");
114
+ });
115
+ // Telegram listener
116
+ const tgPath = `${agentPaths.root}/telegram.enc`;
117
+ const tgEnc = readSecret(tgPath);
118
+ let tgListener = null;
119
+ if (tgEnc) {
120
+ try {
121
+ const raw = decryptWithKey(secrets.suiSecretKey, tgEnc);
122
+ if (raw) {
123
+ const tgConfig = JSON.parse(raw);
124
+ tgListener = new TelegramListener({
125
+ botToken: tgConfig.botToken,
126
+ allowedUserIds: tgConfig.allowedUserIds,
127
+ agentName: agent.name.toLowerCase(),
128
+ });
129
+ tgListener.onDispatch(async (input) => {
130
+ broadcast("tg-message", JSON.stringify({ from: input.displayName, text: input.text.slice(0, 100) }));
131
+ const { text } = await agent.chat(input.text);
132
+ broadcast("tg-response", JSON.stringify({ text: text.slice(0, 100) }));
133
+ return text;
134
+ });
135
+ }
136
+ }
137
+ catch { /* skip */ }
138
+ }
139
+ const PORT = Number(process.env.SOFER_DAEMON_PORT ?? 4242);
140
+ server.listen(PORT, () => {
141
+ writePid(process.pid);
142
+ console.log(`[daemon] sofer running on http://localhost:${PORT}`);
143
+ console.log(`[daemon] agent: ${agent.address.slice(0, 10)}… network: ${config.network}`);
144
+ if (tgListener) {
145
+ console.log("[daemon] telegram: starting bot…");
146
+ tgListener.start().catch((e) => console.error("[daemon] telegram failed:", e.message));
147
+ }
148
+ });
149
+ // Graceful shutdown
150
+ const shutdown = async () => {
151
+ console.log("\n[daemon] shutting down…");
152
+ if (tgListener)
153
+ await tgListener.stop();
154
+ server.close();
155
+ try {
156
+ unlinkSync(PID_FILE);
157
+ }
158
+ catch { /* */ }
159
+ process.exit(0);
160
+ };
161
+ process.on("SIGINT", shutdown);
162
+ process.on("SIGTERM", shutdown);
163
+ }
164
+ function readBody(req) {
165
+ return new Promise((resolve) => {
166
+ let data = "";
167
+ req.on("data", (chunk) => { data += chunk; });
168
+ req.on("end", () => resolve(data));
169
+ });
170
+ }
171
+ //# sourceMappingURL=daemon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sofer_agent/cli",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sofer": "./bin/sofer"
@@ -29,7 +29,7 @@
29
29
  "@clack/prompts": "^1.5.1",
30
30
  "@types/blessed": "^0.1.27",
31
31
  "blessed": "^0.1.81",
32
- "@sofer_agent/core": "0.5.1",
32
+ "@sofer_agent/core": "0.6.0",
33
33
  "@sofer_agent/plugin-telegram": "0.1.0"
34
34
  },
35
35
  "devDependencies": {
package/src/bin.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { agentPaths } from "@sofer_agent/core";
3
3
  import { chatCommand } from "./chat.js";
4
+ import { daemonStart, daemonStop } from "./daemon.js";
4
5
  import { infoCommand } from "./info.js";
5
6
  import { initCommand } from "./init.js";
6
7
  import { telegramCommand } from "./telegram-start.js";
@@ -13,6 +14,8 @@ function printHelp(): void {
13
14
  sofer --resume <id> resume a previous session
14
15
  sofer init interactive setup — mint agent + provision memory
15
16
  sofer info print on-chain agent state
17
+ sofer daemon start start persistent background agent
18
+ sofer daemon stop stop the background agent
16
19
  sofer telegram start the Telegram bot
17
20
  sofer help show this help
18
21
 
@@ -42,6 +45,13 @@ async function main(): Promise<void> {
42
45
  case "telegram":
43
46
  await telegramCommand();
44
47
  break;
48
+ case "daemon": {
49
+ const sub = args[1];
50
+ if (sub === "start") await daemonStart();
51
+ else if (sub === "stop") await daemonStop();
52
+ else console.error("usage: sofer daemon <start|stop>");
53
+ break;
54
+ }
45
55
  case "help":
46
56
  case "-h":
47
57
  case "--help":
package/src/daemon.ts ADDED
@@ -0,0 +1,177 @@
1
+ import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
2
+ import { readFileSync, writeFileSync, unlinkSync, existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import {
5
+ type SoferConfig,
6
+ agentPaths,
7
+ decryptWithKey,
8
+ loadConfig,
9
+ readSecret,
10
+ SoferAgent,
11
+ } from "@sofer_agent/core";
12
+ import { TelegramListener } from "@sofer_agent/plugin-telegram";
13
+ import { loadSecrets } from "./env.js";
14
+
15
+ const PID_FILE = join(agentPaths.root, "daemon.pid");
16
+
17
+ interface SSEClient {
18
+ id: number;
19
+ res: ServerResponse;
20
+ }
21
+
22
+ function writePid(pid: number): void {
23
+ writeFileSync(PID_FILE, String(pid), "utf8");
24
+ }
25
+
26
+ function readPid(): number | null {
27
+ try { return Number(readFileSync(PID_FILE, "utf8").trim()); } catch { return null; }
28
+ }
29
+
30
+ export async function daemonStop(): Promise<void> {
31
+ const pid = readPid();
32
+ if (!pid) { console.log("No daemon running."); return; }
33
+ try { process.kill(pid, "SIGTERM"); console.log(`Sent stop signal to PID ${pid}.`); }
34
+ catch { console.log("Daemon not running (stale PID)."); unlinkSync(PID_FILE); }
35
+ }
36
+
37
+ export async function daemonStart(): Promise<void> {
38
+ const secrets = loadSecrets();
39
+ if (!secrets.suiSecretKey || !secrets.anthropicApiKey) {
40
+ console.error("Missing secrets. Run `sofer init` first.");
41
+ process.exit(1);
42
+ }
43
+
44
+ const config = loadConfig();
45
+ const agent = await SoferAgent.create(config, {
46
+ suiSecretKey: secrets.suiSecretKey,
47
+ anthropicApiKey: secrets.anthropicApiKey,
48
+ });
49
+
50
+ // SSE clients
51
+ let sseId = 0;
52
+ const sseClients = new Set<SSEClient>();
53
+
54
+ function broadcast(event: string, data: string): void {
55
+ for (const c of sseClients) {
56
+ c.res.write(`event: ${event}\ndata: ${data}\n\n`);
57
+ }
58
+ }
59
+
60
+ // HTTP server
61
+ const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
62
+ res.setHeader("Access-Control-Allow-Origin", "*");
63
+
64
+ if (req.method === "OPTIONS") { res.writeHead(204).end(); return; }
65
+
66
+ // GET /healthz
67
+ if (req.url === "/healthz" || req.url === "/") {
68
+ res.writeHead(200, { "content-type": "application/json" });
69
+ res.end(JSON.stringify({ status: "ok", agent: agent.address.slice(0, 10), network: config.network }));
70
+ return;
71
+ }
72
+
73
+ // GET /events (SSE)
74
+ if (req.url === "/events") {
75
+ res.writeHead(200, {
76
+ "content-type": "text/event-stream",
77
+ "cache-control": "no-cache",
78
+ "connection": "keep-alive",
79
+ });
80
+ const client: SSEClient = { id: ++sseId, res };
81
+ sseClients.add(client);
82
+ res.write(`data: ${JSON.stringify({ event: "connected", agent: agent.address })}\n\n`);
83
+ req.on("close", () => sseClients.delete(client));
84
+ return;
85
+ }
86
+
87
+ // POST /chat
88
+ if (req.method === "POST" && req.url === "/chat") {
89
+ const body = await readBody(req);
90
+ try {
91
+ const { message } = JSON.parse(body);
92
+ if (!message) { res.writeHead(400).end("missing message"); return; }
93
+
94
+ broadcast("thinking", JSON.stringify({ status: "thinking" }));
95
+ const { text } = await agent.chat(message);
96
+ broadcast("response", JSON.stringify({ text }));
97
+ broadcast("idle", JSON.stringify({ status: "idle" }));
98
+
99
+ res.writeHead(200, { "content-type": "application/json" });
100
+ res.end(JSON.stringify({ text }));
101
+ } catch (e) {
102
+ broadcast("error", JSON.stringify({ error: (e as Error).message }));
103
+ res.writeHead(500).end((e as Error).message);
104
+ }
105
+ return;
106
+ }
107
+
108
+ // GET /status
109
+ if (req.url === "/status") {
110
+ res.writeHead(200, { "content-type": "application/json" });
111
+ res.end(JSON.stringify({
112
+ agent: agent.address,
113
+ name: agent.name,
114
+ network: config.network,
115
+ tokens: agent.usage.tokens,
116
+ cost: agent.usage.costUsd.toFixed(6),
117
+ }));
118
+ return;
119
+ }
120
+
121
+ res.writeHead(404).end("not found");
122
+ });
123
+
124
+ // Telegram listener
125
+ const tgPath = `${agentPaths.root}/telegram.enc`;
126
+ const tgEnc = readSecret(tgPath);
127
+ let tgListener: TelegramListener | null = null;
128
+ if (tgEnc) {
129
+ try {
130
+ const raw = decryptWithKey(secrets.suiSecretKey, tgEnc);
131
+ if (raw) {
132
+ const tgConfig = JSON.parse(raw) as { botToken: string; allowedUserIds: number[] };
133
+ tgListener = new TelegramListener({
134
+ botToken: tgConfig.botToken,
135
+ allowedUserIds: tgConfig.allowedUserIds,
136
+ agentName: agent.name.toLowerCase(),
137
+ });
138
+ tgListener.onDispatch(async (input) => {
139
+ broadcast("tg-message", JSON.stringify({ from: input.displayName, text: input.text.slice(0, 100) }));
140
+ const { text } = await agent.chat(input.text);
141
+ broadcast("tg-response", JSON.stringify({ text: text.slice(0, 100) }));
142
+ return text;
143
+ });
144
+ }
145
+ } catch { /* skip */ }
146
+ }
147
+
148
+ const PORT = Number(process.env.SOFER_DAEMON_PORT ?? 4242);
149
+ server.listen(PORT, () => {
150
+ writePid(process.pid);
151
+ console.log(`[daemon] sofer running on http://localhost:${PORT}`);
152
+ console.log(`[daemon] agent: ${agent.address.slice(0, 10)}… network: ${config.network}`);
153
+ if (tgListener) {
154
+ console.log("[daemon] telegram: starting bot…");
155
+ tgListener.start().catch((e) => console.error("[daemon] telegram failed:", e.message));
156
+ }
157
+ });
158
+
159
+ // Graceful shutdown
160
+ const shutdown = async () => {
161
+ console.log("\n[daemon] shutting down…");
162
+ if (tgListener) await tgListener.stop();
163
+ server.close();
164
+ try { unlinkSync(PID_FILE); } catch { /* */ }
165
+ process.exit(0);
166
+ };
167
+ process.on("SIGINT", shutdown);
168
+ process.on("SIGTERM", shutdown);
169
+ }
170
+
171
+ function readBody(req: IncomingMessage): Promise<string> {
172
+ return new Promise((resolve) => {
173
+ let data = "";
174
+ req.on("data", (chunk) => { data += chunk; });
175
+ req.on("end", () => resolve(data));
176
+ });
177
+ }