appduct 0.9.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 +122 -52
- package/dist/artifact-inspect.d.ts +5 -4
- 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 -315
- 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
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The typed client handle (issue #8) returned by {@link connect}/{@link waitForSession}: a thin
|
|
3
|
-
* wrapper over the same `tools.list`/`tools.call`/`events.subscribe`/`events.since` RPC the CLI and
|
|
4
|
-
* MCP server use (`rpc/client.ts`'s `DaemonStream`), bound to one resolved session. No new privilege
|
|
5
|
-
* or transport — every call is attributed `caller: "client"` for the audit log (ARCHITECTURE.md
|
|
6
|
-
* §12). {@link AppClient.waitForEvent}'s drain-then-live pattern mirrors the built-in
|
|
7
|
-
* `appduct_wait_for_event` MCP tool (`mcp/events-tool.ts`) — see that module's doc comment for
|
|
8
|
-
* why the notification listener has to be registered before the `events.since` drain call is sent.
|
|
9
|
-
*/
|
|
10
|
-
import { clampToolTimeoutMs, MAX_TOOL_TIMEOUT_MS, RPC_METHODS, } from "@appduct/shared";
|
|
11
|
-
import { AppductError, toAppductError } from "./errors.js";
|
|
12
|
-
const DEFAULT_WAIT_FOR_EVENT_TIMEOUT_MS = 30_000;
|
|
13
|
-
/** Extra headroom so the daemon's own `tool_timeout` always wins the race against this client's
|
|
14
|
-
* transport timeout, even accounting for RPC round-trip and event-loop scheduling delay. Kept
|
|
15
|
-
* local because it is a property of this transport, not of the protocol; the *bounds* it is added
|
|
16
|
-
* to come from `@appduct/shared`, so this client and the daemon cannot disagree about the
|
|
17
|
-
* deadline itself. */
|
|
18
|
-
const CALL_TRANSPORT_TIMEOUT_SLACK_MS = 5_000;
|
|
19
|
-
/**
|
|
20
|
-
* Transport timeout for one `tools.call`. With an explicit `timeoutMs` the daemon-side deadline is
|
|
21
|
-
* known exactly, so this is that clamped value plus slack.
|
|
22
|
-
*
|
|
23
|
-
* Without one the daemon defaults to the *tool's own* declared `timeoutMs` (issue #25), which this
|
|
24
|
-
* client does not know at call time and would need an extra `tools.list` round trip (racy against
|
|
25
|
-
* re-registration) to learn — so it falls back to the largest deadline the daemon could possibly
|
|
26
|
-
* enforce. That is deliberately generous: this watchdog only exists for a daemon that accepts the
|
|
27
|
-
* request and then never answers, since a daemon that dies or drops the socket already rejects
|
|
28
|
-
* every pending call immediately (`rpc/client.ts`'s `close` handler). Sizing it off the 10 s
|
|
29
|
-
* default instead would make this client the thing that fails a long tool call, reporting a
|
|
30
|
-
* generic transport error in place of the daemon's real one.
|
|
31
|
-
*/
|
|
32
|
-
export const transportTimeoutForToolCall = (timeoutMs) => {
|
|
33
|
-
const clamped = timeoutMs === undefined || !Number.isFinite(timeoutMs)
|
|
34
|
-
? MAX_TOOL_TIMEOUT_MS
|
|
35
|
-
: clampToolTimeoutMs(timeoutMs);
|
|
36
|
-
return clamped + CALL_TRANSPORT_TIMEOUT_SLACK_MS;
|
|
37
|
-
};
|
|
38
|
-
export const makeAppClient = (stream, sessionId) => {
|
|
39
|
-
const client = {
|
|
40
|
-
sessionId,
|
|
41
|
-
tools: async () => {
|
|
42
|
-
try {
|
|
43
|
-
return await stream.call(RPC_METHODS.toolsList, { selector: sessionId });
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
throw toAppductError(error);
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
call: async (name, args, options) => {
|
|
50
|
-
try {
|
|
51
|
-
const result = await stream.call(RPC_METHODS.toolsCall, {
|
|
52
|
-
selector: sessionId,
|
|
53
|
-
name,
|
|
54
|
-
args,
|
|
55
|
-
timeoutMs: options?.timeoutMs,
|
|
56
|
-
caller: "client",
|
|
57
|
-
}, transportTimeoutForToolCall(options?.timeoutMs));
|
|
58
|
-
return result.result;
|
|
59
|
-
}
|
|
60
|
-
catch (error) {
|
|
61
|
-
throw toAppductError(error);
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
events: async (options = {}) => {
|
|
65
|
-
try {
|
|
66
|
-
const result = await stream.call(RPC_METHODS.eventsSince, {
|
|
67
|
-
selector: sessionId,
|
|
68
|
-
since: options.since,
|
|
69
|
-
limit: options.limit,
|
|
70
|
-
kinds: ["app_event"],
|
|
71
|
-
});
|
|
72
|
-
return {
|
|
73
|
-
events: result.events
|
|
74
|
-
.map((event) => appEventFrom(event, sessionId))
|
|
75
|
-
.filter((event) => event !== undefined),
|
|
76
|
-
cursor: result.cursor,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
catch (error) {
|
|
80
|
-
throw toAppductError(error);
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
waitForEvent: (name, options = {}) => {
|
|
84
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_WAIT_FOR_EVENT_TIMEOUT_MS;
|
|
85
|
-
const toMatch = (event) => {
|
|
86
|
-
const appEvent = appEventFrom(event, sessionId);
|
|
87
|
-
if (!appEvent || appEvent.name !== name) {
|
|
88
|
-
return undefined;
|
|
89
|
-
}
|
|
90
|
-
if (options.match && !options.match(appEvent.payload)) {
|
|
91
|
-
return undefined;
|
|
92
|
-
}
|
|
93
|
-
return appEvent;
|
|
94
|
-
};
|
|
95
|
-
return new Promise((resolve, reject) => {
|
|
96
|
-
let settled = false;
|
|
97
|
-
const settle = (fn) => {
|
|
98
|
-
if (settled) {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
settled = true;
|
|
102
|
-
clearTimeout(timer);
|
|
103
|
-
unsubscribeClose();
|
|
104
|
-
liveHandler = null;
|
|
105
|
-
fn();
|
|
106
|
-
};
|
|
107
|
-
const timer = setTimeout(() => {
|
|
108
|
-
settle(() => reject(new AppductError("timeout", `Timed out after ${timeoutMs}ms waiting for event "${name}".`)));
|
|
109
|
-
}, timeoutMs);
|
|
110
|
-
// Registered before `events.since` is even sent (not merely before it resolves): the
|
|
111
|
-
// daemon's response to that call and a `notify()` for a brand-new event can arrive in the
|
|
112
|
-
// same socket chunk, and a listener added only after awaiting that response could miss a
|
|
113
|
-
// notification from that same chunk. Buffers into `earlyEvents` until the drain below has
|
|
114
|
-
// merged its own backlog and switched this to `liveHandler`.
|
|
115
|
-
let liveHandler = null;
|
|
116
|
-
const earlyEvents = [];
|
|
117
|
-
const unsubscribeNotification = stream.onNotification((payload) => {
|
|
118
|
-
const event = payload;
|
|
119
|
-
if (liveHandler) {
|
|
120
|
-
liveHandler(event);
|
|
121
|
-
}
|
|
122
|
-
else {
|
|
123
|
-
earlyEvents.push(event);
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
let unsubscribeClose = () => { };
|
|
127
|
-
const fail = (error) => {
|
|
128
|
-
settle(() => reject(toAppductError(error)));
|
|
129
|
-
unsubscribeNotification();
|
|
130
|
-
};
|
|
131
|
-
(async () => {
|
|
132
|
-
await stream.call(RPC_METHODS.eventsSubscribe, { sessionSelector: sessionId, kinds: ["app_event"] });
|
|
133
|
-
const sinceResult = await stream.call(RPC_METHODS.eventsSince, {
|
|
134
|
-
selector: sessionId,
|
|
135
|
-
since: options.since,
|
|
136
|
-
kinds: ["app_event"],
|
|
137
|
-
});
|
|
138
|
-
// Merge the retained backlog with whatever arrived on the live channel while the two
|
|
139
|
-
// calls above were in flight, deduped by `seq` — the same event can legitimately show up
|
|
140
|
-
// in both (the daemon buffers before it fans out). Everything from here to `liveHandler =
|
|
141
|
-
// …` below is synchronous — no `await` — so nothing can arrive on the socket and be missed
|
|
142
|
-
// in the gap.
|
|
143
|
-
const seenSeqs = new Set();
|
|
144
|
-
const backlog = [];
|
|
145
|
-
for (const event of [...sinceResult.events, ...earlyEvents]) {
|
|
146
|
-
if (event.sessionId === sessionId && !seenSeqs.has(event.seq)) {
|
|
147
|
-
seenSeqs.add(event.seq);
|
|
148
|
-
backlog.push(event);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
backlog.sort((a, b) => a.seq - b.seq);
|
|
152
|
-
for (const event of backlog) {
|
|
153
|
-
try {
|
|
154
|
-
const matched = toMatch(event);
|
|
155
|
-
if (matched) {
|
|
156
|
-
settle(() => resolve(matched));
|
|
157
|
-
unsubscribeNotification();
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
catch (error) {
|
|
162
|
-
fail(error);
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
// Nothing matched yet: resume live from the last event actually considered, or — if
|
|
167
|
-
// nothing was retained/arrived at all — `events.since`'s own cursor, which already
|
|
168
|
-
// reflects the session's true high-water mark even when the `kinds` filter matched
|
|
169
|
-
// nothing.
|
|
170
|
-
const highestConsideredSeq = backlog.length > 0 ? backlog[backlog.length - 1].seq : sinceResult.cursor;
|
|
171
|
-
unsubscribeClose = stream.onClose(() => {
|
|
172
|
-
settle(() => reject(new AppductError("connection_error", `The connection to the Appduct daemon closed while waiting for event "${name}".`)));
|
|
173
|
-
unsubscribeNotification();
|
|
174
|
-
});
|
|
175
|
-
// From here on, every notification goes straight to this handler instead of
|
|
176
|
-
// `earlyEvents` — assigned synchronously (no `await` since the backlog scan above), so
|
|
177
|
-
// nothing is missed.
|
|
178
|
-
liveHandler = (event) => {
|
|
179
|
-
if (settled) {
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
if (event.sessionId === sessionId && event.seq <= highestConsideredSeq) {
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
try {
|
|
186
|
-
const matched = toMatch(event);
|
|
187
|
-
if (matched) {
|
|
188
|
-
settle(() => resolve(matched));
|
|
189
|
-
unsubscribeNotification();
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
catch (error) {
|
|
193
|
-
fail(error);
|
|
194
|
-
}
|
|
195
|
-
};
|
|
196
|
-
})().catch((error) => {
|
|
197
|
-
fail(error);
|
|
198
|
-
});
|
|
199
|
-
});
|
|
200
|
-
},
|
|
201
|
-
close: () => {
|
|
202
|
-
stream.close();
|
|
203
|
-
},
|
|
204
|
-
};
|
|
205
|
-
return client;
|
|
206
|
-
};
|
|
207
|
-
const appEventFrom = (event, sessionId) => {
|
|
208
|
-
if (event.kind !== "app_event" || event.sessionId !== sessionId) {
|
|
209
|
-
return undefined;
|
|
210
|
-
}
|
|
211
|
-
const data = event.data;
|
|
212
|
-
if (typeof data.name !== "string") {
|
|
213
|
-
return undefined;
|
|
214
|
-
}
|
|
215
|
-
return {
|
|
216
|
-
name: data.name,
|
|
217
|
-
payload: data.payload,
|
|
218
|
-
ts: event.ts,
|
|
219
|
-
sessionId: event.sessionId,
|
|
220
|
-
alias: event.alias,
|
|
221
|
-
seq: event.seq,
|
|
222
|
-
};
|
|
223
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../src/client/bootstrap.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAErE,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAuD,KAAK,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACrG,OAAO,EAAiB,KAAK,SAAS,EAAE,KAAK,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAK9E,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,uEAAuE;IACvE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;6EACyE;IACzE,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,uFAAuF;IACvF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;wCACoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;iGAC6F;IAC7F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;qFACiF;IACjF,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,gFAAgF;IAChF,SAAS,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,cAAc,CAAC;AAExC;2FAC2F;AAC3F,eAAO,MAAM,IAAI,GAAU,UAAS,WAAgB,KAAG,OAAO,CAAC,UAAU,CAoBxE,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,uEAAuE;IACvE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;8EAC8E;AAC9E,eAAO,MAAM,cAAc,GAAU,MAAM,GAAG,OAAO,EACnD,WAAW,MAAM,EACjB,UAAS,qBAA0B,KAClC,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,CA0F3B,CAAC"}
|
package/dist/client/bootstrap.js
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `link()`/`waitForSession()` (issue #8): the bootstrap half of the client, so a test's
|
|
3
|
-
* `globalSetup` can pair a simulator/emulator without shelling out to `appduct link --open`.
|
|
4
|
-
* `link()` delegates to the same `mintLink` core `commands/link.ts` uses (`../link.js`) so the
|
|
5
|
-
* deep-link shape can't drift between the CLI and this package; `waitForSession()` mirrors
|
|
6
|
-
* `mcp/connect-tool.ts`'s `handleWaitForSessionTool`.
|
|
7
|
-
*/
|
|
8
|
-
import { RPC_METHODS } from "@appduct/shared";
|
|
9
|
-
import {} from "../cli/open-target.js";
|
|
10
|
-
import { resolveStateDir } from "../daemon/state-dir.js";
|
|
11
|
-
import { mintLink } from "../link.js";
|
|
12
|
-
import { openDaemonStream, DaemonRpcError } from "../rpc/client.js";
|
|
13
|
-
import { makeAppClient } from "./app-client.js";
|
|
14
|
-
import { AppductError, toAppductError } from "./errors.js";
|
|
15
|
-
const DEFAULT_WAIT_FOR_SESSION_TIMEOUT_MS = 120_000;
|
|
16
|
-
/** Mints a session link via `link.create` and composes the deep link. With `target` set, delivers
|
|
17
|
-
* it directly to a booted emulator/simulator instead of leaving delivery to the caller. */
|
|
18
|
-
export const link = async (options = {}) => {
|
|
19
|
-
try {
|
|
20
|
-
return await mintLink({
|
|
21
|
-
stateDir: resolveStateDir(options.stateDir),
|
|
22
|
-
spawn: options.spawn,
|
|
23
|
-
autoSpawn: options.autoSpawn,
|
|
24
|
-
ttlSeconds: options.ttlSeconds,
|
|
25
|
-
target: options.target,
|
|
26
|
-
device: options.device,
|
|
27
|
-
bundleId: options.bundleId,
|
|
28
|
-
relaunch: options.relaunch,
|
|
29
|
-
scheme: options.scheme,
|
|
30
|
-
cwd: options.cwd,
|
|
31
|
-
exec: options.exec,
|
|
32
|
-
env: options.env,
|
|
33
|
-
schemeEnv: options.schemeEnv,
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
catch (error) {
|
|
37
|
-
throw toAppductError(error);
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
/** Waits for `sessionId` (from {@link link}) to be claimed by a device, then returns a connected
|
|
41
|
-
* {@link AppClient} for it — resolves immediately if it's already claimed. */
|
|
42
|
-
export const waitForSession = async (sessionId, options = {}) => {
|
|
43
|
-
const stateDir = resolveStateDir(options.stateDir);
|
|
44
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_WAIT_FOR_SESSION_TIMEOUT_MS;
|
|
45
|
-
let stream;
|
|
46
|
-
try {
|
|
47
|
-
stream = await openDaemonStream({ stateDir, spawn: options.spawn, autoSpawn: options.autoSpawn });
|
|
48
|
-
}
|
|
49
|
-
catch (error) {
|
|
50
|
-
throw toAppductError(error);
|
|
51
|
-
}
|
|
52
|
-
try {
|
|
53
|
-
// The session may already be claimed by the time this runs — resolve immediately rather than
|
|
54
|
-
// waiting for an event that already happened.
|
|
55
|
-
try {
|
|
56
|
-
await stream.call(RPC_METHODS.sessionsDescribe, { selector: sessionId });
|
|
57
|
-
return makeAppClient(stream, sessionId);
|
|
58
|
-
}
|
|
59
|
-
catch (error) {
|
|
60
|
-
if (!(error instanceof DaemonRpcError) || error.data?.type !== "unknown_session") {
|
|
61
|
-
throw error;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
await new Promise((resolve, reject) => {
|
|
65
|
-
let settled = false;
|
|
66
|
-
const settle = (fn) => {
|
|
67
|
-
if (settled) {
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
settled = true;
|
|
71
|
-
clearTimeout(timer);
|
|
72
|
-
unsubscribeNotification();
|
|
73
|
-
unsubscribeClose();
|
|
74
|
-
fn();
|
|
75
|
-
};
|
|
76
|
-
const timer = setTimeout(() => {
|
|
77
|
-
settle(() => reject(new AppductError("timeout", `Timed out after ${timeoutMs}ms waiting for session "${sessionId}" to be claimed.`)));
|
|
78
|
-
}, timeoutMs);
|
|
79
|
-
const unsubscribeClose = stream.onClose(() => {
|
|
80
|
-
settle(() => reject(new AppductError("connection_error", `The connection to the Appduct daemon closed while waiting for session "${sessionId}" to be claimed.`)));
|
|
81
|
-
});
|
|
82
|
-
// Registered *before* `events.subscribe` is sent (not after awaiting it) — a
|
|
83
|
-
// `session_claimed` notification that arrives in the same TCP chunk as the subscribe
|
|
84
|
-
// response is dispatched synchronously within that chunk's processing, before an `await`
|
|
85
|
-
// continuation below would get a chance to run; registering the listener first means it's
|
|
86
|
-
// always in place in time to see it.
|
|
87
|
-
const unsubscribeNotification = stream.onNotification((payload) => {
|
|
88
|
-
if (settled) {
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
const event = payload;
|
|
92
|
-
if (event.kind === "session_claimed" && event.sessionId === sessionId) {
|
|
93
|
-
settle(resolve);
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
stream
|
|
97
|
-
.call(RPC_METHODS.eventsSubscribe, { sessionSelector: sessionId, kinds: ["session_claimed"] })
|
|
98
|
-
.catch((error) => {
|
|
99
|
-
settle(() => reject(toAppductError(error)));
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
return makeAppClient(stream, sessionId);
|
|
103
|
-
}
|
|
104
|
-
catch (error) {
|
|
105
|
-
stream.close();
|
|
106
|
-
throw toAppductError(error);
|
|
107
|
-
}
|
|
108
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../../src/client/connect.ts"],"names":[],"mappings":"AAUA,OAAO,EAAuC,KAAK,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EAAiB,KAAK,SAAS,EAAE,KAAK,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAG9E,MAAM,MAAM,cAAc,GAAG;IAC3B;mGAC+F;IAC/F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,uEAAuE;IACvE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,kGAAkG;AAClG,eAAO,MAAM,OAAO,GAAU,MAAM,GAAG,OAAO,EAC5C,UAAS,cAAmB,KAC3B,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,CA0B3B,CAAC"}
|
package/dist/client/connect.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `connect()` (issue #8): the entry point for a test runner that wants to drive an already-claimed
|
|
3
|
-
* session without shelling out to the CLI. Auto-spawns the daemon (same seam `rpc/client.ts` gives
|
|
4
|
-
* the CLI/MCP server) and resolves `selector` up front via `sessions.describe` so a bad/ambiguous
|
|
5
|
-
* selector fails fast with a typed {@link AppductError} rather than surfacing on the first
|
|
6
|
-
* `call()`.
|
|
7
|
-
*/
|
|
8
|
-
import { RPC_METHODS } from "@appduct/shared";
|
|
9
|
-
import { resolveStateDir } from "../daemon/state-dir.js";
|
|
10
|
-
import { openDaemonStream } from "../rpc/client.js";
|
|
11
|
-
import { makeAppClient } from "./app-client.js";
|
|
12
|
-
import { toAppductError } from "./errors.js";
|
|
13
|
-
/** Opens a persistent connection to the daemon and resolves `options.selector` to one session. */
|
|
14
|
-
export const connect = async (options = {}) => {
|
|
15
|
-
const stateDir = resolveStateDir(options.stateDir);
|
|
16
|
-
let stream;
|
|
17
|
-
try {
|
|
18
|
-
stream = await openDaemonStream({
|
|
19
|
-
stateDir,
|
|
20
|
-
spawn: options.spawn,
|
|
21
|
-
autoSpawn: options.autoSpawn,
|
|
22
|
-
requestTimeoutMs: options.requestTimeoutMs,
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
catch (error) {
|
|
26
|
-
throw toAppductError(error);
|
|
27
|
-
}
|
|
28
|
-
try {
|
|
29
|
-
const detail = await stream.call(RPC_METHODS.sessionsDescribe, {
|
|
30
|
-
selector: options.selector,
|
|
31
|
-
});
|
|
32
|
-
return makeAppClient(stream, detail.sessionId);
|
|
33
|
-
}
|
|
34
|
-
catch (error) {
|
|
35
|
-
stream.close();
|
|
36
|
-
throw toAppductError(error);
|
|
37
|
-
}
|
|
38
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/client/errors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAKjD;;;;;uEAKuE;AACvE,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,kBAAkB,GAAG,SAAS,GAAG,cAAc,CAAC;AAE3F,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;gBAEX,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM;CAOtF;AAED;kFACkF;AAClF,eAAO,MAAM,cAAc,GAAI,OAAO,OAAO,KAAG,YAiC/C,CAAC"}
|
package/dist/client/errors.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { isAppductCliError } from "../errors.js";
|
|
2
|
-
import { DaemonRpcError, isDaemonUnreachableError } from "../rpc/client.js";
|
|
3
|
-
export class AppductError extends Error {
|
|
4
|
-
type;
|
|
5
|
-
details;
|
|
6
|
-
/** The JSON-RPC error code, when this wraps a `DaemonRpcError`. */
|
|
7
|
-
code;
|
|
8
|
-
constructor(type, message, details, code) {
|
|
9
|
-
super(message);
|
|
10
|
-
this.name = "AppductError";
|
|
11
|
-
this.type = type;
|
|
12
|
-
this.details = details;
|
|
13
|
-
this.code = code;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
/** Converts any error thrown by the RPC layer into a {@link AppductError}, preserving the wire
|
|
17
|
-
* `data.type` verbatim when present. Idempotent on an already-converted error. */
|
|
18
|
-
export const toAppductError = (error) => {
|
|
19
|
-
if (error instanceof AppductError) {
|
|
20
|
-
return error;
|
|
21
|
-
}
|
|
22
|
-
const withCause = (result) => {
|
|
23
|
-
result.cause = error;
|
|
24
|
-
return result;
|
|
25
|
-
};
|
|
26
|
-
if (error instanceof DaemonRpcError) {
|
|
27
|
-
const type = error.data?.type ?? "client_error";
|
|
28
|
-
return withCause(new AppductError(type, error.message, error.data?.details, error.code));
|
|
29
|
-
}
|
|
30
|
-
if (isDaemonUnreachableError(error)) {
|
|
31
|
-
return withCause(new AppductError("connection_error", error instanceof Error ? error.message : "The Appduct daemon is unreachable."));
|
|
32
|
-
}
|
|
33
|
-
if (isAppductCliError(error)) {
|
|
34
|
-
return withCause(new AppductError("client_error", error.message, error.details));
|
|
35
|
-
}
|
|
36
|
-
if (error instanceof Error) {
|
|
37
|
-
return withCause(new AppductError("client_error", error.message));
|
|
38
|
-
}
|
|
39
|
-
return new AppductError("client_error", "An unexpected error occurred.", error);
|
|
40
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,KAAK,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACrH,OAAO,EAAE,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAClE,YAAY,EACV,SAAS,EACT,QAAQ,EACR,WAAW,EACX,aAAa,EACb,YAAY,EACZ,OAAO,EACP,mBAAmB,GACpB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { CliResult, DaemonRunCommandData, DaemonStartCommandData, DaemonStatusCommandData, DaemonStopCommandData } from "../cli/result-types.js";
|
|
2
|
-
import type { Clock } from "../cli/types.js";
|
|
3
|
-
import { type SpawnFn } from "../rpc/client.js";
|
|
4
|
-
export type DaemonCommandContext = {
|
|
5
|
-
stateDir: string;
|
|
6
|
-
clock?: Clock;
|
|
7
|
-
spawn?: SpawnFn;
|
|
8
|
-
warn?: (message: string) => void;
|
|
9
|
-
/** Test seam: the version `daemon status` compares the daemon against; defaults to this
|
|
10
|
-
* package's own version. */
|
|
11
|
-
clientVersion?: string;
|
|
12
|
-
};
|
|
13
|
-
export type DaemonRunHostedResult = {
|
|
14
|
-
result: CliResult<DaemonRunCommandData>;
|
|
15
|
-
completion: Promise<void>;
|
|
16
|
-
stop: () => void;
|
|
17
|
-
};
|
|
18
|
-
export declare const handleDaemonRunCommand: (context: DaemonCommandContext) => Promise<DaemonRunHostedResult>;
|
|
19
|
-
export declare const handleDaemonStartCommand: (context: DaemonCommandContext) => Promise<CliResult<DaemonStartCommandData>>;
|
|
20
|
-
export declare const handleDaemonStopCommand: (context: DaemonCommandContext) => Promise<CliResult<DaemonStopCommandData>>;
|
|
21
|
-
export declare const handleDaemonStatusCommand: (context: DaemonCommandContext) => Promise<CliResult<DaemonStatusCommandData>>;
|
|
22
|
-
//# sourceMappingURL=daemon.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../../src/commands/daemon.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,SAAS,EACT,oBAAoB,EACpB,sBAAsB,EACtB,uBAAuB,EACvB,qBAAqB,EACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAM7C,OAAO,EAAuD,KAAK,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAErG,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC;gCAC4B;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,EAAE,SAAS,CAAC,oBAAoB,CAAC,CAAC;IACxC,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,SAAS,oBAAoB,KAC5B,OAAO,CAAC,qBAAqB,CAiC/B,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,SAAS,oBAAoB,KAC5B,OAAO,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAiB3C,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,SAAS,oBAAoB,KAC5B,OAAO,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAyB1C,CAAC;AAQF,eAAO,MAAM,yBAAyB,GACpC,SAAS,oBAAoB,KAC5B,OAAO,CAAC,SAAS,CAAC,uBAAuB,CAAC,CA2D5C,CAAC"}
|
package/dist/commands/daemon.js
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { RPC_METHODS } from "@appduct/shared";
|
|
2
|
-
import { startDaemon } from "../daemon/daemon.js";
|
|
3
|
-
import { DaemonAlreadyRunningError, readPidFromFile } from "../daemon/pidfile.js";
|
|
4
|
-
import { getStateDirPaths } from "../daemon/state-dir.js";
|
|
5
|
-
import { connectionError } from "../errors.js";
|
|
6
|
-
import { getPackageVersion } from "../package-version.js";
|
|
7
|
-
import { callDaemon, isDaemonUnreachableError, isSameVersion } from "../rpc/client.js";
|
|
8
|
-
export const handleDaemonRunCommand = async (context) => {
|
|
9
|
-
let daemon;
|
|
10
|
-
try {
|
|
11
|
-
daemon = await startDaemon({
|
|
12
|
-
stateDir: context.stateDir,
|
|
13
|
-
clock: context.clock,
|
|
14
|
-
warn: context.warn,
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
catch (error) {
|
|
18
|
-
if (error instanceof DaemonAlreadyRunningError) {
|
|
19
|
-
throw connectionError(error.message, { pid: error.pid });
|
|
20
|
-
}
|
|
21
|
-
throw error;
|
|
22
|
-
}
|
|
23
|
-
return {
|
|
24
|
-
result: {
|
|
25
|
-
ok: true,
|
|
26
|
-
data: {
|
|
27
|
-
daemon: {
|
|
28
|
-
pid: process.pid,
|
|
29
|
-
state_dir: daemon.paths.root,
|
|
30
|
-
socket_path: daemon.paths.socketPath,
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
completion: daemon.exited,
|
|
35
|
-
stop: () => {
|
|
36
|
-
void daemon.shutdown();
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
};
|
|
40
|
-
export const handleDaemonStartCommand = async (context) => {
|
|
41
|
-
const status = await callDaemon(RPC_METHODS.daemonStatus, {}, { stateDir: context.stateDir, autoSpawn: true, spawn: context.spawn });
|
|
42
|
-
return {
|
|
43
|
-
ok: true,
|
|
44
|
-
data: {
|
|
45
|
-
daemon: {
|
|
46
|
-
pid: status.pid,
|
|
47
|
-
wss_port: status.wssPort,
|
|
48
|
-
started_at: status.startedAt,
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
};
|
|
53
|
-
export const handleDaemonStopCommand = async (context) => {
|
|
54
|
-
try {
|
|
55
|
-
await callDaemon(RPC_METHODS.daemonShutdown, {}, { stateDir: context.stateDir, autoSpawn: false });
|
|
56
|
-
return { ok: true, data: { daemon: { ok: true, method: "rpc" } } };
|
|
57
|
-
}
|
|
58
|
-
catch (error) {
|
|
59
|
-
if (!isDaemonUnreachableError(error)) {
|
|
60
|
-
throw error;
|
|
61
|
-
}
|
|
62
|
-
const paths = getStateDirPaths(context.stateDir);
|
|
63
|
-
const pid = await readPidFromFile(paths.pidFilePath);
|
|
64
|
-
if (pid === undefined) {
|
|
65
|
-
throw connectionError("No Appduct daemon is running.");
|
|
66
|
-
}
|
|
67
|
-
process.kill(pid, "SIGTERM");
|
|
68
|
-
return { ok: true, data: { daemon: { ok: true, method: "sigterm" } } };
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
/** Drops the entries whose value is `undefined`, so spreading the result adds only keys that
|
|
72
|
-
* carry an actual answer. */
|
|
73
|
-
const definedOnly = (fields) => {
|
|
74
|
-
return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined));
|
|
75
|
-
};
|
|
76
|
-
export const handleDaemonStatusCommand = async (context) => {
|
|
77
|
-
const status = await callDaemon(RPC_METHODS.daemonStatus, {}, { stateDir: context.stateDir, autoSpawn: true, spawn: context.spawn });
|
|
78
|
-
// The wire type declares the retention fields required, as it must for any daemon built from
|
|
79
|
-
// this tree — but the daemon on the other end of the socket may predate them, and a running
|
|
80
|
-
// daemon outlives the CLI upgrade that would replace it. Reading them through a partial view
|
|
81
|
-
// keeps that possibility in the types instead of in a comment, and leaves each one `undefined`
|
|
82
|
-
// rather than `0` when it was never reported.
|
|
83
|
-
const retention = status.audit;
|
|
84
|
-
const clientVersion = context.clientVersion ?? getPackageVersion();
|
|
85
|
-
// Guarded the same way the version check guards it (`rpc/client.ts`): a `daemon.status` without
|
|
86
|
-
// `sessions` would otherwise throw from the middle of the command that exists to diagnose a
|
|
87
|
-
// daemon — the one command that must keep working against a daemon it does not fully understand.
|
|
88
|
-
const sessionCount = Array.isArray(status.sessions) ? status.sessions.length : 0;
|
|
89
|
-
const pendingLinkCount = typeof status.pendingLinks === "number" ? status.pendingLinks : 0;
|
|
90
|
-
// Names the same cost the restart path names (`rpc/client.ts`), so the diagnosis an operator
|
|
91
|
-
// reads here and the error they hit on the next command tell one consistent story: an unclaimed
|
|
92
|
-
// link is destroyed by a restart just as surely as a session is.
|
|
93
|
-
const atStake = `${sessionCount} session(s) and ${pendingLinkCount} unclaimed link(s)`;
|
|
94
|
-
const warning = isSameVersion(status.version, clientVersion)
|
|
95
|
-
? undefined
|
|
96
|
-
: `Version drift: this daemon is ${status.version} but the CLI is ${clientVersion}. ` +
|
|
97
|
-
`Newer RPC methods will fail against it — run "appduct daemon stop" (this drops ${atStake}) ` +
|
|
98
|
-
`and the next command will start a matching daemon, or pass "--daemon-restart" to any command to replace it in place.`;
|
|
99
|
-
return {
|
|
100
|
-
ok: true,
|
|
101
|
-
data: {
|
|
102
|
-
warning,
|
|
103
|
-
daemon: {
|
|
104
|
-
version: status.version,
|
|
105
|
-
pid: status.pid,
|
|
106
|
-
started_at: status.startedAt,
|
|
107
|
-
wss_port: status.wssPort,
|
|
108
|
-
pinned_keys: status.pinnedKeys,
|
|
109
|
-
session_count: sessionCount,
|
|
110
|
-
},
|
|
111
|
-
policy: status.policy,
|
|
112
|
-
audit: {
|
|
113
|
-
path: status.audit.path,
|
|
114
|
-
failed_writes: status.audit.failedWrites,
|
|
115
|
-
// Spread so an unreported field is a *missing key*, not a key holding `undefined`. The two
|
|
116
|
-
// serialize identically, but only the first is honestly shaped: a caller can ask whether
|
|
117
|
-
// the daemon answered, and `--json` consumers see the same absence in-process as on the
|
|
118
|
-
// wire.
|
|
119
|
-
...definedOnly({
|
|
120
|
-
failed_prunes: retention.failedPrunes,
|
|
121
|
-
retention_days: retention.retentionDays,
|
|
122
|
-
files: retention.files,
|
|
123
|
-
bytes: retention.bytes,
|
|
124
|
-
}),
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
};
|
|
128
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3E,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAG5E,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,CAAC,EAAE,YAAY,CAAC;CACrB,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC9B,SAAS,oBAAoB,EAC7B,UAAS,oBAAyB,KACjC,OAAO,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAyCtC,CAAC"}
|
package/dist/commands/doctor.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `appduct doctor <artifact>` (docs/tasks/08-appduct-doctor.md): the release-gate replacement
|
|
3
|
-
* for the runtime `debuggable` check removed elsewhere in opt-in hardening — see
|
|
4
|
-
* `artifact-inspect.ts`'s doc comment for the detection strategy and why it never collapses a
|
|
5
|
-
* missing-tool or unreadable-artifact case into "absent".
|
|
6
|
-
*
|
|
7
|
-
* This handler owns only the CLI-facing concerns: option validation (mutually exclusive
|
|
8
|
-
* `--assert-present`/`--assert-absent`) and turning a failed assertion into a distinct
|
|
9
|
-
* {@link assertionError} exit, separate from `inspectionError`'s "couldn't tell" exit. All artifact
|
|
10
|
-
* inspection itself lives in `artifact-inspect.ts`, unit-testable without going through the CLI.
|
|
11
|
-
*/
|
|
12
|
-
import { inspectArtifact } from "../artifact-inspect.js";
|
|
13
|
-
import { assertionError, usageError } from "../errors.js";
|
|
14
|
-
export const handleDoctorCommand = async (options, context = {}) => {
|
|
15
|
-
if (!options.artifactPath) {
|
|
16
|
-
throw usageError("Usage: appduct doctor <path-to-artifact> [--assert-present | --assert-absent]");
|
|
17
|
-
}
|
|
18
|
-
if (options.assertPresent && options.assertAbsent) {
|
|
19
|
-
throw usageError("--assert-present and --assert-absent are mutually exclusive.");
|
|
20
|
-
}
|
|
21
|
-
const inspection = await inspectArtifact(options.artifactPath, { exec: context.exec });
|
|
22
|
-
const data = {
|
|
23
|
-
artifact: options.artifactPath,
|
|
24
|
-
platform: inspection.platform,
|
|
25
|
-
format: inspection.format,
|
|
26
|
-
present: inspection.present,
|
|
27
|
-
signals: inspection.signals,
|
|
28
|
-
};
|
|
29
|
-
if (options.assertPresent && !inspection.present) {
|
|
30
|
-
throw assertionError(`Expected Appduct to be present in "${options.artifactPath}", but no inclusion signal was found.`, data);
|
|
31
|
-
}
|
|
32
|
-
if (options.assertAbsent && inspection.present) {
|
|
33
|
-
throw assertionError(`Expected Appduct to be absent from "${options.artifactPath}", but found: ${inspection.signals.join(", ")}.`, data);
|
|
34
|
-
}
|
|
35
|
-
if (options.assertPresent || options.assertAbsent) {
|
|
36
|
-
data.assertion = {
|
|
37
|
-
expected: options.assertPresent ? "present" : "absent",
|
|
38
|
-
holds: true,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
return { ok: true, data };
|
|
42
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/commands/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAe,KAAK,iBAAiB,EAAsD,MAAM,iBAAiB,CAAC;AAE1H,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAoB,KAAK,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAElE,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;0EACsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC5C;;sGAEkG;IAClG,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC;IACzB,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC;AA4BF,eAAO,MAAM,mBAAmB,GAC9B,SAAS,oBAAoB,EAC7B,SAAS,oBAAoB,KAC5B,OAAO,CAAC,kBAAkB,CAkD5B,CAAC"}
|