@mercury-fw/confirm-engine 0.25.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/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ # @mercury-fw/confirm-engine
2
+
3
+ ## 0.25.0
4
+
5
+ ### Patch Changes
6
+
7
+ - @mercury-fw/plugin-types@0.25.0
8
+ - @mercury-fw/channel-types@0.25.0
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # @mercury-fw/confirm-engine
2
+
3
+ The confirmation mechanism of [Mercury](https://github.com/lucabro81/mercury-fw): stages an action behind a one-time token and runs it when the token comes back. The core uses it and injects it into the channels; you don't depend on it directly.
4
+
5
+ MIT
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Deterministic text interception for the "confirm" half of the
3
+ * confirm-required flow (something stages an action and hands back a token —
4
+ * see `confirmation-staging.ts` for the "propose" half). `tryConfirm` is called
5
+ * from each channel BEFORE the model ever sees the message, same pattern as
6
+ * `/dump` (`tool-log.ts`) and `NO_REPLY` — running a previously-approved
7
+ * mutation must never depend on the model's own tool-calling judgment.
8
+ *
9
+ * It knows nothing about what the staged action is: it runs the opaque `run`
10
+ * thunk (see `StagedAction`) and reports the outcome. A CLI delete, a future
11
+ * memory purge — same path, because the doing was closed over at stage time.
12
+ */
13
+ import type { ConfirmOutcome } from "@mercury-fw/channel-types";
14
+ import { isTokenShaped, type ConfirmationStore } from "./confirmation-store.ts";
15
+
16
+ /**
17
+ * Signature of the persistent confirmation-note writer the core injects (the
18
+ * app's `writeConfirmationNote`, kept in `wiki/`). Declared here so this library
19
+ * stays free of any app import; the real writer is structurally assignable.
20
+ */
21
+ export type WriteConfirmationNote = (
22
+ vaultPath: string,
23
+ userId: string,
24
+ token: string,
25
+ fields: { status: "pending" | "confirmed" | "failed"; requestedAt: string; resolvedAt: string | null; command: string },
26
+ ) => Promise<void>;
27
+
28
+ /**
29
+ * Returns `null` if `input` doesn't look like a bare confirmation token —
30
+ * the caller should proceed with its normal flow (`runTurn`, etc.).
31
+ * Otherwise always returns a user-facing string, resolved without ever
32
+ * invoking the model: an unknown/expired/wrong-session token gets a
33
+ * canned message, a valid one actually runs the staged action and
34
+ * reports the outcome. No `conferma ` keyword to type or match — the
35
+ * real gate was always `store.take()`'s existence/session/expiry check,
36
+ * not that prefix (see `isTokenShaped`'s own doc comment). A card button
37
+ * click on Google Chat and a bare token typed on the terminal both resolve
38
+ * through this exact same path.
39
+ */
40
+ export type ConfirmDeps = {
41
+ store: ConfirmationStore;
42
+ userId: string;
43
+ vaultPath: string;
44
+ writeConfirmationNoteFn: WriteConfirmationNote;
45
+ now?: () => Date;
46
+ };
47
+
48
+ /** Resolves a token to a structured {@link ConfirmOutcome} (from `@mercury-fw/channel-types`), running the staged action for a match. See `tryConfirm` for the string-returning wrapper. */
49
+ export async function resolveConfirmation(
50
+ input: string,
51
+ sessionKey: string,
52
+ deps: ConfirmDeps,
53
+ ): Promise<ConfirmOutcome> {
54
+ const token = input.trim();
55
+ if (!isTokenShaped(token)) {
56
+ return { status: "not-a-token" };
57
+ }
58
+
59
+ const staged = deps.store.take(sessionKey, token);
60
+ if (!staged) {
61
+ return { status: "not-found" };
62
+ }
63
+
64
+ const result = await staged.run();
65
+ const resolvedAt = (deps.now?.() ?? new Date()).toISOString();
66
+ // Overwrites the same note the propose half wrote (see
67
+ // `confirmation-staging.ts`) so the persistent record reflects what actually
68
+ // happened, never stuck saying "pending" — see the stale-primer bug this
69
+ // guards against. Same resilience tradeoff as the propose side: a
70
+ // wiki-write failure must not stop the user from getting their result.
71
+ try {
72
+ await deps.writeConfirmationNoteFn(deps.vaultPath, deps.userId, token, {
73
+ status: result.ok ? "confirmed" : "failed",
74
+ requestedAt: staged.requestedAt ?? resolvedAt,
75
+ resolvedAt,
76
+ command: staged.describe,
77
+ });
78
+ } catch (err) {
79
+ console.error(`[confirm-flow] failed to write confirmation note: ${String(err)}`);
80
+ }
81
+ return result.ok ? { status: "ok", data: result.data } : { status: "failed", error: result.error };
82
+ }
83
+
84
+ export async function tryConfirm(
85
+ input: string,
86
+ sessionKey: string,
87
+ deps: ConfirmDeps,
88
+ ): Promise<string | null> {
89
+ const outcome = await resolveConfirmation(input, sessionKey, deps);
90
+ switch (outcome.status) {
91
+ case "not-a-token":
92
+ return null;
93
+ case "not-found":
94
+ return "Nessuna conferma in sospeso per questo token — potrebbe essere scaduta, già usata, o mai esistita.";
95
+ case "failed":
96
+ return `Confermato, ma l'esecuzione è fallita: ${outcome.error}`;
97
+ case "ok":
98
+ return `Confermato ed eseguito: ${JSON.stringify(outcome.data)}`;
99
+ }
100
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The "stage" side of the confirm-required flow, as a core-owned closure handed
3
+ * to whoever needs to defer an irreversible action (today the CLI tool; a
4
+ * future memory-deleting plugin all the same). It mints the confirmation token,
5
+ * stashes the opaque action in the `ConfirmationStore`, and writes the "pending"
6
+ * paper-trail note — so the caller never touches the store internals or the
7
+ * wiki, and the confirmation subsystem stays the one place that knows an action
8
+ * is confirmable. The "resolve" side (running the staged thunk once the token
9
+ * comes back) is `tryConfirm` in `confirm-flow.ts`.
10
+ *
11
+ * Bound per session (sessionKey/userId/vaultPath) at the composition root, so
12
+ * the returned function takes only the action itself. The note lives outside
13
+ * inferred/users/<userId>/ — see `writeConfirmationNote`'s own doc comment for
14
+ * why — and its write is best-effort: a failure is logged, never thrown, so it
15
+ * can't stop the user from seeing and confirming the action.
16
+ */
17
+ import type { ConfirmationStore } from "./confirmation-store.ts";
18
+ import type { StageConfirmation } from "@mercury-fw/plugin-types";
19
+ import type { WriteConfirmationNote } from "./confirm-flow.ts";
20
+
21
+ export type { StageConfirmation };
22
+
23
+ export function createStageConfirmation(deps: {
24
+ store: ConfirmationStore;
25
+ sessionKey: string;
26
+ /** Where/who the pending confirmation note is written for. */
27
+ userId: string;
28
+ vaultPath: string;
29
+ /** The note writer, injected by the core (the app's `writeConfirmationNote`). */
30
+ writeConfirmationNoteFn: WriteConfirmationNote;
31
+ /** Test seam; defaults to `() => new Date()`. */
32
+ nowFn?: () => Date;
33
+ }): StageConfirmation {
34
+ const write = deps.writeConfirmationNoteFn;
35
+ const nowFn = deps.nowFn ?? (() => new Date());
36
+
37
+ return async (action) => {
38
+ const requestedAt = nowFn().toISOString();
39
+ const token = deps.store.stage(deps.sessionKey, { run: action.run, describe: action.describe, requestedAt });
40
+ try {
41
+ await write(deps.vaultPath, deps.userId, token, {
42
+ status: "pending",
43
+ requestedAt,
44
+ resolvedAt: null,
45
+ command: action.describe,
46
+ });
47
+ } catch (err) {
48
+ console.error(`[confirmation-staging] failed to write confirmation note: ${String(err)}`);
49
+ }
50
+ return token;
51
+ };
52
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * In-memory staging area for an irreversible action that needs explicit
3
+ * confirmation before it runs. Something stages it here, and the channel gets
4
+ * the returned token confirmed back to it — a card button click on Google Chat,
5
+ * a bare token typed on the terminal — before anything actually happens (see
6
+ * `confirm-flow.ts`). Scoped by `sessionKey` so a token proposed to one session
7
+ * (terminal, or a given Google Chat space+sender) can't be confirmed by
8
+ * another.
9
+ *
10
+ * The action is opaque: a `run` thunk that performs it and a `describe` string
11
+ * for the paper trail. The store — and the whole confirmation subsystem — knows
12
+ * nothing about what kind of action it is: whoever stages it closes over the
13
+ * doing, and the core just runs the thunk when the token comes back. */
14
+ import type { ActionResult } from "@mercury-fw/plugin-types";
15
+ export type StagedAction = { run: () => Promise<ActionResult>; describe: string; requestedAt?: string };
16
+
17
+ /** A pending staging as seen from outside, deliberately WITHOUT its token or
18
+ * its executable thunk: a token is a confirm capability, and the HTTP read
19
+ * surface is unauthenticated, so listing pending confirmations must never hand
20
+ * out the tokens (or a way to run the action) — only the human-readable
21
+ * `summary` (the action's `describe`). */
22
+ export type PendingConfirmation = { sessionKey: string; summary: string; expiresAt: number };
23
+
24
+ export type ConfirmationStore = {
25
+ /** Stages `action` for `sessionKey` and returns a fresh token. */
26
+ stage(sessionKey: string, action: StagedAction): string;
27
+ /** Consumes and returns the staged action for `sessionKey`/`token`, or
28
+ * `null` if it doesn't exist, belongs to a different session, or has
29
+ * expired. Always one-shot: a successful take removes the entry. */
30
+ take(sessionKey: string, token: string): StagedAction | null;
31
+ /** The currently staged, non-expired actions, redacted of their tokens — for
32
+ * read-only introspection (see `PendingConfirmation`). */
33
+ pending(): PendingConfirmation[];
34
+ };
35
+
36
+ // Full alphanumeric — a token is only ever copy-pasted, never read or
37
+ // typed from memory, so legibility (avoiding 0/O/1/l/I) was never the
38
+ // actual point. What makes a token distinguishable from ordinary text is
39
+ // its shape below (two groups joined by a fixed hyphen), not a restricted
40
+ // character set — a restricted set doesn't help anyway: it still collides
41
+ // with any real short word that happens to avoid the same few excluded
42
+ // characters (found live: "second", used as plain conversational text,
43
+ // was indistinguishable from a real token under the old bare-6-char shape).
44
+ const TOKEN_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
45
+ const TOKEN_GROUP_LENGTH = 4;
46
+ const DEFAULT_TTL_MS = 5 * 60_000;
47
+
48
+ function randomGroup(): string {
49
+ const bytes = new Uint8Array(TOKEN_GROUP_LENGTH);
50
+ crypto.getRandomValues(bytes);
51
+ let group = "";
52
+ for (const b of bytes) {
53
+ group += TOKEN_ALPHABET[b % TOKEN_ALPHABET.length];
54
+ }
55
+ return group;
56
+ }
57
+
58
+ /** `<4 alphanumeric>-<4 alphanumeric>`, e.g. `k9m2-x7q4` — see `TOKEN_ALPHABET`'s own doc comment for why this shape, not a restricted character set, is what makes a token distinguishable from ordinary text. */
59
+ function defaultTokenFn(): string {
60
+ return `${randomGroup()}-${randomGroup()}`;
61
+ }
62
+
63
+ type Entry = { action: StagedAction; sessionKey: string; expiresAt: number };
64
+
65
+ export function createConfirmationStore(
66
+ opts: { now?: () => number; ttlMs?: number; tokenFn?: () => string } = {},
67
+ ): ConfirmationStore {
68
+ const now = opts.now ?? (() => Date.now());
69
+ const ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
70
+ const tokenFn = opts.tokenFn ?? defaultTokenFn;
71
+ const entries = new Map<string, Entry>();
72
+
73
+ return {
74
+ stage(sessionKey, action) {
75
+ const token = tokenFn();
76
+ entries.set(token, { sessionKey, action, expiresAt: now() + ttlMs });
77
+ return token;
78
+ },
79
+ take(sessionKey, token) {
80
+ const entry = entries.get(token);
81
+ if (!entry) {
82
+ return null;
83
+ }
84
+ if (entry.expiresAt <= now()) {
85
+ entries.delete(token);
86
+ return null;
87
+ }
88
+ if (entry.sessionKey !== sessionKey) {
89
+ return null;
90
+ }
91
+ entries.delete(token);
92
+ return entry.action;
93
+ },
94
+ pending() {
95
+ const t = now();
96
+ const out: PendingConfirmation[] = [];
97
+ for (const entry of entries.values()) {
98
+ if (entry.expiresAt <= t) continue;
99
+ out.push({
100
+ sessionKey: entry.sessionKey,
101
+ summary: entry.action.describe,
102
+ expiresAt: entry.expiresAt,
103
+ });
104
+ }
105
+ return out;
106
+ },
107
+ };
108
+ }
109
+
110
+ const TOKEN_SHAPE_RE = new RegExp(`^[${TOKEN_ALPHABET}]{${TOKEN_GROUP_LENGTH}}-[${TOKEN_ALPHABET}]{${TOKEN_GROUP_LENGTH}}$`);
111
+
112
+ /**
113
+ * True if `input` (trimmed) has the exact shape of a token this store
114
+ * mints — same alphabet, same length. Not a security boundary itself
115
+ * (that's `take()`'s existence/session/expiry check) — just enough to
116
+ * tell apart "this looks like a confirmation attempt" from "this is an
117
+ * ordinary message", so a caller (`tryConfirm` in `confirm-flow.ts`) knows
118
+ * whether to intercept at all before ever touching the store.
119
+ */
120
+ export function isTokenShaped(input: string): boolean {
121
+ return TOKEN_SHAPE_RE.test(input.trim());
122
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Deterministic text interception for the "confirm" half of the
3
+ * confirm-required flow (something stages an action and hands back a token —
4
+ * see `confirmation-staging.ts` for the "propose" half). `tryConfirm` is called
5
+ * from each channel BEFORE the model ever sees the message, same pattern as
6
+ * `/dump` (`tool-log.ts`) and `NO_REPLY` — running a previously-approved
7
+ * mutation must never depend on the model's own tool-calling judgment.
8
+ *
9
+ * It knows nothing about what the staged action is: it runs the opaque `run`
10
+ * thunk (see `StagedAction`) and reports the outcome. A CLI delete, a future
11
+ * memory purge — same path, because the doing was closed over at stage time.
12
+ */
13
+ import type { ConfirmOutcome } from "@mercury-fw/channel-types";
14
+ import { type ConfirmationStore } from "./confirmation-store.ts";
15
+ /**
16
+ * Signature of the persistent confirmation-note writer the core injects (the
17
+ * app's `writeConfirmationNote`, kept in `wiki/`). Declared here so this library
18
+ * stays free of any app import; the real writer is structurally assignable.
19
+ */
20
+ export type WriteConfirmationNote = (vaultPath: string, userId: string, token: string, fields: {
21
+ status: "pending" | "confirmed" | "failed";
22
+ requestedAt: string;
23
+ resolvedAt: string | null;
24
+ command: string;
25
+ }) => Promise<void>;
26
+ /**
27
+ * Returns `null` if `input` doesn't look like a bare confirmation token —
28
+ * the caller should proceed with its normal flow (`runTurn`, etc.).
29
+ * Otherwise always returns a user-facing string, resolved without ever
30
+ * invoking the model: an unknown/expired/wrong-session token gets a
31
+ * canned message, a valid one actually runs the staged action and
32
+ * reports the outcome. No `conferma ` keyword to type or match — the
33
+ * real gate was always `store.take()`'s existence/session/expiry check,
34
+ * not that prefix (see `isTokenShaped`'s own doc comment). A card button
35
+ * click on Google Chat and a bare token typed on the terminal both resolve
36
+ * through this exact same path.
37
+ */
38
+ export type ConfirmDeps = {
39
+ store: ConfirmationStore;
40
+ userId: string;
41
+ vaultPath: string;
42
+ writeConfirmationNoteFn: WriteConfirmationNote;
43
+ now?: () => Date;
44
+ };
45
+ /** Resolves a token to a structured {@link ConfirmOutcome} (from `@mercury-fw/channel-types`), running the staged action for a match. See `tryConfirm` for the string-returning wrapper. */
46
+ export declare function resolveConfirmation(input: string, sessionKey: string, deps: ConfirmDeps): Promise<ConfirmOutcome>;
47
+ export declare function tryConfirm(input: string, sessionKey: string, deps: ConfirmDeps): Promise<string | null>;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The "stage" side of the confirm-required flow, as a core-owned closure handed
3
+ * to whoever needs to defer an irreversible action (today the CLI tool; a
4
+ * future memory-deleting plugin all the same). It mints the confirmation token,
5
+ * stashes the opaque action in the `ConfirmationStore`, and writes the "pending"
6
+ * paper-trail note — so the caller never touches the store internals or the
7
+ * wiki, and the confirmation subsystem stays the one place that knows an action
8
+ * is confirmable. The "resolve" side (running the staged thunk once the token
9
+ * comes back) is `tryConfirm` in `confirm-flow.ts`.
10
+ *
11
+ * Bound per session (sessionKey/userId/vaultPath) at the composition root, so
12
+ * the returned function takes only the action itself. The note lives outside
13
+ * inferred/users/<userId>/ — see `writeConfirmationNote`'s own doc comment for
14
+ * why — and its write is best-effort: a failure is logged, never thrown, so it
15
+ * can't stop the user from seeing and confirming the action.
16
+ */
17
+ import type { ConfirmationStore } from "./confirmation-store.ts";
18
+ import type { StageConfirmation } from "@mercury-fw/plugin-types";
19
+ import type { WriteConfirmationNote } from "./confirm-flow.ts";
20
+ export type { StageConfirmation };
21
+ export declare function createStageConfirmation(deps: {
22
+ store: ConfirmationStore;
23
+ sessionKey: string;
24
+ /** Where/who the pending confirmation note is written for. */
25
+ userId: string;
26
+ vaultPath: string;
27
+ /** The note writer, injected by the core (the app's `writeConfirmationNote`). */
28
+ writeConfirmationNoteFn: WriteConfirmationNote;
29
+ /** Test seam; defaults to `() => new Date()`. */
30
+ nowFn?: () => Date;
31
+ }): StageConfirmation;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * In-memory staging area for an irreversible action that needs explicit
3
+ * confirmation before it runs. Something stages it here, and the channel gets
4
+ * the returned token confirmed back to it — a card button click on Google Chat,
5
+ * a bare token typed on the terminal — before anything actually happens (see
6
+ * `confirm-flow.ts`). Scoped by `sessionKey` so a token proposed to one session
7
+ * (terminal, or a given Google Chat space+sender) can't be confirmed by
8
+ * another.
9
+ *
10
+ * The action is opaque: a `run` thunk that performs it and a `describe` string
11
+ * for the paper trail. The store — and the whole confirmation subsystem — knows
12
+ * nothing about what kind of action it is: whoever stages it closes over the
13
+ * doing, and the core just runs the thunk when the token comes back. */
14
+ import type { ActionResult } from "@mercury-fw/plugin-types";
15
+ export type StagedAction = {
16
+ run: () => Promise<ActionResult>;
17
+ describe: string;
18
+ requestedAt?: string;
19
+ };
20
+ /** A pending staging as seen from outside, deliberately WITHOUT its token or
21
+ * its executable thunk: a token is a confirm capability, and the HTTP read
22
+ * surface is unauthenticated, so listing pending confirmations must never hand
23
+ * out the tokens (or a way to run the action) — only the human-readable
24
+ * `summary` (the action's `describe`). */
25
+ export type PendingConfirmation = {
26
+ sessionKey: string;
27
+ summary: string;
28
+ expiresAt: number;
29
+ };
30
+ export type ConfirmationStore = {
31
+ /** Stages `action` for `sessionKey` and returns a fresh token. */
32
+ stage(sessionKey: string, action: StagedAction): string;
33
+ /** Consumes and returns the staged action for `sessionKey`/`token`, or
34
+ * `null` if it doesn't exist, belongs to a different session, or has
35
+ * expired. Always one-shot: a successful take removes the entry. */
36
+ take(sessionKey: string, token: string): StagedAction | null;
37
+ /** The currently staged, non-expired actions, redacted of their tokens — for
38
+ * read-only introspection (see `PendingConfirmation`). */
39
+ pending(): PendingConfirmation[];
40
+ };
41
+ export declare function createConfirmationStore(opts?: {
42
+ now?: () => number;
43
+ ttlMs?: number;
44
+ tokenFn?: () => string;
45
+ }): ConfirmationStore;
46
+ /**
47
+ * True if `input` (trimmed) has the exact shape of a token this store
48
+ * mints — same alphabet, same length. Not a security boundary itself
49
+ * (that's `take()`'s existence/session/expiry check) — just enough to
50
+ * tell apart "this looks like a confirmation attempt" from "this is an
51
+ * ordinary message", so a caller (`tryConfirm` in `confirm-flow.ts`) knows
52
+ * whether to intercept at all before ever touching the store.
53
+ */
54
+ export declare function isTokenShaped(input: string): boolean;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The confirm-required mechanism the core owns end to end: the stateful
3
+ * `ConfirmationStore` (mints tokens, stashes opaque staged actions with a TTL),
4
+ * the "stage" half (`createStageConfirmation`, handed to whoever defers an
5
+ * irreversible action) and the "resolve" half (`resolveConfirmation`/`tryConfirm`,
6
+ * run before the model ever sees a message so a previously-approved mutation
7
+ * never depends on the model).
8
+ *
9
+ * The core creates the single store instance and binds the store/vault/writer,
10
+ * then hands channels only the injected `confirm`/`resolveConfirmation` closures
11
+ * (via `ChannelRuntimeContext`) — a channel never imports this library. Nothing
12
+ * here imports the app; the note writer is an injected `WriteConfirmationNote`.
13
+ */
14
+ export { createConfirmationStore, isTokenShaped, type ConfirmationStore, type StagedAction, } from "./confirmation-store.ts";
15
+ export { resolveConfirmation, tryConfirm, type ConfirmDeps, type WriteConfirmationNote, } from "./confirm-flow.ts";
16
+ export { createStageConfirmation, type StageConfirmation } from "./confirmation-staging.ts";
package/index.ts ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The confirm-required mechanism the core owns end to end: the stateful
3
+ * `ConfirmationStore` (mints tokens, stashes opaque staged actions with a TTL),
4
+ * the "stage" half (`createStageConfirmation`, handed to whoever defers an
5
+ * irreversible action) and the "resolve" half (`resolveConfirmation`/`tryConfirm`,
6
+ * run before the model ever sees a message so a previously-approved mutation
7
+ * never depends on the model).
8
+ *
9
+ * The core creates the single store instance and binds the store/vault/writer,
10
+ * then hands channels only the injected `confirm`/`resolveConfirmation` closures
11
+ * (via `ChannelRuntimeContext`) — a channel never imports this library. Nothing
12
+ * here imports the app; the note writer is an injected `WriteConfirmationNote`.
13
+ */
14
+ export {
15
+ createConfirmationStore,
16
+ isTokenShaped,
17
+ type ConfirmationStore,
18
+ type StagedAction,
19
+ } from "./confirmation-store.ts";
20
+ export {
21
+ resolveConfirmation,
22
+ tryConfirm,
23
+ type ConfirmDeps,
24
+ type WriteConfirmationNote,
25
+ } from "./confirm-flow.ts";
26
+ export { createStageConfirmation, type StageConfirmation } from "./confirmation-staging.ts";
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@mercury-fw/confirm-engine",
3
+ "version": "0.25.0",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/lucabro81/mercury-fw.git",
8
+ "directory": "packages/libs/confirm-engine"
9
+ },
10
+ "files": [
11
+ "*.ts",
12
+ "dist",
13
+ "CHANGELOG.md",
14
+ "!**/*.test.ts"
15
+ ],
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "exports": {
20
+ ".": {
21
+ "mercury-fw-source": "./index.ts",
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./index.ts"
24
+ }
25
+ },
26
+ "scripts": {
27
+ "test": "bun test",
28
+ "typecheck": "tsc --noEmit"
29
+ },
30
+ "dependencies": {
31
+ "@mercury-fw/channel-types": "0.25.0",
32
+ "@mercury-fw/plugin-types": "0.25.0"
33
+ },
34
+ "devDependencies": {
35
+ "@mercury-fw/typescript-config": "*",
36
+ "@types/bun": "^1.4.0",
37
+ "typescript": "^6.0.3"
38
+ }
39
+ }