appduct 0.13.0 → 0.15.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 (62) hide show
  1. package/README.md +55 -24
  2. package/dist/artifact-inspect.d.ts +38 -39
  3. package/dist/bin.js +106 -7
  4. package/dist/cli/result-types.d.ts +35 -2
  5. package/dist/cli/routes/daemon/index.js +90 -2
  6. package/dist/cli/routes/daemon/run.js +1088 -517
  7. package/dist/cli/routes/daemon/start.js +98 -2
  8. package/dist/cli/routes/daemon/status.js +99 -2
  9. package/dist/cli/routes/daemon/stop.js +98 -2
  10. package/dist/cli/routes/doctor.js +98 -3
  11. package/dist/cli/routes/events/index.d.ts +0 -3
  12. package/dist/cli/routes/events/index.js +92 -3
  13. package/dist/cli/routes/events/ls.d.ts +5 -0
  14. package/dist/cli/routes/events/ls.js +1837 -0
  15. package/dist/cli/routes/events/since.js +98 -2
  16. package/dist/cli/routes/events/tail.js +98 -2
  17. package/dist/cli/routes/init.js +265 -108
  18. package/dist/cli/routes/keygen.js +90 -2
  19. package/dist/cli/routes/mcp.js +243 -25
  20. package/dist/cli/routes/sessions/index.js +90 -2
  21. package/dist/cli/routes/sessions/link.js +199 -22
  22. package/dist/cli/routes/sessions/ls.js +98 -2
  23. package/dist/cli/routes/sessions/revoke.js +98 -2
  24. package/dist/cli/routes/tools/call.js +98 -2
  25. package/dist/cli/routes/tools/describe.js +98 -2
  26. package/dist/cli/routes/tools/index.js +90 -2
  27. package/dist/cli/routes/tools/ls.js +98 -2
  28. package/dist/client/attach-page.d.ts +39 -0
  29. package/dist/client/bootstrap.d.ts +17 -3
  30. package/dist/client/index.d.ts +2 -1
  31. package/dist/client/index.js +286 -112
  32. package/dist/commands/doctor.d.ts +3 -4
  33. package/dist/commands/events-ls.d.ts +21 -0
  34. package/dist/commands/init.d.ts +8 -2
  35. package/dist/commands/link.d.ts +10 -1
  36. package/dist/daemon/calls.d.ts +3 -1
  37. package/dist/daemon/config.d.ts +6 -0
  38. package/dist/daemon/daemon.d.ts +6 -1
  39. package/dist/daemon/event-bus.d.ts +10 -0
  40. package/dist/daemon/links.d.ts +9 -5
  41. package/dist/daemon/listener.d.ts +28 -1
  42. package/dist/daemon/registry.d.ts +20 -12
  43. package/dist/daemon/rpc-errors.d.ts +6 -0
  44. package/dist/daemon/sessions.d.ts +14 -8
  45. package/dist/daemon/tls.d.ts +0 -3
  46. package/dist/devtools-relay/attach-tab.d.ts +33 -0
  47. package/dist/devtools-relay/index.d.ts +8 -0
  48. package/dist/devtools-relay/memory-daemon-sockets.d.ts +20 -0
  49. package/dist/devtools-relay/memory-devtools-browser.d.ts +10 -0
  50. package/dist/devtools-relay/memory-page-channel.d.ts +16 -0
  51. package/dist/devtools-relay/node-daemon-socket.d.ts +3 -0
  52. package/dist/devtools-relay/node-devtools-browser.d.ts +3 -0
  53. package/dist/devtools-relay/ports.d.ts +48 -0
  54. package/dist/devtools-relay/relay.d.ts +16 -0
  55. package/dist/index.js +106 -7
  56. package/dist/link.d.ts +35 -1
  57. package/dist/mcp/app-tools.d.ts +19 -0
  58. package/dist/mcp/connect-tool.d.ts +15 -2
  59. package/dist/mcp/events-tool.d.ts +39 -0
  60. package/dist/native-scheme.d.ts +11 -7
  61. package/dist/scheme.d.ts +5 -1
  62. package/package.json +15 -5
@@ -24,6 +24,7 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
24
24
  "daemonLogMaxBytes",
25
25
  "eventsLogMaxBytes",
26
26
  "policy",
27
+ "webOrigins",
27
28
  "advertisedIp",
28
29
  "scheme",
29
30
  "restartDaemonOnVersionMismatch"
