appduct 0.14.0 → 0.15.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.
Files changed (55) hide show
  1. package/README.md +60 -287
  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 +963 -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 +287 -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
@@ -97,7 +97,7 @@ var EXIT_CODE_BY_ERROR_TYPE = {
97
97
  // EX_NOINPUT (66): "an input file did not exist or was not readable" — `doctor` throws this for a
98
98
  // missing external tool or an unreadable/corrupt artifact, deliberately distinct from every other
99
99
  // code here so a release pipeline can't mistake "we couldn't tell" for either a clean pass or an
100
- // assertion failure (docs/tasks/08-appduct-doctor.md).
100
+ // assertion failure.
101
101
  inspection_error: 66,
102
102
  connection_error: 70,
103
103
  session_error: 71,
@@ -213,14 +213,98 @@ var APP_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/u;
213
213
  var isValidAppId = (value) => APP_ID_PATTERN.test(value);
214
214
  var invalidAppIdMessage = (value) => `"${value}" is not a valid app id (letters, digits, ".", "_" and "-", starting with a letter or digit).`;
215
215
 
216
+ // src/native-scheme.ts
217
+ import { readdir, readFile as readFile2, stat } from "node:fs/promises";
218
+ import { join as join4 } from "node:path";
219
+
216
220
  // src/scheme.ts
217
- import { readFile as readFile2 } from "node:fs/promises";
221
+ import { readFile } from "node:fs/promises";
218
222
  import { homedir } from "node:os";
219
- import { dirname as dirname2, join as join4, resolve } from "node:path";
223
+ import { dirname as dirname2, join as join3, resolve } from "node:path";
224
+ var PROJECT_CONFIG_DIR = ".appduct";
225
+ var PROJECT_CONFIG_FILENAME = "config.json";
226
+ var PROJECT_CONFIG_RELATIVE_PATH = join3(PROJECT_CONFIG_DIR, PROJECT_CONFIG_FILENAME);
227
+ var APP_JSON_FILENAME = "app.json";
228
+ var SCHEME_ENV_VAR = "APPDUCT_SCHEME";
229
+ var SCHEME_PATTERN = /^[a-zA-Z][a-zA-Z0-9+.-]*$/u;
230
+ var isValidScheme = (value) => SCHEME_PATTERN.test(value);
231
+ var requireValidScheme = (value, origin) => {
232
+ if (!isValidScheme(value)) {
233
+ throw usageError(
234
+ `Invalid deep-link scheme ${JSON.stringify(value)} from ${origin}: a scheme must start with a letter and contain only letters, digits, "+", "-" or "." (for example "myapp") \u2014 do not include "://".`
235
+ );
236
+ }
237
+ return value;
238
+ };
239
+ var isPlainObject = (value) => {
240
+ return typeof value === "object" && value !== null && !Array.isArray(value);
241
+ };
242
+ var readJsonFile = async (path) => {
243
+ let text;
244
+ try {
245
+ text = await readFile(path, "utf8");
246
+ } catch (error) {
247
+ const code = error.code;
248
+ if (code === "ENOENT" || code === "ENOTDIR" || code === "EISDIR") {
249
+ return void 0;
250
+ }
251
+ throw error;
252
+ }
253
+ try {
254
+ return { raw: JSON.parse(text) };
255
+ } catch (error) {
256
+ throw usageError(`Could not parse ${path}: ${error.message}`);
257
+ }
258
+ };
259
+ var firstConfiguredScheme = (value) => {
260
+ if (Array.isArray(value)) {
261
+ return value.find((entry) => typeof entry === "string" && entry.length > 0);
262
+ }
263
+ return typeof value === "string" && value.length > 0 ? value : void 0;
264
+ };
265
+ var discoverExpoScheme = async (dir) => {
266
+ const path = join3(resolve(dir), APP_JSON_FILENAME);
267
+ const file = await readJsonFile(path);
268
+ if (!file || !isPlainObject(file.raw)) {
269
+ return void 0;
270
+ }
271
+ const expo = file.raw.expo;
272
+ if (!isPlainObject(expo)) {
273
+ return void 0;
274
+ }
275
+ const scheme = firstConfiguredScheme(expo.scheme);
276
+ return scheme === void 0 ? void 0 : requireValidScheme(scheme, `"expo.scheme" in ${path}`);
277
+ };
278
+ var discoverStaticProjectScheme = async (cwd) => {
279
+ const appJsonPath = join3(resolve(cwd), APP_JSON_FILENAME);
280
+ const tried = [`${appJsonPath} ("expo.scheme")`];
281
+ const appJsonScheme = await discoverExpoScheme(cwd);
282
+ if (appJsonScheme !== void 0) {
283
+ return { scheme: appJsonScheme, source: "app-json", origin: appJsonPath, nativeResults: [], tried };
284
+ }
285
+ const native = await discoverNativeScheme(resolve(cwd));
286
+ tried.push(...native.tried);
287
+ const [first] = native.results;
288
+ if (first !== void 0) {
289
+ return {
290
+ scheme: first.scheme,
291
+ source: first.source,
292
+ origin: first.origin,
293
+ nativeResults: native.results,
294
+ tried
295
+ };
296
+ }
297
+ return { nativeResults: [], tried };
298
+ };
299
+ var globalConfigDirs = (options = {}) => {
300
+ const excluded = /* @__PURE__ */ new Set([join3(resolve(options.homeDir ?? homedir()), PROJECT_CONFIG_DIR)]);
301
+ if (options.stateDirRoot !== void 0) {
302
+ excluded.add(resolve(options.stateDirRoot));
303
+ }
304
+ return excluded;
305
+ };
220
306
 
221
307
  // src/native-scheme.ts
222
- import { readdir, readFile, stat } from "node:fs/promises";
223
- import { join as join3 } from "node:path";
224
308
  var MAX_PROBE_FILE_BYTES = 2 * 1024 * 1024;
225
309
  var MAX_PLIST_WALK_DEPTH = 2;
226
310
  var MAX_WALK_ENTRIES = 5e3;
@@ -247,7 +331,7 @@ var readCappedBuffer = async (path) => {
247
331
  return { ok: false, reason: "too-large" };
248
332
  }
249
333
  try {
250
- return { ok: true, data: await readFile(path) };
334
+ return { ok: true, data: await readFile2(path) };
251
335
  } catch {
252
336
  return { ok: false, reason: "unreadable" };
253
337
  }
@@ -267,7 +351,7 @@ var extractGradleScheme = (text) => {
267
351
  return value !== void 0 && value.length > 0 ? value : void 0;
268
352
  };
269
353
  var probeAndroidGradle = async (root) => {
270
- const candidates = [join3(root, "app", "build.gradle.kts"), join3(root, "app", "build.gradle")];
354
+ const candidates = [join4(root, "app", "build.gradle.kts"), join4(root, "app", "build.gradle")];
271
355
  const notes = [];
272
356
  for (const candidate of candidates) {
273
357
  const read = await readCappedText(candidate);
@@ -314,7 +398,7 @@ var extractManifestScheme = (xml) => {
314
398
  return void 0;
315
399
  };
316
400
  var probeAndroidManifest = async (root) => {
317
- const manifestPath = join3(root, "app", "src", "main", "AndroidManifest.xml");
401
+ const manifestPath = join4(root, "app", "src", "main", "AndroidManifest.xml");
318
402
  const read = await readCappedText(manifestPath);
319
403
  const description = `${manifestPath} (first <data android:scheme> in a VIEW intent-filter)`;
320
404
  if (!read.ok) {
@@ -454,12 +538,12 @@ var walkForFilename = async (root, filename, maxDepth) => {
454
538
  continue;
455
539
  }
456
540
  if (depth < maxDepth) {
457
- await walk(join3(dir, entry.name), depth + 1);
541
+ await walk(join4(dir, entry.name), depth + 1);
458
542
  }
459
543
  continue;
460
544
  }
461
545
  if (entry.isFile() && entry.name === filename) {
462
- hits.push({ path: join3(dir, entry.name), depth });
546
+ hits.push({ path: join4(dir, entry.name), depth });
463
547
  }
464
548
  }
465
549
  };
@@ -513,7 +597,7 @@ var extractProjectYmlScheme = (text) => {
513
597
  return raw !== void 0 && raw.length > 0 ? raw : void 0;
514
598
  };
515
599
  var probeIosProjectYml = async (root) => {
516
- const path = join3(root, "project.yml");
600
+ const path = join4(root, "project.yml");
517
601
  const description = `${path} ("info.properties.CFBundleURLTypes" > "CFBundleURLSchemes")`;
518
602
  const read = await readCappedText(path);
519
603
  if (!read.ok) {
@@ -532,23 +616,34 @@ var probeIosProjectYml = async (root) => {
532
616
  };
533
617
  };
534
618
  var PROBES = [
535
- probeAndroidGradle,
536
- probeAndroidManifest,
537
- probeIosInfoPlist,
538
- probeIosProjectYml
619
+ ["android", probeAndroidGradle],
620
+ ["android", probeAndroidManifest],
621
+ ["ios", probeIosInfoPlist],
622
+ ["ios", probeIosProjectYml]
539
623
  ];
624
+ var isFlutterProject = async (root) => {
625
+ return (await readCappedText(join4(root, "pubspec.yaml"))).ok;
626
+ };
540
627
  var discoverNativeScheme = async (root) => {
541
628
  const tried = [];
542
629
  const results = [];
543
- for (const probe of PROBES) {
544
- const outcome = await probe(root);
545
- tried.push(...outcome.tried);
546
- if (outcome.result !== void 0) {
547
- results.push(outcome.result);
630
+ const flutter = await isFlutterProject(root);
631
+ const androidRoot = flutter ? join4(root, "android") : root;
632
+ const iosRoots = flutter ? [join4(root, "ios"), join4(root, "macos")] : [root];
633
+ for (const [platform, probe] of PROBES) {
634
+ let roots = [root];
635
+ if (platform === "android") {
636
+ roots = [androidRoot];
637
+ } else if (probe === probeIosInfoPlist) {
638
+ roots = iosRoots;
639
+ }
640
+ for (const probeRoot of roots) {
641
+ const outcome = await probe(probeRoot);
642
+ tried.push(...outcome.tried);
643
+ if (outcome.result !== void 0) {
644
+ results.push(outcome.result);
645
+ }
548
646
  }
549
- }
550
- if (results.length === 0) {
551
- return { tried };
552
647
  }
553
648
  const distinctSchemes = new Set(results.map((entry) => entry.scheme));
554
649
  if (distinctSchemes.size > 1) {
@@ -556,89 +651,7 @@ var discoverNativeScheme = async (root) => {
556
651
  "Conflicting deep-link schemes found in this project: " + results.map((entry) => `${entry.origin} says ${JSON.stringify(entry.scheme)}`).join(", but ") + ". Appduct will not guess which one is right \u2014 pass --scheme <scheme> to pick one."
557
652
  );
558
653
  }
559
- return { result: results[0], tried };
560
- };
561
-
562
- // src/scheme.ts
563
- var PROJECT_CONFIG_DIR = ".appduct";
564
- var PROJECT_CONFIG_FILENAME = "config.json";
565
- var PROJECT_CONFIG_RELATIVE_PATH = join4(PROJECT_CONFIG_DIR, PROJECT_CONFIG_FILENAME);
566
- var APP_JSON_FILENAME = "app.json";
567
- var SCHEME_ENV_VAR = "APPDUCT_SCHEME";
568
- var SCHEME_PATTERN = /^[a-zA-Z][a-zA-Z0-9+.-]*$/u;
569
- var isValidScheme = (value) => SCHEME_PATTERN.test(value);
570
- var requireValidScheme = (value, origin) => {
571
- if (!isValidScheme(value)) {
572
- throw usageError(
573
- `Invalid deep-link scheme ${JSON.stringify(value)} from ${origin}: a scheme must start with a letter and contain only letters, digits, "+", "-" or "." (for example "myapp") \u2014 do not include "://".`
574
- );
575
- }
576
- return value;
577
- };
578
- var isPlainObject = (value) => {
579
- return typeof value === "object" && value !== null && !Array.isArray(value);
580
- };
581
- var readJsonFile = async (path) => {
582
- let text;
583
- try {
584
- text = await readFile2(path, "utf8");
585
- } catch (error) {
586
- const code = error.code;
587
- if (code === "ENOENT" || code === "ENOTDIR" || code === "EISDIR") {
588
- return void 0;
589
- }
590
- throw error;
591
- }
592
- try {
593
- return { raw: JSON.parse(text) };
594
- } catch (error) {
595
- throw usageError(`Could not parse ${path}: ${error.message}`);
596
- }
597
- };
598
- var firstConfiguredScheme = (value) => {
599
- if (Array.isArray(value)) {
600
- return value.find((entry) => typeof entry === "string" && entry.length > 0);
601
- }
602
- return typeof value === "string" && value.length > 0 ? value : void 0;
603
- };
604
- var discoverExpoScheme = async (dir) => {
605
- const path = join4(resolve(dir), APP_JSON_FILENAME);
606
- const file = await readJsonFile(path);
607
- if (!file || !isPlainObject(file.raw)) {
608
- return void 0;
609
- }
610
- const expo = file.raw.expo;
611
- if (!isPlainObject(expo)) {
612
- return void 0;
613
- }
614
- const scheme = firstConfiguredScheme(expo.scheme);
615
- return scheme === void 0 ? void 0 : requireValidScheme(scheme, `"expo.scheme" in ${path}`);
616
- };
617
- var discoverStaticProjectScheme = async (cwd) => {
618
- const appJsonPath = join4(resolve(cwd), APP_JSON_FILENAME);
619
- const tried = [`${appJsonPath} ("expo.scheme")`];
620
- const appJsonScheme = await discoverExpoScheme(cwd);
621
- if (appJsonScheme !== void 0) {
622
- return { scheme: appJsonScheme, source: "app-json", origin: appJsonPath, tried };
623
- }
624
- const native = await discoverNativeScheme(resolve(cwd));
625
- tried.push(...native.tried);
626
- if (native.result !== void 0) {
627
- return {
628
- scheme: native.result.scheme,
629
- source: native.result.source,
630
- origin: native.result.origin,
631
- tried
632
- };
633
- }
634
- return { tried };
635
- };
636
- var globalConfigDirs = (options = {}) => {
637
- const excluded = /* @__PURE__ */ new Set([join4(resolve(options.homeDir ?? homedir()), PROJECT_CONFIG_DIR)]);
638
- if (options.stateDirRoot !== void 0) {
639
- excluded.add(resolve(options.stateDirRoot));
640
- }
641
- return excluded;
654
+ return { results, tried };
642
655
  };
643
656
 
644
657
  // src/commands/init.ts
@@ -720,6 +733,60 @@ var readExistingAppId = (existing, path, platform, flagName, replaceable) => {
720
733
  }
721
734
  return value;
722
735
  };
736
+ var MAX_MANIFEST_WALK_UP = 2;
737
+ var declaresReactNative = async (root) => {
738
+ let directory = root;
739
+ for (let levelsUp = 0; levelsUp <= MAX_MANIFEST_WALK_UP; levelsUp += 1) {
740
+ let manifest;
741
+ try {
742
+ manifest = JSON.parse(await readFile3(join5(directory, "package.json"), "utf8"));
743
+ } catch {
744
+ manifest = void 0;
745
+ }
746
+ if (typeof manifest === "object" && manifest !== null) {
747
+ const declared = manifest;
748
+ const dependencies = [declared.dependencies, declared.devDependencies].flatMap(
749
+ (field) => typeof field === "object" && field !== null ? Object.keys(field) : []
750
+ );
751
+ return dependencies.includes("react-native") || dependencies.includes("expo");
752
+ }
753
+ const parent = dirname3(directory);
754
+ if (parent === directory) {
755
+ break;
756
+ }
757
+ directory = parent;
758
+ }
759
+ return false;
760
+ };
761
+ var discoveredPlatforms = (discovered) => {
762
+ const sources = discovered.nativeResults.map((entry) => entry.source);
763
+ return [
764
+ ...sources.some((source) => source.startsWith("android-")) ? ["android"] : [],
765
+ ...sources.some((source) => source.startsWith("ios-")) ? ["ios"] : []
766
+ ];
767
+ };
768
+ var wiringNextSteps = async (root, discovered, scheme, platforms) => {
769
+ if (discovered.source === "app-json" || await declaresReactNative(root)) {
770
+ return [
771
+ 'Add `import "@appduct/react-native/auto";` to your app entry (index.js / App.tsx) \u2014 it is what starts the in-app agent endpoint.'
772
+ ];
773
+ }
774
+ const ios = "Forward every opened URL to Appduct: `_ = Appduct.shared.handle(url)` in SwiftUI's `.onOpenURL` (UIKit: `scene(_:openURLContexts:)`). It is what receives the connection link.";
775
+ const androidPlaceholder = `Check that \`app/build.gradle(.kts)\` sets \`manifestPlaceholders["appductScheme"] = "${scheme}"\` \u2014 Appduct's own activity receives the connection link on that scheme. Placeholders are fixed at build time, so rebuild and reinstall after changing one.`;
776
+ const androidOwnScheme = "Appduct receives links on the scheme in `manifestPlaceholders[\"appductScheme\"]` in `app/build.gradle(.kts)`. Give it a scheme of your own, not one your app already handles: on a shared scheme Appduct's activity can't tell your links from its own and yours stop reaching your app. That scheme is what the CLI opens too, so record it with `appduct init --scheme <scheme> --force` and rebuild. Both options: https://callstackincubator.github.io/appduct/install/android/#deep-links.";
777
+ const showIos = platforms.length === 0 || platforms.includes("ios");
778
+ const showAndroid = platforms.length === 0 || platforms.includes("android");
779
+ const gradleHit = discovered.nativeResults.some((entry) => entry.source === "android-gradle");
780
+ return [
781
+ ...showIos ? [ios] : [],
782
+ ...showAndroid ? [gradleHit ? androidPlaceholder : androidOwnScheme] : []
783
+ ];
784
+ };
785
+ var flutterNextSteps = (scheme) => [
786
+ "Add the package with `flutter pub add appduct`, then call `await Appduct.ensureInitialized();` in `main()` before `runApp` \u2014 it is what starts the in-app agent endpoint.",
787
+ `Register your URL scheme on each platform you run. Android: add an \`<intent-filter>\` to your activity in \`android/app/src/main/AndroidManifest.xml\` with the actions and categories \`android.intent.action.VIEW\`, \`android.intent.category.DEFAULT\` and \`android.intent.category.BROWSABLE\`, and \`<data android:scheme="${scheme}"/>\`. iOS and macOS: add \`CFBundleURLTypes\` > \`CFBundleURLSchemes\` to \`ios/Runner/Info.plist\` (and \`macos/Runner/Info.plist\`).`,
788
+ 'Allow the connection to your computer. Android: add `<uses-permission android:name="android.permission.INTERNET"/>` to `android/app/src/main/AndroidManifest.xml` (Flutter only puts it in the debug and profile manifests, so a release build you opt in to Appduct needs it in the main one). iOS: add `NSLocalNetworkUsageDescription` to `ios/Runner/Info.plist`. macOS: set `com.apple.security.network.client` to `true` in both `macos/Runner/DebugProfile.entitlements` and `macos/Runner/Release.entitlements`.'
789
+ ];
723
790
  var handleInitCommand = async (options, context = {}) => {
724
791
  const root = resolve2(context.cwd ?? process.cwd());
725
792
  const projectDir = join5(root, PROJECT_CONFIG_DIR);
@@ -778,6 +845,7 @@ var handleInitCommand = async (options, context = {}) => {
778
845
  const discovered = await discoverStaticProjectScheme(root);
779
846
  const discoveredScheme = discovered.scheme;
780
847
  const discoveredSourceLabel = discovered.source === void 0 ? void 0 : discovered.source === "app-json" ? "app.json" : discovered.source;
848
+ const platforms = discoveredPlatforms(discovered);
781
849
  const [scheme, source, origin] = (() => {
782
850
  if (options.scheme !== void 0) {
783
851
  return [options.scheme, "--scheme", void 0];
@@ -825,6 +893,7 @@ Run \`appduct init --scheme <scheme>\` with the scheme your app registers for de
825
893
  scheme,
826
894
  source,
827
895
  ...origin === void 0 ? {} : { origin },
896
+ ...platforms.length === 0 ? {} : { platforms },
828
897
  created: existing === void 0,
829
898
  changed: !alreadyCorrect,
830
899
  ...iosAppId === void 0 && androidAppId === void 0 ? {} : {
@@ -839,7 +908,7 @@ Run \`appduct init --scheme <scheme>\` with the scheme your app registers for de
839
908
  args: ["mcp", "--scheme", scheme]
840
909
  },
841
910
  nextSteps: [
842
- 'Add `import "@appduct/react-native/auto";` to your app entry (index.js / App.tsx) \u2014 it is what starts the in-app agent endpoint.',
911
+ ...await isFlutterProject(root) ? flutterNextSteps(scheme) : await wiringNextSteps(root, discovered, scheme, platforms),
843
912
  `Add the Appduct MCP server entry to your agent's MCP config. "--scheme ${scheme}" keeps that entry self-contained; ${SCHEME_ENV_VAR} and this ${PROJECT_CONFIG_RELATIVE_PATH} work too.`,
844
913
  "With the app running, pair a device: `appduct sessions link --open ios-sim` (or `--open android`).",
845
914
  ...origin === void 0 ? [] : [`Scheme "${scheme}" was read from ${origin}.`],
@@ -1197,6 +1266,36 @@ var renderInvokeData = (colors, data, flags) => {
1197
1266
  return [colors.green("Result"), formatScalar(data, flags)];
1198
1267
  };
1199
1268
  var renderLinkData = (colors, data, flags, qr) => {
1269
+ if ("attached" in data) {
1270
+ return [
1271
+ colors.green("Page Attached"),
1272
+ ...renderFields(
1273
+ "Page",
1274
+ [
1275
+ ["Session", data.sessionId],
1276
+ ["Tab", data.targetId],
1277
+ ["URL", data.url],
1278
+ ["Expires", new Date(data.expiresAt * 1e3).toISOString()]
1279
+ ],
1280
+ flags
1281
+ )
1282
+ ];
1283
+ }
1284
+ if ("script" in data) {
1285
+ return [
1286
+ colors.green("Link Created"),
1287
+ ...renderFields(
1288
+ "Link",
1289
+ [
1290
+ ["Session", data.sessionId],
1291
+ ["URL", data.url],
1292
+ ["Script", data.script],
1293
+ ["Expires", new Date(data.expiresAt * 1e3).toISOString()]
1294
+ ],
1295
+ flags
1296
+ )
1297
+ ];
1298
+ }
1200
1299
  const lines = [
1201
1300
  colors.green("Link Created"),
1202
1301
  ...renderFields(
@@ -1317,6 +1416,7 @@ var renderDaemonStatusData = (colors, data, flags) => {
1317
1416
  ["PID", data.daemon.pid],
1318
1417
  ["Started at", data.daemon.started_at],
1319
1418
  ["WSS port", data.daemon.wss_port],
1419
+ ["Web port", data.daemon.web_port],
1320
1420
  ["Pinned keys", data.daemon.pinned_keys.length],
1321
1421
  ["Sessions", data.daemon.session_count]
1322
1422
  ],
@@ -112,7 +112,7 @@ var EXIT_CODE_BY_ERROR_TYPE = {
112
112
  // EX_NOINPUT (66): "an input file did not exist or was not readable" — `doctor` throws this for a
113
113
  // missing external tool or an unreadable/corrupt artifact, deliberately distinct from every other
114
114
  // code here so a release pipeline can't mistake "we couldn't tell" for either a clean pass or an
115
- // assertion failure (docs/tasks/08-appduct-doctor.md).
115
+ // assertion failure.
116
116
  inspection_error: 66,
117
117
  connection_error: 70,
118
118
  session_error: 71,
@@ -646,6 +646,36 @@ var renderInvokeData = (colors, data, flags) => {
646
646
  return [colors.green("Result"), formatScalar(data, flags)];
647
647
  };
648
648
  var renderLinkData = (colors, data, flags, qr) => {
649
+ if ("attached" in data) {
650
+ return [
651
+ colors.green("Page Attached"),
652
+ ...renderFields(
653
+ "Page",
654
+ [
655
+ ["Session", data.sessionId],
656
+ ["Tab", data.targetId],
657
+ ["URL", data.url],
658
+ ["Expires", new Date(data.expiresAt * 1e3).toISOString()]
659
+ ],
660
+ flags
661
+ )
662
+ ];
663
+ }
664
+ if ("script" in data) {
665
+ return [
666
+ colors.green("Link Created"),
667
+ ...renderFields(
668
+ "Link",
669
+ [
670
+ ["Session", data.sessionId],
671
+ ["URL", data.url],
672
+ ["Script", data.script],
673
+ ["Expires", new Date(data.expiresAt * 1e3).toISOString()]
674
+ ],
675
+ flags
676
+ )
677
+ ];
678
+ }
649
679
  const lines = [
650
680
  colors.green("Link Created"),
651
681
  ...renderFields(
@@ -766,6 +796,7 @@ var renderDaemonStatusData = (colors, data, flags) => {
766
796
  ["PID", data.daemon.pid],
767
797
  ["Started at", data.daemon.started_at],
768
798
  ["WSS port", data.daemon.wss_port],
799
+ ["Web port", data.daemon.web_port],
769
800
  ["Pinned keys", data.daemon.pinned_keys.length],
770
801
  ["Sessions", data.daemon.session_count]
771
802
  ],