@aomi-labs/client 0.1.0 → 0.1.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/cli.js ADDED
@@ -0,0 +1,1657 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __defProps = Object.defineProperties;
4
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
5
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
8
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
+ var __spreadValues = (a, b) => {
10
+ for (var prop in b || (b = {}))
11
+ if (__hasOwnProp.call(b, prop))
12
+ __defNormalProp(a, prop, b[prop]);
13
+ if (__getOwnPropSymbols)
14
+ for (var prop of __getOwnPropSymbols(b)) {
15
+ if (__propIsEnum.call(b, prop))
16
+ __defNormalProp(a, prop, b[prop]);
17
+ }
18
+ return a;
19
+ };
20
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
21
+
22
+ // src/sse.ts
23
+ function extractSseData(rawEvent) {
24
+ const dataLines = rawEvent.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart());
25
+ if (!dataLines.length) return null;
26
+ return dataLines.join("\n");
27
+ }
28
+ async function readSseStream(stream, signal, onMessage) {
29
+ const reader = stream.getReader();
30
+ const decoder = new TextDecoder();
31
+ let buffer = "";
32
+ try {
33
+ while (!signal.aborted) {
34
+ const { value, done } = await reader.read();
35
+ if (done) break;
36
+ buffer += decoder.decode(value, { stream: true });
37
+ buffer = buffer.replace(/\r/g, "");
38
+ let separatorIndex = buffer.indexOf("\n\n");
39
+ while (separatorIndex >= 0) {
40
+ const rawEvent = buffer.slice(0, separatorIndex);
41
+ buffer = buffer.slice(separatorIndex + 2);
42
+ const data = extractSseData(rawEvent);
43
+ if (data) {
44
+ onMessage(data);
45
+ }
46
+ separatorIndex = buffer.indexOf("\n\n");
47
+ }
48
+ }
49
+ } finally {
50
+ reader.releaseLock();
51
+ }
52
+ }
53
+ function createSseSubscriber({
54
+ backendUrl,
55
+ getHeaders,
56
+ logger
57
+ }) {
58
+ const subscriptions = /* @__PURE__ */ new Map();
59
+ const subscribe = (sessionId, onUpdate, onError) => {
60
+ const existing = subscriptions.get(sessionId);
61
+ const listener = { onUpdate, onError };
62
+ if (existing) {
63
+ existing.listeners.add(listener);
64
+ logger == null ? void 0 : logger.debug("[aomi][sse] listener added", {
65
+ sessionId,
66
+ listeners: existing.listeners.size
67
+ });
68
+ return () => {
69
+ existing.listeners.delete(listener);
70
+ logger == null ? void 0 : logger.debug("[aomi][sse] listener removed", {
71
+ sessionId,
72
+ listeners: existing.listeners.size
73
+ });
74
+ if (existing.listeners.size === 0) {
75
+ existing.stop("unsubscribe");
76
+ if (subscriptions.get(sessionId) === existing) {
77
+ subscriptions.delete(sessionId);
78
+ }
79
+ }
80
+ };
81
+ }
82
+ const subscription = {
83
+ abortController: null,
84
+ retries: 0,
85
+ retryTimer: null,
86
+ stopped: false,
87
+ listeners: /* @__PURE__ */ new Set([listener]),
88
+ stop: (reason) => {
89
+ var _a2;
90
+ subscription.stopped = true;
91
+ if (subscription.retryTimer) {
92
+ clearTimeout(subscription.retryTimer);
93
+ subscription.retryTimer = null;
94
+ }
95
+ (_a2 = subscription.abortController) == null ? void 0 : _a2.abort();
96
+ subscription.abortController = null;
97
+ logger == null ? void 0 : logger.debug("[aomi][sse] stop", {
98
+ sessionId,
99
+ reason,
100
+ retries: subscription.retries
101
+ });
102
+ }
103
+ };
104
+ const scheduleRetry = () => {
105
+ if (subscription.stopped) return;
106
+ subscription.retries += 1;
107
+ const delayMs = Math.min(500 * 2 ** (subscription.retries - 1), 1e4);
108
+ logger == null ? void 0 : logger.debug("[aomi][sse] retry scheduled", {
109
+ sessionId,
110
+ delayMs,
111
+ retries: subscription.retries
112
+ });
113
+ subscription.retryTimer = setTimeout(() => {
114
+ void open();
115
+ }, delayMs);
116
+ };
117
+ const open = async () => {
118
+ var _a2;
119
+ if (subscription.stopped) return;
120
+ if (subscription.retryTimer) {
121
+ clearTimeout(subscription.retryTimer);
122
+ subscription.retryTimer = null;
123
+ }
124
+ const controller = new AbortController();
125
+ subscription.abortController = controller;
126
+ const openedAt = Date.now();
127
+ try {
128
+ const response = await fetch(`${backendUrl}/api/updates`, {
129
+ headers: getHeaders(sessionId),
130
+ signal: controller.signal
131
+ });
132
+ if (!response.ok) {
133
+ throw new Error(
134
+ `SSE HTTP ${response.status}: ${response.statusText}`
135
+ );
136
+ }
137
+ if (!response.body) {
138
+ throw new Error("SSE response missing body");
139
+ }
140
+ subscription.retries = 0;
141
+ await readSseStream(response.body, controller.signal, (data) => {
142
+ var _a3, _b;
143
+ let parsed2;
144
+ try {
145
+ parsed2 = JSON.parse(data);
146
+ } catch (error) {
147
+ for (const item of subscription.listeners) {
148
+ (_a3 = item.onError) == null ? void 0 : _a3.call(item, error);
149
+ }
150
+ return;
151
+ }
152
+ for (const item of subscription.listeners) {
153
+ try {
154
+ item.onUpdate(parsed2);
155
+ } catch (error) {
156
+ (_b = item.onError) == null ? void 0 : _b.call(item, error);
157
+ }
158
+ }
159
+ });
160
+ logger == null ? void 0 : logger.debug("[aomi][sse] stream ended", {
161
+ sessionId,
162
+ aborted: controller.signal.aborted,
163
+ stopped: subscription.stopped,
164
+ durationMs: Date.now() - openedAt
165
+ });
166
+ } catch (error) {
167
+ if (!controller.signal.aborted && !subscription.stopped) {
168
+ for (const item of subscription.listeners) {
169
+ (_a2 = item.onError) == null ? void 0 : _a2.call(item, error);
170
+ }
171
+ }
172
+ }
173
+ if (!subscription.stopped) {
174
+ scheduleRetry();
175
+ }
176
+ };
177
+ subscriptions.set(sessionId, subscription);
178
+ void open();
179
+ return () => {
180
+ subscription.listeners.delete(listener);
181
+ logger == null ? void 0 : logger.debug("[aomi][sse] listener removed", {
182
+ sessionId,
183
+ listeners: subscription.listeners.size
184
+ });
185
+ if (subscription.listeners.size === 0) {
186
+ subscription.stop("unsubscribe");
187
+ if (subscriptions.get(sessionId) === subscription) {
188
+ subscriptions.delete(sessionId);
189
+ }
190
+ }
191
+ };
192
+ };
193
+ return { subscribe };
194
+ }
195
+
196
+ // src/client.ts
197
+ var SESSION_ID_HEADER = "X-Session-Id";
198
+ var API_KEY_HEADER = "X-API-Key";
199
+ function toQueryString(payload) {
200
+ const params = new URLSearchParams();
201
+ for (const [key, value] of Object.entries(payload)) {
202
+ if (value === void 0 || value === null) continue;
203
+ params.set(key, String(value));
204
+ }
205
+ const qs = params.toString();
206
+ return qs ? `?${qs}` : "";
207
+ }
208
+ function withSessionHeader(sessionId, init) {
209
+ const headers = new Headers(init);
210
+ headers.set(SESSION_ID_HEADER, sessionId);
211
+ return headers;
212
+ }
213
+ async function postState(baseUrl, path, payload, sessionId, apiKey) {
214
+ const query = toQueryString(payload);
215
+ const url = `${baseUrl}${path}${query}`;
216
+ const headers = new Headers(withSessionHeader(sessionId));
217
+ if (apiKey) {
218
+ headers.set(API_KEY_HEADER, apiKey);
219
+ }
220
+ const response = await fetch(url, {
221
+ method: "POST",
222
+ headers
223
+ });
224
+ if (!response.ok) {
225
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
226
+ }
227
+ return await response.json();
228
+ }
229
+ var AomiClient = class {
230
+ constructor(options) {
231
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
232
+ this.apiKey = options.apiKey;
233
+ this.logger = options.logger;
234
+ this.sseSubscriber = createSseSubscriber({
235
+ backendUrl: this.baseUrl,
236
+ getHeaders: (sessionId) => withSessionHeader(sessionId, { Accept: "text/event-stream" }),
237
+ logger: this.logger
238
+ });
239
+ }
240
+ // ===========================================================================
241
+ // Chat & State
242
+ // ===========================================================================
243
+ /**
244
+ * Fetch current session state (messages, processing status, title).
245
+ */
246
+ async fetchState(sessionId, userState) {
247
+ const url = new URL("/api/state", this.baseUrl);
248
+ if (userState) {
249
+ url.searchParams.set("user_state", JSON.stringify(userState));
250
+ }
251
+ const response = await fetch(url.toString(), {
252
+ headers: withSessionHeader(sessionId)
253
+ });
254
+ if (!response.ok) {
255
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
256
+ }
257
+ return await response.json();
258
+ }
259
+ /**
260
+ * Send a chat message and return updated session state.
261
+ */
262
+ async sendMessage(sessionId, message, options) {
263
+ var _a2, _b;
264
+ const namespace = (_a2 = options == null ? void 0 : options.namespace) != null ? _a2 : "default";
265
+ const apiKey = (_b = options == null ? void 0 : options.apiKey) != null ? _b : this.apiKey;
266
+ const payload = { message, namespace };
267
+ if (options == null ? void 0 : options.publicKey) {
268
+ payload.public_key = options.publicKey;
269
+ }
270
+ if (options == null ? void 0 : options.userState) {
271
+ payload.user_state = JSON.stringify(options.userState);
272
+ }
273
+ return postState(
274
+ this.baseUrl,
275
+ "/api/chat",
276
+ payload,
277
+ sessionId,
278
+ apiKey
279
+ );
280
+ }
281
+ /**
282
+ * Send a system-level message (e.g. wallet state changes, context switches).
283
+ */
284
+ async sendSystemMessage(sessionId, message) {
285
+ return postState(
286
+ this.baseUrl,
287
+ "/api/system",
288
+ { message },
289
+ sessionId
290
+ );
291
+ }
292
+ /**
293
+ * Interrupt the AI's current response.
294
+ */
295
+ async interrupt(sessionId) {
296
+ return postState(
297
+ this.baseUrl,
298
+ "/api/interrupt",
299
+ {},
300
+ sessionId
301
+ );
302
+ }
303
+ // ===========================================================================
304
+ // SSE (Real-time Updates)
305
+ // ===========================================================================
306
+ /**
307
+ * Subscribe to real-time SSE updates for a session.
308
+ * Automatically reconnects with exponential backoff on disconnects.
309
+ * Returns an unsubscribe function.
310
+ */
311
+ subscribeSSE(sessionId, onUpdate, onError) {
312
+ return this.sseSubscriber.subscribe(sessionId, onUpdate, onError);
313
+ }
314
+ // ===========================================================================
315
+ // Thread / Session Management
316
+ // ===========================================================================
317
+ /**
318
+ * List all threads for a wallet address.
319
+ */
320
+ async listThreads(publicKey) {
321
+ const url = `${this.baseUrl}/api/sessions?public_key=${encodeURIComponent(publicKey)}`;
322
+ const response = await fetch(url);
323
+ if (!response.ok) {
324
+ throw new Error(`Failed to fetch threads: HTTP ${response.status}`);
325
+ }
326
+ return await response.json();
327
+ }
328
+ /**
329
+ * Get a single thread by ID.
330
+ */
331
+ async getThread(sessionId) {
332
+ const url = `${this.baseUrl}/api/sessions/${encodeURIComponent(sessionId)}`;
333
+ const response = await fetch(url, {
334
+ headers: withSessionHeader(sessionId)
335
+ });
336
+ if (!response.ok) {
337
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
338
+ }
339
+ return await response.json();
340
+ }
341
+ /**
342
+ * Create a new thread. The client generates the session ID.
343
+ */
344
+ async createThread(threadId, publicKey) {
345
+ const body = {};
346
+ if (publicKey) body.public_key = publicKey;
347
+ const url = `${this.baseUrl}/api/sessions`;
348
+ const response = await fetch(url, {
349
+ method: "POST",
350
+ headers: withSessionHeader(threadId, {
351
+ "Content-Type": "application/json"
352
+ }),
353
+ body: JSON.stringify(body)
354
+ });
355
+ if (!response.ok) {
356
+ throw new Error(`Failed to create thread: HTTP ${response.status}`);
357
+ }
358
+ return await response.json();
359
+ }
360
+ /**
361
+ * Delete a thread by ID.
362
+ */
363
+ async deleteThread(sessionId) {
364
+ const url = `${this.baseUrl}/api/sessions/${encodeURIComponent(sessionId)}`;
365
+ const response = await fetch(url, {
366
+ method: "DELETE",
367
+ headers: withSessionHeader(sessionId)
368
+ });
369
+ if (!response.ok) {
370
+ throw new Error(`Failed to delete thread: HTTP ${response.status}`);
371
+ }
372
+ }
373
+ /**
374
+ * Rename a thread.
375
+ */
376
+ async renameThread(sessionId, newTitle) {
377
+ const url = `${this.baseUrl}/api/sessions/${encodeURIComponent(sessionId)}`;
378
+ const response = await fetch(url, {
379
+ method: "PATCH",
380
+ headers: withSessionHeader(sessionId, {
381
+ "Content-Type": "application/json"
382
+ }),
383
+ body: JSON.stringify({ title: newTitle })
384
+ });
385
+ if (!response.ok) {
386
+ throw new Error(`Failed to rename thread: HTTP ${response.status}`);
387
+ }
388
+ }
389
+ /**
390
+ * Archive a thread.
391
+ */
392
+ async archiveThread(sessionId) {
393
+ const url = `${this.baseUrl}/api/sessions/${encodeURIComponent(sessionId)}/archive`;
394
+ const response = await fetch(url, {
395
+ method: "POST",
396
+ headers: withSessionHeader(sessionId)
397
+ });
398
+ if (!response.ok) {
399
+ throw new Error(`Failed to archive thread: HTTP ${response.status}`);
400
+ }
401
+ }
402
+ /**
403
+ * Unarchive a thread.
404
+ */
405
+ async unarchiveThread(sessionId) {
406
+ const url = `${this.baseUrl}/api/sessions/${encodeURIComponent(sessionId)}/unarchive`;
407
+ const response = await fetch(url, {
408
+ method: "POST",
409
+ headers: withSessionHeader(sessionId)
410
+ });
411
+ if (!response.ok) {
412
+ throw new Error(`Failed to unarchive thread: HTTP ${response.status}`);
413
+ }
414
+ }
415
+ // ===========================================================================
416
+ // System Events
417
+ // ===========================================================================
418
+ /**
419
+ * Get system events for a session.
420
+ */
421
+ async getSystemEvents(sessionId, count) {
422
+ const url = new URL("/api/events", this.baseUrl);
423
+ if (count !== void 0) {
424
+ url.searchParams.set("count", String(count));
425
+ }
426
+ const response = await fetch(url.toString(), {
427
+ headers: withSessionHeader(sessionId)
428
+ });
429
+ if (!response.ok) {
430
+ if (response.status === 404) return [];
431
+ throw new Error(`Failed to get system events: HTTP ${response.status}`);
432
+ }
433
+ return await response.json();
434
+ }
435
+ // ===========================================================================
436
+ // Control API
437
+ // ===========================================================================
438
+ /**
439
+ * Get available namespaces.
440
+ */
441
+ async getNamespaces(sessionId, options) {
442
+ var _a2;
443
+ const url = new URL("/api/control/namespaces", this.baseUrl);
444
+ if (options == null ? void 0 : options.publicKey) {
445
+ url.searchParams.set("public_key", options.publicKey);
446
+ }
447
+ const apiKey = (_a2 = options == null ? void 0 : options.apiKey) != null ? _a2 : this.apiKey;
448
+ const headers = new Headers(withSessionHeader(sessionId));
449
+ if (apiKey) {
450
+ headers.set(API_KEY_HEADER, apiKey);
451
+ }
452
+ const response = await fetch(url.toString(), { headers });
453
+ if (!response.ok) {
454
+ throw new Error(`Failed to get namespaces: HTTP ${response.status}`);
455
+ }
456
+ return await response.json();
457
+ }
458
+ /**
459
+ * Get available models.
460
+ */
461
+ async getModels(sessionId) {
462
+ const url = new URL("/api/control/models", this.baseUrl);
463
+ const response = await fetch(url.toString(), {
464
+ headers: withSessionHeader(sessionId)
465
+ });
466
+ if (!response.ok) {
467
+ throw new Error(`Failed to get models: HTTP ${response.status}`);
468
+ }
469
+ return await response.json();
470
+ }
471
+ /**
472
+ * Set the model for a session.
473
+ */
474
+ async setModel(sessionId, rig, options) {
475
+ var _a2;
476
+ const apiKey = (_a2 = options == null ? void 0 : options.apiKey) != null ? _a2 : this.apiKey;
477
+ const payload = { rig };
478
+ if (options == null ? void 0 : options.namespace) {
479
+ payload.namespace = options.namespace;
480
+ }
481
+ return postState(this.baseUrl, "/api/control/model", payload, sessionId, apiKey);
482
+ }
483
+ };
484
+
485
+ // src/event-emitter.ts
486
+ var TypedEventEmitter = class {
487
+ constructor() {
488
+ this.listeners = /* @__PURE__ */ new Map();
489
+ }
490
+ /**
491
+ * Subscribe to an event type. Returns an unsubscribe function.
492
+ */
493
+ on(type, handler) {
494
+ let set = this.listeners.get(type);
495
+ if (!set) {
496
+ set = /* @__PURE__ */ new Set();
497
+ this.listeners.set(type, set);
498
+ }
499
+ set.add(handler);
500
+ return () => {
501
+ set.delete(handler);
502
+ if (set.size === 0) {
503
+ this.listeners.delete(type);
504
+ }
505
+ };
506
+ }
507
+ /**
508
+ * Subscribe to an event type for a single emission, then auto-unsubscribe.
509
+ */
510
+ once(type, handler) {
511
+ const wrapper = ((payload) => {
512
+ unsub();
513
+ handler(payload);
514
+ });
515
+ const unsub = this.on(type, wrapper);
516
+ return unsub;
517
+ }
518
+ /**
519
+ * Emit an event to all listeners of `type` and wildcard `"*"` listeners.
520
+ */
521
+ emit(type, payload) {
522
+ const typeSet = this.listeners.get(type);
523
+ if (typeSet) {
524
+ for (const handler of typeSet) {
525
+ handler(payload);
526
+ }
527
+ }
528
+ if (type !== "*") {
529
+ const wildcardSet = this.listeners.get("*");
530
+ if (wildcardSet) {
531
+ for (const handler of wildcardSet) {
532
+ handler({ type, payload });
533
+ }
534
+ }
535
+ }
536
+ }
537
+ /**
538
+ * Remove a specific handler from an event type.
539
+ */
540
+ off(type, handler) {
541
+ const set = this.listeners.get(type);
542
+ if (set) {
543
+ set.delete(handler);
544
+ if (set.size === 0) {
545
+ this.listeners.delete(type);
546
+ }
547
+ }
548
+ }
549
+ /**
550
+ * Remove all listeners for all event types.
551
+ */
552
+ removeAllListeners() {
553
+ this.listeners.clear();
554
+ }
555
+ };
556
+
557
+ // src/types.ts
558
+ function isInlineCall(event) {
559
+ return "InlineCall" in event;
560
+ }
561
+ function isSystemNotice(event) {
562
+ return "SystemNotice" in event;
563
+ }
564
+ function isSystemError(event) {
565
+ return "SystemError" in event;
566
+ }
567
+ function isAsyncCallback(event) {
568
+ return "AsyncCallback" in event;
569
+ }
570
+
571
+ // src/event-unwrap.ts
572
+ function unwrapSystemEvent(event) {
573
+ var _a2;
574
+ if (isInlineCall(event)) {
575
+ return {
576
+ type: event.InlineCall.type,
577
+ payload: (_a2 = event.InlineCall.payload) != null ? _a2 : event.InlineCall
578
+ };
579
+ }
580
+ if (isSystemNotice(event)) {
581
+ return {
582
+ type: "system_notice",
583
+ payload: { message: event.SystemNotice }
584
+ };
585
+ }
586
+ if (isSystemError(event)) {
587
+ return {
588
+ type: "system_error",
589
+ payload: { message: event.SystemError }
590
+ };
591
+ }
592
+ if (isAsyncCallback(event)) {
593
+ return {
594
+ type: "async_callback",
595
+ payload: event.AsyncCallback
596
+ };
597
+ }
598
+ return null;
599
+ }
600
+
601
+ // src/wallet-utils.ts
602
+ function asRecord(value) {
603
+ if (!value || typeof value !== "object" || Array.isArray(value))
604
+ return void 0;
605
+ return value;
606
+ }
607
+ function getToolArgs(payload) {
608
+ var _a2;
609
+ const root = asRecord(payload);
610
+ const nestedArgs = asRecord(root == null ? void 0 : root.args);
611
+ return (_a2 = nestedArgs != null ? nestedArgs : root) != null ? _a2 : {};
612
+ }
613
+ function parseChainId(value) {
614
+ if (typeof value === "number" && Number.isFinite(value)) return value;
615
+ if (typeof value !== "string") return void 0;
616
+ const trimmed = value.trim();
617
+ if (!trimmed) return void 0;
618
+ if (trimmed.startsWith("0x")) {
619
+ const parsedHex = Number.parseInt(trimmed.slice(2), 16);
620
+ return Number.isFinite(parsedHex) ? parsedHex : void 0;
621
+ }
622
+ const parsed2 = Number.parseInt(trimmed, 10);
623
+ return Number.isFinite(parsed2) ? parsed2 : void 0;
624
+ }
625
+ function normalizeTxPayload(payload) {
626
+ var _a2, _b, _c;
627
+ const root = asRecord(payload);
628
+ const args = getToolArgs(payload);
629
+ const ctx = asRecord(root == null ? void 0 : root.ctx);
630
+ const to = typeof args.to === "string" ? args.to : void 0;
631
+ if (!to) return null;
632
+ const valueRaw = args.value;
633
+ const value = typeof valueRaw === "string" ? valueRaw : typeof valueRaw === "number" && Number.isFinite(valueRaw) ? String(Math.trunc(valueRaw)) : void 0;
634
+ const data = typeof args.data === "string" ? args.data : void 0;
635
+ const chainId = (_c = (_b = (_a2 = parseChainId(args.chainId)) != null ? _a2 : parseChainId(args.chain_id)) != null ? _b : parseChainId(ctx == null ? void 0 : ctx.user_chain_id)) != null ? _c : parseChainId(ctx == null ? void 0 : ctx.userChainId);
636
+ return { to, value, data, chainId };
637
+ }
638
+ function normalizeEip712Payload(payload) {
639
+ var _a2;
640
+ const args = getToolArgs(payload);
641
+ const typedDataRaw = (_a2 = args.typed_data) != null ? _a2 : args.typedData;
642
+ let typedData;
643
+ if (typeof typedDataRaw === "string") {
644
+ try {
645
+ const parsed2 = JSON.parse(typedDataRaw);
646
+ if (parsed2 && typeof parsed2 === "object" && !Array.isArray(parsed2)) {
647
+ typedData = parsed2;
648
+ }
649
+ } catch (e) {
650
+ typedData = void 0;
651
+ }
652
+ } else if (typedDataRaw && typeof typedDataRaw === "object" && !Array.isArray(typedDataRaw)) {
653
+ typedData = typedDataRaw;
654
+ }
655
+ const description = typeof args.description === "string" ? args.description : void 0;
656
+ return { typed_data: typedData, description };
657
+ }
658
+
659
+ // src/session.ts
660
+ var Session = class extends TypedEventEmitter {
661
+ constructor(clientOrOptions, sessionOptions) {
662
+ var _a2, _b, _c;
663
+ super();
664
+ // Internal state
665
+ this.pollTimer = null;
666
+ this.unsubscribeSSE = null;
667
+ this._isProcessing = false;
668
+ this.walletRequests = [];
669
+ this.walletRequestNextId = 1;
670
+ this._messages = [];
671
+ this.closed = false;
672
+ // For send() blocking behavior
673
+ this.pendingResolve = null;
674
+ this.client = clientOrOptions instanceof AomiClient ? clientOrOptions : new AomiClient(clientOrOptions);
675
+ this.sessionId = (_a2 = sessionOptions == null ? void 0 : sessionOptions.sessionId) != null ? _a2 : crypto.randomUUID();
676
+ this.namespace = (_b = sessionOptions == null ? void 0 : sessionOptions.namespace) != null ? _b : "default";
677
+ this.publicKey = sessionOptions == null ? void 0 : sessionOptions.publicKey;
678
+ this.apiKey = sessionOptions == null ? void 0 : sessionOptions.apiKey;
679
+ this.userState = sessionOptions == null ? void 0 : sessionOptions.userState;
680
+ this.pollIntervalMs = (_c = sessionOptions == null ? void 0 : sessionOptions.pollIntervalMs) != null ? _c : 500;
681
+ this.logger = sessionOptions == null ? void 0 : sessionOptions.logger;
682
+ this.unsubscribeSSE = this.client.subscribeSSE(
683
+ this.sessionId,
684
+ (event) => this.handleSSEEvent(event),
685
+ (error) => this.emit("error", { error })
686
+ );
687
+ }
688
+ // ===========================================================================
689
+ // Public API — Chat
690
+ // ===========================================================================
691
+ /**
692
+ * Send a message and wait for the AI to finish processing.
693
+ *
694
+ * The returned promise resolves when `is_processing` becomes `false` AND
695
+ * there are no pending wallet requests. If a wallet request arrives
696
+ * mid-processing, polling continues but the promise pauses until the
697
+ * request is resolved or rejected via `resolve()` / `reject()`.
698
+ */
699
+ async send(message) {
700
+ this.assertOpen();
701
+ const response = await this.client.sendMessage(this.sessionId, message, {
702
+ namespace: this.namespace,
703
+ publicKey: this.publicKey,
704
+ apiKey: this.apiKey,
705
+ userState: this.userState
706
+ });
707
+ this.applyState(response);
708
+ if (!response.is_processing && this.walletRequests.length === 0) {
709
+ return { messages: this._messages, title: this._title };
710
+ }
711
+ this._isProcessing = true;
712
+ this.emit("processing_start", void 0);
713
+ return new Promise((resolve) => {
714
+ this.pendingResolve = resolve;
715
+ this.startPolling();
716
+ });
717
+ }
718
+ /**
719
+ * Send a message without waiting for completion.
720
+ * Polling starts in the background; listen to events for updates.
721
+ */
722
+ async sendAsync(message) {
723
+ this.assertOpen();
724
+ const response = await this.client.sendMessage(this.sessionId, message, {
725
+ namespace: this.namespace,
726
+ publicKey: this.publicKey,
727
+ apiKey: this.apiKey,
728
+ userState: this.userState
729
+ });
730
+ this.applyState(response);
731
+ if (response.is_processing) {
732
+ this._isProcessing = true;
733
+ this.emit("processing_start", void 0);
734
+ this.startPolling();
735
+ }
736
+ return response;
737
+ }
738
+ // ===========================================================================
739
+ // Public API — Wallet Request Resolution
740
+ // ===========================================================================
741
+ /**
742
+ * Resolve a pending wallet request (transaction or EIP-712 signing).
743
+ * Sends the result to the backend and resumes polling.
744
+ */
745
+ async resolve(requestId, result) {
746
+ var _a2;
747
+ const req = this.removeWalletRequest(requestId);
748
+ if (!req) {
749
+ throw new Error(`No pending wallet request with id "${requestId}"`);
750
+ }
751
+ if (req.kind === "transaction") {
752
+ await this.sendSystemEvent("wallet:tx_complete", {
753
+ txHash: (_a2 = result.txHash) != null ? _a2 : "",
754
+ status: "success",
755
+ amount: result.amount
756
+ });
757
+ } else {
758
+ const eip712Payload = req.payload;
759
+ await this.sendSystemEvent("wallet_eip712_response", {
760
+ status: "success",
761
+ signature: result.signature,
762
+ description: eip712Payload.description
763
+ });
764
+ }
765
+ if (this._isProcessing) {
766
+ this.startPolling();
767
+ }
768
+ }
769
+ /**
770
+ * Reject a pending wallet request.
771
+ * Sends an error to the backend and resumes polling.
772
+ */
773
+ async reject(requestId, reason) {
774
+ const req = this.removeWalletRequest(requestId);
775
+ if (!req) {
776
+ throw new Error(`No pending wallet request with id "${requestId}"`);
777
+ }
778
+ if (req.kind === "transaction") {
779
+ await this.sendSystemEvent("wallet:tx_complete", {
780
+ txHash: "",
781
+ status: "failed"
782
+ });
783
+ } else {
784
+ const eip712Payload = req.payload;
785
+ await this.sendSystemEvent("wallet_eip712_response", {
786
+ status: "failed",
787
+ error: reason != null ? reason : "Request rejected",
788
+ description: eip712Payload.description
789
+ });
790
+ }
791
+ if (this._isProcessing) {
792
+ this.startPolling();
793
+ }
794
+ }
795
+ // ===========================================================================
796
+ // Public API — Control
797
+ // ===========================================================================
798
+ /**
799
+ * Cancel the AI's current response.
800
+ */
801
+ async interrupt() {
802
+ this.stopPolling();
803
+ const response = await this.client.interrupt(this.sessionId);
804
+ this.applyState(response);
805
+ this._isProcessing = false;
806
+ this.emit("processing_end", void 0);
807
+ this.resolvePending();
808
+ }
809
+ /**
810
+ * Close the session. Stops polling, unsubscribes SSE, removes all listeners.
811
+ * The session cannot be used after closing.
812
+ */
813
+ close() {
814
+ var _a2;
815
+ if (this.closed) return;
816
+ this.closed = true;
817
+ this.stopPolling();
818
+ (_a2 = this.unsubscribeSSE) == null ? void 0 : _a2.call(this);
819
+ this.unsubscribeSSE = null;
820
+ this.resolvePending();
821
+ this.removeAllListeners();
822
+ }
823
+ // ===========================================================================
824
+ // Public API — Accessors
825
+ // ===========================================================================
826
+ /** Current messages in the session. */
827
+ getMessages() {
828
+ return this._messages;
829
+ }
830
+ /** Current session title. */
831
+ getTitle() {
832
+ return this._title;
833
+ }
834
+ /** Pending wallet requests waiting for resolve/reject. */
835
+ getPendingRequests() {
836
+ return [...this.walletRequests];
837
+ }
838
+ /** Whether the AI is currently processing. */
839
+ getIsProcessing() {
840
+ return this._isProcessing;
841
+ }
842
+ // ===========================================================================
843
+ // Internal — Polling (ported from PollingController)
844
+ // ===========================================================================
845
+ startPolling() {
846
+ var _a2;
847
+ if (this.pollTimer || this.closed) return;
848
+ (_a2 = this.logger) == null ? void 0 : _a2.debug("[session] polling started", this.sessionId);
849
+ this.pollTimer = setInterval(() => {
850
+ void this.pollTick();
851
+ }, this.pollIntervalMs);
852
+ }
853
+ stopPolling() {
854
+ var _a2;
855
+ if (this.pollTimer) {
856
+ clearInterval(this.pollTimer);
857
+ this.pollTimer = null;
858
+ (_a2 = this.logger) == null ? void 0 : _a2.debug("[session] polling stopped", this.sessionId);
859
+ }
860
+ }
861
+ async pollTick() {
862
+ var _a2;
863
+ if (!this.pollTimer) return;
864
+ try {
865
+ const state = await this.client.fetchState(
866
+ this.sessionId,
867
+ this.userState
868
+ );
869
+ if (!this.pollTimer) return;
870
+ this.applyState(state);
871
+ if (!state.is_processing && this.walletRequests.length === 0) {
872
+ this.stopPolling();
873
+ this._isProcessing = false;
874
+ this.emit("processing_end", void 0);
875
+ this.resolvePending();
876
+ }
877
+ } catch (error) {
878
+ (_a2 = this.logger) == null ? void 0 : _a2.debug("[session] poll error", error);
879
+ this.emit("error", { error });
880
+ }
881
+ }
882
+ // ===========================================================================
883
+ // Internal — State Application
884
+ // ===========================================================================
885
+ applyState(state) {
886
+ var _a2;
887
+ if (state.messages) {
888
+ this._messages = state.messages;
889
+ this.emit("messages", this._messages);
890
+ }
891
+ if (state.title) {
892
+ this._title = state.title;
893
+ }
894
+ if ((_a2 = state.system_events) == null ? void 0 : _a2.length) {
895
+ this.dispatchSystemEvents(state.system_events);
896
+ }
897
+ }
898
+ dispatchSystemEvents(events) {
899
+ var _a2;
900
+ for (const event of events) {
901
+ const unwrapped = unwrapSystemEvent(event);
902
+ if (!unwrapped) continue;
903
+ if (unwrapped.type === "wallet_tx_request") {
904
+ const payload = normalizeTxPayload(unwrapped.payload);
905
+ if (payload) {
906
+ const req = this.enqueueWalletRequest("transaction", payload);
907
+ this.emit("wallet_tx_request", req);
908
+ }
909
+ } else if (unwrapped.type === "wallet_eip712_request") {
910
+ const payload = normalizeEip712Payload((_a2 = unwrapped.payload) != null ? _a2 : {});
911
+ const req = this.enqueueWalletRequest("eip712_sign", payload);
912
+ this.emit("wallet_eip712_request", req);
913
+ } else if (unwrapped.type === "system_notice" || unwrapped.type === "system_error" || unwrapped.type === "async_callback") {
914
+ this.emit(
915
+ unwrapped.type,
916
+ unwrapped.payload
917
+ );
918
+ }
919
+ }
920
+ }
921
+ // ===========================================================================
922
+ // Internal — SSE Handling
923
+ // ===========================================================================
924
+ handleSSEEvent(event) {
925
+ if (event.type === "title_changed" && event.new_title) {
926
+ this._title = event.new_title;
927
+ this.emit("title_changed", { title: event.new_title });
928
+ } else if (event.type === "tool_update") {
929
+ this.emit("tool_update", event);
930
+ } else if (event.type === "tool_complete") {
931
+ this.emit("tool_complete", event);
932
+ }
933
+ }
934
+ // ===========================================================================
935
+ // Internal — Wallet Request Queue
936
+ // ===========================================================================
937
+ enqueueWalletRequest(kind, payload) {
938
+ const req = {
939
+ id: `wreq-${this.walletRequestNextId++}`,
940
+ kind,
941
+ payload,
942
+ timestamp: Date.now()
943
+ };
944
+ this.walletRequests.push(req);
945
+ return req;
946
+ }
947
+ removeWalletRequest(id) {
948
+ const idx = this.walletRequests.findIndex((r) => r.id === id);
949
+ if (idx === -1) return null;
950
+ return this.walletRequests.splice(idx, 1)[0];
951
+ }
952
+ // ===========================================================================
953
+ // Internal — Helpers
954
+ // ===========================================================================
955
+ async sendSystemEvent(type, payload) {
956
+ const message = JSON.stringify({ type, payload });
957
+ await this.client.sendSystemMessage(this.sessionId, message);
958
+ }
959
+ resolvePending() {
960
+ if (this.pendingResolve) {
961
+ const resolve = this.pendingResolve;
962
+ this.pendingResolve = null;
963
+ resolve({ messages: this._messages, title: this._title });
964
+ }
965
+ }
966
+ assertOpen() {
967
+ if (this.closed) {
968
+ throw new Error("Session is closed");
969
+ }
970
+ }
971
+ };
972
+
973
+ // src/cli-state.ts
974
+ import { readFileSync, writeFileSync, unlinkSync, existsSync } from "fs";
975
+ import { join } from "path";
976
+ import { tmpdir } from "os";
977
+ var _a;
978
+ var STATE_FILE = join(
979
+ (_a = process.env.XDG_RUNTIME_DIR) != null ? _a : tmpdir(),
980
+ "aomi-session.json"
981
+ );
982
+ function readState() {
983
+ try {
984
+ if (!existsSync(STATE_FILE)) return null;
985
+ const raw = readFileSync(STATE_FILE, "utf-8");
986
+ return JSON.parse(raw);
987
+ } catch (e) {
988
+ return null;
989
+ }
990
+ }
991
+ function writeState(state) {
992
+ writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
993
+ }
994
+ function clearState() {
995
+ try {
996
+ if (existsSync(STATE_FILE)) unlinkSync(STATE_FILE);
997
+ } catch (e) {
998
+ }
999
+ }
1000
+ function getNextTxId(state) {
1001
+ var _a2, _b;
1002
+ const allIds = [
1003
+ ...(_a2 = state.pendingTxs) != null ? _a2 : [],
1004
+ ...(_b = state.signedTxs) != null ? _b : []
1005
+ ].map((t) => {
1006
+ const m = t.id.match(/^tx-(\d+)$/);
1007
+ return m ? parseInt(m[1], 10) : 0;
1008
+ });
1009
+ const max = allIds.length > 0 ? Math.max(...allIds) : 0;
1010
+ return `tx-${max + 1}`;
1011
+ }
1012
+ function addPendingTx(state, tx) {
1013
+ if (!state.pendingTxs) state.pendingTxs = [];
1014
+ const pending = __spreadProps(__spreadValues({}, tx), {
1015
+ id: getNextTxId(state)
1016
+ });
1017
+ state.pendingTxs.push(pending);
1018
+ writeState(state);
1019
+ return pending;
1020
+ }
1021
+ function removePendingTx(state, id) {
1022
+ if (!state.pendingTxs) return null;
1023
+ const idx = state.pendingTxs.findIndex((t) => t.id === id);
1024
+ if (idx === -1) return null;
1025
+ const [removed] = state.pendingTxs.splice(idx, 1);
1026
+ writeState(state);
1027
+ return removed;
1028
+ }
1029
+ function addSignedTx(state, tx) {
1030
+ if (!state.signedTxs) state.signedTxs = [];
1031
+ state.signedTxs.push(tx);
1032
+ writeState(state);
1033
+ }
1034
+
1035
+ // src/cli.ts
1036
+ var CliExit = class extends Error {
1037
+ constructor(code) {
1038
+ super();
1039
+ this.code = code;
1040
+ }
1041
+ };
1042
+ function fatal(message) {
1043
+ console.error(message);
1044
+ throw new CliExit(1);
1045
+ }
1046
+ function printDataFileLocation() {
1047
+ console.log(`Data stored at ${STATE_FILE} \u{1F4DD}`);
1048
+ }
1049
+ function parseArgs(argv) {
1050
+ const raw = argv.slice(2);
1051
+ const command = raw[0] && !raw[0].startsWith("--") ? raw[0] : void 0;
1052
+ const rest = command ? raw.slice(1) : raw;
1053
+ const positional = [];
1054
+ const flags = {};
1055
+ for (let i = 0; i < rest.length; i++) {
1056
+ const arg = rest[i];
1057
+ if (arg.startsWith("--") && arg.includes("=")) {
1058
+ const [key, ...val] = arg.slice(2).split("=");
1059
+ flags[key] = val.join("=");
1060
+ } else if (arg.startsWith("--")) {
1061
+ const key = arg.slice(2);
1062
+ const next = rest[i + 1];
1063
+ if (next && !next.startsWith("-")) {
1064
+ flags[key] = next;
1065
+ i++;
1066
+ } else {
1067
+ flags[key] = "true";
1068
+ }
1069
+ } else if (arg.startsWith("-") && arg.length === 2) {
1070
+ flags[arg.slice(1)] = "true";
1071
+ } else {
1072
+ positional.push(arg);
1073
+ }
1074
+ }
1075
+ return { command, positional, flags };
1076
+ }
1077
+ var parsed = parseArgs(process.argv);
1078
+ function getConfig() {
1079
+ var _a2, _b, _c, _d, _e, _f, _g, _h;
1080
+ return {
1081
+ baseUrl: (_b = (_a2 = parsed.flags["backend-url"]) != null ? _a2 : process.env.AOMI_BASE_URL) != null ? _b : "https://api.aomi.dev",
1082
+ apiKey: (_c = parsed.flags["api-key"]) != null ? _c : process.env.AOMI_API_KEY,
1083
+ namespace: (_e = (_d = parsed.flags["namespace"]) != null ? _d : process.env.AOMI_NAMESPACE) != null ? _e : "default",
1084
+ publicKey: (_f = parsed.flags["public-key"]) != null ? _f : process.env.AOMI_PUBLIC_KEY,
1085
+ privateKey: (_g = parsed.flags["private-key"]) != null ? _g : process.env.PRIVATE_KEY,
1086
+ chainRpcUrl: (_h = parsed.flags["rpc-url"]) != null ? _h : process.env.CHAIN_RPC_URL
1087
+ };
1088
+ }
1089
+ function getOrCreateSession() {
1090
+ const config = getConfig();
1091
+ let state = readState();
1092
+ if (!state) {
1093
+ state = {
1094
+ sessionId: crypto.randomUUID(),
1095
+ baseUrl: config.baseUrl,
1096
+ namespace: config.namespace,
1097
+ apiKey: config.apiKey,
1098
+ publicKey: config.publicKey
1099
+ };
1100
+ writeState(state);
1101
+ } else {
1102
+ let changed = false;
1103
+ if (config.baseUrl && config.baseUrl !== state.baseUrl) {
1104
+ state.baseUrl = config.baseUrl;
1105
+ changed = true;
1106
+ }
1107
+ if (config.namespace && config.namespace !== state.namespace) {
1108
+ state.namespace = config.namespace;
1109
+ changed = true;
1110
+ }
1111
+ if (config.apiKey !== void 0 && config.apiKey !== state.apiKey) {
1112
+ state.apiKey = config.apiKey;
1113
+ changed = true;
1114
+ }
1115
+ if (config.publicKey !== void 0 && config.publicKey !== state.publicKey) {
1116
+ state.publicKey = config.publicKey;
1117
+ changed = true;
1118
+ }
1119
+ if (changed) writeState(state);
1120
+ }
1121
+ const session = new Session(
1122
+ { baseUrl: state.baseUrl, apiKey: state.apiKey },
1123
+ {
1124
+ sessionId: state.sessionId,
1125
+ namespace: state.namespace,
1126
+ apiKey: state.apiKey,
1127
+ publicKey: state.publicKey,
1128
+ userState: state.publicKey ? {
1129
+ address: state.publicKey,
1130
+ chainId: 1,
1131
+ isConnected: true
1132
+ } : void 0
1133
+ }
1134
+ );
1135
+ return { session, state };
1136
+ }
1137
+ function walletRequestToPendingTx(req) {
1138
+ if (req.kind === "transaction") {
1139
+ const p2 = req.payload;
1140
+ return {
1141
+ kind: "transaction",
1142
+ to: p2.to,
1143
+ value: p2.value,
1144
+ data: p2.data,
1145
+ chainId: p2.chainId,
1146
+ timestamp: req.timestamp,
1147
+ payload: req.payload
1148
+ };
1149
+ }
1150
+ const p = req.payload;
1151
+ return {
1152
+ kind: "eip712_sign",
1153
+ description: p.description,
1154
+ timestamp: req.timestamp,
1155
+ payload: req.payload
1156
+ };
1157
+ }
1158
+ function formatTxLine(tx, prefix) {
1159
+ var _a2;
1160
+ const parts = [`${prefix} ${tx.id}`];
1161
+ if (tx.kind === "transaction") {
1162
+ parts.push(`to: ${(_a2 = tx.to) != null ? _a2 : "?"}`);
1163
+ if (tx.value) parts.push(`value: ${tx.value}`);
1164
+ if (tx.chainId) parts.push(`chain: ${tx.chainId}`);
1165
+ if (tx.data) parts.push(`data: ${tx.data.slice(0, 20)}...`);
1166
+ } else {
1167
+ parts.push(`eip712`);
1168
+ if (tx.description) parts.push(tx.description);
1169
+ }
1170
+ parts.push(`(${new Date(tx.timestamp).toLocaleTimeString()})`);
1171
+ return parts.join(" ");
1172
+ }
1173
+ var DIM = "\x1B[2m";
1174
+ var CYAN = "\x1B[36m";
1175
+ var YELLOW = "\x1B[33m";
1176
+ var GREEN = "\x1B[32m";
1177
+ var RESET = "\x1B[0m";
1178
+ function printToolUpdate(event) {
1179
+ var _a2, _b, _c;
1180
+ const name = (_b = (_a2 = event.tool_name) != null ? _a2 : event.name) != null ? _b : "unknown";
1181
+ const status = (_c = event.status) != null ? _c : "running";
1182
+ console.log(`${DIM}\u{1F527} [tool] ${name}: ${status}${RESET}`);
1183
+ }
1184
+ function printToolComplete(event) {
1185
+ var _a2, _b, _c;
1186
+ const name = (_b = (_a2 = event.tool_name) != null ? _a2 : event.name) != null ? _b : "unknown";
1187
+ const result = (_c = event.result) != null ? _c : event.output;
1188
+ const line = result ? `${GREEN}\u2714 [tool] ${name} \u2192 ${result.slice(0, 120)}${result.length > 120 ? "\u2026" : ""}${RESET}` : `${GREEN}\u2714 [tool] ${name} done${RESET}`;
1189
+ console.log(line);
1190
+ }
1191
+ function printNewAgentMessages(messages, lastPrintedCount) {
1192
+ const agentMessages = messages.filter(
1193
+ (m) => m.sender === "agent" || m.sender === "assistant"
1194
+ );
1195
+ let handled = lastPrintedCount;
1196
+ for (let i = lastPrintedCount; i < agentMessages.length; i++) {
1197
+ const msg = agentMessages[i];
1198
+ if (msg.is_streaming) {
1199
+ break;
1200
+ }
1201
+ if (msg.content) {
1202
+ console.log(`${CYAN}\u{1F916} ${msg.content}${RESET}`);
1203
+ }
1204
+ handled = i + 1;
1205
+ }
1206
+ return handled;
1207
+ }
1208
+ async function chatCommand() {
1209
+ const message = parsed.positional.join(" ");
1210
+ if (!message) {
1211
+ fatal("Usage: aomi chat <message>");
1212
+ }
1213
+ const verbose = parsed.flags["verbose"] === "true" || parsed.flags["v"] === "true";
1214
+ const { session, state } = getOrCreateSession();
1215
+ if (state.publicKey) {
1216
+ await session.client.sendSystemMessage(
1217
+ session.sessionId,
1218
+ JSON.stringify({
1219
+ type: "wallet:state_changed",
1220
+ payload: {
1221
+ address: state.publicKey,
1222
+ chainId: 1,
1223
+ isConnected: true
1224
+ }
1225
+ })
1226
+ );
1227
+ }
1228
+ const capturedRequests = [];
1229
+ let printedAgentCount = 0;
1230
+ session.on("wallet_tx_request", (req) => capturedRequests.push(req));
1231
+ session.on("wallet_eip712_request", (req) => capturedRequests.push(req));
1232
+ try {
1233
+ await session.sendAsync(message);
1234
+ const allMsgs = session.getMessages();
1235
+ let seedIdx = allMsgs.length;
1236
+ for (let i = allMsgs.length - 1; i >= 0; i--) {
1237
+ if (allMsgs[i].sender === "user") {
1238
+ seedIdx = i;
1239
+ break;
1240
+ }
1241
+ }
1242
+ printedAgentCount = allMsgs.slice(0, seedIdx).filter(
1243
+ (m) => m.sender === "agent" || m.sender === "assistant"
1244
+ ).length;
1245
+ if (verbose) {
1246
+ if (session.getIsProcessing()) {
1247
+ console.log(`${DIM}\u23F3 Processing\u2026${RESET}`);
1248
+ }
1249
+ printedAgentCount = printNewAgentMessages(allMsgs, printedAgentCount);
1250
+ session.on("tool_update", (event) => printToolUpdate(event));
1251
+ session.on("tool_complete", (event) => printToolComplete(event));
1252
+ session.on("messages", (msgs) => {
1253
+ printedAgentCount = printNewAgentMessages(msgs, printedAgentCount);
1254
+ });
1255
+ session.on("system_notice", ({ message: msg }) => {
1256
+ console.log(`${YELLOW}\u{1F4E2} ${msg}${RESET}`);
1257
+ });
1258
+ session.on("system_error", ({ message: msg }) => {
1259
+ console.log(`\x1B[31m\u274C ${msg}${RESET}`);
1260
+ });
1261
+ }
1262
+ if (session.getIsProcessing()) {
1263
+ await new Promise((resolve) => {
1264
+ const checkWallet = () => {
1265
+ if (capturedRequests.length > 0) resolve();
1266
+ };
1267
+ session.on("wallet_tx_request", checkWallet);
1268
+ session.on("wallet_eip712_request", checkWallet);
1269
+ session.on("processing_end", () => resolve());
1270
+ });
1271
+ }
1272
+ if (verbose) {
1273
+ printNewAgentMessages(session.getMessages(), printedAgentCount);
1274
+ console.log(`${DIM}\u2705 Done${RESET}`);
1275
+ }
1276
+ for (const req of capturedRequests) {
1277
+ const pending = addPendingTx(state, walletRequestToPendingTx(req));
1278
+ console.log(`\u26A1 Wallet request queued: ${pending.id}`);
1279
+ if (req.kind === "transaction") {
1280
+ const p = req.payload;
1281
+ console.log(` to: ${p.to}`);
1282
+ if (p.value) console.log(` value: ${p.value}`);
1283
+ if (p.chainId) console.log(` chain: ${p.chainId}`);
1284
+ } else {
1285
+ const p = req.payload;
1286
+ if (p.description) console.log(` desc: ${p.description}`);
1287
+ }
1288
+ }
1289
+ if (!verbose) {
1290
+ const messages = session.getMessages();
1291
+ const agentMessages = messages.filter(
1292
+ (m) => m.sender === "agent" || m.sender === "assistant"
1293
+ );
1294
+ const last = agentMessages[agentMessages.length - 1];
1295
+ if (last == null ? void 0 : last.content) {
1296
+ console.log(last.content);
1297
+ } else if (capturedRequests.length === 0) {
1298
+ console.log("(no response)");
1299
+ }
1300
+ }
1301
+ if (capturedRequests.length > 0) {
1302
+ console.log(`
1303
+ Run \`aomi tx\` to see pending transactions, \`aomi sign <id>\` to sign.`);
1304
+ }
1305
+ } finally {
1306
+ session.close();
1307
+ }
1308
+ }
1309
+ async function statusCommand() {
1310
+ var _a2, _b, _c, _d, _e, _f, _g, _h;
1311
+ const state = readState();
1312
+ if (!state) {
1313
+ console.log("No active session");
1314
+ printDataFileLocation();
1315
+ return;
1316
+ }
1317
+ const { session } = getOrCreateSession();
1318
+ try {
1319
+ const apiState = await session.client.fetchState(state.sessionId);
1320
+ console.log(
1321
+ JSON.stringify(
1322
+ {
1323
+ sessionId: state.sessionId,
1324
+ baseUrl: state.baseUrl,
1325
+ namespace: state.namespace,
1326
+ isProcessing: (_a2 = apiState.is_processing) != null ? _a2 : false,
1327
+ messageCount: (_c = (_b = apiState.messages) == null ? void 0 : _b.length) != null ? _c : 0,
1328
+ title: (_d = apiState.title) != null ? _d : null,
1329
+ pendingTxs: (_f = (_e = state.pendingTxs) == null ? void 0 : _e.length) != null ? _f : 0,
1330
+ signedTxs: (_h = (_g = state.signedTxs) == null ? void 0 : _g.length) != null ? _h : 0
1331
+ },
1332
+ null,
1333
+ 2
1334
+ )
1335
+ );
1336
+ printDataFileLocation();
1337
+ } finally {
1338
+ session.close();
1339
+ }
1340
+ }
1341
+ async function eventsCommand() {
1342
+ const state = readState();
1343
+ if (!state) {
1344
+ console.log("No active session");
1345
+ return;
1346
+ }
1347
+ const { session } = getOrCreateSession();
1348
+ try {
1349
+ const events = await session.client.getSystemEvents(state.sessionId);
1350
+ console.log(JSON.stringify(events, null, 2));
1351
+ } finally {
1352
+ session.close();
1353
+ }
1354
+ }
1355
+ function txCommand() {
1356
+ var _a2, _b, _c;
1357
+ const state = readState();
1358
+ if (!state) {
1359
+ console.log("No active session");
1360
+ printDataFileLocation();
1361
+ return;
1362
+ }
1363
+ const pending = (_a2 = state.pendingTxs) != null ? _a2 : [];
1364
+ const signed = (_b = state.signedTxs) != null ? _b : [];
1365
+ if (pending.length === 0 && signed.length === 0) {
1366
+ console.log("No transactions.");
1367
+ printDataFileLocation();
1368
+ return;
1369
+ }
1370
+ if (pending.length > 0) {
1371
+ console.log(`Pending (${pending.length}):`);
1372
+ for (const tx of pending) {
1373
+ console.log(formatTxLine(tx, " \u23F3"));
1374
+ }
1375
+ }
1376
+ if (signed.length > 0) {
1377
+ if (pending.length > 0) console.log();
1378
+ console.log(`Signed (${signed.length}):`);
1379
+ for (const tx of signed) {
1380
+ const parts = [` \u2705 ${tx.id}`];
1381
+ if (tx.kind === "eip712_sign") {
1382
+ parts.push(`sig: ${(_c = tx.signature) == null ? void 0 : _c.slice(0, 20)}...`);
1383
+ if (tx.description) parts.push(tx.description);
1384
+ } else {
1385
+ parts.push(`hash: ${tx.txHash}`);
1386
+ if (tx.to) parts.push(`to: ${tx.to}`);
1387
+ if (tx.value) parts.push(`value: ${tx.value}`);
1388
+ }
1389
+ parts.push(`(${new Date(tx.timestamp).toLocaleTimeString()})`);
1390
+ console.log(parts.join(" "));
1391
+ }
1392
+ }
1393
+ printDataFileLocation();
1394
+ }
1395
+ async function signCommand() {
1396
+ var _a2, _b, _c, _d;
1397
+ const txId = parsed.positional[0];
1398
+ if (!txId) {
1399
+ fatal("Usage: aomi sign <tx-id>\nRun `aomi tx` to see pending transaction IDs.");
1400
+ }
1401
+ const config = getConfig();
1402
+ const privateKey = config.privateKey;
1403
+ if (!privateKey) {
1404
+ fatal("Private key required. Pass --private-key or set PRIVATE_KEY env var.");
1405
+ }
1406
+ const state = readState();
1407
+ if (!state) {
1408
+ fatal("No active session. Run `aomi chat` first.");
1409
+ }
1410
+ const pendingTx = ((_a2 = state.pendingTxs) != null ? _a2 : []).find((t) => t.id === txId);
1411
+ if (!pendingTx) {
1412
+ fatal(`No pending transaction with id "${txId}".
1413
+ Run \`aomi tx\` to see available IDs.`);
1414
+ }
1415
+ const { session } = getOrCreateSession();
1416
+ try {
1417
+ let viem;
1418
+ let viemAccounts;
1419
+ let viemChains;
1420
+ try {
1421
+ viem = await import("viem");
1422
+ viemAccounts = await import("viem/accounts");
1423
+ viemChains = await import("viem/chains");
1424
+ } catch (e) {
1425
+ fatal(
1426
+ "viem is required for `aomi sign`. Install it:\n npm install viem\n # or: pnpm add viem"
1427
+ );
1428
+ }
1429
+ const { createWalletClient, http } = viem;
1430
+ const { privateKeyToAccount } = viemAccounts;
1431
+ const account = privateKeyToAccount(privateKey);
1432
+ const rpcUrl = config.chainRpcUrl;
1433
+ const targetChainId = (_b = pendingTx.chainId) != null ? _b : 1;
1434
+ const chain = (_c = Object.values(viemChains).find(
1435
+ (c) => typeof c === "object" && c !== null && "id" in c && c.id === targetChainId
1436
+ )) != null ? _c : { id: targetChainId, name: `Chain ${targetChainId}`, nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 }, rpcUrls: { default: { http: [rpcUrl != null ? rpcUrl : ""] } } };
1437
+ const walletClient = createWalletClient({
1438
+ account,
1439
+ chain,
1440
+ transport: http(rpcUrl)
1441
+ });
1442
+ console.log(`Signer: ${account.address}`);
1443
+ console.log(`ID: ${pendingTx.id}`);
1444
+ console.log(`Kind: ${pendingTx.kind}`);
1445
+ if (pendingTx.kind === "transaction") {
1446
+ console.log(`To: ${pendingTx.to}`);
1447
+ if (pendingTx.value) console.log(`Value: ${pendingTx.value}`);
1448
+ if (pendingTx.chainId) console.log(`Chain: ${pendingTx.chainId}`);
1449
+ if (pendingTx.data) console.log(`Data: ${pendingTx.data.slice(0, 40)}...`);
1450
+ console.log();
1451
+ const hash = await walletClient.sendTransaction({
1452
+ to: pendingTx.to,
1453
+ value: pendingTx.value ? BigInt(pendingTx.value) : /* @__PURE__ */ BigInt("0"),
1454
+ data: (_d = pendingTx.data) != null ? _d : void 0
1455
+ });
1456
+ console.log(`\u2705 Sent! Hash: ${hash}`);
1457
+ removePendingTx(state, txId);
1458
+ const freshState = readState();
1459
+ addSignedTx(freshState, {
1460
+ id: txId,
1461
+ kind: "transaction",
1462
+ txHash: hash,
1463
+ from: account.address,
1464
+ to: pendingTx.to,
1465
+ value: pendingTx.value,
1466
+ chainId: pendingTx.chainId,
1467
+ timestamp: Date.now()
1468
+ });
1469
+ await session.client.sendSystemMessage(
1470
+ state.sessionId,
1471
+ JSON.stringify({
1472
+ type: "wallet:tx_complete",
1473
+ payload: { txHash: hash, status: "success" }
1474
+ })
1475
+ );
1476
+ } else {
1477
+ const typedData = pendingTx.payload.typed_data;
1478
+ if (!typedData) {
1479
+ fatal("EIP-712 request is missing typed_data payload.");
1480
+ }
1481
+ if (pendingTx.description) console.log(`Desc: ${pendingTx.description}`);
1482
+ if (typedData.primaryType) console.log(`Type: ${typedData.primaryType}`);
1483
+ console.log();
1484
+ const { domain, types, primaryType, message } = typedData;
1485
+ const sigTypes = __spreadValues({}, types);
1486
+ delete sigTypes["EIP712Domain"];
1487
+ const signature = await walletClient.signTypedData({
1488
+ domain,
1489
+ types: sigTypes,
1490
+ primaryType,
1491
+ message
1492
+ });
1493
+ console.log(`\u2705 Signed! Signature: ${signature.slice(0, 20)}...`);
1494
+ removePendingTx(state, txId);
1495
+ const freshState = readState();
1496
+ addSignedTx(freshState, {
1497
+ id: txId,
1498
+ kind: "eip712_sign",
1499
+ signature,
1500
+ from: account.address,
1501
+ description: pendingTx.description,
1502
+ timestamp: Date.now()
1503
+ });
1504
+ await session.client.sendSystemMessage(
1505
+ state.sessionId,
1506
+ JSON.stringify({
1507
+ type: "wallet_eip712_response",
1508
+ payload: {
1509
+ status: "success",
1510
+ signature,
1511
+ description: pendingTx.description
1512
+ }
1513
+ })
1514
+ );
1515
+ }
1516
+ console.log("Backend notified.");
1517
+ } catch (err) {
1518
+ if (err instanceof CliExit) throw err;
1519
+ const errMsg = err instanceof Error ? err.message : String(err);
1520
+ fatal(`\u274C Signing failed: ${errMsg}`);
1521
+ } finally {
1522
+ session.close();
1523
+ }
1524
+ }
1525
+ async function logCommand() {
1526
+ var _a2, _b, _c, _d, _e;
1527
+ const state = readState();
1528
+ if (!state) {
1529
+ console.log("No active session");
1530
+ printDataFileLocation();
1531
+ return;
1532
+ }
1533
+ const { session } = getOrCreateSession();
1534
+ try {
1535
+ const apiState = await session.client.fetchState(state.sessionId);
1536
+ const messages = (_a2 = apiState.messages) != null ? _a2 : [];
1537
+ if (messages.length === 0) {
1538
+ console.log("No messages in this session.");
1539
+ printDataFileLocation();
1540
+ return;
1541
+ }
1542
+ for (const msg of messages) {
1543
+ let time = "";
1544
+ if (msg.timestamp) {
1545
+ const raw = msg.timestamp;
1546
+ const n = /^\d+$/.test(raw) ? parseInt(raw, 10) : NaN;
1547
+ const date = !isNaN(n) ? new Date(n < 1e12 ? n * 1e3 : n) : new Date(raw);
1548
+ time = isNaN(date.getTime()) ? "" : `${DIM}${date.toLocaleTimeString()}${RESET} `;
1549
+ }
1550
+ const sender = (_b = msg.sender) != null ? _b : "unknown";
1551
+ if (sender === "user") {
1552
+ console.log(`${time}${CYAN}\u{1F464} You:${RESET} ${(_c = msg.content) != null ? _c : ""}`);
1553
+ } else if (sender === "agent" || sender === "assistant") {
1554
+ if (msg.tool_result) {
1555
+ const [toolName, result] = msg.tool_result;
1556
+ console.log(
1557
+ `${time}${GREEN}\u{1F527} [${toolName}]${RESET} ${result.slice(0, 200)}${result.length > 200 ? "\u2026" : ""}`
1558
+ );
1559
+ }
1560
+ if (msg.content) {
1561
+ console.log(`${time}${CYAN}\u{1F916} Agent:${RESET} ${msg.content}`);
1562
+ }
1563
+ } else if (sender === "system") {
1564
+ console.log(`${time}${YELLOW}\u2699\uFE0F System:${RESET} ${(_d = msg.content) != null ? _d : ""}`);
1565
+ } else {
1566
+ console.log(`${time}${DIM}[${sender}]${RESET} ${(_e = msg.content) != null ? _e : ""}`);
1567
+ }
1568
+ }
1569
+ console.log(`
1570
+ ${DIM}\u2014 ${messages.length} messages \u2014${RESET}`);
1571
+ printDataFileLocation();
1572
+ } finally {
1573
+ session.close();
1574
+ }
1575
+ }
1576
+ function closeCommand() {
1577
+ const state = readState();
1578
+ if (state) {
1579
+ const { session } = getOrCreateSession();
1580
+ session.close();
1581
+ }
1582
+ clearState();
1583
+ console.log("Session closed");
1584
+ }
1585
+ function printUsage() {
1586
+ console.log(`
1587
+ aomi \u2014 CLI client for Aomi on-chain agent
1588
+
1589
+ Usage:
1590
+ aomi chat <message> Send a message and print the response
1591
+ aomi chat --verbose Stream agent responses, tool calls, and events live
1592
+ aomi log Show full conversation history with tool results
1593
+ aomi tx List pending and signed transactions
1594
+ aomi sign <tx-id> Sign and submit a pending transaction
1595
+ aomi status Show current session state
1596
+ aomi events List system events
1597
+ aomi close Close the current session
1598
+
1599
+ Options:
1600
+ --backend-url <url> Backend URL (default: https://api.aomi.dev)
1601
+ --api-key <key> API key for non-default namespaces
1602
+ --namespace <ns> Namespace (default: "default")
1603
+ --public-key <addr> Wallet address (so the agent knows your wallet)
1604
+ --private-key <key> Hex private key for signing
1605
+ --rpc-url <url> RPC URL for transaction submission
1606
+ --verbose, -v Show tool calls and streaming output (for chat)
1607
+
1608
+ Environment (overridden by flags):
1609
+ AOMI_BASE_URL Backend URL
1610
+ AOMI_API_KEY API key
1611
+ AOMI_NAMESPACE Namespace
1612
+ AOMI_PUBLIC_KEY Wallet address
1613
+ PRIVATE_KEY Hex private key for signing
1614
+ CHAIN_RPC_URL RPC URL for transaction submission
1615
+ `.trim());
1616
+ }
1617
+ async function main() {
1618
+ var _a2;
1619
+ const cmd = (_a2 = parsed.command) != null ? _a2 : parsed.flags["help"] || parsed.flags["h"] ? "help" : void 0;
1620
+ switch (cmd) {
1621
+ case "chat":
1622
+ await chatCommand();
1623
+ break;
1624
+ case "log":
1625
+ await logCommand();
1626
+ break;
1627
+ case "tx":
1628
+ txCommand();
1629
+ break;
1630
+ case "sign":
1631
+ await signCommand();
1632
+ break;
1633
+ case "status":
1634
+ await statusCommand();
1635
+ break;
1636
+ case "events":
1637
+ await eventsCommand();
1638
+ break;
1639
+ case "close":
1640
+ closeCommand();
1641
+ break;
1642
+ case "help":
1643
+ printUsage();
1644
+ break;
1645
+ default:
1646
+ printUsage();
1647
+ if (cmd) throw new CliExit(1);
1648
+ }
1649
+ }
1650
+ main().catch((err) => {
1651
+ if (err instanceof CliExit) {
1652
+ process.exit(err.code);
1653
+ return;
1654
+ }
1655
+ console.error(err instanceof Error ? err.message : err);
1656
+ process.exit(1);
1657
+ });