@@ -84,6 +85,7 @@ var defaultConfig = (paths) => {
84
85
  daemonLogMaxBytes: DEFAULT_DAEMON_LOG_MAX_BYTES,
85
86
  eventsLogMaxBytes: 10 * 1024 * 1024,
86
87
  restartDaemonOnVersionMismatch: false,
88
+ webOrigins: [],
87
89
  policy: {
88
90
  default: "allow",
89
91
  destructive: "allow"
@@ -154,6 +156,12 @@ var loadConfig = async (paths, options = {}) => {
154
156
  "restartDaemonOnVersionMismatch"
155
157
  );
156
158
  }
159
+ if (parsed.webOrigins !== void 0) {
160
+ if (!Array.isArray(parsed.webOrigins)) {
161
+ throw configError("webOrigins", "must be an array of origin strings.");
162
+ }
163
+ config.webOrigins = parsed.webOrigins.map((origin) => requireNonEmptyString(origin, "webOrigins"));
164
+ }
157
165
  if (parsed.policy !== void 0) {
158
166
  if (typeof parsed.policy !== "object" || parsed.policy === null || Array.isArray(parsed.policy)) {
159
167
  throw configError("policy", "must be an object.");
@@ -932,7 +940,7 @@ var EXIT_CODE_BY_ERROR_TYPE = {
932
940
  // EX_NOINPUT (66): "an input file did not exist or was not readable" — `doctor` throws this for a
933
941
  // missing external tool or an unreadable/corrupt artifact, deliberately distinct from every other
934
942
  // code here so a release pipeline can't mistake "we couldn't tell" for either a clean pass or an
935
- // assertion failure (docs/tasks/08-appduct-doctor.md).
943
+ // assertion failure.
936
944
  inspection_error: 66,
937
945
  connection_error: 70,
938
946
  session_error: 71,
@@ -1049,6 +1057,7 @@ var formatJson = (value, flags) => {
1049
1057
  import pc from "picocolors";
1050
1058
  import {
1051
1059
  formatAgentWebSocketUrl,
1060
+ renderEventSignature,
1052
1061
  renderToolSignature,
1053
1062
  summarizeToolDescription
1054
1063
  } from "@appduct/shared";
@@ -1144,6 +1153,13 @@ var formatDevice = (device) => {
1144
1153
  const label = parts.length > 0 ? parts.join(" ") : "unknown device";
1145
1154
  return device.os ? `${label} (${device.os})` : label;
1146
1155
  };
1156
+ var SUSPEND_REASON_LABELS = {
1157
+ app_backgrounded: "app in background",
1158
+ connection_lost: "connection lost"
1159
+ };
1160
+ var formatSessionState = (session) => {
1161
+ return session.suspendReason ? `${session.state} (${SUSPEND_REASON_LABELS[session.suspendReason]})` : session.state;
1162
+ };
1147
1163
  var renderLsData = (colors, data, now) => {
1148
1164
  if (data.length === 0) {
1149
1165
  return [colors.dim("Sessions"), " No Appduct sessions are registered."];
@@ -1152,7 +1168,7 @@ var renderLsData = (colors, data, now) => {
1152
1168
  const headers = ["Alias", "State", "Device", "Tools", "Age"];
1153
1169
  const rows = data.map((session) => [
1154
1170
  session.alias,
1155
- session.state,
1171
+ formatSessionState(session),
1156
1172
  formatDevice(session.device),
1157
1173
  String(session.toolCount),
1158
1174
  formatAge(session.claimedAt ?? session.createdAt, nowIso)
@@ -1326,10 +1342,87 @@ var renderToolsListData = (colors, data, flags, full) => {
1326
1342
  }
1327
1343
  return full ? renderToolsFullListing(colors, data, flags) : renderToolSummaryTable(colors, data);
1328
1344
  };
1345
+ var renderEventsTruncationLine = (data) => {
1346
+ const offset = data.offset ?? 0;
1347
+ if (data.events.length >= data.total) {
1348
+ return [];
1349
+ }
1350
+ return [
1351
+ "",
1352
+ `Showing ${data.events.length} of ${data.total} events (offset ${offset}). Narrow with --name <glob> or page with --offset <n>.`
1353
+ ];
1354
+ };
1355
+ var renderEventsListData = (colors, data, flags) => {
1356
+ const [only] = data.events;
1357
+ if (data.name !== void 0 && !data.name.includes("*") && only !== void 0) {
1358
+ return renderFields(
1359
+ colors.green(`Event: ${only.name}`),
1360
+ [
1361
+ ["Signature", renderEventSignature(only)],
1362
+ ["Description", only.description],
1363
+ ["Payload schema", only.payload_schema]
1364
+ ],
1365
+ flags
1366
+ );
1367
+ }
1368
+ if (data.events.length === 0 && data.total > 0) {
1369
+ return [
1370
+ colors.green("Events"),
1371
+ ` No events at offset ${data.offset ?? 0}; ${data.total} matching event${data.total === 1 ? "" : "s"} in total.`
1372
+ ];
1373
+ }
1374
+ if (data.events.length === 0) {
1375
+ return [
1376
+ colors.green("Events"),
1377
+ data.name === void 0 ? " No events declared." : ` No events match ${JSON.stringify(data.name)}.`
1378
+ ];
1379
+ }
1380
+ const lines = data.events.flatMap((event) => [
1381
+ ` ${renderEventSignature(event)}`,
1382
+ ` ${summarizeToolDescription(event.description)}`
1383
+ ]);
1384
+ return [
1385
+ colors.green("Events"),
1386
+ ...lines,
1387
+ ...renderEventsTruncationLine(data),
1388
+ "",
1389
+ "Run `appduct events ls --name <name>` for an event's full payload schema."
1390
+ ];
1391
+ };
1329
1392
  var renderInvokeData = (colors, data, flags) => {
1330
1393
  return [colors.green("Result"), formatScalar(data, flags)];
1331
1394
  };
1332
1395
  var renderLinkData = (colors, data, flags, qr) => {
1396
+ if ("attached" in data) {
1397
+ return [
1398
+ colors.green("Page Attached"),
1399
+ ...renderFields(
1400
+ "Page",
1401
+ [
1402
+ ["Session", data.sessionId],
1403
+ ["Tab", data.targetId],
1404
+ ["URL", data.url],
1405
+ ["Expires", new Date(data.expiresAt * 1e3).toISOString()]
1406
+ ],
1407
+ flags
1408
+ )
1409
+ ];
1410
+ }
1411
+ if ("script" in data) {
1412
+ return [
1413
+ colors.green("Link Created"),
1414
+ ...renderFields(
1415
+ "Link",
1416
+ [
1417
+ ["Session", data.sessionId],
1418
+ ["URL", data.url],
1419
+ ["Script", data.script],
1420
+ ["Expires", new Date(data.expiresAt * 1e3).toISOString()]
1421
+ ],
1422
+ flags
1423
+ )
1424
+ ];
1425
+ }
1333
1426
  const lines = [
1334
1427
  colors.green("Link Created"),
1335
1428
  ...renderFields(
@@ -1450,6 +1543,7 @@ var renderDaemonStatusData = (colors, data, flags) => {
1450
1543
  ["PID", data.daemon.pid],
1451
1544
  ["Started at", data.daemon.started_at],
1452
1545
  ["WSS port", data.daemon.wss_port],
1546
+ ["Web port", data.daemon.web_port],
1453
1547
  ["Pinned keys", data.daemon.pinned_keys.length],
1454
1548
  ["Sessions", data.daemon.session_count]
1455
1549
  ],
@@ -1517,6 +1611,8 @@ var renderSuccessData = (colors, command, data, options) => {
1517
1611
  return renderToolsListData(colors, data, flags, options.full);
1518
1612
  case "tools describe":
1519
1613
  return renderToolDetail(colors, data, flags);
1614
+ case "events ls":
1615
+ return renderEventsListData(colors, data, flags);
1520
1616
  case "tools call":
1521
1617
  return renderInvokeData(colors, data, flags);
1522
1618
  case "sessions revoke":
@@ -68,6 +68,7 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
68
68
  "daemonLogMaxBytes",
69
69
  "eventsLogMaxBytes",
70
70
  "policy",
71
+ "webOrigins",
71
72
  "advertisedIp",
72
73
  "scheme",
73
74
  "restartDaemonOnVersionMismatch"
@@ -128,6 +129,7 @@ var defaultConfig = (paths) => {
128
129
  daemonLogMaxBytes: DEFAULT_DAEMON_LOG_MAX_BYTES,
129
130
  eventsLogMaxBytes: 10 * 1024 * 1024,
130
131
  restartDaemonOnVersionMismatch: false,
132
+ webOrigins: [],
131
133
  policy: {
132
134
  default: "allow",
133
135
  destructive: "allow"
@@ -198,6 +200,12 @@ var loadConfig = async (paths, options = {}) => {
198
200
  "restartDaemonOnVersionMismatch"
199
201
  );
200
202
  }
203
+ if (parsed.webOrigins !== void 0) {
204
+ if (!Array.isArray(parsed.webOrigins)) {
205
+ throw configError("webOrigins", "must be an array of origin strings.");
206
+ }
207
+ config.webOrigins = parsed.webOrigins.map((origin) => requireNonEmptyString(origin, "webOrigins"));
208
+ }
201
209
  if (parsed.policy !== void 0) {
202
210
  if (typeof parsed.policy !== "object" || parsed.policy === null || Array.isArray(parsed.policy)) {
203
211
  throw configError("policy", "must be an object.");
@@ -932,6 +940,7 @@ var handleDaemonStatusCommand = async (context) => {
932
940
  pid: status.pid,
933
941
  started_at: status.startedAt,
934
942
  wss_port: status.wssPort,
943
+ web_port: status.webPort,
935
944
  pinned_keys: status.pinnedKeys,
936
945
  session_count: sessionCount
937
946
  },
@@ -961,7 +970,7 @@ var EXIT_CODE_BY_ERROR_TYPE = {
961
970
  // EX_NOINPUT (66): "an input file did not exist or was not readable" — `doctor` throws this for a
962
971
  // missing external tool or an unreadable/corrupt artifact, deliberately distinct from every other
963
972
  // code here so a release pipeline can't mistake "we couldn't tell" for either a clean pass or an
964
- // assertion failure (docs/tasks/08-appduct-doctor.md).
973
+ // assertion failure.
965
974
  inspection_error: 66,
966
975
  connection_error: 70,
967
976
  session_error: 71,
@@ -1078,6 +1087,7 @@ var formatJson = (value, flags) => {
1078
1087
  import pc from "picocolors";
1079
1088
  import {
1080
1089
  formatAgentWebSocketUrl,
1090
+ renderEventSignature,
1081
1091
  renderToolSignature,
1082
1092
  summarizeToolDescription
1083
1093
  } from "@appduct/shared";
@@ -1173,6 +1183,13 @@ var formatDevice = (device) => {
1173
1183
  const label = parts.length > 0 ? parts.join(" ") : "unknown device";
1174
1184
  return device.os ? `${label} (${device.os})` : label;
1175
1185
  };
1186
+ var SUSPEND_REASON_LABELS = {
1187
+ app_backgrounded: "app in background",
1188
+ connection_lost: "connection lost"
1189
+ };
1190
+ var formatSessionState = (session) => {
1191
+ return session.suspendReason ? `${session.state} (${SUSPEND_REASON_LABELS[session.suspendReason]})` : session.state;
1192
+ };
1176
1193
  var renderLsData = (colors, data, now) => {
1177
1194
  if (data.length === 0) {
1178
1195
  return [colors.dim("Sessions"), " No Appduct sessions are registered."];
@@ -1181,7 +1198,7 @@ var renderLsData = (colors, data, now) => {
1181
1198
  const headers = ["Alias", "State", "Device", "Tools", "Age"];
1182
1199
  const rows = data.map((session) => [
1183
1200
  session.alias,
1184
- session.state,
1201
+ formatSessionState(session),
1185
1202
  formatDevice(session.device),
1186
1203
  String(session.toolCount),
1187
1204
  formatAge(session.claimedAt ?? session.createdAt, nowIso)
@@ -1355,10 +1372,87 @@ var renderToolsListData = (colors, data, flags, full) => {
1355
1372
  }
1356
1373
  return full ? renderToolsFullListing(colors, data, flags) : renderToolSummaryTable(colors, data);
1357
1374
  };
1375
+ var renderEventsTruncationLine = (data) => {
1376
+ const offset = data.offset ?? 0;
1377
+ if (data.events.length >= data.total) {
1378
+ return [];
1379
+ }
1380
+ return [
1381
+ "",
1382
+ `Showing ${data.events.length} of ${data.total} events (offset ${offset}). Narrow with --name <glob> or page with --offset <n>.`
1383
+ ];
1384
+ };
1385
+ var renderEventsListData = (colors, data, flags) => {
1386
+ const [only] = data.events;
1387
+ if (data.name !== void 0 && !data.name.includes("*") && only !== void 0) {
1388
+ return renderFields(
1389
+ colors.green(`Event: ${only.name}`),
1390
+ [
1391
+ ["Signature", renderEventSignature(only)],
1392
+ ["Description", only.description],
1393
+ ["Payload schema", only.payload_schema]
1394
+ ],
1395
+ flags
1396
+ );
1397
+ }
1398
+ if (data.events.length === 0 && data.total > 0) {
1399
+ return [
1400
+ colors.green("Events"),
1401
+ ` No events at offset ${data.offset ?? 0}; ${data.total} matching event${data.total === 1 ? "" : "s"} in total.`
1402
+ ];
1403
+ }
1404
+ if (data.events.length === 0) {
1405
+ return [
1406
+ colors.green("Events"),
1407
+ data.name === void 0 ? " No events declared." : ` No events match ${JSON.stringify(data.name)}.`
1408
+ ];
1409
+ }
1410
+ const lines = data.events.flatMap((event) => [
1411
+ ` ${renderEventSignature(event)}`,
1412
+ ` ${summarizeToolDescription(event.description)}`
1413
+ ]);
1414
+ return [
1415
+ colors.green("Events"),
1416
+ ...lines,
1417
+ ...renderEventsTruncationLine(data),
1418
+ "",
1419
+ "Run `appduct events ls --name <name>` for an event's full payload schema."
1420
+ ];
1421
+ };
1358
1422
  var renderInvokeData = (colors, data, flags) => {
1359
1423
  return [colors.green("Result"), formatScalar(data, flags)];
1360
1424
  };
1361
1425
  var renderLinkData = (colors, data, flags, qr) => {
1426
+ if ("attached" in data) {
1427
+ return [
1428
+ colors.green("Page Attached"),
1429
+ ...renderFields(
1430
+ "Page",
1431
+ [
1432
+ ["Session", data.sessionId],
1433
+ ["Tab", data.targetId],
1434
+ ["URL", data.url],
1435
+ ["Expires", new Date(data.expiresAt * 1e3).toISOString()]
1436
+ ],
1437
+ flags
1438
+ )
1439
+ ];
1440
+ }
1441
+ if ("script" in data) {
1442
+ return [
1443
+ colors.green("Link Created"),
1444
+ ...renderFields(
1445
+ "Link",
1446
+ [
1447
+ ["Session", data.sessionId],
1448
+ ["URL", data.url],
1449
+ ["Script", data.script],
1450
+ ["Expires", new Date(data.expiresAt * 1e3).toISOString()]
1451
+ ],
1452
+ flags
1453
+ )
1454
+ ];
1455
+ }
1362
1456
  const lines = [
1363
1457
  colors.green("Link Created"),
1364
1458
  ...renderFields(
@@ -1479,6 +1573,7 @@ var renderDaemonStatusData = (colors, data, flags) => {
1479
1573
  ["PID", data.daemon.pid],
1480
1574
  ["Started at", data.daemon.started_at],
1481
1575
  ["WSS port", data.daemon.wss_port],
1576
+ ["Web port", data.daemon.web_port],
1482
1577
  ["Pinned keys", data.daemon.pinned_keys.length],
1483
1578
  ["Sessions", data.daemon.session_count]
1484
1579
  ],
@@ -1546,6 +1641,8 @@ var renderSuccessData = (colors, command, data, options) => {
1546
1641
  return renderToolsListData(colors, data, flags, options.full);
1547
1642
  case "tools describe":
1548
1643
  return renderToolDetail(colors, data, flags);
1644
+ case "events ls":
1645
+ return renderEventsListData(colors, data, flags);
1549
1646
  case "tools call":
1550
1647
  return renderInvokeData(colors, data, flags);
1551
1648
  case "sessions revoke":
@@ -87,6 +87,7 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
87
87
  "daemonLogMaxBytes",
88
88
  "eventsLogMaxBytes",
89
89
  "policy",
90
+ "webOrigins",
90
91
  "advertisedIp",
91
92
  "scheme",
92
93
  "restartDaemonOnVersionMismatch"
@@ -147,6 +148,7 @@ var defaultConfig = (paths) => {
147
148
  daemonLogMaxBytes: DEFAULT_DAEMON_LOG_MAX_BYTES,
148
149
  eventsLogMaxBytes: 10 * 1024 * 1024,
149
150
  restartDaemonOnVersionMismatch: false,
151
+ webOrigins: [],
150
152
  policy: {
151
153
  default: "allow",
152
154
  destructive: "allow"
@@ -217,6 +219,12 @@ var loadConfig = async (paths, options = {}) => {
217
219
  "restartDaemonOnVersionMismatch"
218
220
  );
219
221
  }
222
+ if (parsed.webOrigins !== void 0) {
223
+ if (!Array.isArray(parsed.webOrigins)) {
224
+ throw configError("webOrigins", "must be an array of origin strings.");
225
+ }
226
+ config.webOrigins = parsed.webOrigins.map((origin) => requireNonEmptyString(origin, "webOrigins"));
227
+ }
220
228
  if (parsed.policy !== void 0) {
221
229
  if (typeof parsed.policy !== "object" || parsed.policy === null || Array.isArray(parsed.policy)) {
222
230
  throw configError("policy", "must be an object.");
@@ -905,7 +913,7 @@ var EXIT_CODE_BY_ERROR_TYPE = {
905
913
  // EX_NOINPUT (66): "an input file did not exist or was not readable" — `doctor` throws this for a
906
914
  // missing external tool or an unreadable/corrupt artifact, deliberately distinct from every other
907
915
  // code here so a release pipeline can't mistake "we couldn't tell" for either a clean pass or an
908
- // assertion failure (docs/tasks/08-appduct-doctor.md).
916
+ // assertion failure.
909
917
  inspection_error: 66,
910
918
  connection_error: 70,
911
919
  session_error: 71,
@@ -1048,6 +1056,7 @@ var formatJson = (value, flags) => {
1048
1056
  import pc from "picocolors";
1049
1057
  import {
1050
1058
  formatAgentWebSocketUrl,
1059
+ renderEventSignature,
1051
1060
  renderToolSignature,
1052
1061
  summarizeToolDescription
1053
1062
  } from "@appduct/shared";
@@ -1143,6 +1152,13 @@ var formatDevice = (device) => {
1143
1152
  const label = parts.length > 0 ? parts.join(" ") : "unknown device";
1144
1153
  return device.os ? `${label} (${device.os})` : label;
1145
1154
  };
1155
+ var SUSPEND_REASON_LABELS = {
1156
+ app_backgrounded: "app in background",
1157
+ connection_lost: "connection lost"
1158
+ };
1159
+ var formatSessionState = (session) => {
1160
+ return session.suspendReason ? `${session.state} (${SUSPEND_REASON_LABELS[session.suspendReason]})` : session.state;
1161
+ };
1146
1162
  var renderLsData = (colors, data, now) => {
1147
1163
  if (data.length === 0) {
1148
1164
  return [colors.dim("Sessions"), " No Appduct sessions are registered."];
@@ -1151,7 +1167,7 @@ var renderLsData = (colors, data, now) => {
1151
1167
  const headers = ["Alias", "State", "Device", "Tools", "Age"];
1152
1168
  const rows = data.map((session) => [
1153
1169
  session.alias,
1154
- session.state,
1170
+ formatSessionState(session),
1155
1171
  formatDevice(session.device),
1156
1172
  String(session.toolCount),
1157
1173
  formatAge(session.claimedAt ?? session.createdAt, nowIso)
@@ -1325,10 +1341,87 @@ var renderToolsListData = (colors, data, flags, full) => {
1325
1341
  }
1326
1342
  return full ? renderToolsFullListing(colors, data, flags) : renderToolSummaryTable(colors, data);
1327
1343
  };
1344
+ var renderEventsTruncationLine = (data) => {
1345
+ const offset = data.offset ?? 0;
1346
+ if (data.events.length >= data.total) {
1347
+ return [];
1348
+ }
1349
+ return [
1350
+ "",
1351
+ `Showing ${data.events.length} of ${data.total} events (offset ${offset}). Narrow with --name <glob> or page with --offset <n>.`
1352
+ ];
1353
+ };
1354
+ var renderEventsListData = (colors, data, flags) => {
1355
+ const [only] = data.events;
1356
+ if (data.name !== void 0 && !data.name.includes("*") && only !== void 0) {
1357
+ return renderFields(
1358
+ colors.green(`Event: ${only.name}`),
1359
+ [
1360
+ ["Signature", renderEventSignature(only)],
1361
+ ["Description", only.description],
1362
+ ["Payload schema", only.payload_schema]
1363
+ ],
1364
+ flags
1365
+ );
1366
+ }
1367
+ if (data.events.length === 0 && data.total > 0) {
1368
+ return [
1369
+ colors.green("Events"),
1370
+ ` No events at offset ${data.offset ?? 0}; ${data.total} matching event${data.total === 1 ? "" : "s"} in total.`
1371
+ ];
1372
+ }
1373
+ if (data.events.length === 0) {
1374
+ return [
1375
+ colors.green("Events"),
1376
+ data.name === void 0 ? " No events declared." : ` No events match ${JSON.stringify(data.name)}.`
1377
+ ];
1378
+ }
1379
+ const lines = data.events.flatMap((event) => [
1380
+ ` ${renderEventSignature(event)}`,
1381
+ ` ${summarizeToolDescription(event.description)}`
1382
+ ]);
1383
+ return [
1384
+ colors.green("Events"),
1385
+ ...lines,
1386
+ ...renderEventsTruncationLine(data),
1387
+ "",
1388
+ "Run `appduct events ls --name <name>` for an event's full payload schema."
1389
+ ];
1390
+ };
1328
1391
  var renderInvokeData = (colors, data, flags) => {
1329
1392
  return [colors.green("Result"), formatScalar(data, flags)];
1330
1393
  };
1331
1394
  var renderLinkData = (colors, data, flags, qr) => {
1395
+ if ("attached" in data) {
1396
+ return [
1397
+ colors.green("Page Attached"),
1398
+ ...renderFields(
1399
+ "Page",
1400
+ [
1401
+ ["Session", data.sessionId],
1402
+ ["Tab", data.targetId],
1403
+ ["URL", data.url],
1404
+ ["Expires", new Date(data.expiresAt * 1e3).toISOString()]
1405
+ ],
1406
+ flags
1407
+ )
1408
+ ];
1409
+ }
1410
+ if ("script" in data) {
1411
+ return [
1412
+ colors.green("Link Created"),
1413
+ ...renderFields(
1414
+ "Link",
1415
+ [
1416
+ ["Session", data.sessionId],
1417
+ ["URL", data.url],
1418
+ ["Script", data.script],
1419
+ ["Expires", new Date(data.expiresAt * 1e3).toISOString()]
1420
+ ],
1421
+ flags
1422
+ )
1423
+ ];
1424
+ }
1332
1425
  const lines = [
1333
1426
  colors.green("Link Created"),
1334
1427
  ...renderFields(
@@ -1449,6 +1542,7 @@ var renderDaemonStatusData = (colors, data, flags) => {
1449
1542
  ["PID", data.daemon.pid],
1450
1543
  ["Started at", data.daemon.started_at],
1451
1544
  ["WSS port", data.daemon.wss_port],
1545
+ ["Web port", data.daemon.web_port],
1452
1546
  ["Pinned keys", data.daemon.pinned_keys.length],
1453
1547
  ["Sessions", data.daemon.session_count]
1454
1548
  ],
@@ -1516,6 +1610,8 @@ var renderSuccessData = (colors, command, data, options) => {
1516
1610
  return renderToolsListData(colors, data, flags, options.full);
1517
1611
  case "tools describe":
1518
1612
  return renderToolDetail(colors, data, flags);
1613
+ case "events ls":
1614
+ return renderEventsListData(colors, data, flags);
1519
1615
  case "tools call":
1520
1616
  return renderInvokeData(colors, data, flags);
1521
1617
  case "sessions revoke":
@@ -98,7 +98,7 @@ var EXIT_CODE_BY_ERROR_TYPE = {
98
98
  // EX_NOINPUT (66): "an input file did not exist or was not readable" — `doctor` throws this for a
99
99
  // missing external tool or an unreadable/corrupt artifact, deliberately distinct from every other
100
100
  // code here so a release pipeline can't mistake "we couldn't tell" for either a clean pass or an
101
- // assertion failure (docs/tasks/08-appduct-doctor.md).
101
+ // assertion failure.
102
102
  inspection_error: 66,
103
103
  connection_error: 70,
104
104
  session_error: 71,
@@ -230,6 +230,7 @@ var ANDROID_DEX_PACKAGE_MARKERS = [
230
230
  "com.callstackincubator.appduct"
231
231
  ];
232
232
  var ANDROID_MANIFEST_KEY_MARKERS = ["com.callstack.appduct.", "com.callstackincubator.appduct."];
233
+ var FLUTTER_DART_CORE_MARKER = "appduct-dart-core/";
233
234
  var bufferIncludesAscii = (haystack, needle) => {
234
235
  return haystack.includes(Buffer.from(needle, "utf8"));
235
236
  };
@@ -363,6 +364,9 @@ var detectIosSignals = (bytes) => {
363
364
  if (IOS_INFO_PLIST_KEY_MARKERS.some((marker) => bufferIncludesAscii(bytes, marker))) {
364
365
  signals.push("ios-info-plist-keys");
365
366
  }
367
+ if (bufferIncludesAscii(bytes, FLUTTER_DART_CORE_MARKER)) {
368
+ signals.push("flutter-dart-core-marker");
369
+ }
366
370
  return signals;
367
371
  };
368
372
  var detectAndroidSignals = (bytes) => {
@@ -378,6 +382,9 @@ var detectAndroidSignals = (bytes) => {
378
382
  )) {
379
383
  signals.push("android-manifest-meta-data-keys");
380
384
  }
385
+ if (bufferIncludesAscii(bytes, FLUTTER_DART_CORE_MARKER)) {
386
+ signals.push("flutter-dart-core-marker");
387
+ }
381
388
  return signals;
382
389
  };
383
390
  var inspectArtifact = async (artifactPath, options = {}) => {
@@ -403,7 +410,7 @@ var inspectArtifact = async (artifactPath, options = {}) => {
403
410
  bytes = await readZipEntries(exec, artifactPath, format, context);
404
411
  }
405
412
  const signals = platform === "ios" ? detectIosSignals(bytes) : detectAndroidSignals(bytes);
406
- const present = platform === "android" ? signals.includes("android-keep-rule-marker") : signals.includes("ios-core-marker-symbol") || signals.includes("ios-objc-class-symbol");
413
+ const present = signals.includes("flutter-dart-core-marker") || (platform === "android" ? signals.includes("android-keep-rule-marker") : signals.includes("ios-core-marker-symbol") || signals.includes("ios-objc-class-symbol"));
407
414
  return {
408
415
  platform,
409
416
  format,
@@ -458,6 +465,7 @@ var formatJson = (value, flags) => {
458
465
  import pc from "picocolors";
459
466
  import {
460
467
  formatAgentWebSocketUrl,
468
+ renderEventSignature,
461
469
  renderToolSignature,
462
470
  summarizeToolDescription
463
471
  } from "@appduct/shared";
@@ -553,6 +561,13 @@ var formatDevice = (device) => {
553
561
  const label = parts.length > 0 ? parts.join(" ") : "unknown device";
554
562
  return device.os ? `${label} (${device.os})` : label;
555
563
  };
564
+ var SUSPEND_REASON_LABELS = {
565
+ app_backgrounded: "app in background",
566
+ connection_lost: "connection lost"
567
+ };
568
+ var formatSessionState = (session) => {
569
+ return session.suspendReason ? `${session.state} (${SUSPEND_REASON_LABELS[session.suspendReason]})` : session.state;
570
+ };
556
571
  var renderLsData = (colors, data, now) => {
557
572
  if (data.length === 0) {
558
573
  return [colors.dim("Sessions"), " No Appduct sessions are registered."];
@@ -561,7 +576,7 @@ var renderLsData = (colors, data, now) => {
561
576
  const headers = ["Alias", "State", "Device", "Tools", "Age"];
562
577
  const rows = data.map((session) => [
563
578
  session.alias,
564
- session.state,
579
+ formatSessionState(session),
565
580
  formatDevice(session.device),
566
581
  String(session.toolCount),
567
582
  formatAge(session.claimedAt ?? session.createdAt, nowIso)
@@ -735,10 +750,87 @@ var renderToolsListData = (colors, data, flags, full) => {
735
750
  }
736
751
  return full ? renderToolsFullListing(colors, data, flags) : renderToolSummaryTable(colors, data);
737
752
  };
753
+ var renderEventsTruncationLine = (data) => {
754
+ const offset = data.offset ?? 0;
755
+ if (data.events.length >= data.total) {
756
+ return [];
757
+ }
758
+ return [
759
+ "",
760
+ `Showing ${data.events.length} of ${data.total} events (offset ${offset}). Narrow with --name <glob> or page with --offset <n>.`
761
+ ];
762
+ };
763
+ var renderEventsListData = (colors, data, flags) => {
764
+ const [only] = data.events;
765
+ if (data.name !== void 0 && !data.name.includes("*") && only !== void 0) {
766
+ return renderFields(
767
+ colors.green(`Event: ${only.name}`),
768
+ [
769
+ ["Signature", renderEventSignature(only)],
770
+ ["Description", only.description],
771
+ ["Payload schema", only.payload_schema]
772
+ ],
773
+ flags
774
+ );
775
+ }
776
+ if (data.events.length === 0 && data.total > 0) {
777
+ return [
778
+ colors.green("Events"),
779
+ ` No events at offset ${data.offset ?? 0}; ${data.total} matching event${data.total === 1 ? "" : "s"} in total.`
780
+ ];
781
+ }
782
+ if (data.events.length === 0) {
783
+ return [
784
+ colors.green("Events"),
785
+ data.name === void 0 ? " No events declared." : ` No events match ${JSON.stringify(data.name)}.`
786
+ ];
787
+ }
788
+ const lines = data.events.flatMap((event) => [
789
+ ` ${renderEventSignature(event)}`,
790
+ ` ${summarizeToolDescription(event.description)}`
791
+ ]);
792
+ return [
793
+ colors.green("Events"),
794
+ ...lines,
795
+ ...renderEventsTruncationLine(data),
796
+ "",
797
+ "Run `appduct events ls --name <name>` for an event's full payload schema."
798
+ ];
799
+ };
738
800
  var renderInvokeData = (colors, data, flags) => {
739
801
  return [colors.green("Result"), formatScalar(data, flags)];
740
802
  };
741
803
  var renderLinkData = (colors, data, flags, qr) => {
804
+ if ("attached" in data) {
805
+ return [
806
+ colors.green("Page Attached"),
807
+ ...renderFields(
808
+ "Page",
809
+ [
810
+ ["Session", data.sessionId],
811
+ ["Tab", data.targetId],
812
+ ["URL", data.url],
813
+ ["Expires", new Date(data.expiresAt * 1e3).toISOString()]
814
+ ],
815
+ flags
816
+ )
817
+ ];
818
+ }
819
+ if ("script" in data) {
820
+ return [
821
+ colors.green("Link Created"),
822
+ ...renderFields(
823
+ "Link",
824
+ [
825
+ ["Session", data.sessionId],
826
+ ["URL", data.url],
827
+ ["Script", data.script],
828
+ ["Expires", new Date(data.expiresAt * 1e3).toISOString()]
829
+ ],
830
+ flags
831
+ )
832
+ ];
833
+ }
742
834
  const lines = [
743
835
  colors.green("Link Created"),
744
836
  ...renderFields(
@@ -859,6 +951,7 @@ var renderDaemonStatusData = (colors, data, flags) => {
859
951
  ["PID", data.daemon.pid],
860
952
  ["Started at", data.daemon.started_at],
861
953
  ["WSS port", data.daemon.wss_port],
954
+ ["Web port", data.daemon.web_port],
862
955
  ["Pinned keys", data.daemon.pinned_keys.length],
863
956
  ["Sessions", data.daemon.session_count]
864
957
  ],
@@ -926,6 +1019,8 @@ var renderSuccessData = (colors, command, data, options) => {
926
1019
  return renderToolsListData(colors, data, flags, options.full);
927
1020
  case "tools describe":
928
1021
  return renderToolDetail(colors, data, flags);
1022
+ case "events ls":
1023
+ return renderEventsListData(colors, data, flags);
929
1024
  case "tools call":
930
1025
  return renderInvokeData(colors, data, flags);
931
1026
  case "sessions revoke":