appduct 0.14.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 (55) hide show
  1. package/README.md +52 -22
  2. package/dist/artifact-inspect.d.ts +38 -39
  3. package/dist/bin.js +45 -3
  4. package/dist/cli/result-types.d.ts +27 -1
  5. package/dist/cli/routes/daemon/index.js +32 -1
  6. package/dist/cli/routes/daemon/run.js +962 -521
  7. package/dist/cli/routes/daemon/start.js +40 -1
  8. package/dist/cli/routes/daemon/status.js +41 -1
  9. package/dist/cli/routes/daemon/stop.js +40 -1
  10. package/dist/cli/routes/doctor.js +40 -2
  11. package/dist/cli/routes/events/index.js +32 -1
  12. package/dist/cli/routes/events/ls.js +40 -1
  13. package/dist/cli/routes/events/since.js +40 -1
  14. package/dist/cli/routes/events/tail.js +40 -1
  15. package/dist/cli/routes/init.js +207 -107
  16. package/dist/cli/routes/keygen.js +32 -1
  17. package/dist/cli/routes/mcp.js +135 -24
  18. package/dist/cli/routes/sessions/index.js +32 -1
  19. package/dist/cli/routes/sessions/link.js +141 -21
  20. package/dist/cli/routes/sessions/ls.js +40 -1
  21. package/dist/cli/routes/sessions/revoke.js +40 -1
  22. package/dist/cli/routes/tools/call.js +40 -1
  23. package/dist/cli/routes/tools/describe.js +40 -1
  24. package/dist/cli/routes/tools/index.js +32 -1
  25. package/dist/cli/routes/tools/ls.js +40 -1
  26. package/dist/client/attach-page.d.ts +39 -0
  27. package/dist/client/bootstrap.d.ts +17 -3
  28. package/dist/client/index.d.ts +2 -1
  29. package/dist/client/index.js +286 -112
  30. package/dist/commands/doctor.d.ts +3 -4
  31. package/dist/commands/init.d.ts +8 -2
  32. package/dist/commands/link.d.ts +10 -1
  33. package/dist/daemon/calls.d.ts +1 -1
  34. package/dist/daemon/config.d.ts +6 -0
  35. package/dist/daemon/daemon.d.ts +6 -1
  36. package/dist/daemon/links.d.ts +9 -5
  37. package/dist/daemon/listener.d.ts +28 -1
  38. package/dist/daemon/rpc-errors.d.ts +3 -0
  39. package/dist/daemon/sessions.d.ts +11 -7
  40. package/dist/daemon/tls.d.ts +0 -3
  41. package/dist/devtools-relay/attach-tab.d.ts +33 -0
  42. package/dist/devtools-relay/index.d.ts +8 -0
  43. package/dist/devtools-relay/memory-daemon-sockets.d.ts +20 -0
  44. package/dist/devtools-relay/memory-devtools-browser.d.ts +10 -0
  45. package/dist/devtools-relay/memory-page-channel.d.ts +16 -0
  46. package/dist/devtools-relay/node-daemon-socket.d.ts +3 -0
  47. package/dist/devtools-relay/node-devtools-browser.d.ts +3 -0
  48. package/dist/devtools-relay/ports.d.ts +48 -0
  49. package/dist/devtools-relay/relay.d.ts +16 -0
  50. package/dist/index.js +45 -3
  51. package/dist/link.d.ts +35 -1
  52. package/dist/mcp/connect-tool.d.ts +15 -2
  53. package/dist/native-scheme.d.ts +11 -7
  54. package/dist/scheme.d.ts +5 -1
  55. 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,
@@ -1385,6 +1393,36 @@ var renderInvokeData = (colors, data, flags) => {
1385
1393
  return [colors.green("Result"), formatScalar(data, flags)];
1386
1394
  };
