appduct 0.13.0 → 0.14.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.
Files changed (36) hide show
  1. package/README.md +3 -2
  2. package/dist/bin.js +61 -4
  3. package/dist/cli/result-types.d.ts +8 -1
  4. package/dist/cli/routes/daemon/index.js +58 -1
  5. package/dist/cli/routes/daemon/run.js +152 -22
  6. package/dist/cli/routes/daemon/start.js +58 -1
  7. package/dist/cli/routes/daemon/status.js +58 -1
  8. package/dist/cli/routes/daemon/stop.js +58 -1
  9. package/dist/cli/routes/doctor.js +58 -1
  10. package/dist/cli/routes/events/index.d.ts +0 -3
  11. package/dist/cli/routes/events/index.js +60 -2
  12. package/dist/cli/routes/events/ls.d.ts +5 -0
  13. package/dist/cli/routes/events/ls.js +1798 -0
  14. package/dist/cli/routes/events/since.js +58 -1
  15. package/dist/cli/routes/events/tail.js +58 -1
  16. package/dist/cli/routes/init.js +58 -1
  17. package/dist/cli/routes/keygen.js +58 -1
  18. package/dist/cli/routes/mcp.js +108 -1
  19. package/dist/cli/routes/sessions/index.js +58 -1
  20. package/dist/cli/routes/sessions/link.js +58 -1
  21. package/dist/cli/routes/sessions/ls.js +58 -1
  22. package/dist/cli/routes/sessions/revoke.js +58 -1
  23. package/dist/cli/routes/tools/call.js +58 -1
  24. package/dist/cli/routes/tools/describe.js +58 -1
  25. package/dist/cli/routes/tools/index.js +58 -1
  26. package/dist/cli/routes/tools/ls.js +58 -1
  27. package/dist/commands/events-ls.d.ts +21 -0
  28. package/dist/daemon/calls.d.ts +3 -1
  29. package/dist/daemon/event-bus.d.ts +10 -0
  30. package/dist/daemon/registry.d.ts +20 -12
  31. package/dist/daemon/rpc-errors.d.ts +3 -0
  32. package/dist/daemon/sessions.d.ts +4 -2
  33. package/dist/index.js +61 -4
  34. package/dist/mcp/app-tools.d.ts +19 -0
  35. package/dist/mcp/events-tool.d.ts +39 -0
  36. package/package.json +2 -2
