Monorepo for Aesthetic.Computer
aesthetic.computer
1#!/usr/bin/env node
2// memory/cli.mjs
3// Local-first memory CLI for Claude/Codex session continuity.
4
5import {
6 buildEnvProfile,
7 commitEvent,
8 createCheckpoint,
9 createSession,
10 flushRemote,
11 inspectStore,
12 listSessionCheckpoints,
13 listSessions,
14 rememberSession,
15 setDeviceId,
16} from "./index.mjs";
17
18function parseArgs(argv) {
19 const out = { _: [] };
20 for (let i = 0; i < argv.length; i += 1) {
21 const token = argv[i];
22 if (!token.startsWith("--")) {
23 out._.push(token);
24 continue;
25 }
26
27 const key = token.slice(2);
28 const next = argv[i + 1];
29 if (next && !next.startsWith("--")) {
30 out[key] = next;
31 i += 1;
32 } else {
33 out[key] = true;
34 }
35 }
36 return out;
37}
38
39async function readStdinIfAny() {
40 if (process.stdin.isTTY) return "";
41 return new Promise((resolve, reject) => {
42 let data = "";
43 process.stdin.setEncoding("utf8");
44 process.stdin.on("data", (chunk) => {
45 data += chunk;
46 });
47 process.stdin.on("end", () => resolve(data));
48 process.stdin.on("error", reject);
49 });
50}
51
52function toInt(value, fallback) {
53 const parsed = Number(value);
54 if (Number.isFinite(parsed)) return parsed;
55 return fallback;
56}
57
58function printUsage() {
59 console.log(`agent-memory CLI
60
61Commands:
62 create [--session <id>] [--title <title>] [--provider <name>] [--project <name>]
63 event [--session <id>] [--provider <name>] [--model <name>] [--role <role>] [--text <text>] [--source <source>] [--project <name>] [--ticket <id>]
64 checkpoint --session <id> [--reason <reason>] [--summary <text>] [--max-events <n>]
65 checkpoints --session <id>
66 list [--limit <n>] [--project <name>] [--json]
67 remember --from <session-id> [--checkpoint <id>] [--session <new-id>] [--title <title>] [--project <name>] [--provider <name>]
68 set-device --id <device-id>
69 doctor [--json]
70 profile [--include-key] [--exports] [--project <name>] [--provider <name>] [--ticket <id>] [--session <id>] [--device <id>]
71 flush-remote
72`);
73}
74
75async function commandCreate(args) {
76 const session = await createSession({
77 sessionId: args.session || process.env.AGENT_SESSION_ID,
78 title: args.title,
79 provider: args.provider || process.env.AGENT_MEMORY_PROVIDER,
80 project: args.project || process.env.AGENT_MEMORY_PROJECT,
81 });
82 console.log(JSON.stringify(session, null, 2));
83}
84
85async function commandEvent(args) {
86 const stdinRaw = await readStdinIfAny();
87 let stdinPayload = {};
88
89 if (stdinRaw.trim()) {
90 try {
91 stdinPayload = JSON.parse(stdinRaw);
92 } catch {
93 stdinPayload = { text: stdinRaw.trim() };
94 }
95 }
96
97 const payload = {
98 ...stdinPayload,
99 sessionId:
100 args.session ||
101 stdinPayload.sessionId ||
102 stdinPayload.session_id ||
103 process.env.AGENT_SESSION_ID,
104 provider:
105 args.provider ||
106 stdinPayload.provider ||
107 process.env.AGENT_MEMORY_PROVIDER,
108 model: args.model || stdinPayload.model,
109 role: args.role || stdinPayload.role || "user",
110 source: args.source || stdinPayload.source || "manual",
111 project:
112 args.project ||
113 stdinPayload.project ||
114 process.env.AGENT_MEMORY_PROJECT,
115 text: args.text || stdinPayload.text || stdinPayload.content || stdinPayload.message,
116 content: stdinPayload.content,
117 context: stdinPayload.context,
118 tool_calls: stdinPayload.tool_calls,
119 metadata: {
120 ...(stdinPayload.metadata || {}),
121 ...(args.ticket ? { ticket: args.ticket } : {}),
122 },
123 title: args.title || stdinPayload.title,
124 };
125
126 const result = await commitEvent(payload);
127 console.log(JSON.stringify(result, null, 2));
128}
129
130async function commandCheckpoint(args) {
131 const sessionId = args.session || process.env.AGENT_SESSION_ID;
132 if (!sessionId) {
133 throw new Error("--session is required (or set AGENT_SESSION_ID)");
134 }
135
136 const stdinRaw = await readStdinIfAny();
137 const summary = args.summary || stdinRaw.trim() || undefined;
138
139 const checkpoint = await createCheckpoint({
140 sessionId,
141 reason: args.reason || "manual",
142 summary,
143 maxEvents: toInt(args["max-events"], 40),
144 });
145
146 console.log(JSON.stringify(checkpoint, null, 2));
147}
148
149async function commandCheckpoints(args) {
150 const sessionId = args.session || process.env.AGENT_SESSION_ID;
151 if (!sessionId) {
152 throw new Error("--session is required (or set AGENT_SESSION_ID)");
153 }
154
155 const checkpoints = await listSessionCheckpoints(sessionId);
156 console.log(JSON.stringify(checkpoints, null, 2));
157}
158
159async function commandList(args) {
160 const sessions = await listSessions({
161 limit: toInt(args.limit, 30),
162 project: args.project || process.env.AGENT_MEMORY_PROJECT,
163 });
164
165 if (args.json) {
166 console.log(JSON.stringify(sessions, null, 2));
167 return;
168 }
169
170 if (sessions.length === 0) {
171 console.log("No sessions found.");
172 return;
173 }
174
175 for (const session of sessions) {
176 const remembered = session.remembered_from?.session_id
177 ? ` remembered_from=${session.remembered_from.session_id}`
178 : "";
179 console.log(
180 `${session.session_id} project=${session.project} updated=${session.updated_at} seq=${session.last_seq} title="${session.title}"${remembered}`
181 );
182 }
183}
184
185async function commandRemember(args) {
186 if (!args.from) {
187 throw new Error("--from is required");
188 }
189
190 const result = await rememberSession({
191 fromSessionId: args.from,
192 checkpointId: args.checkpoint,
193 sessionId: args.session || process.env.AGENT_SESSION_ID,
194 title: args.title,
195 project: args.project || process.env.AGENT_MEMORY_PROJECT,
196 provider: args.provider || process.env.AGENT_MEMORY_PROVIDER,
197 });
198
199 console.log(JSON.stringify(result, null, 2));
200}
201
202async function commandFlushRemote() {
203 const result = await flushRemote();
204 console.log(JSON.stringify(result, null, 2));
205}
206
207async function commandSetDevice(args) {
208 if (!args.id) {
209 throw new Error("--id is required");
210 }
211
212 const normalized = await setDeviceId(args.id);
213 console.log(`AGENT_DEVICE_ID set to ${normalized}`);
214}
215
216async function commandDoctor(args) {
217 const info = await inspectStore();
218 if (args.json) {
219 console.log(JSON.stringify(info, null, 2));
220 return;
221 }
222
223 console.log(`home: ${info.home}`);
224 console.log(`device: ${info.device_id}`);
225 console.log(`key source: ${info.key_source}`);
226 console.log(`key fingerprint: ${info.key_fingerprint}`);
227 console.log(`sessions: ${info.sessions_count}`);
228 console.log(`event streams: ${info.event_streams_count}`);
229 console.log(`checkpoints: ${info.checkpoint_streams_count}`);
230 console.log(`remote enabled: ${info.remote_enabled}`);
231 if (info.remote_enabled) {
232 console.log(`remote endpoint: ${info.remote_endpoint}`);
233 }
234}
235
236function formatProfileLine(line, useExports) {
237 return useExports ? `export ${line}` : line;
238}
239
240async function commandProfile(args) {
241 const includeKey = Boolean(args["include-key"]);
242 const useExports = Boolean(args.exports);
243
244 const profile = await buildEnvProfile({
245 includeKey,
246 deviceId: args.device,
247 project: args.project,
248 provider: args.provider,
249 ticket: args.ticket,
250 sessionId: args.session,
251 });
252
253 console.log(`# Agent memory profile (${profile.home})`);
254 for (const line of profile.lines) {
255 console.log(formatProfileLine(line, useExports));
256 }
257}
258
259async function main() {
260 const args = parseArgs(process.argv.slice(2));
261 const command = args._[0] || "help";
262
263 if (command === "help" || command === "--help" || command === "-h") {
264 printUsage();
265 return;
266 }
267
268 const handlers = {
269 create: commandCreate,
270 event: commandEvent,
271 checkpoint: commandCheckpoint,
272 checkpoints: commandCheckpoints,
273 list: commandList,
274 remember: commandRemember,
275 "set-device": commandSetDevice,
276 doctor: commandDoctor,
277 profile: commandProfile,
278 "flush-remote": commandFlushRemote,
279 };
280
281 const handler = handlers[command];
282 if (!handler) {
283 printUsage();
284 process.exitCode = 1;
285 return;
286 }
287
288 await handler(args);
289}
290
291main().catch((error) => {
292 console.error(`agent-memory: ${error.message}`);
293 process.exit(1);
294});