@sofer_agent/cli 0.6.1 → 0.7.1

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":"AA2CA,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAKhD;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAuLjD"}
package/dist/daemon.js ADDED
@@ -0,0 +1,238 @@
1
+ import { createServer } from "node:http";
2
+ import { chmodSync, existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { agentPaths, decryptWithKey, loadConfig, readSecret, shortAgentId, SoferAgent, } from "@sofer_agent/core";
5
+ import { TelegramListener } from "@sofer_agent/plugin-telegram";
6
+ import { loadSecrets } from "./env.js";
7
+ const LOCK_FILE = join(agentPaths.root, "daemon.lock");
8
+ const SOCK_PATH = join(agentPaths.root, "daemon.sock");
9
+ // ── Process lock ──────────────────────────────────────────────────────────
10
+ function acquireLock() {
11
+ try {
12
+ if (existsSync(LOCK_FILE)) {
13
+ const pid = Number(readFileSync(LOCK_FILE, "utf8").trim());
14
+ try {
15
+ process.kill(pid, 0);
16
+ return false;
17
+ }
18
+ catch { /* stale */ }
19
+ }
20
+ writeFileSync(LOCK_FILE, String(process.pid), "utf8");
21
+ return true;
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
27
+ function releaseLock() {
28
+ try {
29
+ if (existsSync(LOCK_FILE))
30
+ unlinkSync(LOCK_FILE);
31
+ }
32
+ catch { /* */ }
33
+ }
34
+ let state = "bootstrapping";
35
+ // ── Main ──────────────────────────────────────────────────────────────────
36
+ export async function daemonStop() {
37
+ if (!existsSync(LOCK_FILE)) {
38
+ console.log("No daemon running.");
39
+ return;
40
+ }
41
+ const pid = Number(readFileSync(LOCK_FILE, "utf8").trim());
42
+ try {
43
+ process.kill(pid, "SIGTERM");
44
+ console.log(`Sent stop to PID ${pid}.`);
45
+ }
46
+ catch {
47
+ console.log("Stale lock. Cleaning up.");
48
+ releaseLock();
49
+ }
50
+ }
51
+ export async function daemonStart() {
52
+ if (!acquireLock()) {
53
+ console.error("Daemon already running (lock exists). Use `sofer daemon stop` first.");
54
+ process.exit(1);
55
+ }
56
+ const secrets = loadSecrets();
57
+ if (!secrets.suiSecretKey || !secrets.anthropicApiKey) {
58
+ console.error("Missing secrets. Run `sofer init` first.");
59
+ process.exit(1);
60
+ }
61
+ console.log("[daemon] bootstrapping…");
62
+ const config = loadConfig();
63
+ let agent;
64
+ try {
65
+ agent = await SoferAgent.create(config, {
66
+ suiSecretKey: secrets.suiSecretKey,
67
+ anthropicApiKey: secrets.anthropicApiKey,
68
+ });
69
+ }
70
+ catch (e) {
71
+ console.error("[daemon] failed to create agent:", e.message);
72
+ releaseLock();
73
+ process.exit(1);
74
+ }
75
+ // SSE clients
76
+ let sseId = 0;
77
+ const sseClients = new Set();
78
+ function broadcast(event, data) {
79
+ const payload = JSON.stringify(data);
80
+ for (const c of sseClients) {
81
+ c.res.write(`event: ${event}\ndata: ${payload}\n\n`);
82
+ }
83
+ }
84
+ // ── HTTP server ────────────────────────────────────────────────────────
85
+ const server = createServer(async (req, res) => {
86
+ res.setHeader("Access-Control-Allow-Origin", "*");
87
+ if (req.method === "OPTIONS") {
88
+ res.writeHead(204).end();
89
+ return;
90
+ }
91
+ const url = req.url ?? "/";
92
+ // GET /healthz
93
+ if (req.method === "GET" && url === "/healthz") {
94
+ res.writeHead(200, { "content-type": "application/json" });
95
+ res.end(JSON.stringify({ status: state, agent: agent.address.slice(0, 10), network: config.network }));
96
+ return;
97
+ }
98
+ // GET /events (SSE)
99
+ if (req.method === "GET" && url === "/events") {
100
+ res.writeHead(200, {
101
+ "content-type": "text/event-stream",
102
+ "cache-control": "no-cache",
103
+ connection: "keep-alive",
104
+ });
105
+ const client = { id: ++sseId, res };
106
+ sseClients.add(client);
107
+ res.write(`data: ${JSON.stringify({ event: "connected", agent: agent.address })}\n\n`);
108
+ req.on("close", () => sseClients.delete(client));
109
+ return;
110
+ }
111
+ // POST /chat
112
+ if (req.method === "POST" && url === "/chat") {
113
+ const body = await readBody(req);
114
+ try {
115
+ const { message } = JSON.parse(body);
116
+ if (!message) {
117
+ res.writeHead(400).end("missing message");
118
+ return;
119
+ }
120
+ broadcast("thinking", { status: "thinking" });
121
+ const { text } = await agent.chat(message);
122
+ broadcast("response", { text });
123
+ broadcast("idle", { status: "idle" });
124
+ res.writeHead(200, { "content-type": "application/json" });
125
+ res.end(JSON.stringify({ text }));
126
+ }
127
+ catch (e) {
128
+ broadcast("error", { error: e.message });
129
+ res.writeHead(500).end(e.message);
130
+ }
131
+ return;
132
+ }
133
+ // POST /sync — force memory flush
134
+ if (req.method === "POST" && url === "/sync") {
135
+ broadcast("sync", { status: "syncing" });
136
+ // Memory is already persisted via JSONL; trigger a fresh agent state read
137
+ try {
138
+ const bal = await agent.getBalance();
139
+ broadcast("sync", { status: "done", balance: (Number(bal) / 1e9).toFixed(4) });
140
+ res.writeHead(200).end("synced");
141
+ }
142
+ catch (e) {
143
+ res.writeHead(500).end(e.message);
144
+ }
145
+ return;
146
+ }
147
+ // GET /status
148
+ if (req.method === "GET" && url === "/status") {
149
+ res.writeHead(200, { "content-type": "application/json" });
150
+ res.end(JSON.stringify({
151
+ state,
152
+ agent: agent.address,
153
+ name: agent.name,
154
+ network: config.network,
155
+ tokens: agent.usage.tokens,
156
+ cost: agent.usage.costUsd.toFixed(6),
157
+ }));
158
+ return;
159
+ }
160
+ res.writeHead(404).end("not found");
161
+ });
162
+ // ── Telegram ───────────────────────────────────────────────────────────
163
+ const tgPath = `${agentPaths.root}/telegram.enc`;
164
+ const tgEnc = readSecret(tgPath);
165
+ let tgListener = null;
166
+ if (tgEnc) {
167
+ try {
168
+ const raw = decryptWithKey(secrets.suiSecretKey, tgEnc);
169
+ if (raw) {
170
+ const tgConfig = JSON.parse(raw);
171
+ tgListener = new TelegramListener({
172
+ botToken: tgConfig.botToken,
173
+ allowedUserIds: tgConfig.allowedUserIds,
174
+ agentName: agent.name.toLowerCase(),
175
+ });
176
+ tgListener.onDispatch(async (input) => {
177
+ broadcast("tg-message", { from: input.displayName, text: input.text.slice(0, 200) });
178
+ const { text } = await agent.chat(input.text);
179
+ broadcast("tg-response", { text: text.slice(0, 200) });
180
+ return text;
181
+ });
182
+ }
183
+ }
184
+ catch { /* skip */ }
185
+ }
186
+ // ── Bind ───────────────────────────────────────────────────────────────
187
+ const PORT = Number(process.env.SOFER_DAEMON_PORT ?? 4242);
188
+ // Try Unix socket first, fall back to TCP
189
+ try {
190
+ if (existsSync(SOCK_PATH))
191
+ unlinkSync(SOCK_PATH);
192
+ server.listen(SOCK_PATH, () => {
193
+ chmodSync(SOCK_PATH, 0o600);
194
+ state = "ready";
195
+ console.log(`[daemon] ready — unix socket ${SOCK_PATH}`);
196
+ console.log(`[daemon] agent: ${agent.address.slice(0, 10)}… network: ${config.network}`);
197
+ if (tgListener) {
198
+ console.log("[daemon] telegram: starting bot…");
199
+ tgListener.start().catch((e) => console.error("[daemon] telegram:", e.message));
200
+ }
201
+ });
202
+ }
203
+ catch {
204
+ server.listen(PORT, () => {
205
+ state = "ready";
206
+ console.log(`[daemon] ready — tcp port ${PORT} (SOFER_DAEMON_PORT)`);
207
+ console.log(`[daemon] agent: ${agent.address.slice(0, 10)}… network: ${config.network}`);
208
+ if (tgListener) {
209
+ tgListener.start().catch((e) => console.error("[daemon] telegram:", e.message));
210
+ }
211
+ });
212
+ }
213
+ // ── Shutdown ───────────────────────────────────────────────────────────
214
+ const shutdown = async () => {
215
+ state = "shutting_down";
216
+ console.log("\n[daemon] shutting down…");
217
+ if (tgListener)
218
+ await tgListener.stop().catch(() => { });
219
+ server.close();
220
+ releaseLock();
221
+ try {
222
+ if (existsSync(SOCK_PATH))
223
+ unlinkSync(SOCK_PATH);
224
+ }
225
+ catch { /* */ }
226
+ process.exit(0);
227
+ };
228
+ process.on("SIGINT", shutdown);
229
+ process.on("SIGTERM", shutdown);
230
+ }
231
+ function readBody(req) {
232
+ return new Promise((resolve) => {
233
+ let data = "";
234
+ req.on("data", (chunk) => { data += chunk; });
235
+ req.on("end", () => resolve(data));
236
+ });
237
+ }
238
+ //# 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.1",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sofer": "./bin/sofer"
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,242 @@
1
+ import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
2
+ import { chmodSync, existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import {
5
+ agentPaths,
6
+ decryptWithKey,
7
+ loadConfig,
8
+ readSecret,
9
+ shortAgentId,
10
+ SoferAgent,
11
+ } from "@sofer_agent/core";
12
+ import { TelegramListener } from "@sofer_agent/plugin-telegram";
13
+ import { loadSecrets } from "./env.js";
14
+
15
+ const LOCK_FILE = join(agentPaths.root, "daemon.lock");
16
+ const SOCK_PATH = join(agentPaths.root, "daemon.sock");
17
+
18
+ interface SSEClient { id: number; res: ServerResponse; }
19
+
20
+ // ── Process lock ──────────────────────────────────────────────────────────
21
+
22
+ function acquireLock(): boolean {
23
+ try {
24
+ if (existsSync(LOCK_FILE)) {
25
+ const pid = Number(readFileSync(LOCK_FILE, "utf8").trim());
26
+ try { process.kill(pid, 0); return false; } catch { /* stale */ }
27
+ }
28
+ writeFileSync(LOCK_FILE, String(process.pid), "utf8");
29
+ return true;
30
+ } catch { return false; }
31
+ }
32
+
33
+ function releaseLock(): void {
34
+ try { if (existsSync(LOCK_FILE)) unlinkSync(LOCK_FILE); } catch { /* */ }
35
+ }
36
+
37
+ // ── State machine ─────────────────────────────────────────────────────────
38
+
39
+ type DaemonState = "bootstrapping" | "ready" | "shutting_down";
40
+ let state: DaemonState = "bootstrapping";
41
+
42
+ // ── Main ──────────────────────────────────────────────────────────────────
43
+
44
+ export async function daemonStop(): Promise<void> {
45
+ if (!existsSync(LOCK_FILE)) { console.log("No daemon running."); return; }
46
+ const pid = Number(readFileSync(LOCK_FILE, "utf8").trim());
47
+ try { process.kill(pid, "SIGTERM"); console.log(`Sent stop to PID ${pid}.`); }
48
+ catch { console.log("Stale lock. Cleaning up."); releaseLock(); }
49
+ }
50
+
51
+ export async function daemonStart(): Promise<void> {
52
+ if (!acquireLock()) {
53
+ console.error("Daemon already running (lock exists). Use `sofer daemon stop` first.");
54
+ process.exit(1);
55
+ }
56
+
57
+ const secrets = loadSecrets();
58
+ if (!secrets.suiSecretKey || !secrets.anthropicApiKey) {
59
+ console.error("Missing secrets. Run `sofer init` first.");
60
+ process.exit(1);
61
+ }
62
+
63
+ console.log("[daemon] bootstrapping…");
64
+ const config = loadConfig();
65
+ let agent: SoferAgent;
66
+ try {
67
+ agent = await SoferAgent.create(config, {
68
+ suiSecretKey: secrets.suiSecretKey,
69
+ anthropicApiKey: secrets.anthropicApiKey,
70
+ });
71
+ } catch (e) {
72
+ console.error("[daemon] failed to create agent:", (e as Error).message);
73
+ releaseLock();
74
+ process.exit(1);
75
+ }
76
+
77
+ // SSE clients
78
+ let sseId = 0;
79
+ const sseClients = new Set<SSEClient>();
80
+
81
+ function broadcast(event: string, data: Record<string, unknown>): void {
82
+ const payload = JSON.stringify(data);
83
+ for (const c of sseClients) {
84
+ c.res.write(`event: ${event}\ndata: ${payload}\n\n`);
85
+ }
86
+ }
87
+
88
+ // ── HTTP server ────────────────────────────────────────────────────────
89
+
90
+ const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
91
+ res.setHeader("Access-Control-Allow-Origin", "*");
92
+ if (req.method === "OPTIONS") { res.writeHead(204).end(); return; }
93
+
94
+ const url = req.url ?? "/";
95
+
96
+ // GET /healthz
97
+ if (req.method === "GET" && url === "/healthz") {
98
+ res.writeHead(200, { "content-type": "application/json" });
99
+ res.end(JSON.stringify({ status: state, agent: agent.address.slice(0, 10), network: config.network }));
100
+ return;
101
+ }
102
+
103
+ // GET /events (SSE)
104
+ if (req.method === "GET" && url === "/events") {
105
+ res.writeHead(200, {
106
+ "content-type": "text/event-stream",
107
+ "cache-control": "no-cache",
108
+ connection: "keep-alive",
109
+ });
110
+ const client: SSEClient = { id: ++sseId, res };
111
+ sseClients.add(client);
112
+ res.write(`data: ${JSON.stringify({ event: "connected", agent: agent.address })}\n\n`);
113
+ req.on("close", () => sseClients.delete(client));
114
+ return;
115
+ }
116
+
117
+ // POST /chat
118
+ if (req.method === "POST" && url === "/chat") {
119
+ const body = await readBody(req);
120
+ try {
121
+ const { message } = JSON.parse(body);
122
+ if (!message) { res.writeHead(400).end("missing message"); return; }
123
+ broadcast("thinking", { status: "thinking" });
124
+ const { text } = await agent.chat(message);
125
+ broadcast("response", { text });
126
+ broadcast("idle", { status: "idle" });
127
+ res.writeHead(200, { "content-type": "application/json" });
128
+ res.end(JSON.stringify({ text }));
129
+ } catch (e) {
130
+ broadcast("error", { error: (e as Error).message });
131
+ res.writeHead(500).end((e as Error).message);
132
+ }
133
+ return;
134
+ }
135
+
136
+ // POST /sync — force memory flush
137
+ if (req.method === "POST" && url === "/sync") {
138
+ broadcast("sync", { status: "syncing" });
139
+ // Memory is already persisted via JSONL; trigger a fresh agent state read
140
+ try {
141
+ const bal = await agent.getBalance();
142
+ broadcast("sync", { status: "done", balance: (Number(bal) / 1e9).toFixed(4) });
143
+ res.writeHead(200).end("synced");
144
+ } catch (e) {
145
+ res.writeHead(500).end((e as Error).message);
146
+ }
147
+ return;
148
+ }
149
+
150
+ // GET /status
151
+ if (req.method === "GET" && url === "/status") {
152
+ res.writeHead(200, { "content-type": "application/json" });
153
+ res.end(JSON.stringify({
154
+ state,
155
+ agent: agent.address,
156
+ name: agent.name,
157
+ network: config.network,
158
+ tokens: agent.usage.tokens,
159
+ cost: agent.usage.costUsd.toFixed(6),
160
+ }));
161
+ return;
162
+ }
163
+
164
+ res.writeHead(404).end("not found");
165
+ });
166
+
167
+ // ── Telegram ───────────────────────────────────────────────────────────
168
+
169
+ const tgPath = `${agentPaths.root}/telegram.enc`;
170
+ const tgEnc = readSecret(tgPath);
171
+ let tgListener: TelegramListener | null = null;
172
+ if (tgEnc) {
173
+ try {
174
+ const raw = decryptWithKey(secrets.suiSecretKey, tgEnc);
175
+ if (raw) {
176
+ const tgConfig = JSON.parse(raw) as { botToken: string; allowedUserIds: number[] };
177
+ tgListener = new TelegramListener({
178
+ botToken: tgConfig.botToken,
179
+ allowedUserIds: tgConfig.allowedUserIds,
180
+ agentName: agent.name.toLowerCase(),
181
+ });
182
+ tgListener.onDispatch(async (input) => {
183
+ broadcast("tg-message", { from: input.displayName, text: input.text.slice(0, 200) });
184
+ const { text } = await agent.chat(input.text);
185
+ broadcast("tg-response", { text: text.slice(0, 200) });
186
+ return text;
187
+ });
188
+ }
189
+ } catch { /* skip */ }
190
+ }
191
+
192
+ // ── Bind ───────────────────────────────────────────────────────────────
193
+
194
+ const PORT = Number(process.env.SOFER_DAEMON_PORT ?? 4242);
195
+
196
+ // Try Unix socket first, fall back to TCP
197
+ try {
198
+ if (existsSync(SOCK_PATH)) unlinkSync(SOCK_PATH);
199
+ server.listen(SOCK_PATH, () => {
200
+ chmodSync(SOCK_PATH, 0o600);
201
+ state = "ready";
202
+ console.log(`[daemon] ready — unix socket ${SOCK_PATH}`);
203
+ console.log(`[daemon] agent: ${agent.address.slice(0, 10)}… network: ${config.network}`);
204
+ if (tgListener) {
205
+ console.log("[daemon] telegram: starting bot…");
206
+ tgListener.start().catch((e) => console.error("[daemon] telegram:", e.message));
207
+ }
208
+ });
209
+ } catch {
210
+ server.listen(PORT, () => {
211
+ state = "ready";
212
+ console.log(`[daemon] ready — tcp port ${PORT} (SOFER_DAEMON_PORT)`);
213
+ console.log(`[daemon] agent: ${agent.address.slice(0, 10)}… network: ${config.network}`);
214
+ if (tgListener) {
215
+ tgListener.start().catch((e) => console.error("[daemon] telegram:", e.message));
216
+ }
217
+ });
218
+ }
219
+
220
+ // ── Shutdown ───────────────────────────────────────────────────────────
221
+
222
+ const shutdown = async () => {
223
+ state = "shutting_down";
224
+ console.log("\n[daemon] shutting down…");
225
+ if (tgListener) await tgListener.stop().catch(() => {});
226
+ server.close();
227
+ releaseLock();
228
+ try { if (existsSync(SOCK_PATH)) unlinkSync(SOCK_PATH); } catch { /* */ }
229
+ process.exit(0);
230
+ };
231
+
232
+ process.on("SIGINT", shutdown);
233
+ process.on("SIGTERM", shutdown);
234
+ }
235
+
236
+ function readBody(req: IncomingMessage): Promise<string> {
237
+ return new Promise((resolve) => {
238
+ let data = "";
239
+ req.on("data", (chunk) => { data += chunk; });
240
+ req.on("end", () => resolve(data));
241
+ });
242
+ }