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.
- package/README.md +55 -24
- package/dist/artifact-inspect.d.ts +38 -39
- package/dist/bin.js +106 -7
- package/dist/cli/result-types.d.ts +35 -2
- package/dist/cli/routes/daemon/index.js +90 -2
- package/dist/cli/routes/daemon/run.js +1088 -517
- package/dist/cli/routes/daemon/start.js +98 -2
- package/dist/cli/routes/daemon/status.js +99 -2
- package/dist/cli/routes/daemon/stop.js +98 -2
- package/dist/cli/routes/doctor.js +98 -3
- package/dist/cli/routes/events/index.d.ts +0 -3
- package/dist/cli/routes/events/index.js +92 -3
- package/dist/cli/routes/events/ls.d.ts +5 -0
- package/dist/cli/routes/events/ls.js +1837 -0
- package/dist/cli/routes/events/since.js +98 -2
- package/dist/cli/routes/events/tail.js +98 -2
- package/dist/cli/routes/init.js +265 -108
- package/dist/cli/routes/keygen.js +90 -2
- package/dist/cli/routes/mcp.js +243 -25
- package/dist/cli/routes/sessions/index.js +90 -2
- package/dist/cli/routes/sessions/link.js +199 -22
- package/dist/cli/routes/sessions/ls.js +98 -2
- package/dist/cli/routes/sessions/revoke.js +98 -2
- package/dist/cli/routes/tools/call.js +98 -2
- package/dist/cli/routes/tools/describe.js +98 -2
- package/dist/cli/routes/tools/index.js +90 -2
- package/dist/cli/routes/tools/ls.js +98 -2
- 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/events-ls.d.ts +21 -0
- package/dist/commands/init.d.ts +8 -2
- package/dist/commands/link.d.ts +10 -1
- package/dist/daemon/calls.d.ts +3 -1
- package/dist/daemon/config.d.ts +6 -0
- package/dist/daemon/daemon.d.ts +6 -1
- package/dist/daemon/event-bus.d.ts +10 -0
- package/dist/daemon/links.d.ts +9 -5
- package/dist/daemon/listener.d.ts +28 -1
- package/dist/daemon/registry.d.ts +20 -12
- package/dist/daemon/rpc-errors.d.ts +6 -0
- package/dist/daemon/sessions.d.ts +14 -8
- 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 +106 -7
- package/dist/link.d.ts +35 -1
- package/dist/mcp/app-tools.d.ts +19 -0
- package/dist/mcp/connect-tool.d.ts +15 -2
- package/dist/mcp/events-tool.d.ts +39 -0
- 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}.`],
|
|
@@ -861,6 +930,7 @@ var formatJson = (value, flags) => {
|
|
|
861
930
|
import pc from "picocolors";
|
|
862
931
|
import {
|
|
863
932
|
formatAgentWebSocketUrl,
|
|
933
|
+
renderEventSignature,
|
|
864
934
|
renderToolSignature,
|
|
865
935
|
summarizeToolDescription
|
|
866
936
|
} from "@appduct/shared";
|
|
@@ -956,6 +1026,13 @@ var formatDevice = (device) => {
|
|
|
956
1026
|
const label = parts.length > 0 ? parts.join(" ") : "unknown device";
|
|
957
1027
|
return device.os ? `${label} (${device.os})` : label;
|
|
958
1028
|
};
|
|
1029
|
+
var SUSPEND_REASON_LABELS = {
|
|
1030
|
+
app_backgrounded: "app in background",
|
|
1031
|
+
connection_lost: "connection lost"
|
|
1032
|
+
};
|
|
1033
|
+
var formatSessionState = (session) => {
|
|
1034
|
+
return session.suspendReason ? `${session.state} (${SUSPEND_REASON_LABELS[session.suspendReason]})` : session.state;
|
|
1035
|
+
};
|
|
959
1036
|
var renderLsData = (colors, data, now) => {
|
|
960
1037
|
if (data.length === 0) {
|
|
961
1038
|
return [colors.dim("Sessions"), " No Appduct sessions are registered."];
|
|
@@ -964,7 +1041,7 @@ var renderLsData = (colors, data, now) => {
|
|
|
964
1041
|
const headers = ["Alias", "State", "Device", "Tools", "Age"];
|
|
965
1042
|
const rows = data.map((session) => [
|
|
966
1043
|
session.alias,
|
|
967
|
-
session
|
|
1044
|
+
formatSessionState(session),
|
|
968
1045
|
formatDevice(session.device),
|
|
969
1046
|
String(session.toolCount),
|
|
970
1047
|
formatAge(session.claimedAt ?? session.createdAt, nowIso)
|
|
@@ -1138,10 +1215,87 @@ var renderToolsListData = (colors, data, flags, full) => {
|
|
|
1138
1215
|
}
|
|
1139
1216
|
return full ? renderToolsFullListing(colors, data, flags) : renderToolSummaryTable(colors, data);
|
|
1140
1217
|
};
|
|
1218
|
+
var renderEventsTruncationLine = (data) => {
|
|
1219
|
+
const offset = data.offset ?? 0;
|
|
1220
|
+
if (data.events.length >= data.total) {
|
|
1221
|
+
return [];
|
|
1222
|
+
}
|
|
1223
|
+
return [
|
|
1224
|
+
"",
|
|
1225
|
+
`Showing ${data.events.length} of ${data.total} events (offset ${offset}). Narrow with --name <glob> or page with --offset <n>.`
|
|
1226
|
+
];
|
|
1227
|
+
};
|
|
1228
|
+
var renderEventsListData = (colors, data, flags) => {
|
|
1229
|
+
const [only] = data.events;
|
|
1230
|
+
if (data.name !== void 0 && !data.name.includes("*") && only !== void 0) {
|
|
1231
|
+
return renderFields(
|
|
1232
|
+
colors.green(`Event: ${only.name}`),
|
|
1233
|
+
[
|
|
1234
|
+
["Signature", renderEventSignature(only)],
|
|
1235
|
+
["Description", only.description],
|
|
1236
|
+
["Payload schema", only.payload_schema]
|
|
1237
|
+
],
|
|
1238
|
+
flags
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1241
|
+
if (data.events.length === 0 && data.total > 0) {
|
|
1242
|
+
return [
|
|
1243
|
+
colors.green("Events"),
|
|
1244
|
+
` No events at offset ${data.offset ?? 0}; ${data.total} matching event${data.total === 1 ? "" : "s"} in total.`
|
|
1245
|
+
];
|
|
1246
|
+
}
|
|
1247
|
+
if (data.events.length === 0) {
|
|
1248
|
+
return [
|
|
1249
|
+
colors.green("Events"),
|
|
1250
|
+
data.name === void 0 ? " No events declared." : ` No events match ${JSON.stringify(data.name)}.`
|
|
1251
|
+
];
|
|
1252
|
+
}
|
|
1253
|
+
const lines = data.events.flatMap((event) => [
|
|
1254
|
+
` ${renderEventSignature(event)}`,
|
|
1255
|
+
` ${summarizeToolDescription(event.description)}`
|
|
1256
|
+
]);
|
|
1257
|
+
return [
|
|
1258
|
+
colors.green("Events"),
|
|
1259
|
+
...lines,
|
|
1260
|
+
...renderEventsTruncationLine(data),
|
|
1261
|
+
"",
|
|
1262
|
+
"Run `appduct events ls --name <name>` for an event's full payload schema."
|
|
1263
|
+
];
|
|
1264
|
+
};
|
|
1141
1265
|
var renderInvokeData = (colors, data, flags) => {
|
|
1142
1266
|
return [colors.green("Result"), formatScalar(data, flags)];
|
|
1143
1267
|
};
|
|
1144
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
|
+
}
|
|
1145
1299
|
const lines = [
|
|
1146
1300
|
colors.green("Link Created"),
|
|
1147
1301
|
...renderFields(
|
|
@@ -1262,6 +1416,7 @@ var renderDaemonStatusData = (colors, data, flags) => {
|
|
|
1262
1416
|
["PID", data.daemon.pid],
|
|
1263
1417
|
["Started at", data.daemon.started_at],
|
|
1264
1418
|
["WSS port", data.daemon.wss_port],
|
|
1419
|
+
["Web port", data.daemon.web_port],
|
|
1265
1420
|
["Pinned keys", data.daemon.pinned_keys.length],
|
|
1266
1421
|
["Sessions", data.daemon.session_count]
|
|
1267
1422
|
],
|
|
@@ -1329,6 +1484,8 @@ var renderSuccessData = (colors, command, data, options) => {
|
|
|
1329
1484
|
return renderToolsListData(colors, data, flags, options.full);
|
|
1330
1485
|
case "tools describe":
|
|
1331
1486
|
return renderToolDetail(colors, data, flags);
|
|
1487
|
+
case "events ls":
|
|
1488
|
+
return renderEventsListData(colors, data, flags);
|
|
1332
1489
|
case "tools call":
|
|
1333
1490
|
return renderInvokeData(colors, data, flags);
|
|
1334
1491
|
case "sessions revoke":
|
|
@@ -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,
|
|
@@ -310,6 +310,7 @@ var formatJson = (value, flags) => {
|
|
|
310
310
|
import pc from "picocolors";
|
|
311
311
|
import {
|
|
312
312
|
formatAgentWebSocketUrl,
|
|
313
|
+
renderEventSignature,
|
|
313
314
|
renderToolSignature,
|
|
314
315
|
summarizeToolDescription
|
|
315
316
|
} from "@appduct/shared";
|
|
@@ -405,6 +406,13 @@ var formatDevice = (device) => {
|
|
|
405
406
|
const label = parts.length > 0 ? parts.join(" ") : "unknown device";
|
|
406
407
|
return device.os ? `${label} (${device.os})` : label;
|
|
407
408
|
};
|
|
409
|
+
var SUSPEND_REASON_LABELS = {
|
|
410
|
+
app_backgrounded: "app in background",
|
|
411
|
+
connection_lost: "connection lost"
|
|
412
|
+
};
|
|
413
|
+
var formatSessionState = (session) => {
|
|
414
|
+
return session.suspendReason ? `${session.state} (${SUSPEND_REASON_LABELS[session.suspendReason]})` : session.state;
|
|
415
|
+
};
|
|
408
416
|
var renderLsData = (colors, data, now) => {
|
|
409
417
|
if (data.length === 0) {
|
|
410
418
|
return [colors.dim("Sessions"), " No Appduct sessions are registered."];
|
|
@@ -413,7 +421,7 @@ var renderLsData = (colors, data, now) => {
|
|
|
413
421
|
const headers = ["Alias", "State", "Device", "Tools", "Age"];
|
|
414
422
|
const rows = data.map((session) => [
|
|
415
423
|
session.alias,
|
|
416
|
-
session
|
|
424
|
+
formatSessionState(session),
|
|
417
425
|
formatDevice(session.device),
|
|
418
426
|
String(session.toolCount),
|
|
419
427
|
formatAge(session.claimedAt ?? session.createdAt, nowIso)
|
|
@@ -587,10 +595,87 @@ var renderToolsListData = (colors, data, flags, full) => {
|
|
|
587
595
|
}
|
|
588
596
|
return full ? renderToolsFullListing(colors, data, flags) : renderToolSummaryTable(colors, data);
|
|
589
597
|
};
|
|
598
|
+
var renderEventsTruncationLine = (data) => {
|
|
599
|
+
const offset = data.offset ?? 0;
|
|
600
|
+
if (data.events.length >= data.total) {
|
|
601
|
+
return [];
|
|
602
|
+
}
|
|
603
|
+
return [
|
|
604
|
+
"",
|
|
605
|
+
`Showing ${data.events.length} of ${data.total} events (offset ${offset}). Narrow with --name <glob> or page with --offset <n>.`
|
|
606
|
+
];
|
|
607
|
+
};
|
|
608
|
+
var renderEventsListData = (colors, data, flags) => {
|
|
609
|
+
const [only] = data.events;
|
|
610
|
+
if (data.name !== void 0 && !data.name.includes("*") && only !== void 0) {
|
|
611
|
+
return renderFields(
|
|
612
|
+
colors.green(`Event: ${only.name}`),
|
|
613
|
+
[
|
|
614
|
+
["Signature", renderEventSignature(only)],
|
|
615
|
+
["Description", only.description],
|
|
616
|
+
["Payload schema", only.payload_schema]
|
|
617
|
+
],
|
|
618
|
+
flags
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
if (data.events.length === 0 && data.total > 0) {
|
|
622
|
+
return [
|
|
623
|
+
colors.green("Events"),
|
|
624
|
+
` No events at offset ${data.offset ?? 0}; ${data.total} matching event${data.total === 1 ? "" : "s"} in total.`
|
|
625
|
+
];
|
|
626
|
+
}
|
|
627
|
+
if (data.events.length === 0) {
|
|
628
|
+
return [
|
|
629
|
+
colors.green("Events"),
|
|
630
|
+
data.name === void 0 ? " No events declared." : ` No events match ${JSON.stringify(data.name)}.`
|
|
631
|
+
];
|
|
632
|
+
}
|
|
633
|
+
const lines = data.events.flatMap((event) => [
|
|
634
|
+
` ${renderEventSignature(event)}`,
|
|
635
|
+
` ${summarizeToolDescription(event.description)}`
|
|
636
|
+
]);
|
|
637
|
+
return [
|
|
638
|
+
colors.green("Events"),
|
|
639
|
+
...lines,
|
|
640
|
+
...renderEventsTruncationLine(data),
|
|
641
|
+
"",
|
|
642
|
+
"Run `appduct events ls --name <name>` for an event's full payload schema."
|
|
643
|
+
];
|
|
644
|
+
};
|
|
590
645
|
var renderInvokeData = (colors, data, flags) => {
|
|
591
646
|
return [colors.green("Result"), formatScalar(data, flags)];
|
|
592
647
|
};
|
|
593
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
|
+
}
|
|
594
679
|
const lines = [
|
|
595
680
|
colors.green("Link Created"),
|
|
596
681
|
...renderFields(
|
|
@@ -711,6 +796,7 @@ var renderDaemonStatusData = (colors, data, flags) => {
|
|
|
711
796
|
["PID", data.daemon.pid],
|
|
712
797
|
["Started at", data.daemon.started_at],
|
|
713
798
|
["WSS port", data.daemon.wss_port],
|
|
799
|
+
["Web port", data.daemon.web_port],
|
|
714
800
|
["Pinned keys", data.daemon.pinned_keys.length],
|
|
715
801
|
["Sessions", data.daemon.session_count]
|
|
716
802
|
],
|
|
@@ -778,6 +864,8 @@ var renderSuccessData = (colors, command, data, options) => {
|
|
|
778
864
|
return renderToolsListData(colors, data, flags, options.full);
|
|
779
865
|
case "tools describe":
|
|
780
866
|
return renderToolDetail(colors, data, flags);
|
|
867
|
+
case "events ls":
|
|
868
|
+
return renderEventsListData(colors, data, flags);
|
|
781
869
|
case "tools call":
|
|
782
870
|
return renderInvokeData(colors, data, flags);
|
|
783
871
|
case "sessions revoke":
|