appduct 0.11.1 → 0.13.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 +43 -38
- package/dist/bin.js +50 -36
- package/dist/cli/command-options.d.ts +7 -17
- package/dist/cli/result-types.d.ts +11 -8
- package/dist/cli/routes/daemon/index.js +12 -16
- package/dist/cli/routes/daemon/run.js +216 -46
- package/dist/cli/routes/daemon/start.js +18 -16
- package/dist/cli/routes/daemon/status.js +18 -16
- package/dist/cli/routes/daemon/stop.js +18 -16
- package/dist/cli/routes/doctor.js +12 -16
- package/dist/cli/routes/events/index.d.ts +9 -0
- package/dist/cli/routes/events/index.js +842 -0
- package/dist/cli/routes/events/shared-options.d.ts +12 -0
- package/dist/cli/routes/events/shared-options.js +102 -0
- package/dist/cli/routes/events/since.d.ts +6 -0
- package/dist/cli/routes/events/since.js +1977 -0
- package/dist/cli/routes/events/tail.d.ts +6 -0
- package/dist/cli/routes/{events.js → events/tail.js} +184 -162
- package/dist/cli/routes/init.js +14 -18
- package/dist/cli/routes/keygen.js +13 -16
- package/dist/cli/routes/mcp.js +193 -175
- package/dist/cli/routes/sessions/index.d.ts +7 -0
- package/dist/cli/routes/sessions/index.js +843 -0
- package/dist/cli/routes/sessions/link.d.ts +4 -0
- package/dist/cli/routes/{link.js → sessions/link.js} +19 -17
- package/dist/cli/routes/sessions/ls.d.ts +4 -0
- package/dist/cli/routes/{ls.js → sessions/ls.js} +19 -17
- package/dist/cli/routes/sessions/revoke.d.ts +4 -0
- package/dist/cli/routes/{revoke.js → sessions/revoke.js} +20 -18
- package/dist/cli/routes/tools/call.d.ts +4 -0
- package/dist/cli/routes/{invoke.js → tools/call.js} +21 -19
- package/dist/cli/routes/tools/describe.d.ts +6 -0
- package/dist/cli/routes/tools/describe.js +1837 -0
- package/dist/cli/routes/tools/index.d.ts +6 -0
- package/dist/cli/routes/tools/index.js +843 -0
- package/dist/cli/routes/tools/ls.d.ts +4 -0
- package/dist/cli/routes/{tools.js → tools/ls.js} +46 -73
- package/dist/cli/routes/tools/shared-options.d.ts +16 -0
- package/dist/cli/routes/tools/shared-options.js +122 -0
- package/dist/client/app-client.d.ts +96 -37
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.js +183 -139
- package/dist/commands/events.d.ts +31 -13
- package/dist/commands/invoke.d.ts +1 -1
- package/dist/commands/link.d.ts +1 -1
- package/dist/commands/ls.d.ts +1 -1
- package/dist/commands/revoke.d.ts +1 -1
- package/dist/commands/tools.d.ts +12 -13
- package/dist/daemon/config.d.ts +5 -2
- package/dist/daemon/daemon.d.ts +3 -0
- package/dist/daemon/event-bus.d.ts +36 -4
- package/dist/daemon/events-log.d.ts +31 -0
- package/dist/daemon/memory-append-only-file.d.ts +17 -0
- package/dist/daemon/node-append-only-file.d.ts +9 -0
- package/dist/daemon/rpc-errors.d.ts +1 -1
- package/dist/daemon/state-dir.d.ts +2 -0
- package/dist/errors.d.ts +1 -1
- package/dist/events/index.d.ts +60 -0
- package/dist/index.js +62 -39
- package/dist/link.d.ts +1 -1
- package/dist/mcp/app-tools.d.ts +3 -2
- package/dist/mcp/connect-tool.d.ts +1 -1
- package/dist/mcp/events-tool.d.ts +28 -34
- package/dist/native-scheme.d.ts +1 -1
- package/dist/output.d.ts +29 -6
- package/dist/rpc/client.d.ts +1 -1
- package/dist/scheme.d.ts +4 -4
- package/package.json +2 -2
- package/dist/cli/routes/events.d.ts +0 -3
- package/dist/cli/routes/invoke.d.ts +0 -3
- package/dist/cli/routes/link.d.ts +0 -3
- package/dist/cli/routes/ls.d.ts +0 -3
- package/dist/cli/routes/revoke.d.ts +0 -3
- package/dist/cli/routes/tools.d.ts +0 -3
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* per-session retention ring buffer, and must never let a throwing subscriber take down the daemon
|
|
5
5
|
* or another subscriber.
|
|
6
6
|
*
|
|
7
|
-
* Retention (issue #6): every session-scoped event
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Retention (issue #6, #98): every session-scoped event gets a monotonically increasing per-session
|
|
8
|
+
* `seq`, but only `app_event` is appended to the per-session ring buffer, capped at `bufferSize`
|
|
9
|
+
* entries (oldest dropped first). Appduct's own kinds (lifecycle, tool calls) are fanned out live
|
|
10
|
+
* and never retained, so no number of tool calls can evict an app event. A
|
|
10
11
|
* session's buffer is discarded the moment it emits a terminal event (`session_expired` /
|
|
11
12
|
* `session_revoked`) — "terminal states free the alias" (sessions.ts) applies to retained events
|
|
12
13
|
* too, matching "no persisted history" (ARCHITECTURE.md §3/§13) — or via an explicit `drop()` for
|
|
@@ -20,14 +21,45 @@ export type EventBusEmitInput = Omit<EventNotification, "ts" | "seq"> & {
|
|
|
20
21
|
ts?: number;
|
|
21
22
|
};
|
|
22
23
|
export type EventBusListener = (event: EventNotification) => void;
|
|
24
|
+
export type ProjectAppEventOptions = {
|
|
25
|
+
/** Whole-name, case-sensitive glob (issue #112): `*` matches any run of characters, a pattern
|
|
26
|
+
* with no `*` is an exact name. */
|
|
27
|
+
name?: string;
|
|
28
|
+
/** Caps the payload's JSON to this many UTF-8 bytes (issue #113); see {@link truncateToUtf8Bytes}. */
|
|
29
|
+
payloadMaxBytes?: number;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Whether `event` survives `options.name`'s glob, and truncates its payload to `options
|
|
33
|
+
* .payloadMaxBytes` when given — the one implementation `since()`'s drain and `daemon.ts`'s
|
|
34
|
+
* `events.subscribe` fan-out both call, so the two can't drift apart (issue #112, #113). Returns
|
|
35
|
+
* `event` (unchanged, or with its payload replaced by a preview) when it should be kept,
|
|
36
|
+
* `undefined` when it should be dropped.
|
|
37
|
+
*
|
|
38
|
+
* Only an `app_event` carries a `name`/`payload` to filter or truncate, so every other kind passes
|
|
39
|
+
* through unfiltered and untouched regardless of either option — the `kinds` filter
|
|
40
|
+
* (`events.subscribe`) is what narrows those.
|
|
41
|
+
*/
|
|
42
|
+
export declare const projectAppEvent: (event: EventNotification, options?: ProjectAppEventOptions) => EventNotification | undefined;
|
|
23
43
|
export type EventsSinceQuery = {
|
|
24
44
|
since?: number;
|
|
25
|
-
kinds?: EventKind[];
|
|
26
45
|
limit?: number;
|
|
46
|
+
/** Whole-name, case-sensitive glob (issue #112): `*` matches any run of characters, a pattern
|
|
47
|
+
* with no `*` is an exact name. See {@link projectAppEvent}. */
|
|
48
|
+
name?: string;
|
|
49
|
+
/** Caps a returned `app_event`'s payload to this many UTF-8 bytes of its JSON (issue #113);
|
|
50
|
+
* applied to the page actually returned, after `name`/`limit`, via {@link projectAppEvent}. */
|
|
51
|
+
payloadMaxBytes?: number;
|
|
27
52
|
};
|
|
28
53
|
export type EventsSinceQueryResult = {
|
|
29
54
|
events: EventNotification[];
|
|
30
55
|
cursor: number;
|
|
56
|
+
/** How many app events after `since` were evicted before this call could return them (issue
|
|
57
|
+
* #113): `max(0, oldest.seq - since - 1)`, `oldest` being the buffer's current oldest retained
|
|
58
|
+
* entry. `0` once nothing has fallen off, and `since` defaults to `0` when omitted. */
|
|
59
|
+
dropped: number;
|
|
60
|
+
/** How many events still match `since`/`name` after the returned (possibly `limit`-truncated)
|
|
61
|
+
* page. `0` on the last page. */
|
|
62
|
+
remaining: number;
|
|
31
63
|
};
|
|
32
64
|
export type EventBus = {
|
|
33
65
|
emit: (event: EventBusEmitInput) => void;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The daemon's own event log, `<stateDir>/events.log` (ARCHITECTURE.md §3): one JSON line per
|
|
3
|
+
* event, for debugging Appduct itself. The daemon decides which events to record; this module only
|
|
4
|
+
* writes them and keeps the file bounded.
|
|
5
|
+
*
|
|
6
|
+
* Writes are serialized on a promise queue, so {@link EventsLog.record} returns at once and never
|
|
7
|
+
* blocks the emitter. After each append the file is rotated to a single backup once it is over
|
|
8
|
+
* `maxBytes`, so the current file is never above the cap once a write has settled. A failed write
|
|
9
|
+
* or rotation is warned, never thrown, and never stops later writes.
|
|
10
|
+
*/
|
|
11
|
+
import type { EventNotification } from "@appduct/shared";
|
|
12
|
+
/** A file that only ever grows by whole lines, with one backup slot. */
|
|
13
|
+
export type AppendOnlyFile = {
|
|
14
|
+
/** Appends `line` plus a newline, creating the file at mode `0600` if it is missing. */
|
|
15
|
+
append(line: string): Promise<void>;
|
|
16
|
+
/** Current size in bytes; `0` when the file does not exist. */
|
|
17
|
+
size(): Promise<number>;
|
|
18
|
+
/** Moves the file to the backup slot, replacing any previous backup. */
|
|
19
|
+
rotate(): Promise<void>;
|
|
20
|
+
};
|
|
21
|
+
export type EventsLog = {
|
|
22
|
+
/** Queues `event` as one JSON line; returns immediately. */
|
|
23
|
+
record(event: EventNotification): void;
|
|
24
|
+
/** Resolves once every write queued so far has settled. */
|
|
25
|
+
flush(): Promise<void>;
|
|
26
|
+
};
|
|
27
|
+
export declare const createEventsLog: (options: {
|
|
28
|
+
file: AppendOnlyFile;
|
|
29
|
+
maxBytes: number;
|
|
30
|
+
warn: (message: string) => void;
|
|
31
|
+
}) => EventsLog;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** In-memory {@link AppendOnlyFile} for tests: inspect the lines written and the backup, or make
|
|
2
|
+
* appends fail. */
|
|
3
|
+
import type { AppendOnlyFile } from "./events-log.js";
|
|
4
|
+
export declare class MemoryAppendOnlyFile implements AppendOnlyFile {
|
|
5
|
+
private current;
|
|
6
|
+
private backup;
|
|
7
|
+
private appendError;
|
|
8
|
+
append(line: string): Promise<void>;
|
|
9
|
+
size(): Promise<number>;
|
|
10
|
+
rotate(): Promise<void>;
|
|
11
|
+
/** Lines in the current file, without their newlines. */
|
|
12
|
+
lines(): string[];
|
|
13
|
+
/** Lines in the backup, without their newlines. */
|
|
14
|
+
backupLines(): string[];
|
|
15
|
+
/** Makes every append throw `error` until called again with `undefined`. */
|
|
16
|
+
failAppendsWith(error: Error | undefined): void;
|
|
17
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** {@link AppendOnlyFile} on the real filesystem; the backup is `<path>.1`. */
|
|
2
|
+
import type { AppendOnlyFile } from "./events-log.js";
|
|
3
|
+
export declare class NodeAppendOnlyFile implements AppendOnlyFile {
|
|
4
|
+
private readonly path;
|
|
5
|
+
constructor(path: string);
|
|
6
|
+
append(line: string): Promise<void>;
|
|
7
|
+
size(): Promise<number>;
|
|
8
|
+
rotate(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The error the daemon's RPC handlers throw to answer a request with a typed wire error
|
|
3
3
|
* (ARCHITECTURE.md §5). It lives apart from `rpc-server.ts` so the modules that *raise* it — the
|
|
4
4
|
* session engine, the call engine — don't drag the socket server itself into every bundle that
|
|
5
|
-
* needs, say, a call-timeout constant from `calls.ts` (the CLI's `
|
|
5
|
+
* needs, say, a call-timeout constant from `calls.ts` (the CLI's `tools call` route does).
|
|
6
6
|
*/
|
|
7
7
|
import type { ErrorType } from "@appduct/shared";
|
|
8
8
|
export declare class RpcApplicationError extends Error {
|
|
@@ -11,6 +11,8 @@ export type StateDirPaths = {
|
|
|
11
11
|
socketPath: string;
|
|
12
12
|
pidFilePath: string;
|
|
13
13
|
logFilePath: string;
|
|
14
|
+
/** The daemon's own event log; rotated to `<eventsLogPath>.1`. */
|
|
15
|
+
eventsLogPath: string;
|
|
14
16
|
keyPath: string;
|
|
15
17
|
configPath: string;
|
|
16
18
|
auditDir: string;
|
package/dist/errors.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export declare const isAppductCliError: (value: unknown) => value is AppductCliE
|
|
|
18
18
|
* Converts any error thrown from a command handler into the CLI's rendered `CliError` shape.
|
|
19
19
|
* RPC/app-originated failures (`DaemonRpcError` with a preserved `data.type`) keep that type
|
|
20
20
|
* **verbatim** in `type` — never re-wrapped under a generic bucket (ARCHITECTURE.md §5) — so e.g.
|
|
21
|
-
* `
|
|
21
|
+
* `tools call --json` on a failing tool call renders `error.type === "tool_execution_error"`, not
|
|
22
22
|
* `"tool_error"`. `getExitCodeForError` still buckets that same error into a sysexits class.
|
|
23
23
|
*/
|
|
24
24
|
export declare const toCliError: (error: unknown) => CliError;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `waitForAppEvent` (issue #114): the one drain-then-live wait implementation shared by the
|
|
3
|
+
* built-in `appduct_wait_for_event` MCP tool (`mcp/events-tool.ts`) and `appduct/client`'s
|
|
4
|
+
* `AppClient.waitForEvent` (`client/app-client.ts`), which used to each carry their own copy.
|
|
5
|
+
*
|
|
6
|
+
* Filtering by `name` (a whole-name, case-sensitive glob — issue #112) and truncating a payload
|
|
7
|
+
* over `payloadMaxBytes` (issue #113) both happen daemon-side, via the same `projectAppEvent`
|
|
8
|
+
* (`daemon/event-bus.ts`) that `events.since`'s drain and `events.subscribe`'s live fan-out both
|
|
9
|
+
* already call — this module forwards `name`/`payloadMaxBytes` to both RPCs and never re-checks
|
|
10
|
+
* a name or re-truncates a payload itself.
|
|
11
|
+
*
|
|
12
|
+
* The drain-then-live shape exists to close the race `appduct_wait_for_session` doesn't have to
|
|
13
|
+
* worry about: a session is either claimed or not, but an app event can fire between "the caller
|
|
14
|
+
* decides to wait" and "the wait subscription lands". The notification listener is registered
|
|
15
|
+
* *before* `events.subscribe` is even sent — not merely before it resolves — because the
|
|
16
|
+
* daemon's response to that call and a `notify()` for a brand-new event can arrive in the same
|
|
17
|
+
* socket chunk; `rpc/client.ts`'s `DaemonStream` fans a chunk's notification lines out to
|
|
18
|
+
* whatever listeners exist at the moment it's processed, so a listener added only after `await`ing
|
|
19
|
+
* that response could miss a notification line from that same chunk. Arrivals before the drain
|
|
20
|
+
* has been merged into the backlog are buffered, then merged in (deduped by `seq`) before falling
|
|
21
|
+
* through to the live phase.
|
|
22
|
+
*
|
|
23
|
+
* `stream` is the caller's: opened and closed by the caller. Every caller of this module opens
|
|
24
|
+
* one stream per `waitForAppEvent` call — the MCP tool always did (`openDaemonStream` per
|
|
25
|
+
* `tools/call`), and the SDK now does too, so concurrent `waitForEvent` calls with different
|
|
26
|
+
* `name`s never clobber a shared connection's single `events.subscribe` filter.
|
|
27
|
+
*/
|
|
28
|
+
import { type AppEvent } from "@appduct/shared";
|
|
29
|
+
import type { DaemonStream } from "../rpc/client.js";
|
|
30
|
+
export type WaitForAppEventOptions = {
|
|
31
|
+
sessionId: string;
|
|
32
|
+
/** Whole-name, case-sensitive glob (issue #112): `*` matches any run of characters, a pattern
|
|
33
|
+
* with no `*` is an exact name — forwarded verbatim to `events.since`/`events.subscribe`. */
|
|
34
|
+
name: string;
|
|
35
|
+
/** Exclusive lower bound on `AppEvent.seq`; omitted drains the whole retained buffer. */
|
|
36
|
+
since?: number;
|
|
37
|
+
timeoutMs: number;
|
|
38
|
+
/** Caps the resolved event's payload to this many UTF-8 bytes of its JSON, daemon-side, on
|
|
39
|
+
* both the drain and the live half. Omitted, payloads are delivered whole. */
|
|
40
|
+
payloadMaxBytes?: number;
|
|
41
|
+
};
|
|
42
|
+
export type WaitForAppEventResult = {
|
|
43
|
+
event: AppEvent;
|
|
44
|
+
/** `events.since`'s own `dropped` from the drain: app events after `since` that were evicted
|
|
45
|
+
* before the drain could return them (`0` once nothing has fallen off). Reflects only the
|
|
46
|
+
* drain — an event that resolves this wait live was delivered, not dropped. */
|
|
47
|
+
dropped: number;
|
|
48
|
+
};
|
|
49
|
+
/** Raised when nothing matching `name` arrived within `timeoutMs`. */
|
|
50
|
+
export declare class WaitForAppEventTimeoutError extends Error {
|
|
51
|
+
readonly eventName: string;
|
|
52
|
+
readonly timeoutMs: number;
|
|
53
|
+
constructor(eventName: string, timeoutMs: number);
|
|
54
|
+
}
|
|
55
|
+
/** Raised when `stream` closes (daemon gone, `stop()`, etc.) while a live wait was in flight. */
|
|
56
|
+
export declare class WaitForAppEventConnectionClosedError extends Error {
|
|
57
|
+
readonly eventName: string;
|
|
58
|
+
constructor(eventName: string);
|
|
59
|
+
}
|
|
60
|
+
export declare const waitForAppEvent: (stream: DaemonStream, options: WaitForAppEventOptions) => Promise<WaitForAppEventResult>;
|
package/dist/index.js
CHANGED
|
@@ -121,27 +121,30 @@ var createCli = () => {
|
|
|
121
121
|
'Android package name to write as "appId.android" (needed to deliver a link with --open android).'
|
|
122
122
|
);
|
|
123
123
|
cli.command("keygen", "Generate an Appduct host private key and print its app fingerprint.").option("--out <path>", "Destination path (default: <state-dir>/key.pem).").option("--force", "Overwrite an existing key at the destination path.");
|
|
124
|
-
cli.command("
|
|
124
|
+
cli.command("sessions [...args]", "Manage Appduct sessions: ls, revoke, or link.").usage("sessions <ls|revoke|link> [selector] [args]").option("--ttl <seconds>", "link: time-to-live in seconds (default: from config.json).").option("--qr", "link: also render the deep link as a terminal QR code.").option(
|
|
125
125
|
"--scheme <scheme>",
|
|
126
|
-
`
|
|
126
|
+
`link: deep-link URI scheme (also: APPDUCT_SCHEME; default: .appduct/config.json, then app.json's "expo.scheme", then the Android/iOS project files in <cwd>).`
|
|
127
127
|
).option(
|
|
128
128
|
"--open <target>",
|
|
129
|
-
"
|
|
130
|
-
).option("--device <id>", "adb serial, simulator udid or paired-device udid to target when --open is ambiguous.").option(
|
|
129
|
+
"link: deliver the link automatically via adb/simctl/devicectl (android|ios-sim|ios-device; ios-device is experimental)."
|
|
130
|
+
).option("--device <id>", "link: adb serial, simulator udid or paired-device udid to target when --open is ambiguous.").option(
|
|
131
131
|
"--app-id <id>",
|
|
132
|
-
`
|
|
132
|
+
`link: installed app id for --open android/ios-device (default: .appduct/config.json's "appId.<platform>").`
|
|
133
133
|
).option(
|
|
134
134
|
"--relaunch",
|
|
135
|
-
"
|
|
135
|
+
"link: with --open ios-device, terminate a running instance first (try this if delivery to an already-running app does nothing)."
|
|
136
136
|
);
|
|
137
|
-
cli.command("
|
|
138
|
-
cli.command("tools [selector] [name]", "List a session's tools, or show one tool's full schema.").option("--full", "Render full schemas/annotations for every listed tool.").option("--group <name>", 'Only tools in this group ("checkout" includes "checkout/payment").').option("--groups", "List the session's groups with tool counts instead of its tools.").option("--filter <text>", "Only tools whose name or description contains this text (case-insensitive).").option("--limit <n>", "Show at most n tools.").option("--offset <n>", "Skip the first n tools of the sorted list.");
|
|
139
|
-
cli.command("invoke [selector] [tool]", "Call a tool on a session.").option("--input <json>", "Tool input arguments as a JSON object.").option(
|
|
137
|
+
cli.command("tools [...args]", "List a session's tools, describe one, or call one: ls, describe, or call.").usage("tools <ls|describe|call> [selector] [args]").option("--full", "ls: render full schemas/annotations for every listed tool.").option("--group <name>", 'ls: only tools in this group ("checkout" includes "checkout/payment").').option("--groups", "ls: list the session's groups with tool counts instead of its tools.").option("--filter <text>", "ls: only tools whose name or description contains this text (case-insensitive).").option("--limit <n>", "ls: show at most n tools.").option("--offset <n>", "ls: skip the first n tools of the sorted list.").option("--input <json>", "call: tool input arguments as a JSON object.").option(
|
|
140
138
|
"--timeout <ms>",
|
|
141
|
-
"
|
|
139
|
+
"call: call timeout in milliseconds. Shortens the deadline; it cannot extend one past the app's own timer, which is the tool's declared timeoutMs (else 10000)."
|
|
140
|
+
);
|
|
141
|
+
cli.command("events [...args]", "Stream the app's events, or replay them since a cursor: tail or since.").usage("events <tail|since> [selector] [args]").option("--follow", "tail: accepted for script readability; the default behavior already follows.").option(
|
|
142
|
+
"--name <glob>",
|
|
143
|
+
'Only events whose name matches this whole-name, case-sensitive glob ("*" matches any run of characters).'
|
|
144
|
+
).option(
|
|
145
|
+
"--payload-max-bytes <n>",
|
|
146
|
+
"Cap each event's payload at this many UTF-8 bytes of its JSON; over the cap, the event carries payloadPreview, payloadBytes and truncated: true instead of payload."
|
|
142
147
|
);
|
|
143
|
-
cli.command("events [selector]", "Stream session/tool events until interrupted.").option("--follow", "Accepted for script readability; the default behavior already follows.").option("--since <cursor>", "One-shot: print events retained since this cursor instead of streaming live.");
|
|
144
|
-
cli.command("revoke [selector]", "Revoke a session.");
|
|
145
148
|
cli.command("mcp", "Start a stdio MCP server that gives MCP clients access to connected apps' tools.").option(
|
|
146
149
|
"--scheme <scheme>",
|
|
147
150
|
"Deep-link URI scheme for appduct_connect (also: APPDUCT_SCHEME)."
|
|
@@ -164,6 +167,7 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
164
167
|
"eventBufferSize",
|
|
165
168
|
"auditRetentionDays",
|
|
166
169
|
"daemonLogMaxBytes",
|
|
170
|
+
"eventsLogMaxBytes",
|
|
167
171
|
"policy",
|
|
168
172
|
"advertisedIp",
|
|
169
173
|
"scheme",
|
|
@@ -223,6 +227,7 @@ var defaultConfig = (paths) => {
|
|
|
223
227
|
eventBufferSize: 256,
|
|
224
228
|
auditRetentionDays: DEFAULT_AUDIT_RETENTION_DAYS,
|
|
225
229
|
daemonLogMaxBytes: DEFAULT_DAEMON_LOG_MAX_BYTES,
|
|
230
|
+
eventsLogMaxBytes: 10 * 1024 * 1024,
|
|
226
231
|
restartDaemonOnVersionMismatch: false,
|
|
227
232
|
policy: {
|
|
228
233
|
default: "allow",
|
|
@@ -285,6 +290,9 @@ var loadConfig = async (paths, options = {}) => {
|
|
|
285
290
|
if (parsed.daemonLogMaxBytes !== void 0) {
|
|
286
291
|
config.daemonLogMaxBytes = requirePositiveInteger(parsed.daemonLogMaxBytes, "daemonLogMaxBytes");
|
|
287
292
|
}
|
|
293
|
+
if (parsed.eventsLogMaxBytes !== void 0) {
|
|
294
|
+
config.eventsLogMaxBytes = requirePositiveInteger(parsed.eventsLogMaxBytes, "eventsLogMaxBytes");
|
|
295
|
+
}
|
|
288
296
|
if (parsed.restartDaemonOnVersionMismatch !== void 0) {
|
|
289
297
|
config.restartDaemonOnVersionMismatch = requireBoolean(
|
|
290
298
|
parsed.restartDaemonOnVersionMismatch,
|
|
@@ -333,6 +341,7 @@ var getStateDirPaths = (stateDir) => {
|
|
|
333
341
|
socketPath: join3(stateDir, "daemon.sock"),
|
|
334
342
|
pidFilePath: join3(stateDir, "daemon.pid"),
|
|
335
343
|
logFilePath: join3(stateDir, "daemon.log"),
|
|
344
|
+
eventsLogPath: join3(stateDir, "events.log"),
|
|
336
345
|
keyPath: join3(stateDir, "key.pem"),
|
|
337
346
|
configPath: join3(stateDir, "config.json"),
|
|
338
347
|
auditDir: join3(stateDir, "audit"),
|
|
@@ -654,9 +663,6 @@ var renderLsData = (colors, data, now) => {
|
|
|
654
663
|
...rows.map((row) => ` ${row.map((cell, index) => cell.padEnd(widths[index])).join(" ")}`)
|
|
655
664
|
];
|
|
656
665
|
};
|
|
657
|
-
var isToolsListing = (data) => {
|
|
658
|
-
return typeof data === "object" && data !== null && Array.isArray(data.tools);
|
|
659
|
-
};
|
|
660
666
|
var isToolGroupsListing = (data) => {
|
|
661
667
|
return typeof data === "object" && data !== null && Array.isArray(data.groups) && !Array.isArray(data.tools);
|
|
662
668
|
};
|
|
@@ -674,7 +680,7 @@ var renderEmptyToolsLine = (data) => {
|
|
|
674
680
|
}
|
|
675
681
|
if (data.group !== void 0) {
|
|
676
682
|
const match = data.filter === void 0 ? "" : ` match ${JSON.stringify(data.filter)}`;
|
|
677
|
-
return ` No tools in group ${JSON.stringify(data.group)}${match}. Run \`appduct tools --groups\` to see the session's groups.`;
|
|
683
|
+
return ` No tools in group ${JSON.stringify(data.group)}${match}. Run \`appduct tools ls --groups\` to see the session's groups.`;
|
|
678
684
|
}
|
|
679
685
|
return data.filter === void 0 ? " No tools registered." : ` No tools match ${JSON.stringify(data.filter)}.`;
|
|
680
686
|
};
|
|
@@ -756,7 +762,7 @@ var renderToolSummaryTable = (colors, data) => {
|
|
|
756
762
|
...grouped ? renderGroupedToolLines(colors, data.tools) : data.tools.flatMap((tool) => renderToolSummaryLines(tool, " ")),
|
|
757
763
|
...renderTruncationLine(data),
|
|
758
764
|
"",
|
|
759
|
-
"Run `appduct tools <name>` for a tool's full schema."
|
|
765
|
+
"Run `appduct tools describe <name>` for a tool's full schema."
|
|
760
766
|
];
|
|
761
767
|
};
|
|
762
768
|
var renderToolDetail = (colors, tool, flags) => {
|
|
@@ -808,16 +814,13 @@ var renderToolGroups = (colors, data) => {
|
|
|
808
814
|
colors.green("Groups"),
|
|
809
815
|
...data.groups.map((entry) => ` ${label(entry).padEnd(width)} ${String(entry.total).padStart(countWidth)}`),
|
|
810
816
|
"",
|
|
811
|
-
`${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.")
|
|
817
|
+
`${data.total} tool${data.total === 1 ? "" : "s"} in total. ` + (hasAnyGroup(data.groups) ? "Run `appduct tools ls --group <name>` to list one group's tools." : "No tool declares a group.")
|
|
812
818
|
];
|
|
813
819
|
};
|
|
814
|
-
var
|
|
820
|
+
var renderToolsListData = (colors, data, flags, full) => {
|
|
815
821
|
if (isToolGroupsListing(data)) {
|
|
816
822
|
return renderToolGroups(colors, data);
|
|
817
823
|
}
|
|
818
|
-
if (!isToolsListing(data)) {
|
|
819
|
-
return renderToolDetail(colors, data, flags);
|
|
820
|
-
}
|
|
821
824
|
return full ? renderToolsFullListing(colors, data, flags) : renderToolSummaryTable(colors, data);
|
|
822
825
|
};
|
|
823
826
|
var renderInvokeData = (colors, data, flags) => {
|
|
@@ -1003,15 +1006,17 @@ var renderSuccessData = (colors, command, data, options) => {
|
|
|
1003
1006
|
return renderInitData(colors, data, flags);
|
|
1004
1007
|
case "keygen":
|
|
1005
1008
|
return renderKeygenData(colors, data, flags);
|
|
1006
|
-
case "link":
|
|
1009
|
+
case "sessions link":
|
|
1007
1010
|
return renderLinkData(colors, data, flags, options.qr);
|
|
1008
|
-
case "ls":
|
|
1011
|
+
case "sessions ls":
|
|
1009
1012
|
return renderLsData(colors, data, options.now ?? /* @__PURE__ */ new Date());
|
|
1010
|
-
case "tools":
|
|
1011
|
-
return
|
|
1012
|
-
case "
|
|
1013
|
+
case "tools ls":
|
|
1014
|
+
return renderToolsListData(colors, data, flags, options.full);
|
|
1015
|
+
case "tools describe":
|
|
1016
|
+
return renderToolDetail(colors, data, flags);
|
|
1017
|
+
case "tools call":
|
|
1013
1018
|
return renderInvokeData(colors, data, flags);
|
|
1014
|
-
case "revoke":
|
|
1019
|
+
case "sessions revoke":
|
|
1015
1020
|
return renderRevokeData(colors, data);
|
|
1016
1021
|
case "daemon run":
|
|
1017
1022
|
return renderDaemonRunData(colors, data, flags);
|
|
@@ -1081,12 +1086,21 @@ var renderEventLine = (event, flags) => {
|
|
|
1081
1086
|
const dataSuffix = event.data === void 0 ? "" : ` ${formatJson(event.data, flags)}`;
|
|
1082
1087
|
return `${colors.dim(timestamp)} ${colors.green(event.kind)}${target ? ` ${target}` : ""}${dataSuffix}`;
|
|
1083
1088
|
};
|
|
1084
|
-
var
|
|
1089
|
+
var shellQuote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
|
|
1090
|
+
var renderEventsCursorLine = (cursor, flags, selector, filters) => {
|
|
1085
1091
|
if (flags.json) {
|
|
1086
|
-
return JSON.stringify({ cursor });
|
|
1092
|
+
return JSON.stringify({ cursor: cursor.cursor, dropped: cursor.dropped, remaining: cursor.remaining });
|
|
1087
1093
|
}
|
|
1088
1094
|
const colors = pc.createColors(flags.color);
|
|
1089
|
-
|
|
1095
|
+
const target = selector === void 0 ? `${cursor.cursor}` : `${selector} ${cursor.cursor}`;
|
|
1096
|
+
const flagTokens = [
|
|
1097
|
+
...filters?.name === void 0 ? [] : [`--name ${shellQuote(filters.name)}`],
|
|
1098
|
+
...filters?.payloadMaxBytes === void 0 ? [] : [`--payload-max-bytes ${filters.payloadMaxBytes}`]
|
|
1099
|
+
];
|
|
1100
|
+
const command = [`appduct events since ${target}`, ...flagTokens].join(" ");
|
|
1101
|
+
return colors.dim(
|
|
1102
|
+
`cursor: ${cursor.cursor}, dropped: ${cursor.dropped}, remaining: ${cursor.remaining} (run "${command}" to resume from here)`
|
|
1103
|
+
);
|
|
1090
1104
|
};
|
|
1091
1105
|
|
|
1092
1106
|
// src/cli/envelope.ts
|
|
@@ -1183,21 +1197,30 @@ var DAEMON_RESTART_ENV = "APPDUCT_DAEMON_RESTART";
|
|
|
1183
1197
|
var isEnvTruthy = (value) => {
|
|
1184
1198
|
return value === "1" || value?.toLowerCase() === "true";
|
|
1185
1199
|
};
|
|
1200
|
+
var REMOVED_COMMANDS = {
|
|
1201
|
+
ls: "sessions ls",
|
|
1202
|
+
revoke: "sessions revoke",
|
|
1203
|
+
link: "sessions link",
|
|
1204
|
+
invoke: "tools call"
|
|
1205
|
+
};
|
|
1206
|
+
var rootUnknownCommandError = (word) => {
|
|
1207
|
+
if (word !== void 0 && Object.hasOwn(REMOVED_COMMANDS, word)) {
|
|
1208
|
+
return usageError(`Unknown command "${word}"; use "appduct ${REMOVED_COMMANDS[word]}".`);
|
|
1209
|
+
}
|
|
1210
|
+
return unknownCommandError(word);
|
|
1211
|
+
};
|
|
1186
1212
|
var rootRouter = createRouter(
|
|
1187
1213
|
{
|
|
1188
1214
|
init: () => import("./cli/routes/init.js"),
|
|
1189
1215
|
keygen: () => import("./cli/routes/keygen.js"),
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
invoke: () => import("./cli/routes/invoke.js"),
|
|
1194
|
-
revoke: () => import("./cli/routes/revoke.js"),
|
|
1195
|
-
events: () => import("./cli/routes/events.js"),
|
|
1216
|
+
sessions: () => import("./cli/routes/sessions/index.js"),
|
|
1217
|
+
tools: () => import("./cli/routes/tools/index.js"),
|
|
1218
|
+
events: () => import("./cli/routes/events/index.js"),
|
|
1196
1219
|
mcp: () => import("./cli/routes/mcp.js"),
|
|
1197
1220
|
doctor: () => import("./cli/routes/doctor.js"),
|
|
1198
1221
|
daemon: () => import("./cli/routes/daemon/index.js")
|
|
1199
1222
|
},
|
|
1200
|
-
{ unknown:
|
|
1223
|
+
{ unknown: rootUnknownCommandError }
|
|
1201
1224
|
);
|
|
1202
1225
|
var runCli = async (argv, options = {}) => {
|
|
1203
1226
|
const writers = {
|
|
@@ -1236,7 +1259,7 @@ var runCli = async (argv, options = {}) => {
|
|
|
1236
1259
|
return executeCommand(
|
|
1237
1260
|
"cli",
|
|
1238
1261
|
() => {
|
|
1239
|
-
throw
|
|
1262
|
+
throw rootUnknownCommandError(parsedArgs[0]);
|
|
1240
1263
|
},
|
|
1241
1264
|
{
|
|
1242
1265
|
flags,
|
package/dist/link.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Shared core of `link.create` + deep-link composition + optional emulator/simulator delivery
|
|
3
3
|
* (ARCHITECTURE.md §8, §10): mints a pending session via `link.create`, then composes
|
|
4
4
|
* `<scheme>:///?appduct=<payload>&pin=<spki-pin>` and optionally delivers it to a booted
|
|
5
|
-
* Android emulator/device or iOS simulator. Used by `commands/link.ts` (`appduct link`) and
|
|
5
|
+
* Android emulator/device or iOS simulator. Used by `commands/link.ts` (`appduct sessions link`) and
|
|
6
6
|
* `client/bootstrap.ts` (`appduct/client`'s `link()`) so this shape — scheme resolution, the
|
|
7
7
|
* `pin` query param, the `127.0.0.1` emulator/simulator address override (which the experimental
|
|
8
8
|
* `ios-device` target deliberately opts out of) — can't drift between the CLI and the programmatic
|
package/dist/mcp/app-tools.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The built-in `appduct_list_tools` / `appduct_describe_tool` / `appduct_call_tool` MCP tools
|
|
3
3
|
* (ARCHITECTURE.md §9). The app's own tools are never listed as MCP tools. An agent reaches them
|
|
4
|
-
* the way the CLI does: compact signatures first (`appduct tools`), one full schema on demand
|
|
5
|
-
* (`appduct tools <name>`), then a call by name (`appduct
|
|
4
|
+
* the way the CLI does: compact signatures first (`appduct tools ls`), one full schema on demand
|
|
5
|
+
* (`appduct tools describe <name>`), then a call by name (`appduct tools call`). A registry of
|
|
6
|
+
* hundreds of tools
|
|
6
7
|
* therefore costs a client three fixed tool definitions, and `tools/list` never changes while an
|
|
7
8
|
* agent works.
|
|
8
9
|
*
|
|
@@ -71,7 +71,7 @@ export declare class McpBuiltinToolError extends Error {
|
|
|
71
71
|
export type ConnectToolDeps = {
|
|
72
72
|
call: DaemonCall;
|
|
73
73
|
/** The scheme composing the deep link (`<scheme>:///?appduct=<payload>`), resolved once at
|
|
74
|
-
* server startup against the shared order in `scheme.ts` — the same one `appduct link` uses,
|
|
74
|
+
* server startup against the shared order in `scheme.ts` — the same one `appduct sessions link` uses,
|
|
75
75
|
* so an app root with an `app.json` needs no configuration at all. */
|
|
76
76
|
scheme?: string;
|
|
77
77
|
/** Every location `scheme.ts` consulted, named in the failure below so an agent can tell its
|
|
@@ -4,19 +4,12 @@
|
|
|
4
4
|
* per-session retention buffer on the daemon side — see `daemon/event-bus.ts`). MCP is
|
|
5
5
|
* request/response, so an agent otherwise has no way to observe anything the app pushes.
|
|
6
6
|
*
|
|
7
|
-
* `appduct_wait_for_event`
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* listener is registered *before* the `events.since` drain call is even sent — not merely before
|
|
12
|
-
* it resolves — because the daemon's response to that call and a subsequent `notify()` for a new
|
|
13
|
-
* event can arrive in the same socket chunk; `rpc/client.ts` fans a chunk's notification lines out
|
|
14
|
-
* to whatever listeners exist at the moment it's processed, so a listener added only after `await
|
|
15
|
-
* stream.call(eventsSince, …)` returns can miss an event that was already in that same chunk.
|
|
16
|
-
* Arrivals before the drain has been merged into the backlog are buffered, then merged in
|
|
17
|
-
* (deduped by `seq`) before falling through to the live phase — see `handleWaitForEventTool` below.
|
|
7
|
+
* `appduct_wait_for_event`'s drain-then-live wait is `events/index.ts`'s `waitForAppEvent`
|
|
8
|
+
* (issue #114) — the same implementation `appduct/client`'s `AppClient.waitForEvent` uses — this
|
|
9
|
+
* module only resolves the selector, applies MCP-specific defaults (`payloadMaxBytes`), and maps
|
|
10
|
+
* that module's typed errors onto this tool's `tool_timeout`/`tool_execution_error`.
|
|
18
11
|
*/
|
|
19
|
-
import { type
|
|
12
|
+
import { type AppEvent } from "@appduct/shared";
|
|
20
13
|
import { type SpawnFn } from "../rpc/client.js";
|
|
21
14
|
import type { DaemonCall } from "./daemon-tools.js";
|
|
22
15
|
export declare const EVENTS_TOOL_NAME = "appduct_events";
|
|
@@ -38,17 +31,17 @@ export declare const EVENTS_TOOL_DESCRIPTOR: {
|
|
|
38
31
|
readonly type: "integer";
|
|
39
32
|
readonly minimum: 0;
|
|
40
33
|
};
|
|
41
|
-
readonly kinds: {
|
|
42
|
-
readonly type: "array";
|
|
43
|
-
readonly items: {
|
|
44
|
-
readonly type: "string";
|
|
45
|
-
readonly enum: readonly ["daemon_started", "link_created", "link_expired", "session_claimed", "session_suspended", "session_resumed", "session_revoked", "session_expired", "tools_changed", "app_event", "tool_call_started", "tool_call_progress", "tool_call_finished"];
|
|
46
|
-
};
|
|
47
|
-
};
|
|
48
34
|
readonly limit: {
|
|
49
35
|
readonly type: "integer";
|
|
50
36
|
readonly exclusiveMinimum: 0;
|
|
51
37
|
};
|
|
38
|
+
readonly name: {
|
|
39
|
+
readonly type: "string";
|
|
40
|
+
};
|
|
41
|
+
readonly payloadMaxBytes: {
|
|
42
|
+
readonly type: "integer";
|
|
43
|
+
readonly exclusiveMinimum: 0;
|
|
44
|
+
};
|
|
52
45
|
};
|
|
53
46
|
readonly additionalProperties: false;
|
|
54
47
|
};
|
|
@@ -65,12 +58,6 @@ export declare const WAIT_FOR_EVENT_TOOL_DESCRIPTOR: {
|
|
|
65
58
|
readonly name: {
|
|
66
59
|
readonly type: "string";
|
|
67
60
|
};
|
|
68
|
-
readonly match: {
|
|
69
|
-
readonly type: "object";
|
|
70
|
-
readonly additionalProperties: {
|
|
71
|
-
readonly type: readonly ["string", "number", "boolean", "null"];
|
|
72
|
-
};
|
|
73
|
-
};
|
|
74
61
|
readonly since: {
|
|
75
62
|
readonly type: "integer";
|
|
76
63
|
readonly minimum: 0;
|
|
@@ -79,6 +66,10 @@ export declare const WAIT_FOR_EVENT_TOOL_DESCRIPTOR: {
|
|
|
79
66
|
readonly type: "number";
|
|
80
67
|
readonly exclusiveMinimum: 0;
|
|
81
68
|
};
|
|
69
|
+
readonly payloadMaxBytes: {
|
|
70
|
+
readonly type: "integer";
|
|
71
|
+
readonly exclusiveMinimum: 0;
|
|
72
|
+
};
|
|
82
73
|
};
|
|
83
74
|
readonly required: readonly ["name"];
|
|
84
75
|
readonly additionalProperties: false;
|
|
@@ -87,7 +78,13 @@ export declare const WAIT_FOR_EVENT_TOOL_DESCRIPTOR: {
|
|
|
87
78
|
export type EventsToolDeps = {
|
|
88
79
|
call: DaemonCall;
|
|
89
80
|
};
|
|
90
|
-
export
|
|
81
|
+
export type EventsToolResult = {
|
|
82
|
+
events: AppEvent[];
|
|
83
|
+
cursor: number;
|
|
84
|
+
dropped: number;
|
|
85
|
+
remaining: number;
|
|
86
|
+
};
|
|
87
|
+
export declare const handleEventsTool: (rawArgs: unknown, deps: EventsToolDeps) => Promise<EventsToolResult>;
|
|
91
88
|
export type WaitForEventToolDeps = {
|
|
92
89
|
stateDir: string;
|
|
93
90
|
spawn?: SpawnFn;
|
|
@@ -98,13 +95,10 @@ export type WaitForEventToolDeps = {
|
|
|
98
95
|
sendNotification: (notification: unknown) => Promise<void>;
|
|
99
96
|
};
|
|
100
97
|
};
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
/** Unix ms. */
|
|
107
|
-
ts: number;
|
|
108
|
-
seq: number;
|
|
98
|
+
/** An `AppEvent` (full or truncated — issue #113) plus `dropped` from the drain (issue #114). */
|
|
99
|
+
export type WaitForEventToolResult = AppEvent & {
|
|
100
|
+
/** App events after `since` that were evicted before this call could return them (`0` once
|
|
101
|
+
* nothing has fallen off). */
|
|
102
|
+
dropped: number;
|
|
109
103
|
};
|
|
110
104
|
export declare const handleWaitForEventTool: (rawArgs: unknown, deps: WaitForEventToolDeps) => Promise<WaitForEventToolResult>;
|
package/dist/native-scheme.d.ts
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* **Disagreement is not guessed away.** If more than one probe below resolves to a *different*
|
|
26
26
|
* scheme (e.g. a monorepo whose `android/` and `ios/` declare different values), discovery throws
|
|
27
27
|
* a usage error naming every conflicting source instead of picking one — silently choosing would
|
|
28
|
-
* mean `appduct link` opens the wrong app on whichever platform lost.
|
|
28
|
+
* mean `appduct sessions link` opens the wrong app on whichever platform lost.
|
|
29
29
|
*/
|
|
30
30
|
export type NativeSchemeSource = "android-gradle" | "android-manifest" | "ios-info-plist" | "ios-project-yml";
|
|
31
31
|
export type NativeSchemeProbeResult = {
|
package/dist/output.d.ts
CHANGED
|
@@ -8,18 +8,41 @@ export type RenderOptions = {
|
|
|
8
8
|
* (which is only attached under `--verbose`). Defaults to the wall clock when omitted, matching
|
|
9
9
|
* the pre-`--verbose` fallback behavior for a caller that doesn't care (e.g. non-`ls` tests). */
|
|
10
10
|
now?: Date;
|
|
11
|
-
/** `link`-only: also render the deep link as terminal QR art (never affects `--json`
|
|
11
|
+
/** `sessions link`-only: also render the deep link as terminal QR art (never affects `--json`
|
|
12
|
+
* output). */
|
|
12
13
|
qr?: boolean;
|
|
13
|
-
/** `tools`-only: render full schemas/annotations for every listed tool, not just
|
|
14
|
+
/** `tools ls`-only: render full schemas/annotations for every listed tool, not just
|
|
15
|
+
* name+description. */
|
|
14
16
|
full?: boolean;
|
|
15
17
|
};
|
|
16
18
|
export declare const renderResult: (result: CliResult<unknown>, options: RenderOptions) => {
|
|
17
19
|
stdout?: string;
|
|
18
20
|
stderr?: string;
|
|
19
21
|
};
|
|
20
|
-
/** Renders one `appduct events` line: NDJSON under `--json`, a
|
|
22
|
+
/** Renders one `appduct events tail`/`appduct events since` line: NDJSON under `--json`, a
|
|
23
|
+
* compact human line otherwise. */
|
|
21
24
|
export declare const renderEventLine: (event: EventNotification, flags: GlobalFlags) => string;
|
|
22
|
-
/**
|
|
25
|
+
/** The filter flags to echo back into the hinted resume command in {@link renderEventsCursorLine}
|
|
26
|
+
* (issue #115's should-fix on #96's echoed selector): the same `--name`/`--payload-max-bytes`
|
|
27
|
+
* the caller passed to this pull, forwarded as-is so following the hint doesn't silently widen
|
|
28
|
+
* the query. */
|
|
29
|
+
export type EventsCursorLineFilters = {
|
|
30
|
+
name?: string;
|
|
31
|
+
payloadMaxBytes?: number;
|
|
32
|
+
};
|
|
33
|
+
/** Renders the trailing cursor line for `appduct events since` (issue #6): NDJSON under
|
|
23
34
|
* `--json` so a scripted caller can parse the resume point without maxing `seq` over the printed
|
|
24
|
-
* events (impossible when the response is empty), a human note otherwise.
|
|
25
|
-
|
|
35
|
+
* events (impossible when the response is empty), a human note otherwise. `selector` is the
|
|
36
|
+
* device/session selector the caller passed (issue #96): with two devices connected, a resume
|
|
37
|
+
* hint that dropped it would resolve to `ambiguous_session`, so it is echoed back in the hinted
|
|
38
|
+
* command when present. `dropped`/`remaining` (issue #115) are carried straight from
|
|
39
|
+
* `events.since`'s own result: how many events fell off the retention buffer before this pull,
|
|
40
|
+
* and how many matching events are still waiting beyond this page. `filters` (issue #115) are the
|
|
41
|
+
* `--name`/`--payload-max-bytes` flags this pull used: without them the hint would silently drop
|
|
42
|
+
* the query's filter and cap, so they are echoed into the hinted command exactly like the
|
|
43
|
+
* selector (`--name`'s glob is shell-quoted since it can carry `*` or spaces). */
|
|
44
|
+
export declare const renderEventsCursorLine: (cursor: {
|
|
45
|
+
cursor: number;
|
|
46
|
+
dropped: number;
|
|
47
|
+
remaining: number;
|
|
48
|
+
}, flags: GlobalFlags, selector?: string, filters?: EventsCursorLineFilters) => string;
|