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/link.d.ts
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* (`sha256/<44-char-base64>`, may contain `+`/`/`/`=`), so it is percent-encoded here; native/JS
|
|
18
18
|
* parsers use `URLSearchParams`, which decodes it back.
|
|
19
19
|
*/
|
|
20
|
-
import { type AgentEndpoint, type LinkCreateResult } from "@appduct/shared";
|
|
20
|
+
import { type AgentEndpoint, type LinkCreateResult, type WebAttachParams, type WebAttachResult } from "@appduct/shared";
|
|
21
21
|
import { type ExecFn, type OpenTarget } from "./cli/open-target.js";
|
|
22
22
|
import { type SpawnFn } from "./rpc/client.js";
|
|
23
23
|
/**
|
|
@@ -30,6 +30,40 @@ import { type SpawnFn } from "./rpc/client.js";
|
|
|
30
30
|
* *after* the bootstrap blob, so anything reading `appduct=` must stop at the `&`.
|
|
31
31
|
*/
|
|
32
32
|
export declare const composeDeepLink: (scheme: string, result: LinkCreateResult) => string;
|
|
33
|
+
/** What a web target hands back instead of a deep link: the page URL to open, and the script to
|
|
34
|
+
* run in an already-open page. Both carry the same bootstrap payload. */
|
|
35
|
+
export type WebLinkResult = {
|
|
36
|
+
sessionId: string;
|
|
37
|
+
/** The page URL with `#appduct=<payload>` appended (replacing any fragment it had). */
|
|
38
|
+
url: string;
|
|
39
|
+
script: string;
|
|
40
|
+
/** Unix seconds. */
|
|
41
|
+
expiresAt: number;
|
|
42
|
+
};
|
|
43
|
+
/** Throws a usage error unless `pageUrl` is an http(s) URL. Called before a link is minted, so a
|
|
44
|
+
* bad URL does not strand a pending link. */
|
|
45
|
+
export declare const assertWebPageUrl: (pageUrl: string) => void;
|
|
46
|
+
/** Composes the web target's result from a `link.create` result minted with `transport: "web"`. */
|
|
47
|
+
export declare const composeWebLink: (pageUrl: string, result: LinkCreateResult) => WebLinkResult;
|
|
48
|
+
export type MintWebLinkOptions = {
|
|
49
|
+
stateDir: string;
|
|
50
|
+
spawn?: SpawnFn;
|
|
51
|
+
autoSpawn?: boolean;
|
|
52
|
+
ttlSeconds?: number;
|
|
53
|
+
/** The page the link will be opened in. */
|
|
54
|
+
url: string;
|
|
55
|
+
};
|
|
56
|
+
/** Mints a link only the web listener accepts. Needs no scheme: a web page is reached by URL. */
|
|
57
|
+
export declare const mintWebLink: (options: MintWebLinkOptions) => Promise<WebLinkResult>;
|
|
58
|
+
export type AttachWebTabOptions = WebAttachParams & {
|
|
59
|
+
stateDir: string;
|
|
60
|
+
spawn?: SpawnFn;
|
|
61
|
+
autoSpawn?: boolean;
|
|
62
|
+
};
|
|
63
|
+
/** Asks the daemon to attach a tab of the debugging-port browser at `browserUrl` and claim a session on it. */
|
|
64
|
+
export declare const attachWebTab: ({ stateDir, spawn, autoSpawn, ...params }: AttachWebTabOptions) => Promise<WebAttachResult & {
|
|
65
|
+
attached: true;
|
|
66
|
+
}>;
|
|
33
67
|
export type MintLinkOptions = {
|
|
34
68
|
stateDir: string;
|
|
35
69
|
spawn?: SpawnFn;
|
package/dist/mcp/app-tools.d.ts
CHANGED
|
@@ -107,12 +107,30 @@ export type ResolvedAppTool = {
|
|
|
107
107
|
descriptor: ListedToolDescriptor;
|
|
108
108
|
policy: EffectivePolicyDecision;
|
|
109
109
|
};
|
|
110
|
+
type ResolvedSession = {
|
|
111
|
+
sessionId: string;
|
|
112
|
+
alias: string;
|
|
113
|
+
};
|
|
110
114
|
export type CallToolArgs = {
|
|
111
115
|
selector?: string;
|
|
112
116
|
name: string;
|
|
113
117
|
args: Record<string, unknown>;
|
|
114
118
|
timeoutMs?: number;
|
|
115
119
|
};
|
|
120
|
+
/** Every key an agent sends must be one the tool declares. A misspelled key (`arguments` for
|
|
121
|
+
* `args`, `timeout_ms` for `timeoutMs`) would otherwise be dropped silently, and the tool would run
|
|
122
|
+
* without what the agent meant to pass. */
|
|
123
|
+
export declare const rejectUnknownKeys: (args: Record<string, unknown>, tool: string, allowed: readonly string[]) => void;
|
|
124
|
+
/** `null` counts as absent for every optional field: some clients fill unset optional
|
|
125
|
+
* parameters with `null` rather than leaving them out. */
|
|
126
|
+
export declare const asOptionalString: (value: unknown, field: string) => string | undefined;
|
|
127
|
+
/** Resolves `selector` (an alias, a session id, or nothing) to one concrete session first, with
|
|
128
|
+
* the daemon's own rules and errors (`no_session`, `ambiguous_session`, `unknown_session`).
|
|
129
|
+
* Everything after that is routed by the **session id**, never the alias: the daemon frees an
|
|
130
|
+
* alias when a session ends and gives it to the next device of the same model, so routing by alias
|
|
131
|
+
* could land a call — one the user may already have approved — on a different device. If the
|
|
132
|
+
* session goes away mid-call, the next daemon call fails with `unknown_session` instead. */
|
|
133
|
+
export declare const resolveSession: (call: DaemonCall, selector: string | undefined) => Promise<ResolvedSession>;
|
|
116
134
|
export declare const handleListToolsTool: (rawArgs: unknown, call: DaemonCall) => Promise<{
|
|
117
135
|
groups?: import("@appduct/shared").ToolGroupSummary[] | undefined;
|
|
118
136
|
tools: {
|
|
@@ -152,3 +170,4 @@ export declare const parseCallToolArgs: (rawArgs: unknown) => CallToolArgs;
|
|
|
152
170
|
*/
|
|
153
171
|
export declare const resolveCallDeadline: (tool: ResolvedAppTool, requestedTimeoutMs: number | undefined) => number;
|
|
154
172
|
export declare const resolveAppTool: (call: DaemonCall, selector: string | undefined, name: string) => Promise<ResolvedAppTool>;
|
|
173
|
+
export {};
|
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
* caller asked for it with `target: "none"`), where it carries explicit instructions telling the
|
|
12
12
|
* agent to show the code and ask.
|
|
13
13
|
*/
|
|
14
|
+
import { type WebAttachResult } from "@appduct/shared";
|
|
14
15
|
import { type ExecFn, type OpenTarget } from "../cli/open-target.js";
|
|
16
|
+
import { type WebLinkResult } from "../link.js";
|
|
15
17
|
import { type SpawnFn } from "../rpc/client.js";
|
|
16
18
|
import type { DaemonCall } from "./daemon-tools.js";
|
|
17
19
|
export declare const CONNECT_TOOL_NAME = "appduct_connect";
|
|
@@ -24,7 +26,16 @@ export declare const CONNECT_TOOL_DESCRIPTOR: {
|
|
|
24
26
|
readonly properties: {
|
|
25
27
|
readonly target: {
|
|
26
28
|
readonly type: "string";
|
|
27
|
-
readonly enum: readonly ["android", "ios-sim", "ios-device", "none"];
|
|
29
|
+
readonly enum: readonly ["android", "ios-sim", "ios-device", "none", "web"];
|
|
30
|
+
};
|
|
31
|
+
readonly url: {
|
|
32
|
+
readonly type: "string";
|
|
33
|
+
};
|
|
34
|
+
readonly browserUrl: {
|
|
35
|
+
readonly type: "string";
|
|
36
|
+
};
|
|
37
|
+
readonly targetId: {
|
|
38
|
+
readonly type: "string";
|
|
28
39
|
};
|
|
29
40
|
readonly device: {
|
|
30
41
|
readonly type: "string";
|
|
@@ -108,7 +119,9 @@ export type ConnectToolResult = {
|
|
|
108
119
|
/** Present only on the QR path: what the agent must do before waiting. See {@link QR_INSTRUCTIONS}. */
|
|
109
120
|
instructions?: string;
|
|
110
121
|
};
|
|
111
|
-
export declare const handleConnectTool: (rawArgs: unknown, deps: ConnectToolDeps) => Promise<ConnectToolResult
|
|
122
|
+
export declare const handleConnectTool: (rawArgs: unknown, deps: ConnectToolDeps) => Promise<ConnectToolResult | WebLinkResult | (WebAttachResult & {
|
|
123
|
+
attached: true;
|
|
124
|
+
})>;
|
|
112
125
|
export type WaitForSessionToolDeps = {
|
|
113
126
|
stateDir: string;
|
|
114
127
|
spawn?: SpawnFn;
|
|
@@ -12,12 +12,40 @@
|
|
|
12
12
|
import { type AppEvent } from "@appduct/shared";
|
|
13
13
|
import { type SpawnFn } from "../rpc/client.js";
|
|
14
14
|
import type { DaemonCall } from "./daemon-tools.js";
|
|
15
|
+
export declare const LIST_EVENTS_TOOL_NAME = "appduct_list_events";
|
|
15
16
|
export declare const EVENTS_TOOL_NAME = "appduct_events";
|
|
16
17
|
export declare const WAIT_FOR_EVENT_TOOL_NAME = "appduct_wait_for_event";
|
|
17
18
|
/** Server-side cap on `timeoutMs` (issue #6's "Cap timeoutMs server-side below the idle window
|
|
18
19
|
* rather than trusting the caller") — a pure function so the cap's boundary behavior is
|
|
19
20
|
* unit-testable without spinning up a daemon or actually waiting out either bound. */
|
|
20
21
|
export declare const clampWaitForEventTimeoutMs: (requestedMs: number | undefined) => number;
|
|
22
|
+
export declare const LIST_EVENTS_TOOL_DESCRIPTOR: {
|
|
23
|
+
readonly name: "appduct_list_events";
|
|
24
|
+
readonly description: string;
|
|
25
|
+
readonly inputSchema: {
|
|
26
|
+
readonly type: "object";
|
|
27
|
+
readonly properties: {
|
|
28
|
+
readonly selector: {
|
|
29
|
+
readonly type: "string";
|
|
30
|
+
readonly minLength: 1;
|
|
31
|
+
readonly description: "Session alias or id. Omit to target the sole active/suspended session.";
|
|
32
|
+
};
|
|
33
|
+
readonly name: {
|
|
34
|
+
readonly type: "string";
|
|
35
|
+
readonly minLength: 1;
|
|
36
|
+
};
|
|
37
|
+
readonly limit: {
|
|
38
|
+
readonly type: "integer";
|
|
39
|
+
readonly exclusiveMinimum: 0;
|
|
40
|
+
};
|
|
41
|
+
readonly offset: {
|
|
42
|
+
readonly type: "integer";
|
|
43
|
+
readonly minimum: 0;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
readonly additionalProperties: false;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
21
49
|
export declare const EVENTS_TOOL_DESCRIPTOR: {
|
|
22
50
|
readonly name: "appduct_events";
|
|
23
51
|
readonly description: string;
|
|
@@ -78,6 +106,17 @@ export declare const WAIT_FOR_EVENT_TOOL_DESCRIPTOR: {
|
|
|
78
106
|
export type EventsToolDeps = {
|
|
79
107
|
call: DaemonCall;
|
|
80
108
|
};
|
|
109
|
+
export declare const handleListEventsTool: (rawArgs: unknown, call: DaemonCall) => Promise<{
|
|
110
|
+
session: string;
|
|
111
|
+
total: number;
|
|
112
|
+
limit: {};
|
|
113
|
+
events: {
|
|
114
|
+
payload_schema?: import("@appduct/shared").EventSchemaDescriptor | undefined;
|
|
115
|
+
name: string;
|
|
116
|
+
signature: string;
|
|
117
|
+
description: string;
|
|
118
|
+
}[];
|
|
119
|
+
}>;
|
|
81
120
|
export type EventsToolResult = {
|
|
82
121
|
events: AppEvent[];
|
|
83
122
|
cursor: number;
|
package/dist/native-scheme.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Static-file scheme discovery for plain iOS (Xcode) and Android (Gradle) apps
|
|
3
|
-
* (docs/tasks/20-cli-native-scheme-discovery.md, phase 3 of issue #48).
|
|
2
|
+
* Static-file scheme discovery for plain iOS (Xcode) and Android (Gradle) apps.
|
|
4
3
|
*
|
|
5
4
|
* `scheme.ts`'s `resolveScheme` treats this module as one more thing to try after `<cwd>/app.json`
|
|
6
5
|
* — it never walks up (an app root is where these commands run, same rule as `app.json`), and,
|
|
@@ -8,6 +7,8 @@
|
|
|
8
7
|
* `xcodebuild`, no Gradle evaluation. Every probe is a plain read of a well-known project file,
|
|
9
8
|
* parsed defensively:
|
|
10
9
|
*
|
|
10
|
+
* - Flutter (a root with a `pubspec.yaml`): the Android probes below read `android/app/…` instead
|
|
11
|
+
* of `app/…`; the iOS walk already reaches `ios/Runner` and `macos/Runner`.
|
|
11
12
|
* - Android: `app/build.gradle.kts` / `app/build.gradle` for a `manifestPlaceholders["appductScheme"]`
|
|
12
13
|
* (or `.appductScheme =`) assignment, then `app/src/main/AndroidManifest.xml` for the first
|
|
13
14
|
* `<data android:scheme>` inside an intent filter that also declares
|
|
@@ -35,19 +36,22 @@ export type NativeSchemeProbeResult = {
|
|
|
35
36
|
origin: string;
|
|
36
37
|
};
|
|
37
38
|
export type NativeSchemeDiscovery = {
|
|
38
|
-
/**
|
|
39
|
-
|
|
39
|
+
/** Every probe that resolved a scheme, in probe order (all agree on the scheme, or discovery
|
|
40
|
+
* throws). Empty when none did. */
|
|
41
|
+
results: NativeSchemeProbeResult[];
|
|
40
42
|
/** Every probe's human-readable description, in order, for {@link describeMissingScheme}-style
|
|
41
43
|
* reporting — always as many entries as there are probes, whether or not any of them hit. */
|
|
42
44
|
tried: string[];
|
|
43
45
|
};
|
|
46
|
+
/** Whether `root` is a Flutter project root: it has a `pubspec.yaml`. */
|
|
47
|
+
export declare const isFlutterProject: (root: string) => Promise<boolean>;
|
|
44
48
|
/**
|
|
45
49
|
* Runs every static-file probe against `root` (never walking up — same rule as `app.json`) and
|
|
46
50
|
* decides the result:
|
|
47
51
|
*
|
|
48
|
-
* - No probe resolves a scheme: `
|
|
49
|
-
* - Every probe that resolves one agrees:
|
|
50
|
-
*
|
|
52
|
+
* - No probe resolves a scheme: `results` is empty.
|
|
53
|
+
* - Every probe that resolves one agrees: `results` holds each of them in the order of
|
|
54
|
+
* {@link PROBES}, so a caller can see every platform the project spans, not one winner.
|
|
51
55
|
* - Two probes resolve *different* schemes: throws a usage error naming both sources and their
|
|
52
56
|
* values, since guessing which one is right would silently target the wrong app on one
|
|
53
57
|
* platform.
|
package/dist/scheme.d.ts
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
* redirects the state directory — `--state-dir` / `APPDUCT_STATE_DIR` remain the only way to do
|
|
40
40
|
* that — so a project file can never move the daemon's key, socket or audit log.
|
|
41
41
|
*/
|
|
42
|
-
import { type NativeSchemeSource } from "./native-scheme.js";
|
|
42
|
+
import { type NativeSchemeProbeResult, type NativeSchemeSource } from "./native-scheme.js";
|
|
43
43
|
/** The directory a project-level config lives in, relative to an app root. */
|
|
44
44
|
export declare const PROJECT_CONFIG_DIR = ".appduct";
|
|
45
45
|
/** The project-level config filename, inside {@link PROJECT_CONFIG_DIR}. */
|
|
@@ -85,6 +85,10 @@ export type StaticProjectSchemeDiscovery = {
|
|
|
85
85
|
* it) — distinct from `tried` below, which lists every location whether or not it hit. Only set
|
|
86
86
|
* alongside `scheme`. */
|
|
87
87
|
origin?: string;
|
|
88
|
+
/** Every native-project probe that resolved a scheme, in probe order — `scheme`, `source` and
|
|
89
|
+
* `origin` are the first of them. Empty when `app.json` answered (the native probes do not run)
|
|
90
|
+
* or nothing did; the platforms a project spans are read from here. */
|
|
91
|
+
nativeResults: NativeSchemeProbeResult[];
|
|
88
92
|
/** Every location this step consulted, in order: `app.json` first (unchanged from pre-#48), then
|
|
89
93
|
* every `native-scheme.ts` probe — always present, hit or miss, one entry each. */
|
|
90
94
|
tried: string[];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "appduct",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.15.0",
|
|
4
|
+
"description": "Call functions inside your running iOS, Android or React Native app from a terminal, test or AI agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
},
|
|
25
25
|
"keywords": [
|
|
26
26
|
"appduct",
|
|
27
|
+
"ios",
|
|
28
|
+
"android",
|
|
27
29
|
"react-native",
|
|
28
30
|
"agent"
|
|
29
31
|
],
|
|
@@ -45,14 +47,22 @@
|
|
|
45
47
|
"reflect-metadata": "^0.2.2",
|
|
46
48
|
"toqr": "^0.1.1",
|
|
47
49
|
"ws": "^8.18.3",
|
|
48
|
-
"@appduct/shared": "0.
|
|
50
|
+
"@appduct/shared": "0.15.0"
|
|
49
51
|
},
|
|
50
52
|
"devDependencies": {
|
|
51
53
|
"@types/node": "^22.10.0",
|
|
54
|
+
"@types/react": "~19.1.17",
|
|
52
55
|
"@types/ws": "^8.18.1",
|
|
53
56
|
"esbuild": "^0.25.12",
|
|
54
57
|
"eslint": "^10",
|
|
55
|
-
"
|
|
58
|
+
"playwright-core": "1.56.1",
|
|
59
|
+
"react": "19.1.0",
|
|
60
|
+
"react-dom": "19.1.0",
|
|
61
|
+
"typescript": "~5.9.2",
|
|
62
|
+
"vite": "^8.1.4",
|
|
63
|
+
"webpack": "^5.111.1",
|
|
64
|
+
"@appduct/react-native": "0.15.0",
|
|
65
|
+
"@appduct/web": "0.15.0"
|
|
56
66
|
},
|
|
57
67
|
"files": [
|
|
58
68
|
"bin.js",
|
|
@@ -62,7 +72,7 @@
|
|
|
62
72
|
"build": "tsc -p tsconfig.build.json && node scripts/bundle.mjs",
|
|
63
73
|
"clean": "rm -rf dist",
|
|
64
74
|
"lint": "eslint src",
|
|
65
|
-
"test": "vitest run
|
|
75
|
+
"test": "vitest run src/__tests__",
|
|
66
76
|
"typecheck": "tsc -p tsconfig.json"
|
|
67
77
|
}
|
|
68
78
|
}
|