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.
- package/README.md +52 -22
- package/dist/artifact-inspect.d.ts +38 -39
- package/dist/bin.js +45 -3
- package/dist/cli/result-types.d.ts +27 -1
- package/dist/cli/routes/daemon/index.js +32 -1
- package/dist/cli/routes/daemon/run.js +962 -521
- package/dist/cli/routes/daemon/start.js +40 -1
- package/dist/cli/routes/daemon/status.js +41 -1
- package/dist/cli/routes/daemon/stop.js +40 -1
- package/dist/cli/routes/doctor.js +40 -2
- package/dist/cli/routes/events/index.js +32 -1
- package/dist/cli/routes/events/ls.js +40 -1
- package/dist/cli/routes/events/since.js +40 -1
- package/dist/cli/routes/events/tail.js +40 -1
- package/dist/cli/routes/init.js +207 -107
- package/dist/cli/routes/keygen.js +32 -1
- package/dist/cli/routes/mcp.js +135 -24
- package/dist/cli/routes/sessions/index.js +32 -1
- package/dist/cli/routes/sessions/link.js +141 -21
- package/dist/cli/routes/sessions/ls.js +40 -1
- package/dist/cli/routes/sessions/revoke.js +40 -1
- package/dist/cli/routes/tools/call.js +40 -1
- package/dist/cli/routes/tools/describe.js +40 -1
- package/dist/cli/routes/tools/index.js +32 -1
- package/dist/cli/routes/tools/ls.js +40 -1
- package/dist/client/attach-page.d.ts +39 -0
- package/dist/client/bootstrap.d.ts +17 -3
- package/dist/client/index.d.ts +2 -1
- package/dist/client/index.js +286 -112
- package/dist/commands/doctor.d.ts +3 -4
- package/dist/commands/init.d.ts +8 -2
- package/dist/commands/link.d.ts +10 -1
- package/dist/daemon/calls.d.ts +1 -1
- package/dist/daemon/config.d.ts +6 -0
- package/dist/daemon/daemon.d.ts +6 -1
- package/dist/daemon/links.d.ts +9 -5
- package/dist/daemon/listener.d.ts +28 -1
- package/dist/daemon/rpc-errors.d.ts +3 -0
- package/dist/daemon/sessions.d.ts +11 -7
- package/dist/daemon/tls.d.ts +0 -3
- package/dist/devtools-relay/attach-tab.d.ts +33 -0
- package/dist/devtools-relay/index.d.ts +8 -0
- package/dist/devtools-relay/memory-daemon-sockets.d.ts +20 -0
- package/dist/devtools-relay/memory-devtools-browser.d.ts +10 -0
- package/dist/devtools-relay/memory-page-channel.d.ts +16 -0
- package/dist/devtools-relay/node-daemon-socket.d.ts +3 -0
- package/dist/devtools-relay/node-devtools-browser.d.ts +3 -0
- package/dist/devtools-relay/ports.d.ts +48 -0
- package/dist/devtools-relay/relay.d.ts +16 -0
- package/dist/index.js +45 -3
- package/dist/link.d.ts +35 -1
- package/dist/mcp/connect-tool.d.ts +15 -2
- package/dist/native-scheme.d.ts +11 -7
- package/dist/scheme.d.ts +5 -1
- package/package.json +15 -5
package/dist/cli/routes/init.js
CHANGED
|
@@ -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
|
|
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
|
|
221
|
+
import { readFile } from "node:fs/promises";
|
|
218
222
|
import { homedir } from "node:os";
|
|
219
|
-
import { dirname as dirname2, join as
|
|
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
|
|
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 = [
|
|
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 =
|
|
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(
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
],
|