appduct 0.10.0 → 0.11.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 +121 -51
- package/dist/artifact-inspect.d.ts +0 -1
- package/dist/bin.d.ts +0 -1
- package/dist/bin.js +1250 -2
- package/dist/cli/command-options.d.ts +8 -1
- package/dist/cli/create-cli.d.ts +0 -1
- package/dist/cli/dispatch.d.ts +10 -1
- package/dist/cli/envelope.d.ts +19 -0
- package/dist/cli/global-flags.d.ts +30 -0
- package/dist/cli/open-target.d.ts +25 -8
- package/dist/cli/result-types.d.ts +30 -5
- package/dist/cli/router.d.ts +78 -0
- package/dist/cli/routes/daemon/index.d.ts +9 -0
- package/dist/cli/routes/daemon/index.js +848 -0
- package/dist/cli/routes/daemon/run.d.ts +3 -0
- package/dist/cli/routes/daemon/run.js +3246 -0
- package/dist/cli/routes/daemon/start.d.ts +3 -0
- package/dist/cli/routes/daemon/start.js +1668 -0
- package/dist/cli/routes/daemon/status.d.ts +4 -0
- package/dist/cli/routes/daemon/status.js +1676 -0
- package/dist/cli/routes/daemon/stop.d.ts +4 -0
- package/dist/cli/routes/daemon/stop.js +1646 -0
- package/dist/cli/routes/doctor.d.ts +3 -0
- package/dist/cli/routes/doctor.js +1075 -0
- package/dist/cli/routes/events.d.ts +3 -0
- package/dist/cli/routes/events.js +1906 -0
- package/dist/cli/routes/init.d.ts +3 -0
- package/dist/cli/routes/init.js +1486 -0
- package/dist/cli/routes/invoke.d.ts +3 -0
- package/dist/cli/routes/invoke.js +1884 -0
- package/dist/cli/routes/keygen.d.ts +3 -0
- package/dist/cli/routes/keygen.js +928 -0
- package/dist/cli/routes/link.d.ts +3 -0
- package/dist/cli/routes/link.js +2705 -0
- package/dist/cli/routes/ls.d.ts +3 -0
- package/dist/cli/routes/ls.js +1659 -0
- package/dist/cli/routes/mcp.d.ts +5 -0
- package/dist/cli/routes/mcp.js +3917 -0
- package/dist/cli/routes/revoke.d.ts +3 -0
- package/dist/cli/routes/revoke.js +1675 -0
- package/dist/cli/routes/tools.d.ts +3 -0
- package/dist/cli/routes/tools.js +1851 -0
- package/dist/cli/runner.d.ts +3 -11
- package/dist/cli/types.d.ts +9 -6
- package/dist/cli/version-guard.d.ts +20 -0
- package/dist/cli.d.ts +0 -1
- package/dist/client/app-client.d.ts +0 -1
- package/dist/client/bootstrap.d.ts +4 -4
- package/dist/client/connect.d.ts +0 -1
- package/dist/client/errors.d.ts +0 -1
- package/dist/client/index.d.ts +0 -1
- package/dist/client/index.js +2379 -16
- package/dist/commands/daemon/context.d.ts +14 -0
- package/dist/commands/daemon/run.d.ts +11 -0
- package/dist/commands/daemon/start.d.ts +5 -0
- package/dist/commands/daemon/status.d.ts +5 -0
- package/dist/commands/daemon/stop.d.ts +5 -0
- package/dist/commands/doctor.d.ts +0 -1
- package/dist/commands/events.d.ts +0 -1
- package/dist/commands/init.d.ts +17 -4
- package/dist/commands/invoke.d.ts +0 -1
- package/dist/commands/keygen.d.ts +0 -1
- package/dist/commands/link.d.ts +1 -2
- package/dist/commands/ls.d.ts +0 -1
- package/dist/commands/mcp.d.ts +2 -4
- package/dist/commands/revoke.d.ts +0 -1
- package/dist/commands/tools.d.ts +19 -3
- package/dist/daemon/address.d.ts +0 -1
- package/dist/daemon/audit.d.ts +6 -11
- package/dist/daemon/calls.d.ts +0 -1
- package/dist/daemon/config.d.ts +13 -16
- package/dist/daemon/daemon.d.ts +0 -1
- package/dist/daemon/event-bus.d.ts +0 -1
- package/dist/daemon/links.d.ts +0 -1
- package/dist/daemon/listener.d.ts +0 -1
- package/dist/daemon/log-rotation.d.ts +0 -1
- package/dist/daemon/pidfile.d.ts +14 -8
- package/dist/daemon/policy.d.ts +0 -1
- package/dist/daemon/registry.d.ts +0 -1
- package/dist/daemon/rpc-errors.d.ts +16 -0
- package/dist/daemon/rpc-server.d.ts +0 -11
- package/dist/daemon/sessions.d.ts +0 -1
- package/dist/daemon/socket-probe.d.ts +0 -1
- package/dist/daemon/state-dir.d.ts +0 -1
- package/dist/daemon/timers.d.ts +0 -1
- package/dist/daemon/tls.d.ts +0 -1
- package/dist/errors.d.ts +0 -1
- package/dist/host-certificate.d.ts +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1307 -3
- package/dist/key-material.d.ts +0 -1
- package/dist/link.d.ts +8 -7
- package/dist/mcp/app-tools.d.ts +153 -0
- package/dist/mcp/connect-tool.d.ts +14 -8
- package/dist/mcp/daemon-tools.d.ts +2 -11
- package/dist/mcp/events-tool.d.ts +0 -1
- package/dist/mcp/server.d.ts +33 -14
- package/dist/native-scheme.d.ts +0 -1
- package/dist/output.d.ts +8 -11
- package/dist/package-root.d.ts +11 -0
- package/dist/package-version.d.ts +0 -1
- package/dist/qr-terminal.d.ts +0 -1
- package/dist/rpc/client.d.ts +0 -1
- package/dist/scheme.d.ts +60 -2
- package/dist/spki-pin.d.ts +0 -1
- package/package.json +4 -3
- package/dist/artifact-inspect.d.ts.map +0 -1
- package/dist/artifact-inspect.js +0 -334
- package/dist/bin.d.ts.map +0 -1
- package/dist/cli/command-options.d.ts.map +0 -1
- package/dist/cli/command-options.js +0 -95
- package/dist/cli/create-cli.d.ts.map +0 -1
- package/dist/cli/create-cli.js +0 -56
- package/dist/cli/dispatch.d.ts.map +0 -1
- package/dist/cli/dispatch.js +0 -275
- package/dist/cli/open-target.d.ts.map +0 -1
- package/dist/cli/open-target.js +0 -438
- package/dist/cli/result-types.d.ts.map +0 -1
- package/dist/cli/result-types.js +0 -6
- package/dist/cli/runner.d.ts.map +0 -1
- package/dist/cli/runner.js +0 -111
- package/dist/cli/types.d.ts.map +0 -1
- package/dist/cli/types.js +0 -10
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -2
- package/dist/client/app-client.d.ts.map +0 -1
- package/dist/client/app-client.js +0 -223
- package/dist/client/bootstrap.d.ts.map +0 -1
- package/dist/client/bootstrap.js +0 -108
- package/dist/client/connect.d.ts.map +0 -1
- package/dist/client/connect.js +0 -38
- package/dist/client/errors.d.ts.map +0 -1
- package/dist/client/errors.js +0 -40
- package/dist/client/index.d.ts.map +0 -1
- package/dist/commands/daemon.d.ts +0 -22
- package/dist/commands/daemon.d.ts.map +0 -1
- package/dist/commands/daemon.js +0 -128
- package/dist/commands/doctor.d.ts.map +0 -1
- package/dist/commands/doctor.js +0 -42
- package/dist/commands/events.d.ts.map +0 -1
- package/dist/commands/events.js +0 -76
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js +0 -231
- package/dist/commands/invoke.d.ts.map +0 -1
- package/dist/commands/invoke.js +0 -54
- package/dist/commands/keygen.d.ts.map +0 -1
- package/dist/commands/keygen.js +0 -46
- package/dist/commands/link.d.ts.map +0 -1
- package/dist/commands/link.js +0 -43
- package/dist/commands/ls.d.ts.map +0 -1
- package/dist/commands/ls.js +0 -7
- package/dist/commands/mcp.d.ts.map +0 -1
- package/dist/commands/mcp.js +0 -98
- package/dist/commands/revoke.d.ts.map +0 -1
- package/dist/commands/revoke.js +0 -7
- package/dist/commands/tools.d.ts.map +0 -1
- package/dist/commands/tools.js +0 -52
- package/dist/daemon/address.d.ts.map +0 -1
- package/dist/daemon/address.js +0 -71
- package/dist/daemon/audit.d.ts.map +0 -1
- package/dist/daemon/audit.js +0 -237
- package/dist/daemon/calls.d.ts.map +0 -1
- package/dist/daemon/calls.js +0 -158
- package/dist/daemon/config.d.ts.map +0 -1
- package/dist/daemon/config.js +0 -170
- package/dist/daemon/daemon.d.ts.map +0 -1
- package/dist/daemon/daemon.js +0 -560
- package/dist/daemon/event-bus.d.ts.map +0 -1
- package/dist/daemon/event-bus.js +0 -111
- package/dist/daemon/links.d.ts.map +0 -1
- package/dist/daemon/links.js +0 -118
- package/dist/daemon/listener.d.ts.map +0 -1
- package/dist/daemon/listener.js +0 -132
- package/dist/daemon/log-rotation.d.ts.map +0 -1
- package/dist/daemon/log-rotation.js +0 -136
- package/dist/daemon/pidfile.d.ts.map +0 -1
- package/dist/daemon/pidfile.js +0 -90
- package/dist/daemon/policy.d.ts.map +0 -1
- package/dist/daemon/policy.js +0 -24
- package/dist/daemon/registry.d.ts.map +0 -1
- package/dist/daemon/registry.js +0 -31
- package/dist/daemon/rpc-server.d.ts.map +0 -1
- package/dist/daemon/rpc-server.js +0 -203
- package/dist/daemon/sessions.d.ts.map +0 -1
- package/dist/daemon/sessions.js +0 -429
- package/dist/daemon/socket-probe.d.ts.map +0 -1
- package/dist/daemon/socket-probe.js +0 -28
- package/dist/daemon/state-dir.d.ts.map +0 -1
- package/dist/daemon/state-dir.js +0 -43
- package/dist/daemon/timers.d.ts.map +0 -1
- package/dist/daemon/timers.js +0 -12
- package/dist/daemon/tls.d.ts.map +0 -1
- package/dist/daemon/tls.js +0 -79
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -162
- package/dist/host-certificate.d.ts.map +0 -1
- package/dist/host-certificate.js +0 -111
- package/dist/index.d.ts.map +0 -1
- package/dist/key-material.d.ts.map +0 -1
- package/dist/key-material.js +0 -42
- package/dist/link.d.ts.map +0 -1
- package/dist/link.js +0 -117
- package/dist/mcp/connect-tool.d.ts.map +0 -1
- package/dist/mcp/connect-tool.js +0 -396
- package/dist/mcp/daemon-tools.d.ts.map +0 -1
- package/dist/mcp/daemon-tools.js +0 -29
- package/dist/mcp/events-tool.d.ts.map +0 -1
- package/dist/mcp/events-tool.js +0 -322
- package/dist/mcp/server.d.ts.map +0 -1
- package/dist/mcp/server.js +0 -538
- package/dist/mcp/tool-mapping.d.ts +0 -58
- package/dist/mcp/tool-mapping.d.ts.map +0 -1
- package/dist/mcp/tool-mapping.js +0 -143
- package/dist/mcp/tool-namespace.d.ts +0 -40
- package/dist/mcp/tool-namespace.d.ts.map +0 -1
- package/dist/mcp/tool-namespace.js +0 -65
- package/dist/native-scheme.d.ts.map +0 -1
- package/dist/native-scheme.js +0 -465
- package/dist/output.d.ts.map +0 -1
- package/dist/output.js +0 -342
- package/dist/package-version.d.ts.map +0 -1
- package/dist/package-version.js +0 -47
- package/dist/qr-terminal.d.ts.map +0 -1
- package/dist/qr-terminal.js +0 -45
- package/dist/rpc/client.d.ts.map +0 -1
- package/dist/rpc/client.js +0 -819
- package/dist/scheme.d.ts.map +0 -1
- package/dist/scheme.js +0 -321
- package/dist/spki-pin.d.ts.map +0 -1
- package/dist/spki-pin.js +0 -14
|
@@ -0,0 +1,1486 @@
|
|
|
1
|
+
// src/commands/init.ts
|
|
2
|
+
import { chmod, mkdir, readFile as readFile3, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname as dirname3, join as join5, resolve as resolve2 } from "node:path";
|
|
4
|
+
|
|
5
|
+
// src/rpc/client.ts
|
|
6
|
+
import { join as join2 } from "node:path";
|
|
7
|
+
import { RPC_METHODS } from "@appduct/shared";
|
|
8
|
+
|
|
9
|
+
// src/daemon/config.ts
|
|
10
|
+
var DEFAULT_DAEMON_LOG_MAX_BYTES = 10 * 1024 * 1024;
|
|
11
|
+
|
|
12
|
+
// src/package-root.ts
|
|
13
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
14
|
+
import { dirname, join } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
var PACKAGE_NAME = "appduct";
|
|
17
|
+
var cached;
|
|
18
|
+
var getPackageRoot = () => {
|
|
19
|
+
if (cached !== void 0) {
|
|
20
|
+
return cached;
|
|
21
|
+
}
|
|
22
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
for (; ; ) {
|
|
24
|
+
const candidate = join(dir, "package.json");
|
|
25
|
+
if (existsSync(candidate)) {
|
|
26
|
+
let name;
|
|
27
|
+
try {
|
|
28
|
+
name = JSON.parse(readFileSync(candidate, "utf8")).name;
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
if (name === PACKAGE_NAME) {
|
|
32
|
+
cached = dir;
|
|
33
|
+
return dir;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const parent = dirname(dir);
|
|
37
|
+
if (parent === dir) {
|
|
38
|
+
throw new Error(`Could not locate the "${PACKAGE_NAME}" package root above ${import.meta.url}.`);
|
|
39
|
+
}
|
|
40
|
+
dir = parent;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// src/rpc/client.ts
|
|
45
|
+
var defaultBinPath = join2(getPackageRoot(), "bin.js");
|
|
46
|
+
var DaemonRpcError = class extends Error {
|
|
47
|
+
constructor(code, message, data) {
|
|
48
|
+
super(message);
|
|
49
|
+
this.code = code;
|
|
50
|
+
this.data = data;
|
|
51
|
+
this.name = "DaemonRpcError";
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var DaemonUnavailableError = class extends Error {
|
|
55
|
+
constructor(message, reason = "unreachable") {
|
|
56
|
+
super(message);
|
|
57
|
+
this.reason = reason;
|
|
58
|
+
this.name = "DaemonUnavailableError";
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
var describeLiveState = (state) => {
|
|
62
|
+
const parts = [];
|
|
63
|
+
if (state.sessions > 0) {
|
|
64
|
+
parts.push(`${state.sessions} connected session(s)`);
|
|
65
|
+
}
|
|
66
|
+
if (state.pendingLinks > 0) {
|
|
67
|
+
parts.push(`${state.pendingLinks} unclaimed link(s)`);
|
|
68
|
+
}
|
|
69
|
+
return parts.join(" and ");
|
|
70
|
+
};
|
|
71
|
+
var DaemonVersionMismatchError = class extends Error {
|
|
72
|
+
constructor(daemonVersion, clientVersion, live, message) {
|
|
73
|
+
super(
|
|
74
|
+
message ?? `The running Appduct daemon is version ${daemonVersion}, but this client is version ${clientVersion}. It was not restarted automatically because ${describeLiveState(live)} would be lost. Run "appduct daemon stop" once that state is expendable, or force it with "--daemon-restart".`
|
|
75
|
+
);
|
|
76
|
+
this.daemonVersion = daemonVersion;
|
|
77
|
+
this.clientVersion = clientVersion;
|
|
78
|
+
this.name = "DaemonVersionMismatchError";
|
|
79
|
+
this.sessionCount = live.sessions;
|
|
80
|
+
this.pendingLinkCount = live.pendingLinks;
|
|
81
|
+
}
|
|
82
|
+
sessionCount;
|
|
83
|
+
pendingLinkCount;
|
|
84
|
+
};
|
|
85
|
+
var isConnectionMissingError = (error) => {
|
|
86
|
+
const code = error.code;
|
|
87
|
+
return code === "ENOENT" || code === "ECONNREFUSED";
|
|
88
|
+
};
|
|
89
|
+
var isDaemonUnreachableError = (error) => {
|
|
90
|
+
return error instanceof DaemonUnavailableError || isConnectionMissingError(error);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// src/errors.ts
|
|
94
|
+
var EXIT_CODE_BY_ERROR_TYPE = {
|
|
95
|
+
usage_error: 64,
|
|
96
|
+
validation_error: 65,
|
|
97
|
+
// EX_NOINPUT (66): "an input file did not exist or was not readable" — `doctor` throws this for a
|
|
98
|
+
// missing external tool or an unreadable/corrupt artifact, deliberately distinct from every other
|
|
99
|
+
// code here so a release pipeline can't mistake "we couldn't tell" for either a clean pass or an
|
|
100
|
+
// assertion failure (docs/tasks/08-appduct-doctor.md).
|
|
101
|
+
inspection_error: 66,
|
|
102
|
+
connection_error: 70,
|
|
103
|
+
session_error: 71,
|
|
104
|
+
tool_error: 72,
|
|
105
|
+
permission_error: 77,
|
|
106
|
+
internal_error: 1,
|
|
107
|
+
// `doctor --assert-present`/`--assert-absent`: the artifact was inspected successfully and the
|
|
108
|
+
// assertion did not hold. Distinct from `inspection_error` (66) — this means the answer is known
|
|
109
|
+
// and it's "no", not "couldn't tell" — and distinct from `internal_error` (1) so a caller can't
|
|
110
|
+
// conflate a normal assertion failure with an unexpected crash.
|
|
111
|
+
assertion_error: 3
|
|
112
|
+
};
|
|
113
|
+
var RPC_ERROR_EXIT_CLASS = {
|
|
114
|
+
no_session: "session_error",
|
|
115
|
+
ambiguous_session: "session_error",
|
|
116
|
+
unknown_session: "session_error",
|
|
117
|
+
session_not_active: "session_error",
|
|
118
|
+
session_suspended: "session_error",
|
|
119
|
+
tool_not_found: "tool_error",
|
|
120
|
+
tool_input_validation_error: "tool_error",
|
|
121
|
+
tool_output_validation_error: "tool_error",
|
|
122
|
+
tool_execution_error: "tool_error",
|
|
123
|
+
tool_serialization_error: "tool_error",
|
|
124
|
+
tool_timeout: "tool_error",
|
|
125
|
+
tool_cancelled: "tool_error",
|
|
126
|
+
policy_denied: "permission_error",
|
|
127
|
+
invalid_request: "validation_error"
|
|
128
|
+
};
|
|
129
|
+
var AppductCliError = class extends Error {
|
|
130
|
+
type;
|
|
131
|
+
details;
|
|
132
|
+
constructor(type, message, details) {
|
|
133
|
+
super(message);
|
|
134
|
+
this.name = "AppductCliError";
|
|
135
|
+
this.type = type;
|
|
136
|
+
this.details = details;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
var usageError = (message, details) => {
|
|
140
|
+
return new AppductCliError("usage_error", message, details);
|
|
141
|
+
};
|
|
142
|
+
var isAppductCliError = (value) => {
|
|
143
|
+
return value instanceof AppductCliError;
|
|
144
|
+
};
|
|
145
|
+
var rpcErrorClass = (error) => {
|
|
146
|
+
const type = error.data?.type;
|
|
147
|
+
return type ? RPC_ERROR_EXIT_CLASS[type] : void 0;
|
|
148
|
+
};
|
|
149
|
+
var toCliError = (error) => {
|
|
150
|
+
if (isAppductCliError(error)) {
|
|
151
|
+
return {
|
|
152
|
+
type: error.type,
|
|
153
|
+
message: error.message,
|
|
154
|
+
details: error.details
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
if (error instanceof DaemonVersionMismatchError) {
|
|
158
|
+
return {
|
|
159
|
+
type: "connection_error",
|
|
160
|
+
message: error.message,
|
|
161
|
+
details: {
|
|
162
|
+
daemon_version: error.daemonVersion,
|
|
163
|
+
client_version: error.clientVersion,
|
|
164
|
+
session_count: error.sessionCount,
|
|
165
|
+
pending_link_count: error.pendingLinkCount
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (error instanceof DaemonRpcError && error.data?.type) {
|
|
170
|
+
return {
|
|
171
|
+
type: error.data.type,
|
|
172
|
+
message: error.message,
|
|
173
|
+
details: error.data.details
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
if (isDaemonUnreachableError(error)) {
|
|
177
|
+
return {
|
|
178
|
+
type: "connection_error",
|
|
179
|
+
message: error instanceof Error ? error.message : "The Appduct daemon is unreachable."
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
if (error instanceof Error) {
|
|
183
|
+
return {
|
|
184
|
+
type: "internal_error",
|
|
185
|
+
message: error.message
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
type: "internal_error",
|
|
190
|
+
message: "An unexpected error occurred.",
|
|
191
|
+
details: error
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
var getExitCodeForError = (error) => {
|
|
195
|
+
if (isAppductCliError(error)) {
|
|
196
|
+
return EXIT_CODE_BY_ERROR_TYPE[error.type];
|
|
197
|
+
}
|
|
198
|
+
if (error instanceof DaemonVersionMismatchError) {
|
|
199
|
+
return EXIT_CODE_BY_ERROR_TYPE.connection_error;
|
|
200
|
+
}
|
|
201
|
+
if (error instanceof DaemonRpcError) {
|
|
202
|
+
const cliErrorClass = rpcErrorClass(error);
|
|
203
|
+
return EXIT_CODE_BY_ERROR_TYPE[cliErrorClass ?? "internal_error"];
|
|
204
|
+
}
|
|
205
|
+
if (isDaemonUnreachableError(error)) {
|
|
206
|
+
return EXIT_CODE_BY_ERROR_TYPE.connection_error;
|
|
207
|
+
}
|
|
208
|
+
return EXIT_CODE_BY_ERROR_TYPE.internal_error;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// src/cli/open-target.ts
|
|
212
|
+
var APP_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/u;
|
|
213
|
+
var isValidAppId = (value) => APP_ID_PATTERN.test(value);
|
|
214
|
+
var invalidAppIdMessage = (value) => `"${value}" is not a valid app id (letters, digits, ".", "_" and "-", starting with a letter or digit).`;
|
|
215
|
+
|
|
216
|
+
// src/scheme.ts
|
|
217
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
218
|
+
import { homedir } from "node:os";
|
|
219
|
+
import { dirname as dirname2, join as join4, resolve } from "node:path";
|
|
220
|
+
|
|
221
|
+
// src/native-scheme.ts
|
|
222
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
223
|
+
import { join as join3 } from "node:path";
|
|
224
|
+
var MAX_PROBE_FILE_BYTES = 2 * 1024 * 1024;
|
|
225
|
+
var MAX_PLIST_WALK_DEPTH = 2;
|
|
226
|
+
var MAX_WALK_ENTRIES = 5e3;
|
|
227
|
+
var EXCLUDED_WALK_DIRS = /* @__PURE__ */ new Set(["Pods", "build", "node_modules", "DerivedData"]);
|
|
228
|
+
var readCappedText = async (path) => {
|
|
229
|
+
const buffer = await readCappedBuffer(path);
|
|
230
|
+
if (!buffer.ok) {
|
|
231
|
+
return buffer;
|
|
232
|
+
}
|
|
233
|
+
return { ok: true, text: buffer.data.toString("utf8") };
|
|
234
|
+
};
|
|
235
|
+
var readCappedBuffer = async (path) => {
|
|
236
|
+
let size;
|
|
237
|
+
try {
|
|
238
|
+
const stats = await stat(path);
|
|
239
|
+
if (!stats.isFile()) {
|
|
240
|
+
return { ok: false, reason: "not-found" };
|
|
241
|
+
}
|
|
242
|
+
size = stats.size;
|
|
243
|
+
} catch {
|
|
244
|
+
return { ok: false, reason: "not-found" };
|
|
245
|
+
}
|
|
246
|
+
if (size > MAX_PROBE_FILE_BYTES) {
|
|
247
|
+
return { ok: false, reason: "too-large" };
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
return { ok: true, data: await readFile(path) };
|
|
251
|
+
} catch {
|
|
252
|
+
return { ok: false, reason: "unreadable" };
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
var requireValidNativeScheme = (value, origin) => {
|
|
256
|
+
if (!isValidScheme(value)) {
|
|
257
|
+
throw usageError(
|
|
258
|
+
`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 "://".`
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
return value;
|
|
262
|
+
};
|
|
263
|
+
var GRADLE_PLACEHOLDER_PATTERN = /manifestPlaceholders\s*(?:\[\s*["']appductScheme["']\s*\]|\.\s*appductScheme)\s*=\s*["']([^"']*)["']/u;
|
|
264
|
+
var extractGradleScheme = (text) => {
|
|
265
|
+
const match = GRADLE_PLACEHOLDER_PATTERN.exec(text);
|
|
266
|
+
const value = match?.[1];
|
|
267
|
+
return value !== void 0 && value.length > 0 ? value : void 0;
|
|
268
|
+
};
|
|
269
|
+
var probeAndroidGradle = async (root) => {
|
|
270
|
+
const candidates = [join3(root, "app", "build.gradle.kts"), join3(root, "app", "build.gradle")];
|
|
271
|
+
const notes = [];
|
|
272
|
+
for (const candidate of candidates) {
|
|
273
|
+
const read = await readCappedText(candidate);
|
|
274
|
+
if (!read.ok) {
|
|
275
|
+
if (read.reason === "too-large") {
|
|
276
|
+
notes.push(`${candidate} (too large, skipped)`);
|
|
277
|
+
}
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
const scheme = extractGradleScheme(read.text);
|
|
281
|
+
if (scheme !== void 0) {
|
|
282
|
+
requireValidNativeScheme(scheme, `"appductScheme" in ${candidate}`);
|
|
283
|
+
return {
|
|
284
|
+
result: {
|
|
285
|
+
source: "android-gradle",
|
|
286
|
+
scheme,
|
|
287
|
+
origin: `"appductScheme" in ${candidate}`
|
|
288
|
+
},
|
|
289
|
+
tried: [describeAndroidGradleTried(candidates, notes)]
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return { tried: [describeAndroidGradleTried(candidates, notes)] };
|
|
294
|
+
};
|
|
295
|
+
var describeAndroidGradleTried = (candidates, notes) => {
|
|
296
|
+
const base = `${candidates.join(", ")} ("appductScheme" manifest placeholder)`;
|
|
297
|
+
return notes.length === 0 ? base : `${base} \u2014 ${notes.join(", ")}`;
|
|
298
|
+
};
|
|
299
|
+
var INTENT_FILTER_PATTERN = /<intent-filter\b[^>]*>([\s\S]*?)<\/intent-filter>/giu;
|
|
300
|
+
var VIEW_ACTION_PATTERN = /<action\b[^>]*\bandroid:name\s*=\s*["']android\.intent\.action\.VIEW["']/iu;
|
|
301
|
+
var DATA_SCHEME_PATTERN = /<data\b[^>]*\bandroid:scheme\s*=\s*["']([^"']*)["']/iu;
|
|
302
|
+
var extractManifestScheme = (xml) => {
|
|
303
|
+
for (const match of xml.matchAll(INTENT_FILTER_PATTERN)) {
|
|
304
|
+
const block = match[1] ?? "";
|
|
305
|
+
if (!VIEW_ACTION_PATTERN.test(block)) {
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
const dataMatch = DATA_SCHEME_PATTERN.exec(block);
|
|
309
|
+
const scheme = dataMatch?.[1];
|
|
310
|
+
if (scheme !== void 0 && scheme.length > 0) {
|
|
311
|
+
return scheme;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return void 0;
|
|
315
|
+
};
|
|
316
|
+
var probeAndroidManifest = async (root) => {
|
|
317
|
+
const manifestPath = join3(root, "app", "src", "main", "AndroidManifest.xml");
|
|
318
|
+
const read = await readCappedText(manifestPath);
|
|
319
|
+
const description = `${manifestPath} (first <data android:scheme> in a VIEW intent-filter)`;
|
|
320
|
+
if (!read.ok) {
|
|
321
|
+
return {
|
|
322
|
+
tried: [read.reason === "too-large" ? `${description} \u2014 too large, skipped` : description]
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
const scheme = extractManifestScheme(read.text);
|
|
326
|
+
if (scheme === void 0) {
|
|
327
|
+
return { tried: [description] };
|
|
328
|
+
}
|
|
329
|
+
requireValidNativeScheme(scheme, `"android:scheme" in ${manifestPath}`);
|
|
330
|
+
return {
|
|
331
|
+
result: {
|
|
332
|
+
source: "android-manifest",
|
|
333
|
+
scheme,
|
|
334
|
+
origin: `"android:scheme" in ${manifestPath}`
|
|
335
|
+
},
|
|
336
|
+
tried: [description]
|
|
337
|
+
};
|
|
338
|
+
};
|
|
339
|
+
var XML_ENTITIES = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'" };
|
|
340
|
+
var decodeXmlEntities = (text) => {
|
|
341
|
+
return text.replace(/&(#x[0-9a-fA-F]+|#[0-9]+|[a-zA-Z]+);/gu, (whole, entity) => {
|
|
342
|
+
if (entity.startsWith("#x")) {
|
|
343
|
+
const code = Number.parseInt(entity.slice(2), 16);
|
|
344
|
+
return Number.isNaN(code) ? whole : String.fromCodePoint(code);
|
|
345
|
+
}
|
|
346
|
+
if (entity.startsWith("#")) {
|
|
347
|
+
const code = Number.parseInt(entity.slice(1), 10);
|
|
348
|
+
return Number.isNaN(code) ? whole : String.fromCodePoint(code);
|
|
349
|
+
}
|
|
350
|
+
return XML_ENTITIES[entity] ?? whole;
|
|
351
|
+
});
|
|
352
|
+
};
|
|
353
|
+
var tokenizePlistXml = (xml) => {
|
|
354
|
+
const cleaned = xml.replace(/<\?[\s\S]*?\?>/gu, "").replace(/<!DOCTYPE[\s\S]*?>/giu, "").replace(/<!--[\s\S]*?-->/gu, "");
|
|
355
|
+
const root = { tag: "#root", children: [], text: "" };
|
|
356
|
+
const stack = [root];
|
|
357
|
+
const tagOrTextPattern = /<(\/?)([a-zA-Z][\w.:-]*)\b[^>]*?(\/?)>|([^<]+)/gu;
|
|
358
|
+
for (const match of cleaned.matchAll(tagOrTextPattern)) {
|
|
359
|
+
const [, closing, name, selfClosing, text] = match;
|
|
360
|
+
const top = stack[stack.length - 1];
|
|
361
|
+
if (text !== void 0) {
|
|
362
|
+
top.text += decodeXmlEntities(text);
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
if (closing) {
|
|
366
|
+
if (stack.length > 1) {
|
|
367
|
+
stack.pop();
|
|
368
|
+
}
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
const node = { tag: name ?? "", children: [], text: "" };
|
|
372
|
+
top.children.push(node);
|
|
373
|
+
if (!selfClosing) {
|
|
374
|
+
stack.push(node);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return root;
|
|
378
|
+
};
|
|
379
|
+
var plistNodeToValue = (node) => {
|
|
380
|
+
switch (node.tag) {
|
|
381
|
+
case "true":
|
|
382
|
+
return true;
|
|
383
|
+
case "false":
|
|
384
|
+
return false;
|
|
385
|
+
case "integer": {
|
|
386
|
+
const value = Number.parseInt(node.text.trim(), 10);
|
|
387
|
+
return Number.isNaN(value) ? node.text.trim() : value;
|
|
388
|
+
}
|
|
389
|
+
case "real": {
|
|
390
|
+
const value = Number.parseFloat(node.text.trim());
|
|
391
|
+
return Number.isNaN(value) ? node.text.trim() : value;
|
|
392
|
+
}
|
|
393
|
+
case "array":
|
|
394
|
+
return node.children.map(plistNodeToValue);
|
|
395
|
+
case "dict": {
|
|
396
|
+
const result = {};
|
|
397
|
+
for (let index = 0; index < node.children.length; index += 2) {
|
|
398
|
+
const keyNode = node.children[index];
|
|
399
|
+
const valueNode = node.children[index + 1];
|
|
400
|
+
if (keyNode?.tag === "key" && valueNode !== void 0) {
|
|
401
|
+
result[keyNode.text.trim()] = plistNodeToValue(valueNode);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return result;
|
|
405
|
+
}
|
|
406
|
+
default:
|
|
407
|
+
return node.text.trim();
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
var parsePlistXml = (xml) => {
|
|
411
|
+
const root = tokenizePlistXml(xml);
|
|
412
|
+
const plistNode = root.children.find((child) => child.tag === "plist");
|
|
413
|
+
const valueNode = plistNode?.children[0];
|
|
414
|
+
return valueNode ? plistNodeToValue(valueNode) : void 0;
|
|
415
|
+
};
|
|
416
|
+
var isBinaryPlist = (buffer) => {
|
|
417
|
+
return buffer.length >= 6 && buffer.subarray(0, 6).toString("latin1") === "bplist";
|
|
418
|
+
};
|
|
419
|
+
var firstPlistUrlScheme = (cfBundleURLTypes) => {
|
|
420
|
+
if (!Array.isArray(cfBundleURLTypes)) {
|
|
421
|
+
return void 0;
|
|
422
|
+
}
|
|
423
|
+
for (const entry of cfBundleURLTypes) {
|
|
424
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
const scheme = firstConfiguredScheme(entry.CFBundleURLSchemes);
|
|
428
|
+
if (scheme !== void 0) {
|
|
429
|
+
return scheme;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return void 0;
|
|
433
|
+
};
|
|
434
|
+
var walkForFilename = async (root, filename, maxDepth) => {
|
|
435
|
+
const hits = [];
|
|
436
|
+
let visited = 0;
|
|
437
|
+
const walk = async (dir, depth) => {
|
|
438
|
+
let entries;
|
|
439
|
+
try {
|
|
440
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
441
|
+
} catch {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
for (const entry of entries) {
|
|
445
|
+
if (visited >= MAX_WALK_ENTRIES) {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
visited += 1;
|
|
449
|
+
if (entry.isSymbolicLink()) {
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
if (entry.isDirectory()) {
|
|
453
|
+
if (EXCLUDED_WALK_DIRS.has(entry.name)) {
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
if (depth < maxDepth) {
|
|
457
|
+
await walk(join3(dir, entry.name), depth + 1);
|
|
458
|
+
}
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (entry.isFile() && entry.name === filename) {
|
|
462
|
+
hits.push({ path: join3(dir, entry.name), depth });
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
await walk(root, 0);
|
|
467
|
+
hits.sort((a, b) => a.depth - b.depth || a.path.localeCompare(b.path));
|
|
468
|
+
return hits;
|
|
469
|
+
};
|
|
470
|
+
var probeIosInfoPlist = async (root) => {
|
|
471
|
+
const files = await walkForFilename(root, "Info.plist", MAX_PLIST_WALK_DEPTH);
|
|
472
|
+
const describeSearch = (notes2) => {
|
|
473
|
+
const base = files.length === 0 ? `Info.plist up to ${MAX_PLIST_WALK_DEPTH} levels below ${root} (excluding Pods/build/node_modules/DerivedData) \u2014 none found` : `${files.map((file) => file.path).join(", ")} (first "CFBundleURLSchemes" entry)`;
|
|
474
|
+
return notes2.length === 0 ? base : `${base} \u2014 ${notes2.join(", ")}`;
|
|
475
|
+
};
|
|
476
|
+
const notes = [];
|
|
477
|
+
for (const file of files) {
|
|
478
|
+
const read = await readCappedBuffer(file.path);
|
|
479
|
+
if (!read.ok) {
|
|
480
|
+
notes.push(
|
|
481
|
+
`${file.path} (${read.reason === "too-large" ? "too large, skipped" : "unreadable"})`
|
|
482
|
+
);
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
if (isBinaryPlist(read.data)) {
|
|
486
|
+
notes.push(`${file.path} (binary plist \u2014 unreadable, use --scheme)`);
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
const value = parsePlistXml(read.data.toString("utf8"));
|
|
490
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
const scheme = firstPlistUrlScheme(value.CFBundleURLTypes);
|
|
494
|
+
if (scheme === void 0) {
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
requireValidNativeScheme(scheme, `"CFBundleURLSchemes" in ${file.path}`);
|
|
498
|
+
return {
|
|
499
|
+
result: {
|
|
500
|
+
source: "ios-info-plist",
|
|
501
|
+
scheme,
|
|
502
|
+
origin: `"CFBundleURLSchemes" in ${file.path}`
|
|
503
|
+
},
|
|
504
|
+
tried: [describeSearch(notes)]
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
return { tried: [describeSearch(notes)] };
|
|
508
|
+
};
|
|
509
|
+
var PROJECT_YML_SCHEME_PATTERN = /CFBundleURLSchemes\s*:\s*(?:\[\s*["']?([^"'\],\r\n]*)["']?|\r?\n[ \t]*-[ \t]*["']?([^"'\r\n]*)["']?)/u;
|
|
510
|
+
var extractProjectYmlScheme = (text) => {
|
|
511
|
+
const match = PROJECT_YML_SCHEME_PATTERN.exec(text);
|
|
512
|
+
const raw = (match?.[1] ?? match?.[2])?.trim();
|
|
513
|
+
return raw !== void 0 && raw.length > 0 ? raw : void 0;
|
|
514
|
+
};
|
|
515
|
+
var probeIosProjectYml = async (root) => {
|
|
516
|
+
const path = join3(root, "project.yml");
|
|
517
|
+
const description = `${path} ("info.properties.CFBundleURLTypes" > "CFBundleURLSchemes")`;
|
|
518
|
+
const read = await readCappedText(path);
|
|
519
|
+
if (!read.ok) {
|
|
520
|
+
return {
|
|
521
|
+
tried: [read.reason === "too-large" ? `${description} \u2014 too large, skipped` : description]
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
const scheme = extractProjectYmlScheme(read.text);
|
|
525
|
+
if (scheme === void 0) {
|
|
526
|
+
return { tried: [description] };
|
|
527
|
+
}
|
|
528
|
+
requireValidNativeScheme(scheme, `"CFBundleURLSchemes" in ${path}`);
|
|
529
|
+
return {
|
|
530
|
+
result: { source: "ios-project-yml", scheme, origin: `"CFBundleURLSchemes" in ${path}` },
|
|
531
|
+
tried: [description]
|
|
532
|
+
};
|
|
533
|
+
};
|
|
534
|
+
var PROBES = [
|
|
535
|
+
probeAndroidGradle,
|
|
536
|
+
probeAndroidManifest,
|
|
537
|
+
probeIosInfoPlist,
|
|
538
|
+
probeIosProjectYml
|
|
539
|
+
];
|
|
540
|
+
var discoverNativeScheme = async (root) => {
|
|
541
|
+
const tried = [];
|
|
542
|
+
const results = [];
|
|
543
|
+
for (const probe of PROBES) {
|
|
544
|
+
const outcome = await probe(root);
|
|
545
|
+
tried.push(...outcome.tried);
|
|
546
|
+
if (outcome.result !== void 0) {
|
|
547
|
+
results.push(outcome.result);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
if (results.length === 0) {
|
|
551
|
+
return { tried };
|
|
552
|
+
}
|
|
553
|
+
const distinctSchemes = new Set(results.map((entry) => entry.scheme));
|
|
554
|
+
if (distinctSchemes.size > 1) {
|
|
555
|
+
throw usageError(
|
|
556
|
+
"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
|
+
);
|
|
558
|
+
}
|
|
559
|
+
return { result: results[0], tried };
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
// src/scheme.ts
|
|
563
|
+
var PROJECT_CONFIG_DIR = ".appduct";
|
|
564
|
+
var PROJECT_CONFIG_FILENAME = "config.json";
|
|
565
|
+
var PROJECT_CONFIG_RELATIVE_PATH = join4(PROJECT_CONFIG_DIR, PROJECT_CONFIG_FILENAME);
|
|
566
|
+
var APP_JSON_FILENAME = "app.json";
|
|
567
|
+
var SCHEME_ENV_VAR = "APPDUCT_SCHEME";
|
|
568
|
+
var SCHEME_PATTERN = /^[a-zA-Z][a-zA-Z0-9+.-]*$/u;
|
|
569
|
+
var isValidScheme = (value) => SCHEME_PATTERN.test(value);
|
|
570
|
+
var requireValidScheme = (value, origin) => {
|
|
571
|
+
if (!isValidScheme(value)) {
|
|
572
|
+
throw usageError(
|
|
573
|
+
`Invalid deep-link scheme ${JSON.stringify(value)} from ${origin}: a scheme must start with a letter and contain only letters, digits, "+", "-" or "." (for example "myapp") \u2014 do not include "://".`
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
return value;
|
|
577
|
+
};
|
|
578
|
+
var isPlainObject = (value) => {
|
|
579
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
580
|
+
};
|
|
581
|
+
var readJsonFile = async (path) => {
|
|
582
|
+
let text;
|
|
583
|
+
try {
|
|
584
|
+
text = await readFile2(path, "utf8");
|
|
585
|
+
} catch (error) {
|
|
586
|
+
const code = error.code;
|
|
587
|
+
if (code === "ENOENT" || code === "ENOTDIR" || code === "EISDIR") {
|
|
588
|
+
return void 0;
|
|
589
|
+
}
|
|
590
|
+
throw error;
|
|
591
|
+
}
|
|
592
|
+
try {
|
|
593
|
+
return { raw: JSON.parse(text) };
|
|
594
|
+
} catch (error) {
|
|
595
|
+
throw usageError(`Could not parse ${path}: ${error.message}`);
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
var firstConfiguredScheme = (value) => {
|
|
599
|
+
if (Array.isArray(value)) {
|
|
600
|
+
return value.find((entry) => typeof entry === "string" && entry.length > 0);
|
|
601
|
+
}
|
|
602
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
603
|
+
};
|
|
604
|
+
var discoverExpoScheme = async (dir) => {
|
|
605
|
+
const path = join4(resolve(dir), APP_JSON_FILENAME);
|
|
606
|
+
const file = await readJsonFile(path);
|
|
607
|
+
if (!file || !isPlainObject(file.raw)) {
|
|
608
|
+
return void 0;
|
|
609
|
+
}
|
|
610
|
+
const expo = file.raw.expo;
|
|
611
|
+
if (!isPlainObject(expo)) {
|
|
612
|
+
return void 0;
|
|
613
|
+
}
|
|
614
|
+
const scheme = firstConfiguredScheme(expo.scheme);
|
|
615
|
+
return scheme === void 0 ? void 0 : requireValidScheme(scheme, `"expo.scheme" in ${path}`);
|
|
616
|
+
};
|
|
617
|
+
var discoverStaticProjectScheme = async (cwd) => {
|
|
618
|
+
const appJsonPath = join4(resolve(cwd), APP_JSON_FILENAME);
|
|
619
|
+
const tried = [`${appJsonPath} ("expo.scheme")`];
|
|
620
|
+
const appJsonScheme = await discoverExpoScheme(cwd);
|
|
621
|
+
if (appJsonScheme !== void 0) {
|
|
622
|
+
return { scheme: appJsonScheme, source: "app-json", origin: appJsonPath, tried };
|
|
623
|
+
}
|
|
624
|
+
const native = await discoverNativeScheme(resolve(cwd));
|
|
625
|
+
tried.push(...native.tried);
|
|
626
|
+
if (native.result !== void 0) {
|
|
627
|
+
return {
|
|
628
|
+
scheme: native.result.scheme,
|
|
629
|
+
source: native.result.source,
|
|
630
|
+
origin: native.result.origin,
|
|
631
|
+
tried
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
return { tried };
|
|
635
|
+
};
|
|
636
|
+
var globalConfigDirs = (options = {}) => {
|
|
637
|
+
const excluded = /* @__PURE__ */ new Set([join4(resolve(options.homeDir ?? homedir()), PROJECT_CONFIG_DIR)]);
|
|
638
|
+
if (options.stateDirRoot !== void 0) {
|
|
639
|
+
excluded.add(resolve(options.stateDirRoot));
|
|
640
|
+
}
|
|
641
|
+
return excluded;
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
// src/commands/init.ts
|
|
645
|
+
var readExistingConfig = async (path) => {
|
|
646
|
+
let text;
|
|
647
|
+
try {
|
|
648
|
+
text = await readFile3(path, "utf8");
|
|
649
|
+
} catch (error) {
|
|
650
|
+
const code = error.code;
|
|
651
|
+
if (code === "ENOENT") {
|
|
652
|
+
return void 0;
|
|
653
|
+
}
|
|
654
|
+
if (code === "ENOTDIR") {
|
|
655
|
+
throw usageError(
|
|
656
|
+
`Cannot write ${path}: a file already exists where the "${PROJECT_CONFIG_DIR}" directory needs to go. Remove or rename it, then re-run \`appduct init\`.`
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
if (code === "EISDIR") {
|
|
660
|
+
throw usageError(
|
|
661
|
+
`Cannot write ${path}: it is a directory, not a file. Remove it, then re-run \`appduct init\`.`
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
throw error;
|
|
665
|
+
}
|
|
666
|
+
let raw;
|
|
667
|
+
try {
|
|
668
|
+
raw = JSON.parse(text);
|
|
669
|
+
} catch (error) {
|
|
670
|
+
throw usageError(
|
|
671
|
+
`Could not parse the existing ${path}: ${error.message}. Fix or delete it, then re-run \`appduct init\`.`
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
675
|
+
throw usageError(
|
|
676
|
+
`The existing ${path} is not a JSON object. Fix or delete it, then re-run \`appduct init\`.`
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
return raw;
|
|
680
|
+
};
|
|
681
|
+
var readExistingScheme = (existing, path, replaceable) => {
|
|
682
|
+
const scheme = existing?.scheme;
|
|
683
|
+
if (scheme === void 0) {
|
|
684
|
+
return void 0;
|
|
685
|
+
}
|
|
686
|
+
if (typeof scheme !== "string" || !isValidScheme(scheme)) {
|
|
687
|
+
if (replaceable) {
|
|
688
|
+
return void 0;
|
|
689
|
+
}
|
|
690
|
+
throw usageError(
|
|
691
|
+
`${path} records an invalid deep-link scheme ${JSON.stringify(scheme)}: a scheme must start with a letter and contain only letters, digits, "+", "-" or "." (for example "myapp") \u2014 do not include "://". Fix it, or re-run with \`--scheme <scheme> --force\` to replace it.`
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
return scheme;
|
|
695
|
+
};
|
|
696
|
+
var readExistingAppId = (existing, path, platform, flagName, replaceable) => {
|
|
697
|
+
const appId = existing?.appId;
|
|
698
|
+
if (appId === void 0) {
|
|
699
|
+
return void 0;
|
|
700
|
+
}
|
|
701
|
+
if (typeof appId !== "object" || appId === null || Array.isArray(appId)) {
|
|
702
|
+
if (replaceable) {
|
|
703
|
+
return void 0;
|
|
704
|
+
}
|
|
705
|
+
throw usageError(
|
|
706
|
+
`${path} records an invalid "appId" (must be an object with "ios" and/or "android" keys). Fix it, or re-run with \`${flagName} <id> --force\` to replace it.`
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
const value = appId[platform];
|
|
710
|
+
if (value === void 0) {
|
|
711
|
+
return void 0;
|
|
712
|
+
}
|
|
713
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
714
|
+
if (replaceable) {
|
|
715
|
+
return void 0;
|
|
716
|
+
}
|
|
717
|
+
throw usageError(
|
|
718
|
+
`${path} records an invalid "appId.${platform}" (must be a non-empty string). Fix it, or re-run with \`${flagName} <id> --force\` to replace it.`
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
return value;
|
|
722
|
+
};
|
|
723
|
+
var handleInitCommand = async (options, context = {}) => {
|
|
724
|
+
const root = resolve2(context.cwd ?? process.cwd());
|
|
725
|
+
const projectDir = join5(root, PROJECT_CONFIG_DIR);
|
|
726
|
+
const configPath = join5(projectDir, PROJECT_CONFIG_FILENAME);
|
|
727
|
+
if (globalConfigDirs({ stateDirRoot: context.stateDir, homeDir: context.homeDir }).has(projectDir)) {
|
|
728
|
+
throw usageError(
|
|
729
|
+
`Refusing to write ${configPath}: that is Appduct's own state directory (it holds the daemon's private key and audit log), not a project config. Run \`appduct init\` from your app's root directory instead.`
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
if (options.scheme !== void 0 && !isValidScheme(options.scheme)) {
|
|
733
|
+
throw usageError(
|
|
734
|
+
`Invalid deep-link scheme ${JSON.stringify(options.scheme)} from --scheme: a scheme must start with a letter and contain only letters, digits, "+", "-" or "." (for example "myapp") \u2014 do not include "://".`
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
for (const [flag, value] of [
|
|
738
|
+
["--ios-app-id", options.iosAppId],
|
|
739
|
+
["--android-app-id", options.androidAppId]
|
|
740
|
+
]) {
|
|
741
|
+
if (value !== void 0 && !isValidAppId(value)) {
|
|
742
|
+
throw usageError(`${invalidAppIdMessage(value)} (from ${flag})`);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
const existing = await readExistingConfig(configPath);
|
|
746
|
+
const existingScheme = readExistingScheme(
|
|
747
|
+
existing,
|
|
748
|
+
configPath,
|
|
749
|
+
options.force === true || options.scheme !== void 0
|
|
750
|
+
);
|
|
751
|
+
const existingIosAppId = readExistingAppId(
|
|
752
|
+
existing,
|
|
753
|
+
configPath,
|
|
754
|
+
"ios",
|
|
755
|
+
"--ios-app-id",
|
|
756
|
+
options.force === true || options.iosAppId !== void 0
|
|
757
|
+
);
|
|
758
|
+
const existingAndroidAppId = readExistingAppId(
|
|
759
|
+
existing,
|
|
760
|
+
configPath,
|
|
761
|
+
"android",
|
|
762
|
+
"--android-app-id",
|
|
763
|
+
options.force === true || options.androidAppId !== void 0
|
|
764
|
+
);
|
|
765
|
+
if (options.iosAppId !== void 0 && existingIosAppId !== void 0 && existingIosAppId !== options.iosAppId && !options.force) {
|
|
766
|
+
throw usageError(
|
|
767
|
+
`${configPath} already records "appId.ios" as "${existingIosAppId}", but --ios-app-id asked for "${options.iosAppId}". Re-run with --force to replace it (every other key in the file is preserved), or drop --ios-app-id to keep what is recorded.`
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
if (options.androidAppId !== void 0 && existingAndroidAppId !== void 0 && existingAndroidAppId !== options.androidAppId && !options.force) {
|
|
771
|
+
throw usageError(
|
|
772
|
+
`${configPath} already records "appId.android" as "${existingAndroidAppId}", but --android-app-id asked for "${options.androidAppId}". Re-run with --force to replace it (every other key in the file is preserved), or drop --android-app-id to keep what is recorded.`
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
const iosAppId = options.iosAppId ?? existingIosAppId;
|
|
776
|
+
const androidAppId = options.androidAppId ?? existingAndroidAppId;
|
|
777
|
+
const appIdChanged = iosAppId !== existingIosAppId || androidAppId !== existingAndroidAppId;
|
|
778
|
+
const discovered = await discoverStaticProjectScheme(root);
|
|
779
|
+
const discoveredScheme = discovered.scheme;
|
|
780
|
+
const discoveredSourceLabel = discovered.source === void 0 ? void 0 : discovered.source === "app-json" ? "app.json" : discovered.source;
|
|
781
|
+
const [scheme, source, origin] = (() => {
|
|
782
|
+
if (options.scheme !== void 0) {
|
|
783
|
+
return [options.scheme, "--scheme", void 0];
|
|
784
|
+
}
|
|
785
|
+
if (options.force && discoveredScheme !== void 0) {
|
|
786
|
+
return [discoveredScheme, discoveredSourceLabel ?? "app.json", discovered.origin];
|
|
787
|
+
}
|
|
788
|
+
return existingScheme === void 0 ? [discoveredScheme, discoveredSourceLabel ?? "app.json", discovered.origin] : [existingScheme, "already-recorded", void 0];
|
|
789
|
+
})();
|
|
790
|
+
if (scheme === void 0) {
|
|
791
|
+
throw usageError(
|
|
792
|
+
`No deep-link scheme found for ${root}, and none was given. Looked in, in order:
|
|
793
|
+
${discovered.tried.map((location, index) => ` ${index + 1}. ${location}`).join("\n")}
|
|
794
|
+
Run \`appduct init --scheme <scheme>\` with the scheme your app registers for deep links (e.g. "myapp"), or declare it in one of the locations above.`
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
if (options.scheme !== void 0 && existingScheme !== void 0 && existingScheme !== options.scheme && !options.force) {
|
|
798
|
+
throw usageError(
|
|
799
|
+
`${configPath} already records the scheme "${existingScheme}", but --scheme asked for "${options.scheme}". Re-run with --force to replace it (every other key in the file is preserved), or drop --scheme to keep what is recorded.`
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
const note = options.scheme === void 0 && scheme === existingScheme && discoveredScheme !== void 0 && discoveredScheme !== scheme ? `${discovered.origin} declares "${discoveredScheme}", but ${configPath} records "${existingScheme}", which is what Appduct uses. Run \`appduct init --force\` to adopt that value instead.` : void 0;
|
|
803
|
+
const alreadyCorrect = existingScheme === scheme && !appIdChanged;
|
|
804
|
+
if (!alreadyCorrect) {
|
|
805
|
+
const next = { ...existing ?? {}, scheme };
|
|
806
|
+
if (iosAppId !== void 0 || androidAppId !== void 0) {
|
|
807
|
+
next.appId = {
|
|
808
|
+
...iosAppId === void 0 ? {} : { ios: iosAppId },
|
|
809
|
+
...androidAppId === void 0 ? {} : { android: androidAppId }
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
await mkdir(dirname3(configPath), { recursive: true, mode: 448 });
|
|
813
|
+
await writeFile(configPath, `${JSON.stringify(next, null, 2)}
|
|
814
|
+
`, {
|
|
815
|
+
encoding: "utf8",
|
|
816
|
+
mode: 384
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
await chmod(dirname3(configPath), 448);
|
|
820
|
+
await chmod(configPath, 384);
|
|
821
|
+
return {
|
|
822
|
+
ok: true,
|
|
823
|
+
data: {
|
|
824
|
+
path: configPath,
|
|
825
|
+
scheme,
|
|
826
|
+
source,
|
|
827
|
+
...origin === void 0 ? {} : { origin },
|
|
828
|
+
created: existing === void 0,
|
|
829
|
+
changed: !alreadyCorrect,
|
|
830
|
+
...iosAppId === void 0 && androidAppId === void 0 ? {} : {
|
|
831
|
+
appId: {
|
|
832
|
+
...iosAppId === void 0 ? {} : { ios: iosAppId },
|
|
833
|
+
...androidAppId === void 0 ? {} : { android: androidAppId }
|
|
834
|
+
}
|
|
835
|
+
},
|
|
836
|
+
...note === void 0 ? {} : { note },
|
|
837
|
+
mcpServerEntry: {
|
|
838
|
+
command: "appduct",
|
|
839
|
+
args: ["mcp", "--scheme", scheme]
|
|
840
|
+
},
|
|
841
|
+
nextSteps: [
|
|
842
|
+
'Add `import "@appduct/react-native/auto";` to your app entry (index.js / App.tsx) \u2014 it is what starts the in-app agent endpoint.',
|
|
843
|
+
`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
|
+
"With the app running, pair a device: `appduct link --open ios-sim` (or `--open android`).",
|
|
845
|
+
...origin === void 0 ? [] : [`Scheme "${scheme}" was read from ${origin}.`],
|
|
846
|
+
...iosAppId === void 0 && androidAppId === void 0 ? [
|
|
847
|
+
"Delivering to a physical iPhone (`--open ios-device`) or an Android device/emulator (`--open android`) needs the installed app's id: run `appduct init --ios-app-id <id> --android-app-id <id>`, or pass --app-id on `appduct link`."
|
|
848
|
+
] : [],
|
|
849
|
+
`This file is safe to commit \u2014 it holds only "scheme" and "appId". Do not point --state-dir at this directory: the state dir holds the daemon's private key and audit log, which must never be committed.`
|
|
850
|
+
]
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
// src/cli/global-flags.ts
|
|
856
|
+
var formatJson = (value, flags) => {
|
|
857
|
+
return flags.pretty ? JSON.stringify(value, null, 2) : JSON.stringify(value);
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
// src/output.ts
|
|
861
|
+
import pc from "picocolors";
|
|
862
|
+
import {
|
|
863
|
+
formatAgentWebSocketUrl,
|
|
864
|
+
renderToolSignature,
|
|
865
|
+
summarizeToolDescription
|
|
866
|
+
} from "@appduct/shared";
|
|
867
|
+
|
|
868
|
+
// src/qr-terminal.ts
|
|
869
|
+
import { toQR } from "toqr";
|
|
870
|
+
var HALF_UPPER = "\u2580";
|
|
871
|
+
var HALF_LOWER = "\u2584";
|
|
872
|
+
var FULL = "\u2588";
|
|
873
|
+
var renderQrToTerminal = (content, options) => {
|
|
874
|
+
const margin = options?.margin ?? 0;
|
|
875
|
+
const ecLevel = options?.ecLevel ?? 1;
|
|
876
|
+
const modules = toQR(content, ecLevel);
|
|
877
|
+
const side = Math.sqrt(modules.length);
|
|
878
|
+
if (!Number.isInteger(side)) {
|
|
879
|
+
throw new Error("Invalid QR matrix size");
|
|
880
|
+
}
|
|
881
|
+
const dim = side + 2 * margin;
|
|
882
|
+
const isDark = (row, col) => {
|
|
883
|
+
if (row < margin || col < margin || row >= margin + side || col >= margin + side) {
|
|
884
|
+
return false;
|
|
885
|
+
}
|
|
886
|
+
const mr = row - margin;
|
|
887
|
+
const mc = col - margin;
|
|
888
|
+
return modules[mr * side + mc] === 1;
|
|
889
|
+
};
|
|
890
|
+
const lines = [];
|
|
891
|
+
for (let row = 0; row < dim; row += 2) {
|
|
892
|
+
let line = "";
|
|
893
|
+
for (let col = 0; col < dim; col++) {
|
|
894
|
+
const top = isDark(row, col);
|
|
895
|
+
const bottom = row + 1 < dim ? isDark(row + 1, col) : false;
|
|
896
|
+
line += top ? bottom ? FULL : HALF_UPPER : bottom ? HALF_LOWER : " ";
|
|
897
|
+
}
|
|
898
|
+
lines.push(line);
|
|
899
|
+
}
|
|
900
|
+
return lines.join("\n");
|
|
901
|
+
};
|
|
902
|
+
|
|
903
|
+
// src/output.ts
|
|
904
|
+
var renderMetaLines = (meta) => {
|
|
905
|
+
if (!meta) {
|
|
906
|
+
return [];
|
|
907
|
+
}
|
|
908
|
+
return [
|
|
909
|
+
"Meta",
|
|
910
|
+
` Command: ${meta.command}`,
|
|
911
|
+
` Timestamp: ${meta.timestamp}`,
|
|
912
|
+
...typeof meta.duration_ms === "number" ? [` Duration: ${meta.duration_ms} ms`] : []
|
|
913
|
+
];
|
|
914
|
+
};
|
|
915
|
+
var formatScalar = (value, flags) => {
|
|
916
|
+
if (typeof value === "string") {
|
|
917
|
+
return value;
|
|
918
|
+
}
|
|
919
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
920
|
+
return String(value);
|
|
921
|
+
}
|
|
922
|
+
if (value === null || value === void 0) {
|
|
923
|
+
return "null";
|
|
924
|
+
}
|
|
925
|
+
return formatJson(value, flags);
|
|
926
|
+
};
|
|
927
|
+
var renderFields = (title, fields, flags) => {
|
|
928
|
+
const visibleFields = fields.filter(([, value]) => value !== void 0);
|
|
929
|
+
if (visibleFields.length === 0) {
|
|
930
|
+
return [];
|
|
931
|
+
}
|
|
932
|
+
const width = Math.max(...visibleFields.map(([label]) => label.length));
|
|
933
|
+
return [
|
|
934
|
+
title,
|
|
935
|
+
...visibleFields.map(([label, value]) => ` ${label.padEnd(width)} ${formatScalar(value, flags)}`)
|
|
936
|
+
];
|
|
937
|
+
};
|
|
938
|
+
var formatAge = (fromIso, toIso) => {
|
|
939
|
+
const elapsedMs = Math.max(0, new Date(toIso).getTime() - new Date(fromIso).getTime());
|
|
940
|
+
const elapsedSeconds = Math.floor(elapsedMs / 1e3);
|
|
941
|
+
if (elapsedSeconds < 60) {
|
|
942
|
+
return `${elapsedSeconds}s`;
|
|
943
|
+
}
|
|
944
|
+
const elapsedMinutes = Math.floor(elapsedSeconds / 60);
|
|
945
|
+
if (elapsedMinutes < 60) {
|
|
946
|
+
return `${elapsedMinutes}m`;
|
|
947
|
+
}
|
|
948
|
+
const elapsedHours = Math.floor(elapsedMinutes / 60);
|
|
949
|
+
if (elapsedHours < 24) {
|
|
950
|
+
return `${elapsedHours}h`;
|
|
951
|
+
}
|
|
952
|
+
return `${Math.floor(elapsedHours / 24)}d`;
|
|
953
|
+
};
|
|
954
|
+
var formatDevice = (device) => {
|
|
955
|
+
const parts = [device.manufacturer, device.model].filter((part) => Boolean(part));
|
|
956
|
+
const label = parts.length > 0 ? parts.join(" ") : "unknown device";
|
|
957
|
+
return device.os ? `${label} (${device.os})` : label;
|
|
958
|
+
};
|
|
959
|
+
var renderLsData = (colors, data, now) => {
|
|
960
|
+
if (data.length === 0) {
|
|
961
|
+
return [colors.dim("Sessions"), " No Appduct sessions are registered."];
|
|
962
|
+
}
|
|
963
|
+
const nowIso = now.toISOString();
|
|
964
|
+
const headers = ["Alias", "State", "Device", "Tools", "Age"];
|
|
965
|
+
const rows = data.map((session) => [
|
|
966
|
+
session.alias,
|
|
967
|
+
session.state,
|
|
968
|
+
formatDevice(session.device),
|
|
969
|
+
String(session.toolCount),
|
|
970
|
+
formatAge(session.claimedAt ?? session.createdAt, nowIso)
|
|
971
|
+
]);
|
|
972
|
+
const widths = headers.map(
|
|
973
|
+
(header, index) => Math.max(header.length, ...rows.map((row) => row[index].length))
|
|
974
|
+
);
|
|
975
|
+
return [
|
|
976
|
+
colors.green("Sessions"),
|
|
977
|
+
` ${headers.map((header, index) => header.padEnd(widths[index])).join(" ")}`,
|
|
978
|
+
...rows.map((row) => ` ${row.map((cell, index) => cell.padEnd(widths[index])).join(" ")}`)
|
|
979
|
+
];
|
|
980
|
+
};
|
|
981
|
+
var isToolsListing = (data) => {
|
|
982
|
+
return typeof data === "object" && data !== null && Array.isArray(data.tools);
|
|
983
|
+
};
|
|
984
|
+
var isToolGroupsListing = (data) => {
|
|
985
|
+
return typeof data === "object" && data !== null && Array.isArray(data.groups) && !Array.isArray(data.tools);
|
|
986
|
+
};
|
|
987
|
+
var UNGROUPED_LABEL = "(ungrouped)";
|
|
988
|
+
var MAX_FOOTER_GROUPS = 10;
|
|
989
|
+
var hasAnyGroup = (groups) => {
|
|
990
|
+
return (groups ?? []).some((entry) => entry.group !== null);
|
|
991
|
+
};
|
|
992
|
+
var topLevelGroups = (groups) => {
|
|
993
|
+
return (groups ?? []).filter((entry) => entry.group !== null && !entry.group.includes("/"));
|
|
994
|
+
};
|
|
995
|
+
var renderEmptyToolsLine = (data) => {
|
|
996
|
+
if (data.total > 0) {
|
|
997
|
+
return ` No tools at offset ${data.offset ?? 0}; ${data.total} matching tool${data.total === 1 ? "" : "s"} in total.`;
|
|
998
|
+
}
|
|
999
|
+
if (data.group !== void 0) {
|
|
1000
|
+
const match = data.filter === void 0 ? "" : ` match ${JSON.stringify(data.filter)}`;
|
|
1001
|
+
return ` No tools in group ${JSON.stringify(data.group)}${match}. Run \`appduct tools --groups\` to see the session's groups.`;
|
|
1002
|
+
}
|
|
1003
|
+
return data.filter === void 0 ? " No tools registered." : ` No tools match ${JSON.stringify(data.filter)}.`;
|
|
1004
|
+
};
|
|
1005
|
+
var renderGroupHint = (data) => {
|
|
1006
|
+
if (data.group !== void 0 || !hasAnyGroup(data.groups)) {
|
|
1007
|
+
return void 0;
|
|
1008
|
+
}
|
|
1009
|
+
const top = topLevelGroups(data.groups);
|
|
1010
|
+
const named = top.slice(0, MAX_FOOTER_GROUPS).map((entry) => `${entry.group} ${entry.total}`);
|
|
1011
|
+
const more = top.length > MAX_FOOTER_GROUPS ? `, ... ${top.length - MAX_FOOTER_GROUPS} more; see --groups` : "";
|
|
1012
|
+
return `--group <name> (groups: ${named.join(", ")}${more})`;
|
|
1013
|
+
};
|
|
1014
|
+
var renderTruncationLine = (data) => {
|
|
1015
|
+
if (data.tools.length >= data.total) {
|
|
1016
|
+
return [];
|
|
1017
|
+
}
|
|
1018
|
+
const groupHint = renderGroupHint(data);
|
|
1019
|
+
const narrow = groupHint === void 0 ? "--filter <text> or" : `${groupHint} or --filter <text>, or`;
|
|
1020
|
+
return [
|
|
1021
|
+
"",
|
|
1022
|
+
`Showing ${data.tools.length} of ${data.total} tools (offset ${data.offset ?? 0}). Narrow with ${narrow} page with --offset <n>.`
|
|
1023
|
+
];
|
|
1024
|
+
};
|
|
1025
|
+
var renderToolSummaryLines = (tool, indent) => {
|
|
1026
|
+
const tag = tool.policy === "allow" ? "" : ` [${tool.policy}]`;
|
|
1027
|
+
return [`${indent}${renderToolSignature(tool)}${tag}`, `${indent} ${summarizeToolDescription(tool.description)}`];
|
|
1028
|
+
};
|
|
1029
|
+
var renderGroupedToolLines = (colors, tools) => {
|
|
1030
|
+
const byGroup = /* @__PURE__ */ new Map();
|
|
1031
|
+
for (const tool of tools) {
|
|
1032
|
+
const key = tool.group ?? null;
|
|
1033
|
+
const bucket = byGroup.get(key);
|
|
1034
|
+
if (bucket) {
|
|
1035
|
+
bucket.push(tool);
|
|
1036
|
+
} else {
|
|
1037
|
+
byGroup.set(key, [tool]);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
const tops = /* @__PURE__ */ new Map();
|
|
1041
|
+
for (const key of byGroup.keys()) {
|
|
1042
|
+
if (key === null) {
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
1045
|
+
const slash = key.indexOf("/");
|
|
1046
|
+
const top = slash === -1 ? key : key.slice(0, slash);
|
|
1047
|
+
const subs = tops.get(top) ?? [];
|
|
1048
|
+
if (slash !== -1) {
|
|
1049
|
+
subs.push(key);
|
|
1050
|
+
}
|
|
1051
|
+
tops.set(top, subs);
|
|
1052
|
+
}
|
|
1053
|
+
const byCodePoint = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
1054
|
+
const lines = [];
|
|
1055
|
+
for (const top of [...tops.keys()].sort(byCodePoint)) {
|
|
1056
|
+
lines.push(` ${colors.cyan(top)}`);
|
|
1057
|
+
lines.push(...(byGroup.get(top) ?? []).flatMap((tool) => renderToolSummaryLines(tool, " ")));
|
|
1058
|
+
for (const sub of tops.get(top).sort(byCodePoint)) {
|
|
1059
|
+
lines.push(` ${colors.cyan(sub)}`);
|
|
1060
|
+
lines.push(...byGroup.get(sub).flatMap((tool) => renderToolSummaryLines(tool, " ")));
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
const ungrouped = byGroup.get(null);
|
|
1064
|
+
if (ungrouped) {
|
|
1065
|
+
lines.push(` ${colors.cyan(UNGROUPED_LABEL)}`);
|
|
1066
|
+
lines.push(...ungrouped.flatMap((tool) => renderToolSummaryLines(tool, " ")));
|
|
1067
|
+
}
|
|
1068
|
+
return lines;
|
|
1069
|
+
};
|
|
1070
|
+
var renderToolsTitle = (colors, data) => {
|
|
1071
|
+
return colors.green(data.group === void 0 ? "Tools" : `Tools in group ${data.group}`);
|
|
1072
|
+
};
|
|
1073
|
+
var renderToolSummaryTable = (colors, data) => {
|
|
1074
|
+
if (data.tools.length === 0) {
|
|
1075
|
+
return [renderToolsTitle(colors, data), renderEmptyToolsLine(data)];
|
|
1076
|
+
}
|
|
1077
|
+
const grouped = data.group === void 0 && hasAnyGroup(data.groups);
|
|
1078
|
+
return [
|
|
1079
|
+
renderToolsTitle(colors, data),
|
|
1080
|
+
...grouped ? renderGroupedToolLines(colors, data.tools) : data.tools.flatMap((tool) => renderToolSummaryLines(tool, " ")),
|
|
1081
|
+
...renderTruncationLine(data),
|
|
1082
|
+
"",
|
|
1083
|
+
"Run `appduct tools <name>` for a tool's full schema."
|
|
1084
|
+
];
|
|
1085
|
+
};
|
|
1086
|
+
var renderToolDetail = (colors, tool, flags) => {
|
|
1087
|
+
return renderFields(
|
|
1088
|
+
colors.green(`Tool: ${tool.name}`),
|
|
1089
|
+
[
|
|
1090
|
+
["Signature", renderToolSignature(tool)],
|
|
1091
|
+
["Description", tool.description],
|
|
1092
|
+
["Input schema", tool.input_schema],
|
|
1093
|
+
["Output schema", tool.output_schema],
|
|
1094
|
+
// `?? undefined` keeps an ungrouped tool's row hidden rather than printing a bare "null":
|
|
1095
|
+
// `renderFields` drops undefined rows, and the machine form's `null` is for consumers, not
|
|
1096
|
+
// for a human scanning one tool's schema.
|
|
1097
|
+
["Group", tool.group ?? void 0],
|
|
1098
|
+
["Annotations", tool.annotations],
|
|
1099
|
+
// Only rendered for a tool that declares one; `renderFields` drops undefined rows, so a
|
|
1100
|
+
// tool on the daemon's default deadline shows no line at all rather than a misleading
|
|
1101
|
+
// "10000".
|
|
1102
|
+
["Timeout (ms)", tool.timeout_ms]
|
|
1103
|
+
],
|
|
1104
|
+
flags
|
|
1105
|
+
);
|
|
1106
|
+
};
|
|
1107
|
+
var renderToolsFullListing = (colors, data, flags) => {
|
|
1108
|
+
if (data.tools.length === 0) {
|
|
1109
|
+
return [renderToolsTitle(colors, data), renderEmptyToolsLine(data)];
|
|
1110
|
+
}
|
|
1111
|
+
return [
|
|
1112
|
+
...data.tools.flatMap((tool, index) => [
|
|
1113
|
+
...index > 0 ? [""] : [],
|
|
1114
|
+
...renderToolDetail(colors, tool, flags)
|
|
1115
|
+
]),
|
|
1116
|
+
...renderTruncationLine(data)
|
|
1117
|
+
];
|
|
1118
|
+
};
|
|
1119
|
+
var renderToolGroups = (colors, data) => {
|
|
1120
|
+
if (data.groups.length === 0) {
|
|
1121
|
+
return [colors.green("Groups"), " No tools registered."];
|
|
1122
|
+
}
|
|
1123
|
+
const label = (entry) => {
|
|
1124
|
+
if (entry.group === null) {
|
|
1125
|
+
return UNGROUPED_LABEL;
|
|
1126
|
+
}
|
|
1127
|
+
return entry.group.includes("/") ? ` ${entry.group}` : entry.group;
|
|
1128
|
+
};
|
|
1129
|
+
const width = Math.max(...data.groups.map((entry) => label(entry).length));
|
|
1130
|
+
const countWidth = Math.max(...data.groups.map((entry) => String(entry.total).length));
|
|
1131
|
+
return [
|
|
1132
|
+
colors.green("Groups"),
|
|
1133
|
+
...data.groups.map((entry) => ` ${label(entry).padEnd(width)} ${String(entry.total).padStart(countWidth)}`),
|
|
1134
|
+
"",
|
|
1135
|
+
`${data.total} tool${data.total === 1 ? "" : "s"} in total. ` + (hasAnyGroup(data.groups) ? "Run `appduct tools --group <name>` to list one group's tools." : "No tool declares a group.")
|
|
1136
|
+
];
|
|
1137
|
+
};
|
|
1138
|
+
var renderToolsData = (colors, data, flags, full) => {
|
|
1139
|
+
if (isToolGroupsListing(data)) {
|
|
1140
|
+
return renderToolGroups(colors, data);
|
|
1141
|
+
}
|
|
1142
|
+
if (!isToolsListing(data)) {
|
|
1143
|
+
return renderToolDetail(colors, data, flags);
|
|
1144
|
+
}
|
|
1145
|
+
return full ? renderToolsFullListing(colors, data, flags) : renderToolSummaryTable(colors, data);
|
|
1146
|
+
};
|
|
1147
|
+
var renderInvokeData = (colors, data, flags) => {
|
|
1148
|
+
return [colors.green("Result"), formatScalar(data, flags)];
|
|
1149
|
+
};
|
|
1150
|
+
var renderLinkData = (colors, data, flags, qr) => {
|
|
1151
|
+
const lines = [
|
|
1152
|
+
colors.green("Link Created"),
|
|
1153
|
+
...renderFields(
|
|
1154
|
+
"Link",
|
|
1155
|
+
[
|
|
1156
|
+
["Session", data.sessionId],
|
|
1157
|
+
["Deep link", data.deepLink],
|
|
1158
|
+
["Endpoint", formatAgentWebSocketUrl(data.endpoint)],
|
|
1159
|
+
["Pin", data.pin],
|
|
1160
|
+
["Expires", new Date(data.expiresAt * 1e3).toISOString()],
|
|
1161
|
+
["Delivered", data.delivered ? `yes (${data.target})` : void 0]
|
|
1162
|
+
],
|
|
1163
|
+
flags
|
|
1164
|
+
)
|
|
1165
|
+
];
|
|
1166
|
+
if (qr) {
|
|
1167
|
+
lines.push("", "QR", renderQrToTerminal(data.deepLink));
|
|
1168
|
+
}
|
|
1169
|
+
return lines;
|
|
1170
|
+
};
|
|
1171
|
+
var renderRevokeData = (colors, _data) => {
|
|
1172
|
+
return [colors.green("Session Revoked")];
|
|
1173
|
+
};
|
|
1174
|
+
var renderKeygenData = (colors, data, flags) => {
|
|
1175
|
+
return [
|
|
1176
|
+
colors.green("Key Ready"),
|
|
1177
|
+
...renderFields(
|
|
1178
|
+
"Key",
|
|
1179
|
+
[
|
|
1180
|
+
["Path", data.path],
|
|
1181
|
+
["Fingerprint", data.pin]
|
|
1182
|
+
],
|
|
1183
|
+
flags
|
|
1184
|
+
)
|
|
1185
|
+
];
|
|
1186
|
+
};
|
|
1187
|
+
var renderInitData = (colors, data, flags) => {
|
|
1188
|
+
const snippet = JSON.stringify(
|
|
1189
|
+
{ mcpServers: { appduct: data.mcpServerEntry } },
|
|
1190
|
+
null,
|
|
1191
|
+
2
|
|
1192
|
+
);
|
|
1193
|
+
return [
|
|
1194
|
+
colors.green(data.changed ? "Project Initialized" : "Project Already Initialized"),
|
|
1195
|
+
...renderFields(
|
|
1196
|
+
"Config",
|
|
1197
|
+
[
|
|
1198
|
+
["Path", data.path],
|
|
1199
|
+
["Scheme", data.scheme],
|
|
1200
|
+
["Source", data.source],
|
|
1201
|
+
// Only present for a scheme discovery read off a static project file (app.json, or one of
|
|
1202
|
+
// the native Android/iOS probes) — names the exact file/key, not just which platform.
|
|
1203
|
+
["Read from", data.origin],
|
|
1204
|
+
["Written", data.changed ? data.created ? "created" : "updated" : "unchanged"]
|
|
1205
|
+
],
|
|
1206
|
+
flags
|
|
1207
|
+
),
|
|
1208
|
+
...data.note === void 0 ? [] : ["", colors.yellow(`Note: ${data.note}`)],
|
|
1209
|
+
"",
|
|
1210
|
+
"MCP server entry",
|
|
1211
|
+
...snippet.split("\n").map((line) => ` ${line}`),
|
|
1212
|
+
"",
|
|
1213
|
+
"Next",
|
|
1214
|
+
...data.nextSteps.map((step, index) => ` ${index + 1}. ${step}`)
|
|
1215
|
+
];
|
|
1216
|
+
};
|
|
1217
|
+
var renderDaemonRunData = (colors, data, flags) => {
|
|
1218
|
+
return [
|
|
1219
|
+
colors.green("Daemon Running"),
|
|
1220
|
+
...renderFields(
|
|
1221
|
+
"Daemon",
|
|
1222
|
+
[
|
|
1223
|
+
["PID", data.daemon.pid],
|
|
1224
|
+
["State dir", data.daemon.state_dir],
|
|
1225
|
+
["Socket", data.daemon.socket_path]
|
|
1226
|
+
],
|
|
1227
|
+
flags
|
|
1228
|
+
)
|
|
1229
|
+
];
|
|
1230
|
+
};
|
|
1231
|
+
var renderDaemonStartData = (colors, data, flags) => {
|
|
1232
|
+
return [
|
|
1233
|
+
colors.green("Daemon Started"),
|
|
1234
|
+
...renderFields(
|
|
1235
|
+
"Daemon",
|
|
1236
|
+
[
|
|
1237
|
+
["PID", data.daemon.pid],
|
|
1238
|
+
["WSS port", data.daemon.wss_port],
|
|
1239
|
+
["Started at", data.daemon.started_at]
|
|
1240
|
+
],
|
|
1241
|
+
flags
|
|
1242
|
+
)
|
|
1243
|
+
];
|
|
1244
|
+
};
|
|
1245
|
+
var renderDaemonStopData = (colors, data, flags) => {
|
|
1246
|
+
return [
|
|
1247
|
+
colors.green("Daemon Stopped"),
|
|
1248
|
+
...renderFields("Daemon", [["Method", data.daemon.method]], flags)
|
|
1249
|
+
];
|
|
1250
|
+
};
|
|
1251
|
+
var formatByteSize = (bytes) => {
|
|
1252
|
+
const units = ["B", "KiB", "MiB", "GiB", "TiB"];
|
|
1253
|
+
let value = bytes;
|
|
1254
|
+
let unit = 0;
|
|
1255
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
1256
|
+
value /= 1024;
|
|
1257
|
+
unit += 1;
|
|
1258
|
+
}
|
|
1259
|
+
return `${unit === 0 ? value : value.toFixed(1)} ${units[unit]}`;
|
|
1260
|
+
};
|
|
1261
|
+
var renderDaemonStatusData = (colors, data, flags) => {
|
|
1262
|
+
return [
|
|
1263
|
+
colors.green("Daemon Status"),
|
|
1264
|
+
...renderFields(
|
|
1265
|
+
"Daemon",
|
|
1266
|
+
[
|
|
1267
|
+
["Version", data.daemon.version],
|
|
1268
|
+
["PID", data.daemon.pid],
|
|
1269
|
+
["Started at", data.daemon.started_at],
|
|
1270
|
+
["WSS port", data.daemon.wss_port],
|
|
1271
|
+
["Pinned keys", data.daemon.pinned_keys.length],
|
|
1272
|
+
["Sessions", data.daemon.session_count]
|
|
1273
|
+
],
|
|
1274
|
+
flags
|
|
1275
|
+
),
|
|
1276
|
+
"",
|
|
1277
|
+
...renderFields(
|
|
1278
|
+
"Policy",
|
|
1279
|
+
[
|
|
1280
|
+
["Default", data.policy.default],
|
|
1281
|
+
["Destructive", data.policy.destructive],
|
|
1282
|
+
["Overrides", data.policy.tools ? Object.keys(data.policy.tools).length : 0]
|
|
1283
|
+
],
|
|
1284
|
+
flags
|
|
1285
|
+
),
|
|
1286
|
+
"",
|
|
1287
|
+
// The retention rows drop out entirely against a daemon that predates them (`renderFields`
|
|
1288
|
+
// skips `undefined`), rather than printing "undefined" or an invented zero.
|
|
1289
|
+
...renderFields(
|
|
1290
|
+
"Audit",
|
|
1291
|
+
[
|
|
1292
|
+
["Path", data.audit.path],
|
|
1293
|
+
["Retention", data.audit.retention_days === void 0 ? void 0 : `${data.audit.retention_days} days`],
|
|
1294
|
+
["Files", data.audit.files],
|
|
1295
|
+
["Size", data.audit.bytes === void 0 ? void 0 : formatByteSize(data.audit.bytes)],
|
|
1296
|
+
["Failed writes", data.audit.failed_writes],
|
|
1297
|
+
["Failed prunes", data.audit.failed_prunes]
|
|
1298
|
+
],
|
|
1299
|
+
flags
|
|
1300
|
+
),
|
|
1301
|
+
// Version drift (issue #30) is the one thing here an operator has to act on, so it goes last
|
|
1302
|
+
// — the line still on screen after the block scrolls — and in yellow, not a quiet field row.
|
|
1303
|
+
...data.warning ? ["", colors.yellow(`Warning: ${data.warning}`)] : []
|
|
1304
|
+
];
|
|
1305
|
+
};
|
|
1306
|
+
var renderDoctorData = (colors, data, flags) => {
|
|
1307
|
+
return [
|
|
1308
|
+
data.present ? colors.green("Appduct Present") : colors.yellow("Appduct Absent"),
|
|
1309
|
+
...renderFields(
|
|
1310
|
+
"Artifact",
|
|
1311
|
+
[
|
|
1312
|
+
["Path", data.artifact],
|
|
1313
|
+
["Platform", data.platform],
|
|
1314
|
+
["Format", data.format],
|
|
1315
|
+
["Present", data.present],
|
|
1316
|
+
["Signals", data.signals.length > 0 ? data.signals.join(", ") : "none"],
|
|
1317
|
+
["Assertion", data.assertion ? `${data.assertion.expected} (holds)` : void 0]
|
|
1318
|
+
],
|
|
1319
|
+
flags
|
|
1320
|
+
)
|
|
1321
|
+
];
|
|
1322
|
+
};
|
|
1323
|
+
var renderSuccessData = (colors, command, data, options) => {
|
|
1324
|
+
const flags = options.flags;
|
|
1325
|
+
switch (command) {
|
|
1326
|
+
case "init":
|
|
1327
|
+
return renderInitData(colors, data, flags);
|
|
1328
|
+
case "keygen":
|
|
1329
|
+
return renderKeygenData(colors, data, flags);
|
|
1330
|
+
case "link":
|
|
1331
|
+
return renderLinkData(colors, data, flags, options.qr);
|
|
1332
|
+
case "ls":
|
|
1333
|
+
return renderLsData(colors, data, options.now ?? /* @__PURE__ */ new Date());
|
|
1334
|
+
case "tools":
|
|
1335
|
+
return renderToolsData(colors, data, flags, options.full);
|
|
1336
|
+
case "invoke":
|
|
1337
|
+
return renderInvokeData(colors, data, flags);
|
|
1338
|
+
case "revoke":
|
|
1339
|
+
return renderRevokeData(colors, data);
|
|
1340
|
+
case "daemon run":
|
|
1341
|
+
return renderDaemonRunData(colors, data, flags);
|
|
1342
|
+
case "daemon start":
|
|
1343
|
+
return renderDaemonStartData(colors, data, flags);
|
|
1344
|
+
case "daemon stop":
|
|
1345
|
+
return renderDaemonStopData(colors, data, flags);
|
|
1346
|
+
case "daemon status":
|
|
1347
|
+
return renderDaemonStatusData(colors, data, flags);
|
|
1348
|
+
case "doctor":
|
|
1349
|
+
return renderDoctorData(colors, data, flags);
|
|
1350
|
+
default:
|
|
1351
|
+
return [colors.green("Command Complete"), formatJson(data, flags)];
|
|
1352
|
+
}
|
|
1353
|
+
};
|
|
1354
|
+
var renderHumanError = (colors, error, flags, meta) => {
|
|
1355
|
+
return [
|
|
1356
|
+
colors.red("Command Failed"),
|
|
1357
|
+
...renderFields(
|
|
1358
|
+
"Error",
|
|
1359
|
+
[
|
|
1360
|
+
["Type", error.type],
|
|
1361
|
+
["Message", error.message],
|
|
1362
|
+
["Details", error.details]
|
|
1363
|
+
],
|
|
1364
|
+
flags
|
|
1365
|
+
),
|
|
1366
|
+
...meta ? ["", ...renderMetaLines(meta)] : []
|
|
1367
|
+
].join("\n");
|
|
1368
|
+
};
|
|
1369
|
+
var renderResult = (result, options) => {
|
|
1370
|
+
if (options.flags.json) {
|
|
1371
|
+
return {
|
|
1372
|
+
stdout: `${formatJson(result, options.flags)}
|
|
1373
|
+
`
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
const colors = pc.createColors(options.flags.color);
|
|
1377
|
+
if (!result.ok) {
|
|
1378
|
+
return {
|
|
1379
|
+
stderr: `${renderHumanError(colors, result.error, options.flags, result.meta)}
|
|
1380
|
+
`
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
const lines = [
|
|
1384
|
+
...renderSuccessData(colors, options.command, result.data, options),
|
|
1385
|
+
"",
|
|
1386
|
+
...renderMetaLines(result.meta)
|
|
1387
|
+
].filter((line, index, collection) => {
|
|
1388
|
+
if (line !== "") {
|
|
1389
|
+
return true;
|
|
1390
|
+
}
|
|
1391
|
+
return index > 0 && collection[index - 1] !== "" && index < collection.length - 1;
|
|
1392
|
+
});
|
|
1393
|
+
return {
|
|
1394
|
+
stdout: `${lines.join("\n")}
|
|
1395
|
+
`
|
|
1396
|
+
};
|
|
1397
|
+
};
|
|
1398
|
+
|
|
1399
|
+
// src/cli/envelope.ts
|
|
1400
|
+
var createCommandMeta = (command, startedAt, finishedAt) => {
|
|
1401
|
+
return {
|
|
1402
|
+
command,
|
|
1403
|
+
timestamp: finishedAt.toISOString(),
|
|
1404
|
+
duration_ms: finishedAt.getTime() - startedAt.getTime()
|
|
1405
|
+
};
|
|
1406
|
+
};
|
|
1407
|
+
var finalizeResult = (result, timing, flags) => {
|
|
1408
|
+
if (!flags.verbose) {
|
|
1409
|
+
return result;
|
|
1410
|
+
}
|
|
1411
|
+
return {
|
|
1412
|
+
...result,
|
|
1413
|
+
meta: createCommandMeta(timing.command, timing.startedAt, timing.finishedAt)
|
|
1414
|
+
};
|
|
1415
|
+
};
|
|
1416
|
+
|
|
1417
|
+
// src/cli/runner.ts
|
|
1418
|
+
var writeRenderedOutput = (rendered, writers) => {
|
|
1419
|
+
if (rendered.stdout) {
|
|
1420
|
+
writers.stdout.write(rendered.stdout);
|
|
1421
|
+
}
|
|
1422
|
+
if (rendered.stderr) {
|
|
1423
|
+
writers.stderr.write(rendered.stderr);
|
|
1424
|
+
}
|
|
1425
|
+
};
|
|
1426
|
+
var executeCommand = async (command, handler, env, render = {}) => {
|
|
1427
|
+
const startedAt = env.clock.now();
|
|
1428
|
+
try {
|
|
1429
|
+
const result = await handler();
|
|
1430
|
+
const finishedAt = env.clock.now();
|
|
1431
|
+
const finalized = finalizeResult(result, { command, startedAt, finishedAt }, env.flags);
|
|
1432
|
+
const renderOptions = {
|
|
1433
|
+
command,
|
|
1434
|
+
flags: env.flags,
|
|
1435
|
+
now: finishedAt,
|
|
1436
|
+
qr: render.qr,
|
|
1437
|
+
full: render.full
|
|
1438
|
+
};
|
|
1439
|
+
writeRenderedOutput(renderResult(finalized, renderOptions), env);
|
|
1440
|
+
return 0;
|
|
1441
|
+
} catch (error) {
|
|
1442
|
+
const finishedAt = env.clock.now();
|
|
1443
|
+
const cliError = toCliError(error);
|
|
1444
|
+
const result = { ok: false, error: cliError };
|
|
1445
|
+
const finalized = finalizeResult(result, { command, startedAt, finishedAt }, env.flags);
|
|
1446
|
+
const renderOptions = {
|
|
1447
|
+
command,
|
|
1448
|
+
flags: env.flags,
|
|
1449
|
+
now: finishedAt,
|
|
1450
|
+
qr: render.qr,
|
|
1451
|
+
full: render.full
|
|
1452
|
+
};
|
|
1453
|
+
writeRenderedOutput(renderResult(finalized, renderOptions), env);
|
|
1454
|
+
return getExitCodeForError(error);
|
|
1455
|
+
}
|
|
1456
|
+
};
|
|
1457
|
+
|
|
1458
|
+
// src/cli/router.ts
|
|
1459
|
+
var commandName = (context) => {
|
|
1460
|
+
return context.path.join(" ");
|
|
1461
|
+
};
|
|
1462
|
+
|
|
1463
|
+
// src/cli/routes/init.ts
|
|
1464
|
+
var route = async (context) => {
|
|
1465
|
+
const { options, stateDir } = context;
|
|
1466
|
+
return executeCommand(
|
|
1467
|
+
commandName(context),
|
|
1468
|
+
() => handleInitCommand(
|
|
1469
|
+
{
|
|
1470
|
+
scheme: typeof options.scheme === "string" ? options.scheme : void 0,
|
|
1471
|
+
force: Boolean(options.force),
|
|
1472
|
+
// cac camelCases `--ios-app-id`/`--android-app-id`; the dashed spellings are kept as a
|
|
1473
|
+
// fallback so a parser change can't silently drop the flag.
|
|
1474
|
+
iosAppId: typeof options.iosAppId === "string" ? options.iosAppId : typeof options["ios-app-id"] === "string" ? options["ios-app-id"] : void 0,
|
|
1475
|
+
androidAppId: typeof options.androidAppId === "string" ? options.androidAppId : typeof options["android-app-id"] === "string" ? options["android-app-id"] : void 0
|
|
1476
|
+
},
|
|
1477
|
+
// `init` never reads the state dir, but it must know which directory it is so it can
|
|
1478
|
+
// refuse to write a "safe to commit" project config into the daemon's own state.
|
|
1479
|
+
{ stateDir }
|
|
1480
|
+
),
|
|
1481
|
+
context.env
|
|
1482
|
+
);
|
|
1483
|
+
};
|
|
1484
|
+
export {
|
|
1485
|
+
route
|
|
1486
|
+
};
|