1387
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
+ }
1388
1426
  const lines = [
1389
1427
  colors.green("Link Created"),
1390
1428
  ...renderFields(
@@ -1505,6 +1543,7 @@ var renderDaemonStatusData = (colors, data, flags) => {
1505
1543
  ["PID", data.daemon.pid],
1506
1544
  ["Started at", data.daemon.started_at],
1507
1545
  ["WSS port", data.daemon.wss_port],
1546
+ ["Web port", data.daemon.web_port],
1508
1547
  ["Pinned keys", data.daemon.pinned_keys.length],
1509
1548
  ["Sessions", data.daemon.session_count]
1510
1549
  ],
@@ -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,
@@ -1414,6 +1423,36 @@ var renderInvokeData = (colors, data, flags) => {
1414
1423
  return [colors.green("Result"), formatScalar(data, flags)];
1415
1424
  };
1416
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
+ }
1417
1456
  const lines = [
1418
1457
  colors.green("Link Created"),
1419
1458
  ...renderFields(
@@ -1534,6 +1573,7 @@ var renderDaemonStatusData = (colors, data, flags) => {
1534
1573
  ["PID", data.daemon.pid],
1535
1574
  ["Started at", data.daemon.started_at],
1536
1575
  ["WSS port", data.daemon.wss_port],
1576
+ ["Web port", data.daemon.web_port],
1537
1577
  ["Pinned keys", data.daemon.pinned_keys.length],
1538
1578
  ["Sessions", data.daemon.session_count]
1539
1579
  ],
@@ -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,
@@ -1384,6 +1392,36 @@ var renderInvokeData = (colors, data, flags) => {
1384
1392
  return [colors.green("Result"), formatScalar(data, flags)];
1385
1393
  };
1386
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
+ }
1387
1425
  const lines = [
1388
1426
  colors.green("Link Created"),
1389
1427
  ...renderFields(
@@ -1504,6 +1542,7 @@ var renderDaemonStatusData = (colors, data, flags) => {
1504
1542
  ["PID", data.daemon.pid],
1505
1543
  ["Started at", data.daemon.started_at],
1506
1544
  ["WSS port", data.daemon.wss_port],
1545
+ ["Web port", data.daemon.web_port],
1507
1546
  ["Pinned keys", data.daemon.pinned_keys.length],
1508
1547
  ["Sessions", data.daemon.session_count]
1509
1548
  ],
@@ -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,
@@ -794,6 +801,36 @@ var renderInvokeData = (colors, data, flags) => {
794
801
  return [colors.green("Result"), formatScalar(data, flags)];
795
802
  };
796
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
+ }
797
834
  const lines = [
798
835
  colors.green("Link Created"),
799
836
  ...renderFields(
@@ -914,6 +951,7 @@ var renderDaemonStatusData = (colors, data, flags) => {
914
951
  ["PID", data.daemon.pid],
915
952
  ["Started at", data.daemon.started_at],
916
953
  ["WSS port", data.daemon.wss_port],
954
+ ["Web port", data.daemon.web_port],
917
955
  ["Pinned keys", data.daemon.pinned_keys.length],
918
956
  ["Sessions", data.daemon.session_count]
919
957
  ],
@@ -93,7 +93,7 @@ var EXIT_CODE_BY_ERROR_TYPE = {
93
93
  // EX_NOINPUT (66): "an input file did not exist or was not readable" — `doctor` throws this for a
94
94
  // missing external tool or an unreadable/corrupt artifact, deliberately distinct from every other
95
95
  // code here so a release pipeline can't mistake "we couldn't tell" for either a clean pass or an
96
- // assertion failure (docs/tasks/08-appduct-doctor.md).
96
+ // assertion failure.
97
97
  inspection_error: 66,
98
98
  connection_error: 70,
99
99
  session_error: 71,
@@ -549,6 +549,36 @@ var renderInvokeData = (colors, data, flags) => {
549
549
  return [colors.green("Result"), formatScalar(data, flags)];
550
550
  };
551
551
  var renderLinkData = (colors, data, flags, qr) => {
552
+ if ("attached" in data) {
553
+ return [
554
+ colors.green("Page Attached"),
555
+ ...renderFields(
556
+ "Page",
557
+ [
558
+ ["Session", data.sessionId],
559
+ ["Tab", data.targetId],
560
+ ["URL", data.url],
561
+ ["Expires", new Date(data.expiresAt * 1e3).toISOString()]
562
+ ],
563
+ flags
564
+ )
565
+ ];
566
+ }
567
+ if ("script" in data) {
568
+ return [
569
+ colors.green("Link Created"),
570
+ ...renderFields(
571
+ "Link",
572
+ [
573
+ ["Session", data.sessionId],
574
+ ["URL", data.url],
575
+ ["Script", data.script],
576
+ ["Expires", new Date(data.expiresAt * 1e3).toISOString()]
577
+ ],
578
+ flags
579
+ )
580
+ ];
581
+ }
552
582
  const lines = [
553
583
  colors.green("Link Created"),
554
584
  ...renderFields(
@@ -669,6 +699,7 @@ var renderDaemonStatusData = (colors, data, flags) => {
669
699
  ["PID", data.daemon.pid],
670
700
  ["Started at", data.daemon.started_at],
671
701
  ["WSS port", data.daemon.wss_port],
702
+ ["Web port", data.daemon.web_port],
672
703
  ["Pinned keys", data.daemon.pinned_keys.length],
673
704
  ["Sessions", data.daemon.session_count]
674
705
  ],
@@ -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.");
@@ -931,7 +939,7 @@ var EXIT_CODE_BY_ERROR_TYPE = {
931
939
  // EX_NOINPUT (66): "an input file did not exist or was not readable" — `doctor` throws this for a
932
940
  // missing external tool or an unreadable/corrupt artifact, deliberately distinct from every other
933
941
  // code here so a release pipeline can't mistake "we couldn't tell" for either a clean pass or an
934
- // assertion failure (docs/tasks/08-appduct-doctor.md).
942
+ // assertion failure.
935
943
  inspection_error: 66,
936
944
  connection_error: 70,
937
945
  session_error: 71,
@@ -1437,6 +1445,36 @@ var renderInvokeData = (colors, data, flags) => {
1437
1445
  return [colors.green("Result"), formatScalar(data, flags)];
1438
1446
  };
1439
1447
  var renderLinkData = (colors, data, flags, qr) => {
1448
+ if ("attached" in data) {
1449
+ return [
1450
+ colors.green("Page Attached"),
1451
+ ...renderFields(
1452
+ "Page",
1453
+ [
1454
+ ["Session", data.sessionId],
1455
+ ["Tab", data.targetId],
1456
+ ["URL", data.url],
1457
+ ["Expires", new Date(data.expiresAt * 1e3).toISOString()]
1458
+ ],
1459
+ flags
1460
+ )
1461
+ ];
1462
+ }
1463
+ if ("script" in data) {
1464
+ return [
1465
+ colors.green("Link Created"),
1466
+ ...renderFields(
1467
+ "Link",
1468
+ [
1469
+ ["Session", data.sessionId],
1470
+ ["URL", data.url],
1471
+ ["Script", data.script],
1472
+ ["Expires", new Date(data.expiresAt * 1e3).toISOString()]
1473
+ ],
1474
+ flags
1475
+ )
1476
+ ];
1477
+ }
1440
1478
  const lines = [
1441
1479
  colors.green("Link Created"),
1442
1480
  ...renderFields(
@@ -1557,6 +1595,7 @@ var renderDaemonStatusData = (colors, data, flags) => {
1557
1595
  ["PID", data.daemon.pid],
1558
1596
  ["Started at", data.daemon.started_at],
1559
1597
  ["WSS port", data.daemon.wss_port],
1598
+ ["Web port", data.daemon.web_port],
1560
1599
  ["Pinned keys", data.daemon.pinned_keys.length],
1561
1600
  ["Sessions", data.daemon.session_count]
1562
1601
  ],
@@ -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.");
@@ -1420,6 +1428,36 @@ var renderInvokeData = (colors, data, flags) => {
1420
1428
  return [colors.green("Result"), formatScalar(data, flags)];
1421
1429
  };
1422
1430
  var renderLinkData = (colors, data, flags, qr) => {
1431
+ if ("attached" in data) {
1432
+ return [
1433
+ colors.green("Page Attached"),
1434
+ ...renderFields(
1435
+ "Page",
1436
+ [
1437
+ ["Session", data.sessionId],
1438
+ ["Tab", data.targetId],
1439
+ ["URL", data.url],
1440
+ ["Expires", new Date(data.expiresAt * 1e3).toISOString()]
1441
+ ],
1442
+ flags
1443
+ )
1444
+ ];
1445
+ }
1446
+ if ("script" in data) {
1447
+ return [
1448
+ colors.green("Link Created"),
1449
+ ...renderFields(
1450
+ "Link",
1451
+ [
1452
+ ["Session", data.sessionId],
1453
+ ["URL", data.url],
1454
+ ["Script", data.script],
1455
+ ["Expires", new Date(data.expiresAt * 1e3).toISOString()]
1456
+ ],
1457
+ flags
1458
+ )
1459
+ ];
1460
+ }
1423
1461
  const lines = [
1424
1462
  colors.green("Link Created"),
1425
1463
  ...renderFields(
@@ -1540,6 +1578,7 @@ var renderDaemonStatusData = (colors, data, flags) => {
1540
1578
  ["PID", data.daemon.pid],
1541
1579
  ["Started at", data.daemon.started_at],
1542
1580
  ["WSS port", data.daemon.wss_port],
1581
+ ["Web port", data.daemon.web_port],
1543
1582
  ["Pinned keys", data.daemon.pinned_keys.length],
1544
1583
  ["Sessions", data.daemon.session_count]
1545
1584
  ],
@@ -1705,7 +1744,7 @@ var EXIT_CODE_BY_ERROR_TYPE = {
1705
1744
  // EX_NOINPUT (66): "an input file did not exist or was not readable" — `doctor` throws this for a
1706
1745
  // missing external tool or an unreadable/corrupt artifact, deliberately distinct from every other
1707
1746
  // code here so a release pipeline can't mistake "we couldn't tell" for either a clean pass or an
1708
- // assertion failure (docs/tasks/08-appduct-doctor.md).
1747
+ // assertion failure.
1709
1748
  inspection_error: 66,
1710
1749
  connection_error: 70,
1711
1750
  session_error: 71,
@@ -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.");
@@ -1420,6 +1428,36 @@ var renderInvokeData = (colors, data, flags) => {
1420
1428
  return [colors.green("Result"), formatScalar(data, flags)];
1421
1429
  };
1422
1430
  var renderLinkData = (colors, data, flags, qr) => {
1431
+ if ("attached" in data) {
1432
+ return [
1433
+ colors.green("Page Attached"),
1434
+ ...renderFields(
1435
+ "Page",
1436
+ [
1437
+ ["Session", data.sessionId],
1438
+ ["Tab", data.targetId],
1439
+ ["URL", data.url],
1440
+ ["Expires", new Date(data.expiresAt * 1e3).toISOString()]
1441
+ ],
1442
+ flags
1443
+ )
1444
+ ];
1445
+ }
1446
+ if ("script" in data) {
1447
+ return [
1448
+ colors.green("Link Created"),
1449
+ ...renderFields(
1450
+ "Link",
1451
+ [
1452
+ ["Session", data.sessionId],
1453
+ ["URL", data.url],
1454
+ ["Script", data.script],
1455
+ ["Expires", new Date(data.expiresAt * 1e3).toISOString()]
1456
+ ],
1457
+ flags
1458
+ )
1459
+ ];
1460
+ }
1423
1461
  const lines = [
1424
1462
  colors.green("Link Created"),
1425
1463
  ...renderFields(
@@ -1540,6 +1578,7 @@ var renderDaemonStatusData = (colors, data, flags) => {
1540
1578
  ["PID", data.daemon.pid],
1541
1579
  ["Started at", data.daemon.started_at],
1542
1580
  ["WSS port", data.daemon.wss_port],
1581
+ ["Web port", data.daemon.web_port],
1543
1582
  ["Pinned keys", data.daemon.pinned_keys.length],
1544
1583
  ["Sessions", data.daemon.session_count]
1545
1584
  ],
@@ -1689,7 +1728,7 @@ var EXIT_CODE_BY_ERROR_TYPE = {
1689
1728
  // EX_NOINPUT (66): "an input file did not exist or was not readable" — `doctor` throws this for a
1690
1729
  // missing external tool or an unreadable/corrupt artifact, deliberately distinct from every other
1691
1730
  // code here so a release pipeline can't mistake "we couldn't tell" for either a clean pass or an
1692
- // assertion failure (docs/tasks/08-appduct-doctor.md).
1731
+ // assertion failure.
1693
1732
  inspection_error: 66,
1694
1733
  connection_error: 70,
1695
1734
  session_error: 71,