@@ -1079,6 +1079,7 @@ var handleEventsCommand = async (options, context) => {
1079
1079
  import pc from "picocolors";
1080
1080
  import {
1081
1081
  formatAgentWebSocketUrl,
1082
+ renderEventSignature,
1082
1083
  renderToolSignature,
1083
1084
  summarizeToolDescription
1084
1085
  } from "@appduct/shared";
@@ -1179,6 +1180,13 @@ var formatDevice = (device) => {
1179
1180
  const label = parts.length > 0 ? parts.join(" ") : "unknown device";
1180
1181
  return device.os ? `${label} (${device.os})` : label;
1181
1182
  };
1183
+ var SUSPEND_REASON_LABELS = {
1184
+ app_backgrounded: "app in background",
1185
+ connection_lost: "connection lost"
1186
+ };
1187
+ var formatSessionState = (session) => {
1188
+ return session.suspendReason ? `${session.state} (${SUSPEND_REASON_LABELS[session.suspendReason]})` : session.state;
1189
+ };
1182
1190
  var renderLsData = (colors, data, now) => {
1183
1191
  if (data.length === 0) {
1184
1192
  return [colors.dim("Sessions"), " No Appduct sessions are registered."];
@@ -1187,7 +1195,7 @@ var renderLsData = (colors, data, now) => {
1187
1195
  const headers = ["Alias", "State", "Device", "Tools", "Age"];
1188
1196
  const rows = data.map((session) => [
1189
1197
  session.alias,
1190
- session.state,
1198
+ formatSessionState(session),
1191
1199
  formatDevice(session.device),
1192
1200
  String(session.toolCount),
1193
1201
  formatAge(session.claimedAt ?? session.createdAt, nowIso)
@@ -1361,6 +1369,53 @@ var renderToolsListData = (colors, data, flags, full) => {
1361
1369
  }
1362
1370
  return full ? renderToolsFullListing(colors, data, flags) : renderToolSummaryTable(colors, data);
1363
1371
  };
1372
+ var renderEventsTruncationLine = (data) => {
1373
+ const offset = data.offset ?? 0;
1374
+ if (data.events.length >= data.total) {
1375
+ return [];
1376
+ }
1377
+ return [
1378
+ "",
1379
+ `Showing ${data.events.length} of ${data.total} events (offset ${offset}). Narrow with --name <glob> or page with --offset <n>.`
1380
+ ];
1381
+ };
1382
+ var renderEventsListData = (colors, data, flags) => {
1383
+ const [only] = data.events;
1384
+ if (data.name !== void 0 && !data.name.includes("*") && only !== void 0) {
1385
+ return renderFields(
1386
+ colors.green(`Event: ${only.name}`),
1387
+ [
1388
+ ["Signature", renderEventSignature(only)],
1389
+ ["Description", only.description],
1390
+ ["Payload schema", only.payload_schema]
1391
+ ],
1392
+ flags
1393
+ );
1394
+ }
1395
+ if (data.events.length === 0 && data.total > 0) {
1396
+ return [
1397
+ colors.green("Events"),
1398
+ ` No events at offset ${data.offset ?? 0}; ${data.total} matching event${data.total === 1 ? "" : "s"} in total.`
1399
+ ];
1400
+ }
1401
+ if (data.events.length === 0) {
1402
+ return [
1403
+ colors.green("Events"),
1404
+ data.name === void 0 ? " No events declared." : ` No events match ${JSON.stringify(data.name)}.`
1405
+ ];
1406
+ }
1407
+ const lines = data.events.flatMap((event) => [
1408
+ ` ${renderEventSignature(event)}`,
1409
+ ` ${summarizeToolDescription(event.description)}`
1410
+ ]);
1411
+ return [
1412
+ colors.green("Events"),
1413
+ ...lines,
1414
+ ...renderEventsTruncationLine(data),
1415
+ "",
1416
+ "Run `appduct events ls --name <name>` for an event's full payload schema."
1417
+ ];
1418
+ };
1364
1419
  var renderInvokeData = (colors, data, flags) => {
1365
1420
  return [colors.green("Result"), formatScalar(data, flags)];
1366
1421
  };
@@ -1552,6 +1607,8 @@ var renderSuccessData = (colors, command, data, options) => {
1552
1607
  return renderToolsListData(colors, data, flags, options.full);
1553
1608
  case "tools describe":
1554
1609
  return renderToolDetail(colors, data, flags);
1610
+ case "events ls":
1611
+ return renderEventsListData(colors, data, flags);
1555
1612
  case "tools call":
1556
1613
  return renderInvokeData(colors, data, flags);
1557
1614
  case "sessions revoke":
@@ -1079,6 +1079,7 @@ var handleEventsCommand = async (options, context) => {
1079
1079
  import pc from "picocolors";
1080
1080
  import {
1081
1081
  formatAgentWebSocketUrl,
1082
+ renderEventSignature,
1082
1083
  renderToolSignature,
1083
1084
  summarizeToolDescription
1084
1085
  } from "@appduct/shared";
@@ -1179,6 +1180,13 @@ var formatDevice = (device) => {
1179
1180
  const label = parts.length > 0 ? parts.join(" ") : "unknown device";
1180
1181
  return device.os ? `${label} (${device.os})` : label;
1181
1182
  };
1183
+ var SUSPEND_REASON_LABELS = {
1184
+ app_backgrounded: "app in background",
1185
+ connection_lost: "connection lost"
1186
+ };
1187
+ var formatSessionState = (session) => {
1188
+ return session.suspendReason ? `${session.state} (${SUSPEND_REASON_LABELS[session.suspendReason]})` : session.state;
1189
+ };
1182
1190
  var renderLsData = (colors, data, now) => {
1183
1191
  if (data.length === 0) {
1184
1192
  return [colors.dim("Sessions"), " No Appduct sessions are registered."];
@@ -1187,7 +1195,7 @@ var renderLsData = (colors, data, now) => {
1187
1195
  const headers = ["Alias", "State", "Device", "Tools", "Age"];
1188
1196
  const rows = data.map((session) => [
1189
1197
  session.alias,
1190
- session.state,
1198
+ formatSessionState(session),
1191
1199
  formatDevice(session.device),
1192
1200
  String(session.toolCount),
1193
1201
  formatAge(session.claimedAt ?? session.createdAt, nowIso)
@@ -1361,6 +1369,53 @@ var renderToolsListData = (colors, data, flags, full) => {
1361
1369
  }
1362
1370
  return full ? renderToolsFullListing(colors, data, flags) : renderToolSummaryTable(colors, data);
1363
1371
  };
1372
+ var renderEventsTruncationLine = (data) => {
1373
+ const offset = data.offset ?? 0;
1374
+ if (data.events.length >= data.total) {
1375
+ return [];
1376
+ }
1377
+ return [
1378
+ "",
1379
+ `Showing ${data.events.length} of ${data.total} events (offset ${offset}). Narrow with --name <glob> or page with --offset <n>.`
1380
+ ];
1381
+ };
1382
+ var renderEventsListData = (colors, data, flags) => {
1383
+ const [only] = data.events;
1384
+ if (data.name !== void 0 && !data.name.includes("*") && only !== void 0) {
1385
+ return renderFields(
1386
+ colors.green(`Event: ${only.name}`),
1387
+ [
1388
+ ["Signature", renderEventSignature(only)],
1389
+ ["Description", only.description],
1390
+ ["Payload schema", only.payload_schema]
1391
+ ],
1392
+ flags
1393
+ );
1394
+ }
1395
+ if (data.events.length === 0 && data.total > 0) {
1396
+ return [
1397
+ colors.green("Events"),
1398
+ ` No events at offset ${data.offset ?? 0}; ${data.total} matching event${data.total === 1 ? "" : "s"} in total.`
1399
+ ];
1400
+ }
1401
+ if (data.events.length === 0) {
1402
+ return [
1403
+ colors.green("Events"),
1404
+ data.name === void 0 ? " No events declared." : ` No events match ${JSON.stringify(data.name)}.`
1405
+ ];
1406
+ }
1407
+ const lines = data.events.flatMap((event) => [
1408
+ ` ${renderEventSignature(event)}`,
1409
+ ` ${summarizeToolDescription(event.description)}`
1410
+ ]);
1411
+ return [
1412
+ colors.green("Events"),
1413
+ ...lines,
1414
+ ...renderEventsTruncationLine(data),
1415
+ "",
1416
+ "Run `appduct events ls --name <name>` for an event's full payload schema."
1417
+ ];
1418
+ };
1364
1419
  var renderInvokeData = (colors, data, flags) => {
1365
1420
  return [colors.green("Result"), formatScalar(data, flags)];
1366
1421
  };
@@ -1552,6 +1607,8 @@ var renderSuccessData = (colors, command, data, options) => {
1552
1607
  return renderToolsListData(colors, data, flags, options.full);
1553
1608
  case "tools describe":
1554
1609
  return renderToolDetail(colors, data, flags);
1610
+ case "events ls":
1611
+ return renderEventsListData(colors, data, flags);
1555
1612
  case "tools call":
1556
1613
  return renderInvokeData(colors, data, flags);
1557
1614
  case "sessions revoke":
@@ -861,6 +861,7 @@ var formatJson = (value, flags) => {
861
861
  import pc from "picocolors";
862
862
  import {
863
863
  formatAgentWebSocketUrl,
864
+ renderEventSignature,
864
865
  renderToolSignature,
865
866
  summarizeToolDescription
866
867
  } from "@appduct/shared";
@@ -956,6 +957,13 @@ var formatDevice = (device) => {
956
957
  const label = parts.length > 0 ? parts.join(" ") : "unknown device";
957
958
  return device.os ? `${label} (${device.os})` : label;
958
959
  };
960
+ var SUSPEND_REASON_LABELS = {
961
+ app_backgrounded: "app in background",
962
+ connection_lost: "connection lost"
963
+ };
964
+ var formatSessionState = (session) => {
965
+ return session.suspendReason ? `${session.state} (${SUSPEND_REASON_LABELS[session.suspendReason]})` : session.state;
966
+ };
959
967
  var renderLsData = (colors, data, now) => {
960
968
  if (data.length === 0) {
961
969
  return [colors.dim("Sessions"), " No Appduct sessions are registered."];
@@ -964,7 +972,7 @@ var renderLsData = (colors, data, now) => {
964
972
  const headers = ["Alias", "State", "Device", "Tools", "Age"];
965
973
  const rows = data.map((session) => [
966
974
  session.alias,
967
- session.state,
975
+ formatSessionState(session),
968
976
  formatDevice(session.device),
969
977
  String(session.toolCount),
970
978
  formatAge(session.claimedAt ?? session.createdAt, nowIso)
@@ -1138,6 +1146,53 @@ var renderToolsListData = (colors, data, flags, full) => {
1138
1146
  }
1139
1147
  return full ? renderToolsFullListing(colors, data, flags) : renderToolSummaryTable(colors, data);
1140
1148
  };
1149
+ var renderEventsTruncationLine = (data) => {
1150
+ const offset = data.offset ?? 0;
1151
+ if (data.events.length >= data.total) {
1152
+ return [];
1153
+ }
1154
+ return [
1155
+ "",
1156
+ `Showing ${data.events.length} of ${data.total} events (offset ${offset}). Narrow with --name <glob> or page with --offset <n>.`
1157
+ ];
1158
+ };
1159
+ var renderEventsListData = (colors, data, flags) => {
1160
+ const [only] = data.events;
1161
+ if (data.name !== void 0 && !data.name.includes("*") && only !== void 0) {
1162
+ return renderFields(
1163
+ colors.green(`Event: ${only.name}`),
1164
+ [
1165
+ ["Signature", renderEventSignature(only)],
1166
+ ["Description", only.description],
1167
+ ["Payload schema", only.payload_schema]
1168
+ ],
1169
+ flags
1170
+ );
1171
+ }
1172
+ if (data.events.length === 0 && data.total > 0) {
1173
+ return [
1174
+ colors.green("Events"),
1175
+ ` No events at offset ${data.offset ?? 0}; ${data.total} matching event${data.total === 1 ? "" : "s"} in total.`
1176
+ ];
1177
+ }
1178
+ if (data.events.length === 0) {
1179
+ return [
1180
+ colors.green("Events"),
1181
+ data.name === void 0 ? " No events declared." : ` No events match ${JSON.stringify(data.name)}.`
1182
+ ];
1183
+ }
1184
+ const lines = data.events.flatMap((event) => [
1185
+ ` ${renderEventSignature(event)}`,
1186
+ ` ${summarizeToolDescription(event.description)}`
1187
+ ]);
1188
+ return [
1189
+ colors.green("Events"),
1190
+ ...lines,
1191
+ ...renderEventsTruncationLine(data),
1192
+ "",
1193
+ "Run `appduct events ls --name <name>` for an event's full payload schema."
1194
+ ];
1195
+ };
1141
1196
  var renderInvokeData = (colors, data, flags) => {
1142
1197
  return [colors.green("Result"), formatScalar(data, flags)];
1143
1198
  };
@@ -1329,6 +1384,8 @@ var renderSuccessData = (colors, command, data, options) => {
1329
1384
  return renderToolsListData(colors, data, flags, options.full);
1330
1385
  case "tools describe":
1331
1386
  return renderToolDetail(colors, data, flags);
1387
+ case "events ls":
1388
+ return renderEventsListData(colors, data, flags);
1332
1389
  case "tools call":
1333
1390
  return renderInvokeData(colors, data, flags);
1334
1391
  case "sessions revoke":
@@ -310,6 +310,7 @@ var formatJson = (value, flags) => {
310
310
  import pc from "picocolors";
311
311
  import {
312
312
  formatAgentWebSocketUrl,
313
+ renderEventSignature,
313
314
  renderToolSignature,
314
315
  summarizeToolDescription
315
316
  } from "@appduct/shared";
@@ -405,6 +406,13 @@ var formatDevice = (device) => {
405
406
  const label = parts.length > 0 ? parts.join(" ") : "unknown device";
406
407
  return device.os ? `${label} (${device.os})` : label;
407
408
  };
409
+ var SUSPEND_REASON_LABELS = {
410
+ app_backgrounded: "app in background",
411
+ connection_lost: "connection lost"
412
+ };
413
+ var formatSessionState = (session) => {
414
+ return session.suspendReason ? `${session.state} (${SUSPEND_REASON_LABELS[session.suspendReason]})` : session.state;
415
+ };
408
416
  var renderLsData = (colors, data, now) => {
409
417
  if (data.length === 0) {
410
418
  return [colors.dim("Sessions"), " No Appduct sessions are registered."];
@@ -413,7 +421,7 @@ var renderLsData = (colors, data, now) => {
413
421
  const headers = ["Alias", "State", "Device", "Tools", "Age"];
414
422
  const rows = data.map((session) => [
415
423
  session.alias,
416
- session.state,
424
+ formatSessionState(session),
417
425
  formatDevice(session.device),
418
426
  String(session.toolCount),
419
427
  formatAge(session.claimedAt ?? session.createdAt, nowIso)
@@ -587,6 +595,53 @@ var renderToolsListData = (colors, data, flags, full) => {
587
595
  }
588
596
  return full ? renderToolsFullListing(colors, data, flags) : renderToolSummaryTable(colors, data);
589
597
  };
598
+ var renderEventsTruncationLine = (data) => {
599
+ const offset = data.offset ?? 0;
600
+ if (data.events.length >= data.total) {
601
+ return [];
602
+ }
603
+ return [
604
+ "",
605
+ `Showing ${data.events.length} of ${data.total} events (offset ${offset}). Narrow with --name <glob> or page with --offset <n>.`
606
+ ];
607
+ };
608
+ var renderEventsListData = (colors, data, flags) => {
609
+ const [only] = data.events;
610
+ if (data.name !== void 0 && !data.name.includes("*") && only !== void 0) {
611
+ return renderFields(
612
+ colors.green(`Event: ${only.name}`),
613
+ [
614
+ ["Signature", renderEventSignature(only)],
615
+ ["Description", only.description],
616
+ ["Payload schema", only.payload_schema]
617
+ ],
618
+ flags
619
+ );
620
+ }
621
+ if (data.events.length === 0 && data.total > 0) {
622
+ return [
623
+ colors.green("Events"),
624
+ ` No events at offset ${data.offset ?? 0}; ${data.total} matching event${data.total === 1 ? "" : "s"} in total.`
625
+ ];
626
+ }
627
+ if (data.events.length === 0) {
628
+ return [
629
+ colors.green("Events"),
630
+ data.name === void 0 ? " No events declared." : ` No events match ${JSON.stringify(data.name)}.`
631
+ ];
632
+ }
633
+ const lines = data.events.flatMap((event) => [
634
+ ` ${renderEventSignature(event)}`,
635
+ ` ${summarizeToolDescription(event.description)}`
636
+ ]);
637
+ return [
638
+ colors.green("Events"),
639
+ ...lines,
640
+ ...renderEventsTruncationLine(data),
641
+ "",
642
+ "Run `appduct events ls --name <name>` for an event's full payload schema."
643
+ ];
644
+ };
590
645
  var renderInvokeData = (colors, data, flags) => {
591
646
  return [colors.green("Result"), formatScalar(data, flags)];
592
647
  };
@@ -778,6 +833,8 @@ var renderSuccessData = (colors, command, data, options) => {
778
833
  return renderToolsListData(colors, data, flags, options.full);
779
834
  case "tools describe":
780
835
  return renderToolDetail(colors, data, flags);
836
+ case "events ls":
837
+ return renderEventsListData(colors, data, flags);
781
838
  case "tools call":
782
839
  return renderInvokeData(colors, data, flags);
783
840
  case "sessions revoke":
@@ -2657,6 +2657,7 @@ var resolveAppTool = async (call, selector, name) => {
2657
2657
  // src/mcp/events-tool.ts
2658
2658
  import {
2659
2659
  RPC_METHODS as RPC_METHODS6,
2660
+ renderEventSignature,
2660
2661
  toAppEvent as toAppEvent2
2661
2662
  } from "@appduct/shared";
2662
2663
 
@@ -2760,16 +2761,36 @@ var waitForAppEvent = async (stream, options) => {
2760
2761
  };
2761
2762
 
2762
2763
  // src/mcp/events-tool.ts
2764
+ var LIST_EVENTS_TOOL_NAME = "appduct_list_events";
2763
2765
  var EVENTS_TOOL_NAME = "appduct_events";
2764
2766
  var WAIT_FOR_EVENT_TOOL_NAME = "appduct_wait_for_event";
2765
2767
  var DEFAULT_EVENTS_LIMIT = 50;
2766
2768
  var DEFAULT_EVENTS_PAYLOAD_MAX_BYTES = 4096;
2769
+ var DEFAULT_LIST_EVENTS_LIMIT = 50;
2767
2770
  var DEFAULT_WAIT_FOR_EVENT_TIMEOUT_MS = 12e4;
2768
2771
  var MAX_WAIT_FOR_EVENT_TIMEOUT_MS = 25 * 60 * 1e3;
2769
2772
  var WAIT_FOR_EVENT_PROGRESS_INTERVAL_MS = 6e4;
2770
2773
  var clampWaitForEventTimeoutMs = (requestedMs) => {
2771
2774
  return Math.min(requestedMs ?? DEFAULT_WAIT_FOR_EVENT_TIMEOUT_MS, MAX_WAIT_FOR_EVENT_TIMEOUT_MS);
2772
2775
  };
2776
+ var LIST_EVENTS_TOOL_DESCRIPTOR = {
2777
+ name: LIST_EVENTS_TOOL_NAME,
2778
+ description: `List the events the connected app declared, as one-line signatures (\`name { field: type }\`) with each event's description. Call this before appduct_wait_for_event or appduct_events so you wait on a name the app really posts instead of guessing one. An app that declares no events returns an empty list, which does not mean it posts none. name is a whole-name, case-sensitive glob (* matches any run of characters), e.g. "cart.*"; a name without * that matches a declared event also returns that event's payload_schema. limit (default ${DEFAULT_LIST_EVENTS_LIMIT}) and offset page the name-sorted list, and total counts every match before paging. Returns { session, total, limit, events }, each event { name, signature, description }.`,
2779
+ inputSchema: {
2780
+ type: "object",
2781
+ properties: {
2782
+ selector: {
2783
+ type: "string",
2784
+ minLength: 1,
2785
+ description: "Session alias or id. Omit to target the sole active/suspended session."
2786
+ },
2787
+ name: { type: "string", minLength: 1 },
2788
+ limit: { type: "integer", exclusiveMinimum: 0 },
2789
+ offset: { type: "integer", minimum: 0 }
2790
+ },
2791
+ additionalProperties: false
2792
+ }
2793
+ };
2773
2794
  var EVENTS_TOOL_DESCRIPTOR = {
2774
2795
  name: EVENTS_TOOL_NAME,
2775
2796
  description: `Drain the events the app posted with postEvent(name, payload) that the daemon retained for a session since a cursor, for checking what the app reported after calling a tool or triggering app behavior. With no selector, targets the sole active/suspended session. name filters to events whose name matches a whole-name, case-sensitive glob (* matches any run of characters; a pattern without * is an exact name), e.g. "cart.*". Returns { events, cursor, dropped, remaining } with each event as { name, payload, ts, seq, sessionId, alias } or, once a payload's JSON exceeds payloadMaxBytes (default 4096), as { name, payloadPreview, truncated: true, payloadBytes, ts, seq, sessionId, alias } instead \u2014 check truncated before reading payload. pass cursor back as since on the next call to avoid re-reading events you've already seen; dropped counts app events after since that were evicted before this call could return them (0 once nothing has fallen off), and remaining counts events still matching this query after the returned page (0 on the last page). limit (default 50) keeps the OLDEST events in the window and advances cursor only past what was actually returned, so repeated calls page forward through everything retained rather than skipping ahead.`,
@@ -2859,6 +2880,31 @@ var rejectMatch = (args) => {
2859
2880
  );
2860
2881
  }
2861
2882
  };
2883
+ var handleListEventsTool = async (rawArgs, call) => {
2884
+ const args = asRecord3(rawArgs);
2885
+ rejectUnknownKeys(args, LIST_EVENTS_TOOL_NAME, ["selector", "name", "limit", "offset"]);
2886
+ const session = await resolveSession(call, asOptionalString(args.selector, "selector"));
2887
+ const name = asOptionalString(args.name, "name");
2888
+ const limit = args.limit ?? DEFAULT_LIST_EVENTS_LIMIT;
2889
+ const result = await call(RPC_METHODS6.eventsList, {
2890
+ selector: session.sessionId,
2891
+ ...name !== void 0 ? { name } : {},
2892
+ limit,
2893
+ ...args.offset !== void 0 && args.offset !== null ? { offset: args.offset } : {}
2894
+ });
2895
+ const lookup = name !== void 0 && !name.includes("*");
2896
+ return {
2897
+ session: session.alias,
2898
+ total: result.total,
2899
+ limit,
2900
+ events: result.events.map((event) => ({
2901
+ name: event.name,
2902
+ signature: renderEventSignature(event),
2903
+ description: event.description,
2904
+ ...lookup && event.payload_schema !== void 0 ? { payload_schema: event.payload_schema } : {}
2905
+ }))
2906
+ };
2907
+ };
2862
2908
  var handleEventsTool = async (rawArgs, deps) => {
2863
2909
  const args = asRecord3(rawArgs);
2864
2910
  rejectKinds(args);
@@ -3153,6 +3199,7 @@ var createMcpServer = async (options) => {
3153
3199
  LIST_TOOLS_TOOL_DESCRIPTOR,
3154
3200
  DESCRIBE_TOOL_TOOL_DESCRIPTOR,
3155
3201
  CALL_TOOL_TOOL_DESCRIPTOR,
3202
+ LIST_EVENTS_TOOL_DESCRIPTOR,
3156
3203
  EVENTS_TOOL_DESCRIPTOR,
3157
3204
  WAIT_FOR_EVENT_TOOL_DESCRIPTOR
3158
3205
  ]
@@ -3181,6 +3228,9 @@ var createMcpServer = async (options) => {
3181
3228
  await handleWaitForSessionTool(args, { stateDir: options.stateDir, spawn: options.spawn })
3182
3229
  );
3183
3230
  }
3231
+ if (name === LIST_EVENTS_TOOL_NAME) {
3232
+ return toolSuccessContent(await handleListEventsTool(args, stream.call));
3233
+ }
3184
3234
  if (name === EVENTS_TOOL_NAME) {
3185
3235
  return toolSuccessContent(await handleEventsTool(args, { call: stream.call }));
3186
3236
  }
@@ -3329,6 +3379,7 @@ var formatJson = (value, flags) => {
3329
3379
  import pc from "picocolors";
3330
3380
  import {
3331
3381
  formatAgentWebSocketUrl,
3382
+ renderEventSignature as renderEventSignature2,
3332
3383
  renderToolSignature as renderToolSignature2,
3333
3384
  summarizeToolDescription as summarizeToolDescription2
3334
3385
  } from "@appduct/shared";
@@ -3387,6 +3438,13 @@ var formatDevice = (device) => {
3387
3438
  const label = parts.length > 0 ? parts.join(" ") : "unknown device";
3388
3439
  return device.os ? `${label} (${device.os})` : label;
3389
3440
  };
3441
+ var SUSPEND_REASON_LABELS = {
3442
+ app_backgrounded: "app in background",
3443
+ connection_lost: "connection lost"
3444
+ };
3445
+ var formatSessionState = (session) => {
3446
+ return session.suspendReason ? `${session.state} (${SUSPEND_REASON_LABELS[session.suspendReason]})` : session.state;
3447
+ };
3390
3448
  var renderLsData = (colors, data, now) => {
3391
3449
  if (data.length === 0) {
3392
3450
  return [colors.dim("Sessions"), " No Appduct sessions are registered."];
@@ -3395,7 +3453,7 @@ var renderLsData = (colors, data, now) => {
3395
3453
  const headers = ["Alias", "State", "Device", "Tools", "Age"];
3396
3454
  const rows = data.map((session) => [
3397
3455
  session.alias,
3398
- session.state,
3456
+ formatSessionState(session),
3399
3457
  formatDevice(session.device),
3400
3458
  String(session.toolCount),
3401
3459
  formatAge(session.claimedAt ?? session.createdAt, nowIso)
@@ -3569,6 +3627,53 @@ var renderToolsListData = (colors, data, flags, full) => {
3569
3627
  }
3570
3628
  return full ? renderToolsFullListing(colors, data, flags) : renderToolSummaryTable(colors, data);
3571
3629
  };
3630
+ var renderEventsTruncationLine = (data) => {
3631
+ const offset = data.offset ?? 0;
3632
+ if (data.events.length >= data.total) {
3633
+ return [];
3634
+ }
3635
+ return [
3636
+ "",
3637
+ `Showing ${data.events.length} of ${data.total} events (offset ${offset}). Narrow with --name <glob> or page with --offset <n>.`
3638
+ ];
3639
+ };
3640
+ var renderEventsListData = (colors, data, flags) => {
3641
+ const [only] = data.events;
3642
+ if (data.name !== void 0 && !data.name.includes("*") && only !== void 0) {
3643
+ return renderFields(
3644
+ colors.green(`Event: ${only.name}`),
3645
+ [
3646
+ ["Signature", renderEventSignature2(only)],
3647
+ ["Description", only.description],
3648
+ ["Payload schema", only.payload_schema]
3649
+ ],
3650
+ flags
3651
+ );
3652
+ }
3653
+ if (data.events.length === 0 && data.total > 0) {
3654
+ return [
3655
+ colors.green("Events"),
3656
+ ` No events at offset ${data.offset ?? 0}; ${data.total} matching event${data.total === 1 ? "" : "s"} in total.`
3657
+ ];
3658
+ }
3659
+ if (data.events.length === 0) {
3660
+ return [
3661
+ colors.green("Events"),
3662
+ data.name === void 0 ? " No events declared." : ` No events match ${JSON.stringify(data.name)}.`
3663
+ ];
3664
+ }
3665
+ const lines = data.events.flatMap((event) => [
3666
+ ` ${renderEventSignature2(event)}`,
3667
+ ` ${summarizeToolDescription2(event.description)}`
3668
+ ]);
3669
+ return [
3670
+ colors.green("Events"),
3671
+ ...lines,
3672
+ ...renderEventsTruncationLine(data),
3673
+ "",
3674
+ "Run `appduct events ls --name <name>` for an event's full payload schema."
3675
+ ];
3676
+ };
3572
3677
  var renderInvokeData = (colors, data, flags) => {
3573
3678
  return [colors.green("Result"), formatScalar(data, flags)];
3574
3679
  };
@@ -3760,6 +3865,8 @@ var renderSuccessData = (colors, command, data, options) => {
3760
3865
  return renderToolsListData(colors, data, flags, options.full);
3761
3866
  case "tools describe":
3762
3867
  return renderToolDetail(colors, data, flags);
3868
+ case "events ls":
3869
+ return renderEventsListData(colors, data, flags);
3763
3870
  case "tools call":
3764
3871
  return renderInvokeData(colors, data, flags);
3765
3872
  case "sessions revoke":
@@ -213,6 +213,7 @@ var formatJson = (value, flags) => {
213
213
  import pc from "picocolors";
214
214
  import {
215
215
  formatAgentWebSocketUrl,
216
+ renderEventSignature,
216
217
  renderToolSignature,
217
218
  summarizeToolDescription
218
219
  } from "@appduct/shared";
@@ -308,6 +309,13 @@ var formatDevice = (device) => {
308
309
  const label = parts.length > 0 ? parts.join(" ") : "unknown device";
309
310
  return device.os ? `${label} (${device.os})` : label;
310
311
  };
312
+ var SUSPEND_REASON_LABELS = {
313
+ app_backgrounded: "app in background",
314
+ connection_lost: "connection lost"
315
+ };
316
+ var formatSessionState = (session) => {
317
+ return session.suspendReason ? `${session.state} (${SUSPEND_REASON_LABELS[session.suspendReason]})` : session.state;
318
+ };
311
319
  var renderLsData = (colors, data, now) => {
312
320
  if (data.length === 0) {
313
321
  return [colors.dim("Sessions"), " No Appduct sessions are registered."];
@@ -316,7 +324,7 @@ var renderLsData = (colors, data, now) => {
316
324
  const headers = ["Alias", "State", "Device", "Tools", "Age"];
317
325
  const rows = data.map((session) => [
318
326
  session.alias,
319
- session.state,
327
+ formatSessionState(session),
320
328
  formatDevice(session.device),
321
329
  String(session.toolCount),
322
330
  formatAge(session.claimedAt ?? session.createdAt, nowIso)
@@ -490,6 +498,53 @@ var renderToolsListData = (colors, data, flags, full) => {
490
498
  }
491
499
  return full ? renderToolsFullListing(colors, data, flags) : renderToolSummaryTable(colors, data);
492
500
  };
501
+ var renderEventsTruncationLine = (data) => {
502
+ const offset = data.offset ?? 0;
503
+ if (data.events.length >= data.total) {
504
+ return [];
505
+ }
506
+ return [
507
+ "",
508
+ `Showing ${data.events.length} of ${data.total} events (offset ${offset}). Narrow with --name <glob> or page with --offset <n>.`
509
+ ];
510
+ };
511
+ var renderEventsListData = (colors, data, flags) => {
512
+ const [only] = data.events;
513
+ if (data.name !== void 0 && !data.name.includes("*") && only !== void 0) {
514
+ return renderFields(
515
+ colors.green(`Event: ${only.name}`),
516
+ [
517
+ ["Signature", renderEventSignature(only)],
518
+ ["Description", only.description],
519
+ ["Payload schema", only.payload_schema]
520
+ ],
521
+ flags
522
+ );
523
+ }
524
+ if (data.events.length === 0 && data.total > 0) {
525
+ return [
526
+ colors.green("Events"),
527
+ ` No events at offset ${data.offset ?? 0}; ${data.total} matching event${data.total === 1 ? "" : "s"} in total.`
528
+ ];
529
+ }
530
+ if (data.events.length === 0) {
531
+ return [
532
+ colors.green("Events"),
533
+ data.name === void 0 ? " No events declared." : ` No events match ${JSON.stringify(data.name)}.`
534
+ ];
535
+ }
536
+ const lines = data.events.flatMap((event) => [
537
+ ` ${renderEventSignature(event)}`,
538
+ ` ${summarizeToolDescription(event.description)}`
539
+ ]);
540
+ return [
541
+ colors.green("Events"),
542
+ ...lines,
543
+ ...renderEventsTruncationLine(data),
544
+ "",
545
+ "Run `appduct events ls --name <name>` for an event's full payload schema."
546
+ ];
547
+ };
493
548
  var renderInvokeData = (colors, data, flags) => {
494
549
  return [colors.green("Result"), formatScalar(data, flags)];
495
550
  };
@@ -681,6 +736,8 @@ var renderSuccessData = (colors, command, data, options) => {
681
736
  return renderToolsListData(colors, data, flags, options.full);
682
737
  case "tools describe":
683
738
  return renderToolDetail(colors, data, flags);
739
+ case "events ls":
740
+ return renderEventsListData(colors, data, flags);
684
741
  case "tools call":
685
742
  return renderInvokeData(colors, data, flags);
686
743
  case "sessions revoke":