appduct 0.9.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/LICENSE +21 -0
- package/README.md +274 -0
- package/bin.js +2 -0
- package/dist/artifact-inspect.d.ts +110 -0
- package/dist/artifact-inspect.d.ts.map +1 -0
- package/dist/artifact-inspect.js +315 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +4 -0
- package/dist/cli/command-options.d.ts +44 -0
- package/dist/cli/command-options.d.ts.map +1 -0
- package/dist/cli/command-options.js +95 -0
- package/dist/cli/create-cli.d.ts +2 -0
- package/dist/cli/create-cli.d.ts.map +1 -0
- package/dist/cli/create-cli.js +56 -0
- package/dist/cli/dispatch.d.ts +3 -0
- package/dist/cli/dispatch.d.ts.map +1 -0
- package/dist/cli/dispatch.js +275 -0
- package/dist/cli/open-target.d.ts +112 -0
- package/dist/cli/open-target.d.ts.map +1 -0
- package/dist/cli/open-target.js +438 -0
- package/dist/cli/result-types.d.ts +194 -0
- package/dist/cli/result-types.d.ts.map +1 -0
- package/dist/cli/result-types.js +6 -0
- package/dist/cli/runner.d.ts +35 -0
- package/dist/cli/runner.d.ts.map +1 -0
- package/dist/cli/runner.js +111 -0
- package/dist/cli/types.d.ts +20 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +10 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +2 -0
- package/dist/client/app-client.d.ts +126 -0
- package/dist/client/app-client.d.ts.map +1 -0
- package/dist/client/app-client.js +223 -0
- package/dist/client/bootstrap.d.ts +48 -0
- package/dist/client/bootstrap.d.ts.map +1 -0
- package/dist/client/bootstrap.js +108 -0
- package/dist/client/connect.d.ts +17 -0
- package/dist/client/connect.d.ts.map +1 -0
- package/dist/client/connect.js +38 -0
- package/dist/client/errors.d.ts +26 -0
- package/dist/client/errors.d.ts.map +1 -0
- package/dist/client/errors.js +40 -0
- package/dist/client/index.d.ts +19 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +16 -0
- package/dist/commands/daemon.d.ts +22 -0
- package/dist/commands/daemon.d.ts.map +1 -0
- package/dist/commands/daemon.js +128 -0
- package/dist/commands/doctor.d.ts +23 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +42 -0
- package/dist/commands/events.d.ts +37 -0
- package/dist/commands/events.d.ts.map +1 -0
- package/dist/commands/events.js +76 -0
- package/dist/commands/init.d.ts +51 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +231 -0
- package/dist/commands/invoke.d.ts +22 -0
- package/dist/commands/invoke.d.ts.map +1 -0
- package/dist/commands/invoke.js +54 -0
- package/dist/commands/keygen.d.ts +23 -0
- package/dist/commands/keygen.d.ts.map +1 -0
- package/dist/commands/keygen.js +46 -0
- package/dist/commands/link.d.ts +30 -0
- package/dist/commands/link.d.ts.map +1 -0
- package/dist/commands/link.js +43 -0
- package/dist/commands/ls.d.ts +9 -0
- package/dist/commands/ls.d.ts.map +1 -0
- package/dist/commands/ls.js +7 -0
- package/dist/commands/mcp.d.ts +49 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +98 -0
- package/dist/commands/revoke.d.ts +12 -0
- package/dist/commands/revoke.d.ts.map +1 -0
- package/dist/commands/revoke.js +7 -0
- package/dist/commands/tools.d.ts +23 -0
- package/dist/commands/tools.d.ts.map +1 -0
- package/dist/commands/tools.js +52 -0
- package/dist/daemon/address.d.ts +30 -0
- package/dist/daemon/address.d.ts.map +1 -0
- package/dist/daemon/address.js +71 -0
- package/dist/daemon/audit.d.ts +107 -0
- package/dist/daemon/audit.d.ts.map +1 -0
- package/dist/daemon/audit.js +237 -0
- package/dist/daemon/calls.d.ts +84 -0
- package/dist/daemon/calls.d.ts.map +1 -0
- package/dist/daemon/calls.js +158 -0
- package/dist/daemon/config.d.ts +79 -0
- package/dist/daemon/config.d.ts.map +1 -0
- package/dist/daemon/config.js +170 -0
- package/dist/daemon/daemon.d.ts +43 -0
- package/dist/daemon/daemon.d.ts.map +1 -0
- package/dist/daemon/daemon.js +560 -0
- package/dist/daemon/event-bus.d.ts +49 -0
- package/dist/daemon/event-bus.d.ts.map +1 -0
- package/dist/daemon/event-bus.js +111 -0
- package/dist/daemon/links.d.ts +54 -0
- package/dist/daemon/links.d.ts.map +1 -0
- package/dist/daemon/links.js +118 -0
- package/dist/daemon/listener.d.ts +34 -0
- package/dist/daemon/listener.d.ts.map +1 -0
- package/dist/daemon/listener.js +132 -0
- package/dist/daemon/log-rotation.d.ts +82 -0
- package/dist/daemon/log-rotation.d.ts.map +1 -0
- package/dist/daemon/log-rotation.js +136 -0
- package/dist/daemon/pidfile.d.ts +38 -0
- package/dist/daemon/pidfile.d.ts.map +1 -0
- package/dist/daemon/pidfile.js +90 -0
- package/dist/daemon/policy.d.ts +22 -0
- package/dist/daemon/policy.d.ts.map +1 -0
- package/dist/daemon/policy.js +24 -0
- package/dist/daemon/registry.d.ts +19 -0
- package/dist/daemon/registry.d.ts.map +1 -0
- package/dist/daemon/registry.js +31 -0
- package/dist/daemon/rpc-server.d.ts +54 -0
- package/dist/daemon/rpc-server.d.ts.map +1 -0
- package/dist/daemon/rpc-server.js +203 -0
- package/dist/daemon/sessions.d.ts +86 -0
- package/dist/daemon/sessions.d.ts.map +1 -0
- package/dist/daemon/sessions.js +429 -0
- package/dist/daemon/socket-probe.d.ts +16 -0
- package/dist/daemon/socket-probe.d.ts.map +1 -0
- package/dist/daemon/socket-probe.js +28 -0
- package/dist/daemon/state-dir.d.ts +31 -0
- package/dist/daemon/state-dir.d.ts.map +1 -0
- package/dist/daemon/state-dir.js +43 -0
- package/dist/daemon/timers.d.ts +16 -0
- package/dist/daemon/timers.d.ts.map +1 -0
- package/dist/daemon/timers.js +12 -0
- package/dist/daemon/tls.d.ts +58 -0
- package/dist/daemon/tls.d.ts.map +1 -0
- package/dist/daemon/tls.js +79 -0
- package/dist/errors.d.ts +28 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +162 -0
- package/dist/host-certificate.d.ts +8 -0
- package/dist/host-certificate.d.ts.map +1 -0
- package/dist/host-certificate.js +111 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/key-material.d.ts +14 -0
- package/dist/key-material.d.ts.map +1 -0
- package/dist/key-material.js +42 -0
- package/dist/link.d.ts +76 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +117 -0
- package/dist/mcp/connect-tool.d.ts +115 -0
- package/dist/mcp/connect-tool.d.ts.map +1 -0
- package/dist/mcp/connect-tool.js +396 -0
- package/dist/mcp/daemon-tools.d.ts +13 -0
- package/dist/mcp/daemon-tools.d.ts.map +1 -0
- package/dist/mcp/daemon-tools.js +29 -0
- package/dist/mcp/events-tool.d.ts +111 -0
- package/dist/mcp/events-tool.d.ts.map +1 -0
- package/dist/mcp/events-tool.js +322 -0
- package/dist/mcp/server.d.ts +58 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +538 -0
- package/dist/mcp/tool-mapping.d.ts +58 -0
- package/dist/mcp/tool-mapping.d.ts.map +1 -0
- package/dist/mcp/tool-mapping.js +143 -0
- package/dist/mcp/tool-namespace.d.ts +40 -0
- package/dist/mcp/tool-namespace.d.ts.map +1 -0
- package/dist/mcp/tool-namespace.js +65 -0
- package/dist/native-scheme.d.ts +59 -0
- package/dist/native-scheme.d.ts.map +1 -0
- package/dist/native-scheme.js +465 -0
- package/dist/output.d.ts +28 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +342 -0
- package/dist/package-version.d.ts +30 -0
- package/dist/package-version.d.ts.map +1 -0
- package/dist/package-version.js +47 -0
- package/dist/qr-terminal.d.ts +13 -0
- package/dist/qr-terminal.d.ts.map +1 -0
- package/dist/qr-terminal.js +45 -0
- package/dist/rpc/client.d.ts +137 -0
- package/dist/rpc/client.d.ts.map +1 -0
- package/dist/rpc/client.js +819 -0
- package/dist/scheme.d.ts +181 -0
- package/dist/scheme.d.ts.map +1 -0
- package/dist/scheme.js +321 -0
- package/dist/spki-pin.d.ts +3 -0
- package/dist/spki-pin.d.ts.map +1 -0
- package/dist/spki-pin.js +14 -0
- package/package.json +65 -0
package/dist/scheme.d.ts
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep-link scheme resolution (ARCHITECTURE.md §10, issue #29).
|
|
3
|
+
*
|
|
4
|
+
* A scheme is needed to compose `<scheme>:///?appduct=<payload>` in `appduct link`,
|
|
5
|
+
* `appduct/client`'s `link()` and the MCP `appduct_connect` tool. Before this module the only
|
|
6
|
+
* source was `<state-dir>/config.json` — a single global file, which meant two apps with different
|
|
7
|
+
* schemes on one machine required hand-editing it on every switch.
|
|
8
|
+
*
|
|
9
|
+
* The resolution order below is shared by every one of those callers so it cannot drift, first
|
|
10
|
+
* match wins:
|
|
11
|
+
*
|
|
12
|
+
* 1. an explicit flag/option (`--scheme`)
|
|
13
|
+
* 2. the `APPDUCT_SCHEME` environment variable
|
|
14
|
+
* 3. the nearest `.appduct/config.json` that declares a `scheme`, walking up from the working
|
|
15
|
+
* directory (never `~/.appduct` or the state dir in use — see {@link findProjectConfigs};
|
|
16
|
+
* a project config without a `scheme` key does not stop the walk)
|
|
17
|
+
* 4. `scheme` in the state directory's `config.json` (the pre-#29 behaviour)
|
|
18
|
+
* 5. a static-file project probe, tried in this order (no walk-up — an app root is where you
|
|
19
|
+
* run these commands), with {@link discoverStaticProjectScheme} owning the whole step:
|
|
20
|
+
* a. `<cwd>/app.json`'s `expo.scheme` (the pre-#48 behaviour, unchanged)
|
|
21
|
+
* b. Android: `app/build.gradle(.kts)`'s `appductScheme` manifest placeholder, then
|
|
22
|
+
* `app/src/main/AndroidManifest.xml`'s first `<data android:scheme>` in a `VIEW`
|
|
23
|
+
* intent filter
|
|
24
|
+
* c. iOS: any `Info.plist` up to two levels deep (excluding `Pods`/`build`/`node_modules`/
|
|
25
|
+
* `DerivedData`) for the first `CFBundleURLSchemes` entry, then xcodegen's `project.yml`
|
|
26
|
+
* for the same key — see `native-scheme.ts`, which owns 5b/5c and never guesses when two
|
|
27
|
+
* of them disagree (throws instead, naming both)
|
|
28
|
+
*
|
|
29
|
+
* Nothing here executes project code: `app.config.js`/`app.config.ts` are deliberately *not*
|
|
30
|
+
* evaluated (running arbitrary project JS to read one string is a much larger blast radius than
|
|
31
|
+
* this feature warrants), and neither is `xcodebuild`/`plutil`/a Gradle evaluation for the native
|
|
32
|
+
* probes added in issue #48 — every one of them is a plain, defensively-parsed read of a static
|
|
33
|
+
* project file (`native-scheme.ts`'s doc comment has the detail). Dynamic-config projects use
|
|
34
|
+
* `--scheme`, `APPDUCT_SCHEME`, or a project `.appduct/config.json` instead.
|
|
35
|
+
*
|
|
36
|
+
* A project `.appduct/config.json` carries client-side keys only (`scheme` today). It never
|
|
37
|
+
* redirects the state directory — `--state-dir` / `APPDUCT_STATE_DIR` remain the only way to do
|
|
38
|
+
* that — so a project file can never move the daemon's key, socket or audit log.
|
|
39
|
+
*/
|
|
40
|
+
import { type NativeSchemeSource } from "./native-scheme.js";
|
|
41
|
+
/** The directory a project-level config lives in, relative to an app root. */
|
|
42
|
+
export declare const PROJECT_CONFIG_DIR = ".appduct";
|
|
43
|
+
/** The project-level config filename, inside {@link PROJECT_CONFIG_DIR}. */
|
|
44
|
+
export declare const PROJECT_CONFIG_FILENAME = "config.json";
|
|
45
|
+
/** Relative path used in messages/docs: `.appduct/config.json`. */
|
|
46
|
+
export declare const PROJECT_CONFIG_RELATIVE_PATH: string;
|
|
47
|
+
/** The Expo app manifest discovery reads. Only static JSON — see this module's doc comment. */
|
|
48
|
+
export declare const APP_JSON_FILENAME = "app.json";
|
|
49
|
+
export declare const SCHEME_ENV_VAR = "APPDUCT_SCHEME";
|
|
50
|
+
export declare const isValidScheme: (value: string) => boolean;
|
|
51
|
+
/** Which step of the order above produced the scheme. The four `NativeSchemeSource` values are
|
|
52
|
+
* step 5's native-project probes (`native-scheme.ts`); `"app-json"` is step 5's original,
|
|
53
|
+
* unchanged probe. */
|
|
54
|
+
export type SchemeSource = "flag" | "env" | "project-config" | "state-config" | "app-json" | NativeSchemeSource;
|
|
55
|
+
export type ResolvedScheme = {
|
|
56
|
+
/** Undefined when no source produced one; `tried` then explains where we looked. */
|
|
57
|
+
scheme?: string;
|
|
58
|
+
source?: SchemeSource;
|
|
59
|
+
/** Human-readable descriptions of every location consulted, in order, for error messages. */
|
|
60
|
+
tried: string[];
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Normalizes Expo's `scheme` field, which is either a single string or an array of them. Mirrors
|
|
64
|
+
* `packages/react-native/app.plugin.js`'s `configuredSchemes` so the CLI and the config plugin
|
|
65
|
+
* agree on what "the app's scheme" means; the first entry wins for an array.
|
|
66
|
+
*
|
|
67
|
+
* Exported so {@link ../native-scheme.js} can apply the same "string, or first non-empty entry of
|
|
68
|
+
* an array" rule to `CFBundleURLSchemes` (`Info.plist`/`project.yml`) — the shape is identical.
|
|
69
|
+
*/
|
|
70
|
+
export declare const firstConfiguredScheme: (value: unknown) => string | undefined;
|
|
71
|
+
/**
|
|
72
|
+
* Reads `<dir>/app.json`'s `expo.scheme`. Returns `undefined` when the file is missing, is not a
|
|
73
|
+
* JSON object, has no `expo` object, or declares no usable scheme — every one of which just means
|
|
74
|
+
* "discovery found nothing here". A *malformed* `app.json` throws, because silently ignoring a
|
|
75
|
+
* syntax error in the file the user is pointing us at reads as "Appduct ignored my config".
|
|
76
|
+
*/
|
|
77
|
+
export declare const discoverExpoScheme: (dir: string) => Promise<string | undefined>;
|
|
78
|
+
export type StaticProjectSchemeDiscovery = {
|
|
79
|
+
/** `undefined` when nothing in this step resolved a scheme. */
|
|
80
|
+
scheme?: string;
|
|
81
|
+
source?: "app-json" | NativeSchemeSource;
|
|
82
|
+
/** The human-readable location `scheme` was actually read from (a path, plus what was read from
|
|
83
|
+
* it) — distinct from `tried` below, which lists every location whether or not it hit. Only set
|
|
84
|
+
* alongside `scheme`. */
|
|
85
|
+
origin?: string;
|
|
86
|
+
/** Every location this step consulted, in order: `app.json` first (unchanged from pre-#48), then
|
|
87
|
+
* every `native-scheme.ts` probe — always present, hit or miss, one entry each. */
|
|
88
|
+
tried: string[];
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Step 5 of {@link resolveScheme} in full: `<cwd>/app.json` first (unchanged), then the native
|
|
92
|
+
* Android/iOS project probes from `native-scheme.ts`. Exported as its own step — not inlined into
|
|
93
|
+
* `resolveScheme` — because `appduct init` (`commands/init.ts`) runs exactly this same
|
|
94
|
+
* discovery on its own, deliberately skipping steps 1-4 (see that file's doc comment for why): the
|
|
95
|
+
* two must never disagree about what "discovery" means for the tail of the precedence order, or
|
|
96
|
+
* `init` could write a scheme `resolveScheme` would never have found on its own.
|
|
97
|
+
*/
|
|
98
|
+
export declare const discoverStaticProjectScheme: (cwd: string) => Promise<StaticProjectSchemeDiscovery>;
|
|
99
|
+
export type ProjectConfigLookupOptions = {
|
|
100
|
+
/** The state dir actually in use, so the walk can never mistake it for a project config. */
|
|
101
|
+
stateDirRoot?: string;
|
|
102
|
+
/** The home directory whose `.appduct` is the *default* state dir. Injectable so tests can
|
|
103
|
+
* point it at a temp tree instead of the real `$HOME`; defaults to `os.homedir()`. */
|
|
104
|
+
homeDir?: string;
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* The `.appduct` directories that are *global* state, never a project's own config.
|
|
108
|
+
*
|
|
109
|
+
* Shared by the walk-up (which must not read them at the project tier) and `appduct init`
|
|
110
|
+
* (which must not *write* into them: it would drop a config next to the daemon's `key.pem` and
|
|
111
|
+
* call it safe to commit), so the two can never disagree about which directory is which.
|
|
112
|
+
*/
|
|
113
|
+
export declare const globalConfigDirs: (options?: ProjectConfigLookupOptions) => Set<string>;
|
|
114
|
+
/**
|
|
115
|
+
* Every `<dir>/.appduct/config.json` on the path from `startDir` up to the filesystem root,
|
|
116
|
+
* nearest first.
|
|
117
|
+
*
|
|
118
|
+
* The walk terminates at the root (`dirname("/") === "/"`, and likewise for a Windows drive root)
|
|
119
|
+
* — it cannot escape above it, and it never follows `..` out of a caller-supplied path because
|
|
120
|
+
* every candidate is derived from the resolved absolute `startDir`.
|
|
121
|
+
*
|
|
122
|
+
* Two directories are never treated as a project root, because a hit there would silently apply a
|
|
123
|
+
* *global* file at the project tier — one step above the state dir's own config, inverting the
|
|
124
|
+
* documented precedence:
|
|
125
|
+
*
|
|
126
|
+
* - `<homeDir>/.appduct`, the default state dir. Essentially every project lives somewhere under
|
|
127
|
+
* the home directory, so without this a plain `appduct link` in any repo would pick up the
|
|
128
|
+
* global `config.json` as if it were the project's — and with `--state-dir` pointing elsewhere it
|
|
129
|
+
* would shadow the state dir the operator explicitly chose.
|
|
130
|
+
* - `stateDirRoot`, the state dir actually in use, for the same reason when it is not the default.
|
|
131
|
+
*/
|
|
132
|
+
export declare const findProjectConfigs: (startDir: string, options?: ProjectConfigLookupOptions) => string[];
|
|
133
|
+
/** The nearest project config on the path from `startDir` upwards, if any. */
|
|
134
|
+
export declare const findProjectConfig: (startDir: string, options?: ProjectConfigLookupOptions) => string | undefined;
|
|
135
|
+
/**
|
|
136
|
+
* Reads the `scheme` key out of a project `.appduct/config.json`. Unlike `app.json` this *is*
|
|
137
|
+
* Appduct's own file, so anything wrong with it (not an object, `scheme` not a non-empty
|
|
138
|
+
* string) is a hard error rather than a silent fall-through — a typo here must not quietly
|
|
139
|
+
* degrade to a different scheme from a lower-precedence source.
|
|
140
|
+
*
|
|
141
|
+
* Keys other than `scheme` are ignored: a project file is client-side settings only, and
|
|
142
|
+
* accepting (say) `keyPath` here would let a checked-in file redirect the daemon's key material.
|
|
143
|
+
*/
|
|
144
|
+
export declare const readProjectConfigScheme: (path: string) => Promise<string | undefined>;
|
|
145
|
+
export type ResolveSchemeOptions = {
|
|
146
|
+
/** `--scheme` (or a programmatic `scheme` option) — highest precedence. */
|
|
147
|
+
flagScheme?: string;
|
|
148
|
+
/**
|
|
149
|
+
* The environment `APPDUCT_SCHEME` is read from. Defaults to `process.env`.
|
|
150
|
+
*
|
|
151
|
+
* Deliberately separate from the environment callers hand to `adb`/`simctl`: those callers
|
|
152
|
+
* narrow that env on purpose, and reusing it here would silently drop a `APPDUCT_SCHEME` the
|
|
153
|
+
* user really had exported.
|
|
154
|
+
*/
|
|
155
|
+
env?: NodeJS.ProcessEnv;
|
|
156
|
+
/** Where the project walk-up and `app.json` discovery start. Defaults to `process.cwd()`. */
|
|
157
|
+
cwd?: string;
|
|
158
|
+
/** `scheme` already loaded from the state directory's `config.json`. */
|
|
159
|
+
configScheme?: string;
|
|
160
|
+
/** The state directory's `config.json` path, named in `tried` (and used to skip it in the walk-up). */
|
|
161
|
+
stateConfigPath?: string;
|
|
162
|
+
/** The state directory root, so the walk-up never mistakes it for a project config. */
|
|
163
|
+
stateDirRoot?: string;
|
|
164
|
+
/** Overrides the home directory whose `.appduct` the walk-up skips (tests point this at a
|
|
165
|
+
* temp tree rather than the real `$HOME`). */
|
|
166
|
+
homeDir?: string;
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* Resolves the deep-link scheme against the documented order, reporting both the winner and every
|
|
170
|
+
* location consulted so callers can render an error that says where to put one.
|
|
171
|
+
*/
|
|
172
|
+
export declare const resolveScheme: (options?: ResolveSchemeOptions) => Promise<ResolvedScheme>;
|
|
173
|
+
/**
|
|
174
|
+
* The shared "no scheme anywhere" message. Every caller renders the same body so the locations
|
|
175
|
+
* listed (and the fixes suggested) can't drift between `appduct link`, `appduct mcp` and
|
|
176
|
+
* `appduct_connect`.
|
|
177
|
+
*/
|
|
178
|
+
export declare const describeMissingScheme: (tried: string[]) => string;
|
|
179
|
+
/** {@link resolveScheme}, throwing {@link usageError} with {@link describeMissingScheme} when nothing matched. */
|
|
180
|
+
export declare const resolveSchemeOrThrow: (options?: ResolveSchemeOptions) => Promise<string>;
|
|
181
|
+
//# sourceMappingURL=scheme.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheme.d.ts","sourceRoot":"","sources":["../src/scheme.ts"],"names":[],"mappings":"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"}
|
package/dist/scheme.js
ADDED
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep-link scheme resolution (ARCHITECTURE.md §10, issue #29).
|
|
3
|
+
*
|
|
4
|
+
* A scheme is needed to compose `<scheme>:///?appduct=<payload>` in `appduct link`,
|
|
5
|
+
* `appduct/client`'s `link()` and the MCP `appduct_connect` tool. Before this module the only
|
|
6
|
+
* source was `<state-dir>/config.json` — a single global file, which meant two apps with different
|
|
7
|
+
* schemes on one machine required hand-editing it on every switch.
|
|
8
|
+
*
|
|
9
|
+
* The resolution order below is shared by every one of those callers so it cannot drift, first
|
|
10
|
+
* match wins:
|
|
11
|
+
*
|
|
12
|
+
* 1. an explicit flag/option (`--scheme`)
|
|
13
|
+
* 2. the `APPDUCT_SCHEME` environment variable
|
|
14
|
+
* 3. the nearest `.appduct/config.json` that declares a `scheme`, walking up from the working
|
|
15
|
+
* directory (never `~/.appduct` or the state dir in use — see {@link findProjectConfigs};
|
|
16
|
+
* a project config without a `scheme` key does not stop the walk)
|
|
17
|
+
* 4. `scheme` in the state directory's `config.json` (the pre-#29 behaviour)
|
|
18
|
+
* 5. a static-file project probe, tried in this order (no walk-up — an app root is where you
|
|
19
|
+
* run these commands), with {@link discoverStaticProjectScheme} owning the whole step:
|
|
20
|
+
* a. `<cwd>/app.json`'s `expo.scheme` (the pre-#48 behaviour, unchanged)
|
|
21
|
+
* b. Android: `app/build.gradle(.kts)`'s `appductScheme` manifest placeholder, then
|
|
22
|
+
* `app/src/main/AndroidManifest.xml`'s first `<data android:scheme>` in a `VIEW`
|
|
23
|
+
* intent filter
|
|
24
|
+
* c. iOS: any `Info.plist` up to two levels deep (excluding `Pods`/`build`/`node_modules`/
|
|
25
|
+
* `DerivedData`) for the first `CFBundleURLSchemes` entry, then xcodegen's `project.yml`
|
|
26
|
+
* for the same key — see `native-scheme.ts`, which owns 5b/5c and never guesses when two
|
|
27
|
+
* of them disagree (throws instead, naming both)
|
|
28
|
+
*
|
|
29
|
+
* Nothing here executes project code: `app.config.js`/`app.config.ts` are deliberately *not*
|
|
30
|
+
* evaluated (running arbitrary project JS to read one string is a much larger blast radius than
|
|
31
|
+
* this feature warrants), and neither is `xcodebuild`/`plutil`/a Gradle evaluation for the native
|
|
32
|
+
* probes added in issue #48 — every one of them is a plain, defensively-parsed read of a static
|
|
33
|
+
* project file (`native-scheme.ts`'s doc comment has the detail). Dynamic-config projects use
|
|
34
|
+
* `--scheme`, `APPDUCT_SCHEME`, or a project `.appduct/config.json` instead.
|
|
35
|
+
*
|
|
36
|
+
* A project `.appduct/config.json` carries client-side keys only (`scheme` today). It never
|
|
37
|
+
* redirects the state directory — `--state-dir` / `APPDUCT_STATE_DIR` remain the only way to do
|
|
38
|
+
* that — so a project file can never move the daemon's key, socket or audit log.
|
|
39
|
+
*/
|
|
40
|
+
import { existsSync } from "node:fs";
|
|
41
|
+
import { readFile } from "node:fs/promises";
|
|
42
|
+
import { homedir } from "node:os";
|
|
43
|
+
import { dirname, join, resolve } from "node:path";
|
|
44
|
+
import { usageError } from "./errors.js";
|
|
45
|
+
import { discoverNativeScheme } from "./native-scheme.js";
|
|
46
|
+
/** The directory a project-level config lives in, relative to an app root. */
|
|
47
|
+
export const PROJECT_CONFIG_DIR = ".appduct";
|
|
48
|
+
/** The project-level config filename, inside {@link PROJECT_CONFIG_DIR}. */
|
|
49
|
+
export const PROJECT_CONFIG_FILENAME = "config.json";
|
|
50
|
+
/** Relative path used in messages/docs: `.appduct/config.json`. */
|
|
51
|
+
export const PROJECT_CONFIG_RELATIVE_PATH = join(PROJECT_CONFIG_DIR, PROJECT_CONFIG_FILENAME);
|
|
52
|
+
/** The Expo app manifest discovery reads. Only static JSON — see this module's doc comment. */
|
|
53
|
+
export const APP_JSON_FILENAME = "app.json";
|
|
54
|
+
export const SCHEME_ENV_VAR = "APPDUCT_SCHEME";
|
|
55
|
+
/**
|
|
56
|
+
* RFC 3986 §3.1 scheme grammar: `ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )`.
|
|
57
|
+
*
|
|
58
|
+
* Enforced so a value like `"myapp://"` (a very natural thing to paste into `--scheme`) fails with
|
|
59
|
+
* a message naming the offending source instead of silently composing the unopenable
|
|
60
|
+
* `myapp://:///?appduct=…`.
|
|
61
|
+
*/
|
|
62
|
+
const SCHEME_PATTERN = /^[a-zA-Z][a-zA-Z0-9+.-]*$/u;
|
|
63
|
+
export const isValidScheme = (value) => SCHEME_PATTERN.test(value);
|
|
64
|
+
const requireValidScheme = (value, origin) => {
|
|
65
|
+
if (!isValidScheme(value)) {
|
|
66
|
+
throw usageError(`Invalid deep-link scheme ${JSON.stringify(value)} from ${origin}: a scheme must start with ` +
|
|
67
|
+
'a letter and contain only letters, digits, "+", "-" or "." (for example "myapp") — do not ' +
|
|
68
|
+
'include "://".');
|
|
69
|
+
}
|
|
70
|
+
return value;
|
|
71
|
+
};
|
|
72
|
+
const isPlainObject = (value) => {
|
|
73
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
74
|
+
};
|
|
75
|
+
/** Reads and JSON-parses a file; `undefined` when it does not exist. Other I/O errors propagate. */
|
|
76
|
+
const readJsonFile = async (path) => {
|
|
77
|
+
let text;
|
|
78
|
+
try {
|
|
79
|
+
text = await readFile(path, "utf8");
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
const code = error.code;
|
|
83
|
+
// A `.appduct` that is a file, or an `app.json` that is a directory, is "nothing usable
|
|
84
|
+
// here" rather than a hard failure — the caller falls through to the next source.
|
|
85
|
+
if (code === "ENOENT" || code === "ENOTDIR" || code === "EISDIR") {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
return { raw: JSON.parse(text) };
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
throw usageError(`Could not parse ${path}: ${error.message}`);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Normalizes Expo's `scheme` field, which is either a single string or an array of them. Mirrors
|
|
99
|
+
* `packages/react-native/app.plugin.js`'s `configuredSchemes` so the CLI and the config plugin
|
|
100
|
+
* agree on what "the app's scheme" means; the first entry wins for an array.
|
|
101
|
+
*
|
|
102
|
+
* Exported so {@link ../native-scheme.js} can apply the same "string, or first non-empty entry of
|
|
103
|
+
* an array" rule to `CFBundleURLSchemes` (`Info.plist`/`project.yml`) — the shape is identical.
|
|
104
|
+
*/
|
|
105
|
+
export const firstConfiguredScheme = (value) => {
|
|
106
|
+
if (Array.isArray(value)) {
|
|
107
|
+
return value.find((entry) => typeof entry === "string" && entry.length > 0);
|
|
108
|
+
}
|
|
109
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* Reads `<dir>/app.json`'s `expo.scheme`. Returns `undefined` when the file is missing, is not a
|
|
113
|
+
* JSON object, has no `expo` object, or declares no usable scheme — every one of which just means
|
|
114
|
+
* "discovery found nothing here". A *malformed* `app.json` throws, because silently ignoring a
|
|
115
|
+
* syntax error in the file the user is pointing us at reads as "Appduct ignored my config".
|
|
116
|
+
*/
|
|
117
|
+
export const discoverExpoScheme = async (dir) => {
|
|
118
|
+
const path = join(resolve(dir), APP_JSON_FILENAME);
|
|
119
|
+
const file = await readJsonFile(path);
|
|
120
|
+
if (!file || !isPlainObject(file.raw)) {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
const expo = file.raw.expo;
|
|
124
|
+
if (!isPlainObject(expo)) {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
const scheme = firstConfiguredScheme(expo.scheme);
|
|
128
|
+
return scheme === undefined ? undefined : requireValidScheme(scheme, `"expo.scheme" in ${path}`);
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* Step 5 of {@link resolveScheme} in full: `<cwd>/app.json` first (unchanged), then the native
|
|
132
|
+
* Android/iOS project probes from `native-scheme.ts`. Exported as its own step — not inlined into
|
|
133
|
+
* `resolveScheme` — because `appduct init` (`commands/init.ts`) runs exactly this same
|
|
134
|
+
* discovery on its own, deliberately skipping steps 1-4 (see that file's doc comment for why): the
|
|
135
|
+
* two must never disagree about what "discovery" means for the tail of the precedence order, or
|
|
136
|
+
* `init` could write a scheme `resolveScheme` would never have found on its own.
|
|
137
|
+
*/
|
|
138
|
+
export const discoverStaticProjectScheme = async (cwd) => {
|
|
139
|
+
const appJsonPath = join(resolve(cwd), APP_JSON_FILENAME);
|
|
140
|
+
const tried = [`${appJsonPath} ("expo.scheme")`];
|
|
141
|
+
const appJsonScheme = await discoverExpoScheme(cwd);
|
|
142
|
+
if (appJsonScheme !== undefined) {
|
|
143
|
+
return { scheme: appJsonScheme, source: "app-json", origin: appJsonPath, tried };
|
|
144
|
+
}
|
|
145
|
+
const native = await discoverNativeScheme(resolve(cwd));
|
|
146
|
+
tried.push(...native.tried);
|
|
147
|
+
if (native.result !== undefined) {
|
|
148
|
+
return {
|
|
149
|
+
scheme: native.result.scheme,
|
|
150
|
+
source: native.result.source,
|
|
151
|
+
origin: native.result.origin,
|
|
152
|
+
tried,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return { tried };
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* The `.appduct` directories that are *global* state, never a project's own config.
|
|
159
|
+
*
|
|
160
|
+
* Shared by the walk-up (which must not read them at the project tier) and `appduct init`
|
|
161
|
+
* (which must not *write* into them: it would drop a config next to the daemon's `key.pem` and
|
|
162
|
+
* call it safe to commit), so the two can never disagree about which directory is which.
|
|
163
|
+
*/
|
|
164
|
+
export const globalConfigDirs = (options = {}) => {
|
|
165
|
+
const excluded = new Set([join(resolve(options.homeDir ?? homedir()), PROJECT_CONFIG_DIR)]);
|
|
166
|
+
if (options.stateDirRoot !== undefined) {
|
|
167
|
+
excluded.add(resolve(options.stateDirRoot));
|
|
168
|
+
}
|
|
169
|
+
return excluded;
|
|
170
|
+
};
|
|
171
|
+
/**
|
|
172
|
+
* Every `<dir>/.appduct/config.json` on the path from `startDir` up to the filesystem root,
|
|
173
|
+
* nearest first.
|
|
174
|
+
*
|
|
175
|
+
* The walk terminates at the root (`dirname("/") === "/"`, and likewise for a Windows drive root)
|
|
176
|
+
* — it cannot escape above it, and it never follows `..` out of a caller-supplied path because
|
|
177
|
+
* every candidate is derived from the resolved absolute `startDir`.
|
|
178
|
+
*
|
|
179
|
+
* Two directories are never treated as a project root, because a hit there would silently apply a
|
|
180
|
+
* *global* file at the project tier — one step above the state dir's own config, inverting the
|
|
181
|
+
* documented precedence:
|
|
182
|
+
*
|
|
183
|
+
* - `<homeDir>/.appduct`, the default state dir. Essentially every project lives somewhere under
|
|
184
|
+
* the home directory, so without this a plain `appduct link` in any repo would pick up the
|
|
185
|
+
* global `config.json` as if it were the project's — and with `--state-dir` pointing elsewhere it
|
|
186
|
+
* would shadow the state dir the operator explicitly chose.
|
|
187
|
+
* - `stateDirRoot`, the state dir actually in use, for the same reason when it is not the default.
|
|
188
|
+
*/
|
|
189
|
+
export const findProjectConfigs = (startDir, options = {}) => {
|
|
190
|
+
const excluded = globalConfigDirs(options);
|
|
191
|
+
const found = [];
|
|
192
|
+
let dir = resolve(startDir);
|
|
193
|
+
for (;;) {
|
|
194
|
+
const projectDir = join(dir, PROJECT_CONFIG_DIR);
|
|
195
|
+
if (!excluded.has(projectDir)) {
|
|
196
|
+
const candidate = join(projectDir, PROJECT_CONFIG_FILENAME);
|
|
197
|
+
if (existsSync(candidate)) {
|
|
198
|
+
found.push(candidate);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const parent = dirname(dir);
|
|
202
|
+
if (parent === dir) {
|
|
203
|
+
return found;
|
|
204
|
+
}
|
|
205
|
+
dir = parent;
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
/** The nearest project config on the path from `startDir` upwards, if any. */
|
|
209
|
+
export const findProjectConfig = (startDir, options = {}) => {
|
|
210
|
+
return findProjectConfigs(startDir, options)[0];
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* Reads the `scheme` key out of a project `.appduct/config.json`. Unlike `app.json` this *is*
|
|
214
|
+
* Appduct's own file, so anything wrong with it (not an object, `scheme` not a non-empty
|
|
215
|
+
* string) is a hard error rather than a silent fall-through — a typo here must not quietly
|
|
216
|
+
* degrade to a different scheme from a lower-precedence source.
|
|
217
|
+
*
|
|
218
|
+
* Keys other than `scheme` are ignored: a project file is client-side settings only, and
|
|
219
|
+
* accepting (say) `keyPath` here would let a checked-in file redirect the daemon's key material.
|
|
220
|
+
*/
|
|
221
|
+
export const readProjectConfigScheme = async (path) => {
|
|
222
|
+
const file = await readJsonFile(path);
|
|
223
|
+
if (!file) {
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
if (!isPlainObject(file.raw)) {
|
|
227
|
+
throw usageError(`Invalid Appduct project config at ${path}: must be a JSON object.`);
|
|
228
|
+
}
|
|
229
|
+
const scheme = file.raw.scheme;
|
|
230
|
+
if (scheme === undefined) {
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
if (typeof scheme !== "string" || scheme.length === 0) {
|
|
234
|
+
throw usageError(`Invalid Appduct project config at ${path}: "scheme" must be a non-empty string.`);
|
|
235
|
+
}
|
|
236
|
+
return requireValidScheme(scheme, `"scheme" in ${path}`);
|
|
237
|
+
};
|
|
238
|
+
/**
|
|
239
|
+
* Resolves the deep-link scheme against the documented order, reporting both the winner and every
|
|
240
|
+
* location consulted so callers can render an error that says where to put one.
|
|
241
|
+
*/
|
|
242
|
+
export const resolveScheme = async (options = {}) => {
|
|
243
|
+
const env = options.env ?? process.env;
|
|
244
|
+
const cwd = resolve(options.cwd ?? process.cwd());
|
|
245
|
+
const tried = [];
|
|
246
|
+
tried.push("the --scheme flag");
|
|
247
|
+
if (options.flagScheme !== undefined && options.flagScheme.length > 0) {
|
|
248
|
+
return {
|
|
249
|
+
scheme: requireValidScheme(options.flagScheme, "the --scheme flag"),
|
|
250
|
+
source: "flag",
|
|
251
|
+
tried,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
tried.push(`the ${SCHEME_ENV_VAR} environment variable`);
|
|
255
|
+
const envScheme = env[SCHEME_ENV_VAR];
|
|
256
|
+
if (typeof envScheme === "string" && envScheme.length > 0) {
|
|
257
|
+
return {
|
|
258
|
+
scheme: requireValidScheme(envScheme, `the ${SCHEME_ENV_VAR} environment variable`),
|
|
259
|
+
source: "env",
|
|
260
|
+
tried,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
// Every project config on the way up, not just the nearest: one that exists but declares no
|
|
264
|
+
// `scheme` (a future version's file holding some other client-side key, say) is not an answer,
|
|
265
|
+
// so the walk continues past it rather than treating its silence as "no project scheme".
|
|
266
|
+
const projectConfigPaths = findProjectConfigs(cwd, {
|
|
267
|
+
stateDirRoot: options.stateDirRoot,
|
|
268
|
+
homeDir: options.homeDir,
|
|
269
|
+
});
|
|
270
|
+
tried.push(projectConfigPaths.length === 0
|
|
271
|
+
? `${PROJECT_CONFIG_RELATIVE_PATH} (searched upwards from ${cwd})`
|
|
272
|
+
: projectConfigPaths.join(", "));
|
|
273
|
+
for (const projectConfigPath of projectConfigPaths) {
|
|
274
|
+
const projectScheme = await readProjectConfigScheme(projectConfigPath);
|
|
275
|
+
if (projectScheme !== undefined) {
|
|
276
|
+
return { scheme: projectScheme, source: "project-config", tried };
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
tried.push(options.stateConfigPath === undefined
|
|
280
|
+
? 'the state directory\'s config.json ("scheme")'
|
|
281
|
+
: `${options.stateConfigPath} ("scheme")`);
|
|
282
|
+
if (options.configScheme !== undefined && options.configScheme.length > 0) {
|
|
283
|
+
return {
|
|
284
|
+
scheme: requireValidScheme(options.configScheme, options.stateConfigPath === undefined
|
|
285
|
+
? 'the state directory\'s config.json "scheme"'
|
|
286
|
+
: `"scheme" in ${options.stateConfigPath}`),
|
|
287
|
+
source: "state-config",
|
|
288
|
+
tried,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
const discovered = await discoverStaticProjectScheme(cwd);
|
|
292
|
+
tried.push(...discovered.tried);
|
|
293
|
+
if (discovered.scheme !== undefined) {
|
|
294
|
+
return { scheme: discovered.scheme, source: discovered.source, tried };
|
|
295
|
+
}
|
|
296
|
+
return { tried };
|
|
297
|
+
};
|
|
298
|
+
/**
|
|
299
|
+
* The shared "no scheme anywhere" message. Every caller renders the same body so the locations
|
|
300
|
+
* listed (and the fixes suggested) can't drift between `appduct link`, `appduct mcp` and
|
|
301
|
+
* `appduct_connect`.
|
|
302
|
+
*/
|
|
303
|
+
export const describeMissingScheme = (tried) => {
|
|
304
|
+
const locations = tried.length === 0
|
|
305
|
+
? ""
|
|
306
|
+
: `Looked in, in order:\n${tried
|
|
307
|
+
.map((location, index) => ` ${index + 1}. ${location}`)
|
|
308
|
+
.join("\n")}\n`;
|
|
309
|
+
return ("A deep-link scheme is required to compose the link, and none was found. " +
|
|
310
|
+
locations +
|
|
311
|
+
`Set one with --scheme <scheme>, ${SCHEME_ENV_VAR}=<scheme>, "expo.scheme" in ${APP_JSON_FILENAME}, ` +
|
|
312
|
+
`or run \`appduct init\` in your app root to write ${PROJECT_CONFIG_RELATIVE_PATH}.`);
|
|
313
|
+
};
|
|
314
|
+
/** {@link resolveScheme}, throwing {@link usageError} with {@link describeMissingScheme} when nothing matched. */
|
|
315
|
+
export const resolveSchemeOrThrow = async (options = {}) => {
|
|
316
|
+
const resolved = await resolveScheme(options);
|
|
317
|
+
if (resolved.scheme === undefined) {
|
|
318
|
+
throw usageError(describeMissingScheme(resolved.tried));
|
|
319
|
+
}
|
|
320
|
+
return resolved.scheme;
|
|
321
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spki-pin.d.ts","sourceRoot":"","sources":["../src/spki-pin.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,KAAG,MAI/C,CAAC;AAEF,eAAO,MAAM,2BAA2B,GAAI,QAAQ,MAAM,KAAG,MAW5D,CAAC"}
|
package/dist/spki-pin.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createHash, createPrivateKey, createPublicKey } from "node:crypto";
|
|
2
|
+
export const createSpkiPin = (spkiDer) => {
|
|
3
|
+
const digest = createHash("sha256").update(spkiDer).digest("base64");
|
|
4
|
+
return `sha256/${digest}`;
|
|
5
|
+
};
|
|
6
|
+
export const getSpkiPinFromPrivateKeyPem = (keyPem) => {
|
|
7
|
+
const privateKey = createPrivateKey(keyPem);
|
|
8
|
+
const publicKey = createPublicKey(privateKey);
|
|
9
|
+
const spkiDer = Buffer.from(publicKey.export({
|
|
10
|
+
type: "spki",
|
|
11
|
+
format: "der",
|
|
12
|
+
}));
|
|
13
|
+
return createSpkiPin(spkiDer);
|
|
14
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "appduct",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "Driving React Native tools without in-app debug UI.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./client": {
|
|
16
|
+
"types": "./dist/client/index.d.ts",
|
|
17
|
+
"import": "./dist/client/index.js",
|
|
18
|
+
"default": "./dist/client/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./package.json": "./package.json"
|
|
21
|
+
},
|
|
22
|
+
"bin": {
|
|
23
|
+
"appduct": "./bin.js"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"appduct",
|
|
27
|
+
"react-native",
|
|
28
|
+
"agent"
|
|
29
|
+
],
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/callstackincubator/appduct"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/callstackincubator/appduct/issues"
|
|
36
|
+
},
|
|
37
|
+
"author": "Szymon Chmal <szymon@chmal.it> (V3RON)",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"homepage": "https://github.com/callstackincubator/appduct/tree/main/packages/appduct#readme",
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
42
|
+
"@peculiar/x509": "^2.0.0",
|
|
43
|
+
"cac": "^6.7.14",
|
|
44
|
+
"picocolors": "^1.1.1",
|
|
45
|
+
"reflect-metadata": "^0.2.2",
|
|
46
|
+
"toqr": "^0.1.1",
|
|
47
|
+
"ws": "^8.18.3",
|
|
48
|
+
"@appduct/shared": "0.9.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^22.10.0",
|
|
52
|
+
"@types/ws": "^8.18.1",
|
|
53
|
+
"typescript": "~5.9.2"
|
|
54
|
+
},
|
|
55
|
+
"files": [
|
|
56
|
+
"bin.js",
|
|
57
|
+
"dist"
|
|
58
|
+
],
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "tsc -p tsconfig.build.json",
|
|
61
|
+
"clean": "rm -rf dist",
|
|
62
|
+
"test": "vitest run --config ../../vitest.config.ts src/__tests__",
|
|
63
|
+
"typecheck": "tsc -p tsconfig.json"
|
|
64
|
+
}
|
|
65
|
+
}
|