appduct 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +274 -0
- package/bin.js +2 -0
- package/dist/artifact-inspect.d.ts +110 -0
- package/dist/artifact-inspect.d.ts.map +1 -0
- package/dist/artifact-inspect.js +315 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +4 -0
- package/dist/cli/command-options.d.ts +44 -0
- package/dist/cli/command-options.d.ts.map +1 -0
- package/dist/cli/command-options.js +95 -0
- package/dist/cli/create-cli.d.ts +2 -0
- package/dist/cli/create-cli.d.ts.map +1 -0
- package/dist/cli/create-cli.js +56 -0
- package/dist/cli/dispatch.d.ts +3 -0
- package/dist/cli/dispatch.d.ts.map +1 -0
- package/dist/cli/dispatch.js +275 -0
- package/dist/cli/open-target.d.ts +112 -0
- package/dist/cli/open-target.d.ts.map +1 -0
- package/dist/cli/open-target.js +438 -0
- package/dist/cli/result-types.d.ts +194 -0
- package/dist/cli/result-types.d.ts.map +1 -0
- package/dist/cli/result-types.js +6 -0
- package/dist/cli/runner.d.ts +35 -0
- package/dist/cli/runner.d.ts.map +1 -0
- package/dist/cli/runner.js +111 -0
- package/dist/cli/types.d.ts +20 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +10 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +2 -0
- package/dist/client/app-client.d.ts +126 -0
- package/dist/client/app-client.d.ts.map +1 -0
- package/dist/client/app-client.js +223 -0
- package/dist/client/bootstrap.d.ts +48 -0
- package/dist/client/bootstrap.d.ts.map +1 -0
- package/dist/client/bootstrap.js +108 -0
- package/dist/client/connect.d.ts +17 -0
- package/dist/client/connect.d.ts.map +1 -0
- package/dist/client/connect.js +38 -0
- package/dist/client/errors.d.ts +26 -0
- package/dist/client/errors.d.ts.map +1 -0
- package/dist/client/errors.js +40 -0
- package/dist/client/index.d.ts +19 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +16 -0
- package/dist/commands/daemon.d.ts +22 -0
- package/dist/commands/daemon.d.ts.map +1 -0
- package/dist/commands/daemon.js +128 -0
- package/dist/commands/doctor.d.ts +23 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +42 -0
- package/dist/commands/events.d.ts +37 -0
- package/dist/commands/events.d.ts.map +1 -0
- package/dist/commands/events.js +76 -0
- package/dist/commands/init.d.ts +51 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +231 -0
- package/dist/commands/invoke.d.ts +22 -0
- package/dist/commands/invoke.d.ts.map +1 -0
- package/dist/commands/invoke.js +54 -0
- package/dist/commands/keygen.d.ts +23 -0
- package/dist/commands/keygen.d.ts.map +1 -0
- package/dist/commands/keygen.js +46 -0
- package/dist/commands/link.d.ts +30 -0
- package/dist/commands/link.d.ts.map +1 -0
- package/dist/commands/link.js +43 -0
- package/dist/commands/ls.d.ts +9 -0
- package/dist/commands/ls.d.ts.map +1 -0
- package/dist/commands/ls.js +7 -0
- package/dist/commands/mcp.d.ts +49 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +98 -0
- package/dist/commands/revoke.d.ts +12 -0
- package/dist/commands/revoke.d.ts.map +1 -0
- package/dist/commands/revoke.js +7 -0
- package/dist/commands/tools.d.ts +23 -0
- package/dist/commands/tools.d.ts.map +1 -0
- package/dist/commands/tools.js +52 -0
- package/dist/daemon/address.d.ts +30 -0
- package/dist/daemon/address.d.ts.map +1 -0
- package/dist/daemon/address.js +71 -0
- package/dist/daemon/audit.d.ts +107 -0
- package/dist/daemon/audit.d.ts.map +1 -0
- package/dist/daemon/audit.js +237 -0
- package/dist/daemon/calls.d.ts +84 -0
- package/dist/daemon/calls.d.ts.map +1 -0
- package/dist/daemon/calls.js +158 -0
- package/dist/daemon/config.d.ts +79 -0
- package/dist/daemon/config.d.ts.map +1 -0
- package/dist/daemon/config.js +170 -0
- package/dist/daemon/daemon.d.ts +43 -0
- package/dist/daemon/daemon.d.ts.map +1 -0
- package/dist/daemon/daemon.js +560 -0
- package/dist/daemon/event-bus.d.ts +49 -0
- package/dist/daemon/event-bus.d.ts.map +1 -0
- package/dist/daemon/event-bus.js +111 -0
- package/dist/daemon/links.d.ts +54 -0
- package/dist/daemon/links.d.ts.map +1 -0
- package/dist/daemon/links.js +118 -0
- package/dist/daemon/listener.d.ts +34 -0
- package/dist/daemon/listener.d.ts.map +1 -0
- package/dist/daemon/listener.js +132 -0
- package/dist/daemon/log-rotation.d.ts +82 -0
- package/dist/daemon/log-rotation.d.ts.map +1 -0
- package/dist/daemon/log-rotation.js +136 -0
- package/dist/daemon/pidfile.d.ts +38 -0
- package/dist/daemon/pidfile.d.ts.map +1 -0
- package/dist/daemon/pidfile.js +90 -0
- package/dist/daemon/policy.d.ts +22 -0
- package/dist/daemon/policy.d.ts.map +1 -0
- package/dist/daemon/policy.js +24 -0
- package/dist/daemon/registry.d.ts +19 -0
- package/dist/daemon/registry.d.ts.map +1 -0
- package/dist/daemon/registry.js +31 -0
- package/dist/daemon/rpc-server.d.ts +54 -0
- package/dist/daemon/rpc-server.d.ts.map +1 -0
- package/dist/daemon/rpc-server.js +203 -0
- package/dist/daemon/sessions.d.ts +86 -0
- package/dist/daemon/sessions.d.ts.map +1 -0
- package/dist/daemon/sessions.js +429 -0
- package/dist/daemon/socket-probe.d.ts +16 -0
- package/dist/daemon/socket-probe.d.ts.map +1 -0
- package/dist/daemon/socket-probe.js +28 -0
- package/dist/daemon/state-dir.d.ts +31 -0
- package/dist/daemon/state-dir.d.ts.map +1 -0
- package/dist/daemon/state-dir.js +43 -0
- package/dist/daemon/timers.d.ts +16 -0
- package/dist/daemon/timers.d.ts.map +1 -0
- package/dist/daemon/timers.js +12 -0
- package/dist/daemon/tls.d.ts +58 -0
- package/dist/daemon/tls.d.ts.map +1 -0
- package/dist/daemon/tls.js +79 -0
- package/dist/errors.d.ts +28 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +162 -0
- package/dist/host-certificate.d.ts +8 -0
- package/dist/host-certificate.d.ts.map +1 -0
- package/dist/host-certificate.js +111 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/key-material.d.ts +14 -0
- package/dist/key-material.d.ts.map +1 -0
- package/dist/key-material.js +42 -0
- package/dist/link.d.ts +76 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +117 -0
- package/dist/mcp/connect-tool.d.ts +115 -0
- package/dist/mcp/connect-tool.d.ts.map +1 -0
- package/dist/mcp/connect-tool.js +396 -0
- package/dist/mcp/daemon-tools.d.ts +13 -0
- package/dist/mcp/daemon-tools.d.ts.map +1 -0
- package/dist/mcp/daemon-tools.js +29 -0
- package/dist/mcp/events-tool.d.ts +111 -0
- package/dist/mcp/events-tool.d.ts.map +1 -0
- package/dist/mcp/events-tool.js +322 -0
- package/dist/mcp/server.d.ts +58 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +538 -0
- package/dist/mcp/tool-mapping.d.ts +58 -0
- package/dist/mcp/tool-mapping.d.ts.map +1 -0
- package/dist/mcp/tool-mapping.js +143 -0
- package/dist/mcp/tool-namespace.d.ts +40 -0
- package/dist/mcp/tool-namespace.d.ts.map +1 -0
- package/dist/mcp/tool-namespace.js +65 -0
- package/dist/native-scheme.d.ts +59 -0
- package/dist/native-scheme.d.ts.map +1 -0
- package/dist/native-scheme.js +465 -0
- package/dist/output.d.ts +28 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +342 -0
- package/dist/package-version.d.ts +30 -0
- package/dist/package-version.d.ts.map +1 -0
- package/dist/package-version.js +47 -0
- package/dist/qr-terminal.d.ts +13 -0
- package/dist/qr-terminal.d.ts.map +1 -0
- package/dist/qr-terminal.js +45 -0
- package/dist/rpc/client.d.ts +137 -0
- package/dist/rpc/client.d.ts.map +1 -0
- package/dist/rpc/client.js +819 -0
- package/dist/scheme.d.ts +181 -0
- package/dist/scheme.d.ts.map +1 -0
- package/dist/scheme.js +321 -0
- package/dist/spki-pin.d.ts +3 -0
- package/dist/spki-pin.d.ts.map +1 -0
- package/dist/spki-pin.js +14 -0
- package/package.json +65 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { CliResult } from "./result-types.js";
|
|
2
|
+
import { type RenderOptions } from "../output.js";
|
|
3
|
+
import { type Clock, type CliIoWriters } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Long-lived command result shape shared by any CLI command that keeps a process alive
|
|
6
|
+
* until completion/cancellation (e.g. a foreground daemon run). Not tied to any specific
|
|
7
|
+
* command's data type.
|
|
8
|
+
*/
|
|
9
|
+
type HostedCommandResult = {
|
|
10
|
+
result: CliResult<unknown>;
|
|
11
|
+
completion: Promise<void>;
|
|
12
|
+
stop: () => void;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Minimal reporter contract for {@link executeHostedCommand}: an optional live-rendering
|
|
16
|
+
* reporter that receives lifecycle events and must be disposed once the hosted command
|
|
17
|
+
* completes. `kind: "plain"` reporters render their own bootstrap output instead of the
|
|
18
|
+
* default `renderResult` output.
|
|
19
|
+
*/
|
|
20
|
+
export type HostedCommandReporter = {
|
|
21
|
+
kind: "interactive" | "plain";
|
|
22
|
+
onEvent: (event: unknown) => void | Promise<void>;
|
|
23
|
+
dispose: () => void;
|
|
24
|
+
};
|
|
25
|
+
export declare const executeCommand: (command: string, handler: () => CliResult<unknown> | Promise<CliResult<unknown>>, options: CliIoWriters & Pick<RenderOptions, "json" | "color"> & Partial<Pick<RenderOptions, "qr" | "full">> & {
|
|
26
|
+
clock: Clock;
|
|
27
|
+
}) => Promise<number>;
|
|
28
|
+
export declare const executeHostedCommand: (command: string, handler: () => Promise<HostedCommandResult>, options: CliIoWriters & {
|
|
29
|
+
json: boolean;
|
|
30
|
+
color: boolean;
|
|
31
|
+
clock: Clock;
|
|
32
|
+
reporter?: HostedCommandReporter;
|
|
33
|
+
}) => Promise<number>;
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/cli/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAGnD,OAAO,EAAgB,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAqB,KAAK,KAAK,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAE9E;;;;GAIG;AACH,KAAK,mBAAmB,GAAG;IACzB,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IAC3B,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC;IAC9B,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAeF,eAAO,MAAM,cAAc,GACzB,SAAS,MAAM,EACf,SAAS,MAAM,SAAS,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,EAC/D,SAAS,YAAY,GACnB,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,GACrC,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,GAAG,MAAM,CAAC,CAAC,GAAG;IAC5C,KAAK,EAAE,KAAK,CAAC;CACd,KACF,OAAO,CAAC,MAAM,CAkChB,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAC/B,SAAS,MAAM,EACf,SAAS,MAAM,OAAO,CAAC,mBAAmB,CAAC,EAC3C,SAAS,YAAY,GAAG;IACtB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,CAAC,EAAE,qBAAqB,CAAC;CAClC,KACA,OAAO,CAAC,MAAM,CAoFhB,CAAC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { getExitCodeForError, toCliError } from "../errors.js";
|
|
2
|
+
import { renderResult } from "../output.js";
|
|
3
|
+
import { createCommandMeta } from "./types.js";
|
|
4
|
+
const writeRenderedOutput = (rendered, writers) => {
|
|
5
|
+
if (rendered.stdout) {
|
|
6
|
+
writers.stdout.write(rendered.stdout);
|
|
7
|
+
}
|
|
8
|
+
if (rendered.stderr) {
|
|
9
|
+
writers.stderr.write(rendered.stderr);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
export const executeCommand = async (command, handler, options) => {
|
|
13
|
+
const startedAt = options.clock.now();
|
|
14
|
+
const renderOptions = {
|
|
15
|
+
command,
|
|
16
|
+
json: options.json,
|
|
17
|
+
color: options.color,
|
|
18
|
+
qr: options.qr,
|
|
19
|
+
full: options.full,
|
|
20
|
+
};
|
|
21
|
+
try {
|
|
22
|
+
const result = await handler();
|
|
23
|
+
const finishedAt = options.clock.now();
|
|
24
|
+
const withMeta = {
|
|
25
|
+
...result,
|
|
26
|
+
meta: createCommandMeta(command, startedAt, finishedAt),
|
|
27
|
+
};
|
|
28
|
+
writeRenderedOutput(renderResult(withMeta, renderOptions), options);
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
const finishedAt = options.clock.now();
|
|
33
|
+
const cliError = toCliError(error);
|
|
34
|
+
const result = {
|
|
35
|
+
ok: false,
|
|
36
|
+
error: cliError,
|
|
37
|
+
meta: createCommandMeta(command, startedAt, finishedAt),
|
|
38
|
+
};
|
|
39
|
+
writeRenderedOutput(renderResult(result, renderOptions), options);
|
|
40
|
+
return getExitCodeForError(error);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
export const executeHostedCommand = async (command, handler, options) => {
|
|
44
|
+
const startedAt = options.clock.now();
|
|
45
|
+
let renderedSuccess = false;
|
|
46
|
+
try {
|
|
47
|
+
const hosted = await handler();
|
|
48
|
+
const finishedAt = options.clock.now();
|
|
49
|
+
const withMeta = {
|
|
50
|
+
...hosted.result,
|
|
51
|
+
meta: createCommandMeta(command, startedAt, finishedAt),
|
|
52
|
+
};
|
|
53
|
+
// A live reporter (e.g. `events`'s streaming NDJSON/human lines) renders its own output as it
|
|
54
|
+
// goes; only the absence of a reporter (or an explicit "plain" one) falls back to the default
|
|
55
|
+
// one-shot `renderResult` bootstrap rendering.
|
|
56
|
+
const liveReporter = options.reporter;
|
|
57
|
+
const shouldRenderBootstrap = !liveReporter || liveReporter.kind === "plain";
|
|
58
|
+
if (shouldRenderBootstrap) {
|
|
59
|
+
writeRenderedOutput(renderResult(withMeta, {
|
|
60
|
+
command,
|
|
61
|
+
json: options.json,
|
|
62
|
+
color: options.color,
|
|
63
|
+
}), options);
|
|
64
|
+
renderedSuccess = true;
|
|
65
|
+
}
|
|
66
|
+
let resolved = false;
|
|
67
|
+
const stop = () => {
|
|
68
|
+
if (resolved) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
resolved = true;
|
|
72
|
+
hosted.stop();
|
|
73
|
+
};
|
|
74
|
+
process.once("SIGINT", stop);
|
|
75
|
+
process.once("SIGTERM", stop);
|
|
76
|
+
try {
|
|
77
|
+
await hosted.completion;
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
resolved = true;
|
|
81
|
+
process.off("SIGINT", stop);
|
|
82
|
+
process.off("SIGTERM", stop);
|
|
83
|
+
options.reporter?.dispose();
|
|
84
|
+
}
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
options.reporter?.dispose();
|
|
89
|
+
const finishedAt = options.clock.now();
|
|
90
|
+
const cliError = toCliError(error);
|
|
91
|
+
const result = {
|
|
92
|
+
ok: false,
|
|
93
|
+
error: cliError,
|
|
94
|
+
meta: createCommandMeta(command, startedAt, finishedAt),
|
|
95
|
+
};
|
|
96
|
+
if (renderedSuccess && options.json) {
|
|
97
|
+
// The single-JSON-object-on-stdout contract was already fulfilled by the bootstrap render
|
|
98
|
+
// above; a failure that happens later (e.g. during a long-running `completion`) must still
|
|
99
|
+
// be a JSON object, just on stderr instead — bare text here was v1's defect (leaked
|
|
100
|
+
// unparseable output onto stderr in `--json` mode).
|
|
101
|
+
options.stderr.write(`${JSON.stringify(result)}\n`);
|
|
102
|
+
return getExitCodeForError(error);
|
|
103
|
+
}
|
|
104
|
+
writeRenderedOutput(renderResult(result, {
|
|
105
|
+
command,
|
|
106
|
+
json: options.json,
|
|
107
|
+
color: options.color,
|
|
108
|
+
}), options);
|
|
109
|
+
return getExitCodeForError(error);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CommandMeta } from "./result-types.js";
|
|
2
|
+
export type Clock = {
|
|
3
|
+
now: () => Date;
|
|
4
|
+
};
|
|
5
|
+
export declare const systemClock: Clock;
|
|
6
|
+
export declare const createCommandMeta: (command: string, startedAt: Date, finishedAt: Date) => CommandMeta;
|
|
7
|
+
export type RunCliOptions = {
|
|
8
|
+
stdout?: Pick<typeof process.stdout, "write" | "isTTY">;
|
|
9
|
+
stderr?: Pick<typeof process.stderr, "write">;
|
|
10
|
+
clock?: Clock;
|
|
11
|
+
};
|
|
12
|
+
export type CliIoWriters = Required<Pick<RunCliOptions, "stdout" | "stderr">>;
|
|
13
|
+
export type CliRenderContext = {
|
|
14
|
+
json: boolean;
|
|
15
|
+
color: boolean;
|
|
16
|
+
stdout: CliIoWriters["stdout"];
|
|
17
|
+
stderr: CliIoWriters["stderr"];
|
|
18
|
+
clock: Clock;
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/cli/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,MAAM,KAAK,GAAG;IAClB,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB,CAAC;AAEF,eAAO,MAAM,WAAW,EAAE,KAEzB,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,SAAS,MAAM,EACf,WAAW,IAAI,EACf,YAAY,IAAI,KACf,WAMF,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,OAAO,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC;IACxD,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9C,KAAK,CAAC,EAAE,KAAK,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC,IAAI,CAAC,aAAa,EAAE,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC;AAE9E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;IAC/B,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;IAC/B,KAAK,EAAE,KAAK,CAAC;CACd,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const systemClock = {
|
|
2
|
+
now: () => new Date(),
|
|
3
|
+
};
|
|
4
|
+
export const createCommandMeta = (command, startedAt, finishedAt) => {
|
|
5
|
+
return {
|
|
6
|
+
command,
|
|
7
|
+
timestamp: finishedAt.toISOString(),
|
|
8
|
+
duration_ms: finishedAt.getTime() - startedAt.getTime(),
|
|
9
|
+
};
|
|
10
|
+
};
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
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 { type ToolDescriptor } from "@appduct/shared";
|
|
11
|
+
import type { DaemonStream } from "../rpc/client.js";
|
|
12
|
+
/** A project's own tool map — declared once, e.g. `type Tools = { sum: { args: { a: number; b:
|
|
13
|
+
* number }; result: { total: number } } }` — to get typed {@link AppClient.call} throughout a test
|
|
14
|
+
* suite. Tool names and arg/result types can't be statically known here (they're registered by the
|
|
15
|
+
* connected app at runtime): the default `result` is `any` so the untyped form (`connect()` with no
|
|
16
|
+
* generic) still lets a caller destructure a call's result without a cast, e.g. `const { total } =
|
|
17
|
+
* await app.call("sum", { a: 2, b: 3 })`. `AppClient` itself is deliberately unconstrained (no
|
|
18
|
+
* `extends Record<string, ...>`) so both `type` aliases and plain `interface`s work here — TS only
|
|
19
|
+
* infers an implicit index signature for the former, so a constrained generic would silently reject
|
|
20
|
+
* the latter. */
|
|
21
|
+
export type ToolMap = Record<string, {
|
|
22
|
+
args: Record<string, unknown>;
|
|
23
|
+
result: any;
|
|
24
|
+
}>;
|
|
25
|
+
type ToolArgs<TTools, K extends keyof TTools> = TTools[K] extends {
|
|
26
|
+
args: infer TArgs;
|
|
27
|
+
} ? TArgs : Record<string, unknown>;
|
|
28
|
+
type ToolResult<TTools, K extends keyof TTools> = TTools[K] extends {
|
|
29
|
+
result: infer TResult;
|
|
30
|
+
} ? TResult : unknown;
|
|
31
|
+
export type CallOptions = {
|
|
32
|
+
/** Forwarded to the daemon as this call's deadline, clamped server-side to [1s, 600s]. Omitted,
|
|
33
|
+
* the daemon falls back to the tool's own declared `timeoutMs` (`registerTool`), and to 10s for a
|
|
34
|
+
* tool that declares none. Either way this is NOT the call's transport timeout, which is derived
|
|
35
|
+
* automatically so a `tool_timeout` from the daemon always arrives before this client's own
|
|
36
|
+
* transport timeout would otherwise fire and misreport it as `connection_error`. */
|
|
37
|
+
timeoutMs?: number;
|
|
38
|
+
};
|
|
39
|
+
export type WaitForEventOptions = {
|
|
40
|
+
/** Defaults to 30s. */
|
|
41
|
+
timeoutMs?: number;
|
|
42
|
+
/** Extra filter over the event's payload; the event still must match `name` first. A predicate
|
|
43
|
+
* that throws rejects the wait with that error rather than crashing the connection. */
|
|
44
|
+
match?: (payload: unknown) => boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Exclusive lower bound on `AppEvent.seq` (a cursor from a previous {@link AppClient.events} or
|
|
47
|
+
* {@link AppClient.waitForEvent} call) — skips already-retained events at/before it instead of
|
|
48
|
+
* resolving with an old match instantly. Omitted, the retained buffer is searched from its start,
|
|
49
|
+
* so a matching event that already fired before this call still resolves immediately.
|
|
50
|
+
*/
|
|
51
|
+
since?: number;
|
|
52
|
+
};
|
|
53
|
+
export type EventsOptions = {
|
|
54
|
+
/** Exclusive lower bound on `AppEvent.seq`; omitted returns the whole retained buffer (oldest
|
|
55
|
+
* first, subject to `limit`). */
|
|
56
|
+
since?: number;
|
|
57
|
+
/** Caps the number of events returned (oldest-first truncation); omitted returns everything
|
|
58
|
+
* matching `since` up to the buffer's own retention limit. */
|
|
59
|
+
limit?: number;
|
|
60
|
+
};
|
|
61
|
+
export type EventsResult = {
|
|
62
|
+
events: AppEvent[];
|
|
63
|
+
/** The highest `seq` currently retained for this session (not just among the returned events) —
|
|
64
|
+
* pass it back as `since`/`since` on the next {@link AppClient.events}/{@link
|
|
65
|
+
* AppClient.waitForEvent} call to resume after it. */
|
|
66
|
+
cursor: number;
|
|
67
|
+
};
|
|
68
|
+
/** An app-pushed event (`postEvent(name, payload)`), narrowed from the daemon's generic
|
|
69
|
+
* `EventNotification` envelope to the shape a `waitForEvent`/`events` caller actually wants. */
|
|
70
|
+
export type AppEvent<TPayload = unknown> = {
|
|
71
|
+
name: string;
|
|
72
|
+
payload: TPayload;
|
|
73
|
+
/** Unix ms. */
|
|
74
|
+
ts: number;
|
|
75
|
+
sessionId: string;
|
|
76
|
+
alias?: string;
|
|
77
|
+
/** Monotonically increasing per-session cursor (ARCHITECTURE.md §5) assigned by the daemon's
|
|
78
|
+
* retention buffer at emit time — pass back into `since` to resume after this event. */
|
|
79
|
+
seq: number;
|
|
80
|
+
};
|
|
81
|
+
export type AppClient<TTools = ToolMap> = {
|
|
82
|
+
readonly sessionId: string;
|
|
83
|
+
/** `tools.list` for this session. */
|
|
84
|
+
tools(): Promise<ToolDescriptor[]>;
|
|
85
|
+
/** `tools.call`; rejects with a {@link AppductError} whose `type` preserves the wire error
|
|
86
|
+
* type verbatim (e.g. `"tool_timeout"`, `"policy_denied"`, `"session_suspended"`). */
|
|
87
|
+
call<K extends keyof TTools & string>(name: K, args: ToolArgs<TTools, K>, options?: CallOptions): Promise<ToolResult<TTools, K>>;
|
|
88
|
+
/** Drains `app_event`s retained in the daemon's per-session ring buffer since a cursor — the pull
|
|
89
|
+
* counterpart to {@link AppClient.waitForEvent}, for checking what already happened instead of
|
|
90
|
+
* waiting for the next one. Pass `cursor` from the result back as `since` on the next call to
|
|
91
|
+
* avoid re-reading events already seen. */
|
|
92
|
+
events(options?: EventsOptions): Promise<EventsResult>;
|
|
93
|
+
/**
|
|
94
|
+
* Waits for the next `app_event` (as pushed by the connected app's `postEvent(name, payload)`)
|
|
95
|
+
* matching `name`. Checks the daemon's retained buffer for an already-arrived match first, then
|
|
96
|
+
* falls back to a live wait — so an event emitted between "the caller decides to wait" and "the
|
|
97
|
+
* subscription lands" is never missed; pass `since` (a cursor from a previous {@link
|
|
98
|
+
* AppClient.events}/`waitForEvent` call) to skip events already handled and wait only for a new
|
|
99
|
+
* one, since omitting it can return an old match instantly on every call.
|
|
100
|
+
*
|
|
101
|
+
* This shares the connection's single `events.subscribe` filter (the daemon keeps one per
|
|
102
|
+
* connection, replaced — not merged — on each call); concurrent `waitForEvent` calls on the same
|
|
103
|
+
* `AppClient` all see every `app_event`, so this is safe today, but it means the connection stays
|
|
104
|
+
* subscribed to `app_event` for its lifetime once any `waitForEvent` has run.
|
|
105
|
+
*/
|
|
106
|
+
waitForEvent<TPayload = unknown>(name: string, options?: WaitForEventOptions): Promise<AppEvent<TPayload>>;
|
|
107
|
+
/** Closes the underlying connection. Safe to call more than once. */
|
|
108
|
+
close(): void;
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Transport timeout for one `tools.call`. With an explicit `timeoutMs` the daemon-side deadline is
|
|
112
|
+
* known exactly, so this is that clamped value plus slack.
|
|
113
|
+
*
|
|
114
|
+
* Without one the daemon defaults to the *tool's own* declared `timeoutMs` (issue #25), which this
|
|
115
|
+
* client does not know at call time and would need an extra `tools.list` round trip (racy against
|
|
116
|
+
* re-registration) to learn — so it falls back to the largest deadline the daemon could possibly
|
|
117
|
+
* enforce. That is deliberately generous: this watchdog only exists for a daemon that accepts the
|
|
118
|
+
* request and then never answers, since a daemon that dies or drops the socket already rejects
|
|
119
|
+
* every pending call immediately (`rpc/client.ts`'s `close` handler). Sizing it off the 10 s
|
|
120
|
+
* default instead would make this client the thing that fails a long tool call, reporting a
|
|
121
|
+
* generic transport error in place of the daemon's real one.
|
|
122
|
+
*/
|
|
123
|
+
export declare const transportTimeoutForToolCall: (timeoutMs: number | undefined) => number;
|
|
124
|
+
export declare const makeAppClient: <TTools = ToolMap>(stream: DaemonStream, sessionId: string) => AppClient<TTools>;
|
|
125
|
+
export {};
|
|
126
|
+
//# sourceMappingURL=app-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-client.d.ts","sourceRoot":"","sources":["../../src/client/app-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAML,KAAK,cAAc,EAGpB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAGrD;;;;;;;;iBAQiB;AACjB,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,CAAC,CAAC;AAErF,KAAK,QAAQ,CAAC,MAAM,EAAE,CAAC,SAAS,MAAM,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,KAAK,CAAA;CAAE,GAAG,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC1H,KAAK,UAAU,CAAC,MAAM,EAAE,CAAC,SAAS,MAAM,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC,SAAS;IAAE,MAAM,EAAE,MAAM,OAAO,CAAA;CAAE,GAAG,OAAO,GAAG,OAAO,CAAC;AAElH,MAAM,MAAM,WAAW,GAAG;IACxB;;;;wFAIoF;IACpF,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;2FACuF;IACvF,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC;IACtC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B;qCACiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;kEAC8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,QAAQ,EAAE,CAAC;IACnB;;0DAEsD;IACtD,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;gGACgG;AAChG,MAAM,MAAM,QAAQ,CAAC,QAAQ,GAAG,OAAO,IAAI;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,QAAQ,CAAC;IAClB,eAAe;IACf,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;4FACwF;IACxF,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,SAAS,CAAC,MAAM,GAAG,OAAO,IAAI;IACxC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,qCAAqC;IACrC,KAAK,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IAEnC;0FACsF;IACtF,IAAI,CAAC,CAAC,SAAS,MAAM,MAAM,GAAG,MAAM,EAClC,IAAI,EAAE,CAAC,EACP,IAAI,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,EACzB,OAAO,CAAC,EAAE,WAAW,GACpB,OAAO,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;IAElC;;;+CAG2C;IAC3C,MAAM,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAEvD;;;;;;;;;;;;OAYG;IACH,YAAY,CAAC,QAAQ,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;IAE3G,qEAAqE;IACrE,KAAK,IAAI,IAAI,CAAC;CACf,CAAC;AAWF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,2BAA2B,GAAI,WAAW,MAAM,GAAG,SAAS,KAAG,MAO3E,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,MAAM,GAAG,OAAO,EAAE,QAAQ,YAAY,EAAE,WAAW,MAAM,KAAG,SAAS,CAAC,MAAM,CAiNzG,CAAC"}
|
|
@@ -0,0 +1,223 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type ExecFn, type OpenTarget } from "../cli/open-target.js";
|
|
2
|
+
import { type MintLinkResult } from "../link.js";
|
|
3
|
+
import { type SpawnFn } from "../rpc/client.js";
|
|
4
|
+
import { type AppClient, type ToolMap } from "./app-client.js";
|
|
5
|
+
export type LinkOptions = {
|
|
6
|
+
stateDir?: string;
|
|
7
|
+
spawn?: SpawnFn;
|
|
8
|
+
/** Auto-spawn a daemon on a missing connection. Defaults to `true`. */
|
|
9
|
+
autoSpawn?: boolean;
|
|
10
|
+
ttlSeconds?: number;
|
|
11
|
+
/** Delivers the link directly to a booted Android emulator/device or iOS simulator instead of
|
|
12
|
+
* just minting it (the CI/agent path — no human needed to scan a QR). */
|
|
13
|
+
target?: OpenTarget;
|
|
14
|
+
/** `--device` equivalent: an adb serial, a simulator udid, or a paired-device udid. */
|
|
15
|
+
device?: string;
|
|
16
|
+
/** `--bundle-id` equivalent; only valid with `target: "ios-device"`, where it overrides
|
|
17
|
+
* `config.json`'s `iosBundleId`. */
|
|
18
|
+
bundleId?: string;
|
|
19
|
+
/** `--relaunch` equivalent; only valid with `target: "ios-device"`. */
|
|
20
|
+
relaunch?: boolean;
|
|
21
|
+
/** Highest-precedence scheme source, ahead of `APPDUCT_SCHEME`, a project
|
|
22
|
+
* `.appduct/config.json`, the state dir's `config.json` and `app.json` (see `scheme.ts`). */
|
|
23
|
+
scheme?: string;
|
|
24
|
+
/** Where scheme discovery starts; defaults to `process.cwd()`. */
|
|
25
|
+
cwd?: string;
|
|
26
|
+
exec?: ExecFn;
|
|
27
|
+
/** The environment handed to `adb`/`simctl`, *not* the one `APPDUCT_SCHEME` is read from
|
|
28
|
+
* (see {@link schemeEnv}) — narrowing this must not change scheme resolution. */
|
|
29
|
+
env?: NodeJS.ProcessEnv;
|
|
30
|
+
/** The environment `APPDUCT_SCHEME` is read from; defaults to `process.env`. */
|
|
31
|
+
schemeEnv?: NodeJS.ProcessEnv;
|
|
32
|
+
};
|
|
33
|
+
export type LinkResult = MintLinkResult;
|
|
34
|
+
/** Mints a session link via `link.create` and composes the deep link. With `target` set, delivers
|
|
35
|
+
* it directly to a booted emulator/simulator instead of leaving delivery to the caller. */
|
|
36
|
+
export declare const link: (options?: LinkOptions) => Promise<LinkResult>;
|
|
37
|
+
export type WaitForSessionOptions = {
|
|
38
|
+
stateDir?: string;
|
|
39
|
+
spawn?: SpawnFn;
|
|
40
|
+
/** Auto-spawn a daemon on a missing connection. Defaults to `true`. */
|
|
41
|
+
autoSpawn?: boolean;
|
|
42
|
+
/** Defaults to 120s. */
|
|
43
|
+
timeoutMs?: number;
|
|
44
|
+
};
|
|
45
|
+
/** Waits for `sessionId` (from {@link link}) to be claimed by a device, then returns a connected
|
|
46
|
+
* {@link AppClient} for it — resolves immediately if it's already claimed. */
|
|
47
|
+
export declare const waitForSession: <TTools = ToolMap>(sessionId: string, options?: WaitForSessionOptions) => Promise<AppClient<TTools>>;
|
|
48
|
+
//# sourceMappingURL=bootstrap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|