@prismshadow/penguin-server 0.2.7 → 0.2.8

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.
Files changed (58) hide show
  1. package/README.md +4 -4
  2. package/dist/api/types.d.ts +389 -18
  3. package/dist/auth-token.d.ts +29 -0
  4. package/dist/auth-token.js +51 -0
  5. package/dist/auth-token.js.map +1 -0
  6. package/dist/{chunk-WKJ3ANJP.js → chunk-622KL7BQ.js} +92 -381
  7. package/dist/chunk-622KL7BQ.js.map +1 -0
  8. package/dist/chunk-K4ZSUT72.js +34 -0
  9. package/dist/chunk-K4ZSUT72.js.map +1 -0
  10. package/dist/chunk-UGAFPYVX.js +374 -0
  11. package/dist/chunk-UGAFPYVX.js.map +1 -0
  12. package/dist/extension/index.d.ts +212 -0
  13. package/dist/extension/index.js +1 -0
  14. package/dist/extension/index.js.map +1 -0
  15. package/dist/index.js +5641 -1299
  16. package/dist/index.js.map +1 -1
  17. package/dist/initial-password.d.ts +9 -12
  18. package/dist/initial-password.js +3 -7
  19. package/dist/install.ps1 +729 -0
  20. package/dist/install.sh +785 -0
  21. package/dist/reset-admin-password.d.ts +4 -7
  22. package/dist/reset-admin-password.js +11 -9
  23. package/dist/reset-admin-password.js.map +1 -1
  24. package/dist/secret-file.d.ts +3 -0
  25. package/dist/secret-file.js +17 -0
  26. package/dist/secret-file.js.map +1 -0
  27. package/package.json +19 -3
  28. package/web-dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  29. package/web-dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  30. package/web-dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  31. package/web-dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  32. package/web-dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  33. package/web-dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  34. package/web-dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  35. package/web-dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  36. package/web-dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  37. package/web-dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  38. package/web-dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  39. package/web-dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  40. package/web-dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  41. package/web-dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  42. package/web-dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  43. package/web-dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  44. package/web-dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  45. package/web-dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  46. package/web-dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  47. package/web-dist/assets/{addon-fit-BXTX0Xqu.js → addon-fit-DHOzI3Ur.js} +1 -1
  48. package/web-dist/assets/{addon-web-links-KebkunbU.js → addon-web-links-DC7759H1.js} +1 -1
  49. package/web-dist/assets/{highlighter-B3HctNDB.js → highlighter-CGPO6Q4b.js} +1 -1
  50. package/web-dist/assets/index-CaFDHl-w.js +451 -0
  51. package/web-dist/assets/index-D8Bk9LtJ.css +1 -0
  52. package/web-dist/assets/{xterm-fEUfDV0F.js → xterm-DY5ko-BU.js} +1 -1
  53. package/web-dist/index.html +2 -2
  54. package/dist/chunk-UUJCF46V.js +0 -50
  55. package/dist/chunk-UUJCF46V.js.map +0 -1
  56. package/dist/chunk-WKJ3ANJP.js.map +0 -1
  57. package/web-dist/assets/index-BbLm3DrY.css +0 -1
  58. package/web-dist/assets/index-DwgTjTHO.js +0 -189
package/README.md CHANGED
@@ -21,7 +21,7 @@ The full route tables and the SSE protocol are documented in the [Server API ref
21
21
  | `PENGUIN_HOME` | Data root (shared with SDK/CLI) | `~/.penguin/data` |
22
22
  | `PENGUIN_WEB_DB` | SQLite file path | `<root>/web.db` |
23
23
  | `PENGUIN_WEB_DIST` | Front-end build dir (static hosting + SPA fallback when present) | `../web/dist`, or the bundled `web-dist/` in the npm package |
24
- | `PENGUIN_TRUST_PROXY` | Trust `x-forwarded-proto` for the hot-update network gate (`1` to enable) — only behind a reverse proxy that sets/strips the header itself | unset (untrusted) |
24
+ | `PENGUIN_TRUST_PROXY` | Trust `x-forwarded-proto` (`1` to enable): it opens the hot-update network gate over proxied HTTPS and marks session cookies `Secure` — only behind a reverse proxy that sets/strips the header itself | unset (untrusted) |
25
25
 
26
26
  `.env` in the process cwd is loaded automatically.
27
27
 
@@ -39,10 +39,10 @@ pnpm --filter @prismshadow/penguin-server start # node dist/index.js
39
39
 
40
40
  - **CSRF**: session cookie is `SameSite=Lax` and writes accept only `Content-Type: application/json`; no CSRF token yet.
41
41
  - **Login throttling**: per-username exponential backoff after 5 consecutive failures (1s doubling to a 60s cap, `429 too_many_attempts` inside the window, reset on success; in-memory, so a restart clears it). Unknown usernames are throttled identically, so it is not an account-existence oracle. A reverse proxy can still add IP-level limits for public deployments.
42
- - **Built-in admin `admin` starts with a random initial password** of the form `penguin-1234`, printed once to the server console at seed time (`PENGUIN_SEED_ADMIN_PASSWORD` pins it for tests/e2e): change it immediately (a banner keeps reminding until you do).
43
- - Passwords use `node:crypto` scrypt (`scrypt$N$r$p$salt$hash`, timingSafeEqual); login sessions renew on a 7-day sliding window; the DB stores only the token's sha256.
42
+ - **Built-in admin `admin` is seeded with a random password that is hashed and discarded unseen** (`PENGUIN_SEED_ADMIN_PASSWORD` pins it for tests/e2e). Until a password is set, every start prints a first-login link that signs the browser in to choose one; a banner keeps reminding until the initial flag clears.
43
+ - Passwords use `node:crypto` scrypt (`scrypt$N$r$p$salt$hash`, timingSafeEqual). A session is a row in `auth_sessions` (the cookie holds a random token, the row its sha256); it survives a restart, renews the expiry in place, and logout deletes the row. Sessions run 30 days with sliding renewal.
44
44
  - Model credentials live in the Project's hidden 0600 config file; the API always masks them.
45
45
  - **Terminals are a shell as the OS account running the server**, i.e. the most privileged thing the API hands out — they are not Project-scoped. A terminal is only visible to the account that created it (someone else's id answers 404, not 403), and the stream WebSocket checks `Origin` on top of the session cookie because a WebSocket handshake is not subject to CORS. Do not expose a deployment with terminals enabled to accounts you would not give SSH.
46
- - Behind a reverse proxy, disable response buffering for SSE paths (the server already sends `X-Accel-Buffering: no`) and forward `x-forwarded-proto` to enable Secure cookies.
46
+ - Behind a reverse proxy, disable response buffering for SSE paths (the server already sends `X-Accel-Buffering: no`), and set `PENGUIN_TRUST_PROXY=1` with `x-forwarded-proto` forwarded so session cookies are marked `Secure` — the header alone is not trusted, so without the opt-in an HTTPS deployment issues its cookies without the flag.
47
47
 
48
48
  Part of [PenguinHarness](https://github.com/Prism-Shadow/penguin-harness) · Apache-2.0
@@ -47,6 +47,19 @@ interface AuthLoginRequest {
47
47
  interface AuthResponse {
48
48
  user: UserInfo;
49
49
  }
50
+ /**
51
+ * GET /api/install — the identity of the data root this server is serving (install-id.ts),
52
+ * read by the web app before it mounts so state persisted against a DIFFERENT root can be
53
+ * swept. Public, like the login route it sits next to.
54
+ */
55
+ interface InstallResponse {
56
+ /**
57
+ * Opaque per-data-root id, or null when the server could not establish one (an
58
+ * unreadable or unwritable root). Null means "unknown", and a client must change
59
+ * nothing on it — never treat it as a new install.
60
+ */
61
+ installId: string | null;
62
+ }
50
63
  interface MeResponse {
51
64
  user: UserInfo;
52
65
  /**
@@ -62,18 +75,19 @@ interface MeResponse {
62
75
  /**
63
76
  * Whether this server runs in desktop mode (spawned by the desktop shell with
64
77
  * PENGUIN_DESKTOP_TOKEN). The web app then hides the logout entry, the
65
- * initial-password banner and the self-update entry, and omits the old-password
66
- * field when changing the password.
78
+ * initial-password banner and the self-update entry.
67
79
  */
68
80
  desktopMode: boolean;
69
81
  /**
70
- * How THIS session was established. Distinct from desktopMode: a browser signed into a
71
- * desktop-mode server holds a "password" session and must still provide the old
72
- * password when changing it — only "desktop" sessions (opened by the shell's one-shot
73
- * token) may omit it. "token" marks a request authenticated by the local API token's
74
- * Bearer header (the CLI and agent-driven calls).
82
+ * How THIS session was established — distinct from desktopMode, which describes the
83
+ * server: a browser signed into a desktop-mode server holds a "password" session.
84
+ * "desktop" is the shell's own window (one-shot token); "setup" was claimed through the
85
+ * first-login link on a server whose admin password has never been set. Both may set a
86
+ * password without the old one (it is random and was never shown); only "desktop" opens
87
+ * desktop-only routes. "token" marks a request authenticated by the local API token's
88
+ * Bearer header (the CLI and agent-driven calls) — no stored session at all.
75
89
  */
76
- sessionVia: "password" | "desktop" | "token";
90
+ sessionVia: "password" | "desktop" | "setup" | "token";
77
91
  /**
78
92
  * The upload limits currently in force, so the composer can refuse an oversize pick before
79
93
  * reading it and can name the real number in the message. They are admin-settable and ride
@@ -108,7 +122,7 @@ interface UploadLimits {
108
122
  attachmentLimitMaxMb: number;
109
123
  }
110
124
  interface PasswordChangeRequest {
111
- /** Omitted only by desktop-established sessions (desktop mode); required otherwise. */
125
+ /** Omitted only by a "desktop" or "setup" session (see {@link MeResponse.sessionVia}); required otherwise. */
112
126
  oldPassword?: string;
113
127
  /** At least 8 characters. */
114
128
  newPassword: string;
@@ -220,6 +234,14 @@ interface UiPrefs {
220
234
  credentialGuideSeen?: boolean;
221
235
  /** The initial-password notice banner (app layout) was permanently dismissed by the user. */
222
236
  initialPasswordBannerDismissed?: boolean;
237
+ /**
238
+ * "I have dealt with this" markers for the dismissible to-do badges, per Project id and then
239
+ * per trail (`skills` / `models` / `errors`). Each value is the SIGNATURE of what was waved
240
+ * away rather than a hidden flag, so anything new raises the dot again (web's
241
+ * `lib/todo-badges.ts`). Replaced whole on every write, like `draftShortcuts`: the merge is
242
+ * shallow, so this whole map is one field.
243
+ */
244
+ todoDismissed?: Record<string, Record<string, string>>;
223
245
  /**
224
246
  * The draft screen's user-defined shortcuts, in display order. Replaced whole on every write
225
247
  * (the merge is shallow, so the array is one field like any other) and bounded on write by
@@ -679,6 +701,13 @@ interface VaultEntryUpdate {
679
701
  interface VaultUpdateRequest {
680
702
  entries: VaultEntryUpdate[];
681
703
  }
704
+ /** One installed Skill the library carries a higher version of (see {@link AgentSummary.skillUpdates}). */
705
+ interface SkillUpdateRef {
706
+ /** Skill directory name — the identity install / uninstall address it by. */
707
+ name: string;
708
+ /** The LIBRARY's version, i.e. what installing again would bring, not what is on disk. */
709
+ version: number;
710
+ }
682
711
  interface AgentSummary {
683
712
  agentId: string;
684
713
  name?: string;
@@ -704,6 +733,14 @@ interface AgentSummary {
704
733
  scheduleCount: number;
705
734
  /** Installed Skill count (number of agent_state/skills/<name>/ directories with a SKILL.md). */
706
735
  skillCount: number;
736
+ /**
737
+ * Installed Skills the built-in library has moved past, each with the library version on
738
+ * offer — the Skill-library update gate, riding along on the Agent list so a badge over
739
+ * that page costs no request of its own. Empty when nothing is behind. Skills that are not
740
+ * in the library (installed from a zip or a picked directory) are never listed: there is no
741
+ * library version for them to be behind.
742
+ */
743
+ skillUpdates: SkillUpdateRef[];
707
744
  /** Memory count (topic files summed over the scope directories under agent_state/memory/, independent of the memory switch). */
708
745
  memoryCount: number;
709
746
  }
@@ -1446,15 +1483,64 @@ interface SessionProcessesResponse {
1446
1483
  processes: SessionProcessInfo[];
1447
1484
  }
1448
1485
  /** Messaging channels a Session can bind to. */
1449
- type MessagingChannel = "feishu" | "telegram";
1486
+ type MessagingChannel = "feishu" | "telegram" | "qq";
1450
1487
  /** Event-connection runtime state of one binding (kept in memory, not persisted). */
1451
1488
  type MessagingRuntimeState = "disconnected" | "connecting" | "connected" | "error";
1489
+ /**
1490
+ * A failure that happened AFTER an inbound message was accepted — as opposed to a
1491
+ * connection failure, which `MessagingRuntimeStatus.lastError` reports.
1492
+ *
1493
+ * The two stages fail in completely different places and lead to different actions, and
1494
+ * from the chat they are indistinguishable (both produce silence), which is why the stage
1495
+ * is stated rather than folded into one message.
1496
+ */
1497
+ interface MessagingDeliveryError {
1498
+ /** ISO 8601 timestamp of the failure. */
1499
+ at: string;
1500
+ /** `inbound` — the message arrived and its Task never started; `send` — a reply never reached the chat. */
1501
+ stage: "inbound" | "send";
1502
+ /** The failure's own message, as the channel or the Session reported it. */
1503
+ detail: string;
1504
+ }
1452
1505
  interface MessagingRuntimeStatus {
1453
1506
  state: MessagingRuntimeState;
1454
1507
  /** Failure detail; present only in the `error` state. */
1455
1508
  lastError?: string;
1456
1509
  /** When the state last changed (ISO 8601); absent for a binding that never connected. */
1457
1510
  changedAt?: string;
1511
+ /**
1512
+ * When this binding last accepted an inbound message (ISO 8601).
1513
+ *
1514
+ * "Connected, and nothing has arrived" is the answer to the one question a chat cannot
1515
+ * answer for itself — whether the platform is delivering anything at all. A channel that
1516
+ * withholds messages (Telegram's group privacy, a bot that is not a member) produces
1517
+ * exactly that, with no error anywhere.
1518
+ *
1519
+ * In-process AND scoped to one connection: it starts empty on every (re)connect, and a
1520
+ * re-enable or a credential save opens a new one. So an absent field means "nothing since
1521
+ * this connection opened", never "nothing ever" — anything reporting it has to say which,
1522
+ * or it sends a reader off to fix a channel that is working.
1523
+ */
1524
+ lastInboundAt?: string;
1525
+ /**
1526
+ * The most recent post-acceptance failure; absent when none has happened since this
1527
+ * connection opened, and never cleared by a later success — an intermittent failure would
1528
+ * otherwise be erased by the next ordinary message. `at` is what says how stale it is.
1529
+ */
1530
+ lastDeliveryError?: MessagingDeliveryError;
1531
+ /**
1532
+ * The most recent CONNECTION failure, kept after the connection recovers — unlike
1533
+ * `lastError`, which belongs to the `error` state and is gone the moment the state leaves it.
1534
+ *
1535
+ * A connection that fails and recovers repeatedly (a second program polling the same bot
1536
+ * token takes turns with this one) reads as `connected` in any snapshot taken between
1537
+ * flaps, with nothing at all to show for the failures in between. This is what is left
1538
+ * behind — for the life of the connection, like the two above it.
1539
+ */
1540
+ lastConnectionError?: {
1541
+ at: string;
1542
+ detail: string;
1543
+ };
1458
1544
  }
1459
1545
  /** The stored Feishu config, secret masked (plaintext never leaves the server). */
1460
1546
  interface FeishuBindingInfo {
@@ -1470,6 +1556,26 @@ interface FeishuBindingInfo {
1470
1556
  baseDomain: string;
1471
1557
  /** Connection INTENT (the state toggle's value); new bindings start disabled, and at most one of a Session's channels is enabled. */
1472
1558
  enabled: boolean;
1559
+ /**
1560
+ * Send each non-blank line of a relayed assistant reply as its own message instead of one
1561
+ * message per reply. Off by default; off is the original one-message-per-reply behaviour.
1562
+ */
1563
+ linePerMessage: boolean;
1564
+ /**
1565
+ * Relay only the LAST completed assistant message of a run, delivered when the run ends,
1566
+ * instead of mirroring each completed message as it completes. Off by default; off is the
1567
+ * original every-message behaviour. Independent of `linePerMessage`, which then applies to
1568
+ * that one final message. The approval notice is not a reply and is unaffected.
1569
+ */
1570
+ finalReplyOnly: boolean;
1571
+ /**
1572
+ * Render a relayed reply's Markdown in this channel's own markup instead of sending its
1573
+ * characters as written. ON by default. Each channel shows what it can — Telegram has no
1574
+ * headings, lists or tables, QQ has no code or tables, Feishu has all of them — and a
1575
+ * rendering the channel refuses falls back to the plain source, so this can cost
1576
+ * formatting and never a message.
1577
+ */
1578
+ renderMarkdown: boolean;
1473
1579
  /**
1474
1580
  * Whether an inbound Feishu chat is known (the bot has been messaged at least once).
1475
1581
  * Replies and test messages target that chat; until it exists nothing can be sent.
@@ -1492,6 +1598,26 @@ interface TelegramBindingInfo {
1492
1598
  botTokenMasked?: string;
1493
1599
  /** Connection INTENT (the state toggle's value); new bindings start disabled, and at most one of a Session's channels is enabled. */
1494
1600
  enabled: boolean;
1601
+ /**
1602
+ * Send each non-blank line of a relayed assistant reply as its own message instead of one
1603
+ * message per reply. Off by default; off is the original one-message-per-reply behaviour.
1604
+ */
1605
+ linePerMessage: boolean;
1606
+ /**
1607
+ * Relay only the LAST completed assistant message of a run, delivered when the run ends,
1608
+ * instead of mirroring each completed message as it completes. Off by default; off is the
1609
+ * original every-message behaviour. Independent of `linePerMessage`, which then applies to
1610
+ * that one final message. The approval notice is not a reply and is unaffected.
1611
+ */
1612
+ finalReplyOnly: boolean;
1613
+ /**
1614
+ * Render a relayed reply's Markdown in this channel's own markup instead of sending its
1615
+ * characters as written. ON by default. Each channel shows what it can — Telegram has no
1616
+ * headings, lists or tables, QQ has no code or tables, Feishu has all of them — and a
1617
+ * rendering the channel refuses falls back to the plain source, so this can cost
1618
+ * formatting and never a message.
1619
+ */
1620
+ renderMarkdown: boolean;
1495
1621
  /**
1496
1622
  * Whether an inbound Telegram chat is known (the bot has been messaged at least once).
1497
1623
  * Replies and test messages target that chat; until it exists nothing can be sent.
@@ -1500,8 +1626,61 @@ interface TelegramBindingInfo {
1500
1626
  createdAt: string;
1501
1627
  updatedAt: string;
1502
1628
  }
1629
+ /** The stored QQ config, secret masked (plaintext never leaves the server). */
1630
+ interface QQBindingInfo {
1631
+ channel: "qq";
1632
+ sessionId: string;
1633
+ /** The bot's App ID — the channel-scoped account identity, never secret. */
1634
+ appId: string;
1635
+ /**
1636
+ * Masked App Secret (site-wide mask rule: `***`, or `first4…last4` for long values);
1637
+ * absent when no secret is stored (never entered, or cleared) — the binding cannot be
1638
+ * enabled until one is saved.
1639
+ */
1640
+ appSecretMasked?: string;
1641
+ /** Connection INTENT (the state toggle's value); new bindings start disabled, and at most one of a Session's channels is enabled. */
1642
+ enabled: boolean;
1643
+ /**
1644
+ * Send each non-blank line of a relayed assistant reply as its own message instead of one
1645
+ * message per reply. Off by default. On QQ the split is additionally capped at the
1646
+ * platform's passive-reply budget rather than the channel-neutral ceiling, so it yields
1647
+ * far fewer messages here than on the other channels.
1648
+ */
1649
+ linePerMessage: boolean;
1650
+ /**
1651
+ * Relay only the LAST completed assistant message of a run, delivered when the run ends,
1652
+ * instead of mirroring each completed message as it completes. Off by default; off is the
1653
+ * original every-message behaviour. Independent of `linePerMessage`, which then applies to
1654
+ * that one final message. The approval notice is not a reply and is unaffected.
1655
+ *
1656
+ * On QQ it cuts both ways, which is worth knowing before setting it here. It spends the
1657
+ * least of the platform's passive-reply budget a run can spend — one message, where an
1658
+ * every-message relay spends one per completed message. But a passive reply is accepted
1659
+ * only for a few minutes after the inbound message that funds it, and holding the reply
1660
+ * to the run's end spends that window on the run: a run that outlives it delivers nothing
1661
+ * at all, where the every-message relay would have sent whatever completed inside it.
1662
+ */
1663
+ finalReplyOnly: boolean;
1664
+ /**
1665
+ * Render a relayed reply's Markdown in this channel's own markup instead of sending its
1666
+ * characters as written. ON by default. QQ's own subset is the widest for prose and the
1667
+ * narrowest for code: headings, lists, blockquotes and rules render, while inline code,
1668
+ * fenced code and tables have no syntax at all here — a code block arrives as plain
1669
+ * escaped lines. A rendering the platform refuses falls back to the plain source, at the
1670
+ * cost of one more slot from the passive-reply budget.
1671
+ */
1672
+ renderMarkdown: boolean;
1673
+ /**
1674
+ * Whether an inbound QQ chat is known (the bot has been messaged at least once).
1675
+ * Weaker than it looks on this channel: QQ accepts only replies to a recent message, so a
1676
+ * known chat is necessary but not sufficient for anything to be deliverable right now.
1677
+ */
1678
+ lastChatKnown: boolean;
1679
+ createdAt: string;
1680
+ updatedAt: string;
1681
+ }
1503
1682
  /** A Session's saved config for one messaging channel (`channel` is the discriminant). */
1504
- type MessagingBindingInfo = FeishuBindingInfo | TelegramBindingInfo;
1683
+ type MessagingBindingInfo = FeishuBindingInfo | TelegramBindingInfo | QQBindingInfo;
1505
1684
  /** One saved channel config with its event-connection runtime status. */
1506
1685
  interface MessagingChannelState {
1507
1686
  binding: MessagingBindingInfo;
@@ -1526,6 +1705,11 @@ interface TelegramBindingResponse {
1526
1705
  binding: TelegramBindingInfo | null;
1527
1706
  status: MessagingRuntimeStatus;
1528
1707
  }
1708
+ /** GET / PUT …/messaging/qq response (the QQ narrowing of the same envelope). */
1709
+ interface QQBindingResponse {
1710
+ binding: QQBindingInfo | null;
1711
+ status: MessagingRuntimeStatus;
1712
+ }
1529
1713
  /**
1530
1714
  * PUT …/messaging/feishu — saves credentials/config ONLY, never flipping the connection
1531
1715
  * (exception: an enabled binding's connector restarts with the new credentials so stored
@@ -1542,11 +1726,27 @@ interface FeishuBindingPutRequest {
1542
1726
  * it). Refused with 409 `messaging_disable_before_clear` while the binding is enabled.
1543
1727
  */
1544
1728
  clearAppSecret?: boolean;
1729
+ /**
1730
+ * Delivery preference (see `FeishuBindingInfo.linePerMessage`). Omitted keeps the stored
1731
+ * value; a binding created without it starts with it off.
1732
+ */
1733
+ linePerMessage?: boolean;
1734
+ /**
1735
+ * Delivery preference (see `FeishuBindingInfo.finalReplyOnly`). Omitted keeps the stored
1736
+ * value; a binding created without it starts with it off.
1737
+ */
1738
+ finalReplyOnly?: boolean;
1739
+ /**
1740
+ * Delivery preference (see `FeishuBindingInfo.renderMarkdown`). Omitted keeps the stored
1741
+ * value; a binding created without it starts with it ON.
1742
+ */
1743
+ renderMarkdown?: boolean;
1545
1744
  }
1546
1745
  /**
1547
1746
  * PUT …/messaging/telegram — saves the credential ONLY, same contract as the Feishu PUT
1548
1747
  * (an enabled binding's connector restarts with the new token; the connection toggle is
1549
- * POST …/state). 409 `telegram_bot_in_use` when the token's bot id is bound elsewhere.
1748
+ * POST …/state). Saving never conflicts across Sessions: the same bot may sit saved on
1749
+ * several, and only enabling it is exclusive.
1550
1750
  */
1551
1751
  interface TelegramBindingPutRequest {
1552
1752
  /** Omitted or blank keeps the stored token (the masked value never round-trips). */
@@ -1557,12 +1757,60 @@ interface TelegramBindingPutRequest {
1557
1757
  * `messaging_disable_before_clear` while the binding is enabled.
1558
1758
  */
1559
1759
  clearBotToken?: boolean;
1760
+ /**
1761
+ * Delivery preference (see `TelegramBindingInfo.linePerMessage`). Omitted keeps the stored
1762
+ * value; a binding created without it starts with it off.
1763
+ */
1764
+ linePerMessage?: boolean;
1765
+ /**
1766
+ * Delivery preference (see `TelegramBindingInfo.finalReplyOnly`). Omitted keeps the stored
1767
+ * value; a binding created without it starts with it off.
1768
+ */
1769
+ finalReplyOnly?: boolean;
1770
+ /**
1771
+ * Delivery preference (see `TelegramBindingInfo.renderMarkdown`). Omitted keeps the stored
1772
+ * value; a binding created without it starts with it ON.
1773
+ */
1774
+ renderMarkdown?: boolean;
1775
+ }
1776
+ /**
1777
+ * PUT …/messaging/qq — saves the credential pair ONLY, same contract as the Feishu PUT
1778
+ * (an enabled binding's connector restarts with the new credentials; the connection toggle
1779
+ * is POST …/state). The App ID is the account identity, so changing it rebinds the row to
1780
+ * a different bot and drops the remembered chat.
1781
+ */
1782
+ interface QQBindingPutRequest {
1783
+ appId: string;
1784
+ /** Omitted or blank keeps the stored secret (the masked value never round-trips). */
1785
+ appSecret?: string;
1786
+ /**
1787
+ * Drops the STORED secret (the models-page clear idiom; a typed `appSecret` wins over
1788
+ * it). Refused with 409 `messaging_disable_before_clear` while the binding is enabled.
1789
+ */
1790
+ clearAppSecret?: boolean;
1791
+ /**
1792
+ * Delivery preference (see `QQBindingInfo.linePerMessage`). Omitted keeps the stored
1793
+ * value; a binding created without it starts with it off.
1794
+ */
1795
+ linePerMessage?: boolean;
1796
+ /**
1797
+ * Delivery preference (see `QQBindingInfo.finalReplyOnly`). Omitted keeps the stored
1798
+ * value; a binding created without it starts with it off.
1799
+ */
1800
+ finalReplyOnly?: boolean;
1801
+ /**
1802
+ * Delivery preference (see `QQBindingInfo.renderMarkdown`). Omitted keeps the stored
1803
+ * value; a binding created without it starts with it ON.
1804
+ */
1805
+ renderMarkdown?: boolean;
1560
1806
  }
1561
1807
  /**
1562
1808
  * POST …/messaging/<channel>/state — enable connects with the STORED credentials,
1563
- * disable terminates. Enabling is mutually exclusive per Session: while another channel
1564
- * is enabled it answers 409 `another_channel_enabled` (turn that one off first), and a
1565
- * config whose secret is missing answers its channel's 400 `*_required`.
1809
+ * disable terminates. Enabling is what binds the bot account to this Session, and it is
1810
+ * mutually exclusive twice over: while another channel of the same Session is enabled it
1811
+ * answers 409 `another_channel_enabled`, and while another SESSION has the same account
1812
+ * enabled it answers 409 `account_enabled_elsewhere` (both say: turn that one off first).
1813
+ * A config whose secret is missing answers its channel's 400 `*_required`.
1566
1814
  */
1567
1815
  interface MessagingBindingStateRequest {
1568
1816
  enabled: boolean;
@@ -1589,11 +1837,69 @@ interface TelegramTestResponse {
1589
1837
  latencyMs?: number;
1590
1838
  /** The bot's `@username`, when the API reports one. */
1591
1839
  botUsername?: string;
1840
+ /**
1841
+ * True when @BotFather's **Group Privacy** is ON for this bot, which is the default. In
1842
+ * every group where the bot is not an administrator it then receives only a command
1843
+ * addressed to it or a reply to one of its own messages — an ordinary sentence is never
1844
+ * delivered, so a binding that answers fine in a direct chat stays silent there. The
1845
+ * setting is account-wide and Telegram overrides it for a group the bot administers, so
1846
+ * this reports the setting and never the outcome in any particular group. Absent when the
1847
+ * API did not report the setting; never inferred.
1848
+ */
1849
+ groupPrivacy?: boolean;
1592
1850
  error?: string;
1593
1851
  }
1852
+ /** POST …/messaging/qq/test — draft values; each omitted field falls back to the stored binding. */
1853
+ interface QQTestRequest {
1854
+ appId?: string;
1855
+ appSecret?: string;
1856
+ }
1857
+ /**
1858
+ * QQ credential-test outcome. There is no account label: the platform's only credential
1859
+ * call is the access-token exchange, which identifies nothing beyond the App ID that was
1860
+ * sent to it.
1861
+ */
1862
+ interface QQTestResponse {
1863
+ ok: boolean;
1864
+ latencyMs?: number;
1865
+ error?: string;
1866
+ }
1867
+ /**
1868
+ * POST …/messaging/qq/scan — starts a scan-to-connect flow: the server registers a bind
1869
+ * task under a fresh AES key it keeps to itself, and answers with what the browser may
1870
+ * know. The key is absent from this type on purpose — it decrypts the App Secret, so it
1871
+ * never leaves the server (the same rule that keeps a stored secret from round-tripping).
1872
+ */
1873
+ interface QQScanStartResponse {
1874
+ /** Opaque bind-task handle, passed back to the poll endpoint. */
1875
+ taskId: string;
1876
+ /** The URL to ENCODE into a QR code. It is opened by the QQ app, never fetched by the browser. */
1877
+ qrUrl: string;
1878
+ /** How often to poll, in milliseconds (the interval the protocol is designed around). */
1879
+ pollMs: number;
1880
+ }
1881
+ /**
1882
+ * POST …/messaging/qq/scan/poll — one step of the scan. `pending` means keep polling,
1883
+ * `expired` means start a new task and show a new QR, and `completed` means the server has
1884
+ * already decrypted the App Secret and SAVED the binding: `appId` names the bot that landed.
1885
+ * Saving is all it does — enabling the connection stays the separate, exclusive act it is on
1886
+ * every channel. A task id that is unknown, belongs to another Session, was already
1887
+ * resolved, or is being resolved by a poll still in flight answers 404
1888
+ * `qq_scan_task_unknown`: the task is claimed by one poll, so a client whose interval fires
1889
+ * before the previous request came back binds once rather than once per overlapping poll.
1890
+ */
1891
+ interface QQScanPollResponse {
1892
+ status: "none" | "pending" | "completed" | "expired";
1893
+ /** The bound bot's App ID; present only on `completed`. Never the secret. */
1894
+ appId?: string;
1895
+ /** The saved binding, present on `completed` so the editor refreshes without a second GET. */
1896
+ binding?: QQBindingInfo;
1897
+ }
1594
1898
  /**
1595
1899
  * POST …/messaging/<channel>/test-message — sent to the last known chat (409
1596
- * `feishu_no_chat` / `telegram_no_chat` before one exists).
1900
+ * `feishu_no_chat` / `telegram_no_chat` / `qq_no_chat` before one exists). On QQ this can
1901
+ * still fail with 502 afterwards: the platform accepts only replies to a message sent from
1902
+ * QQ minutes earlier, so a known chat does not mean a deliverable one.
1597
1903
  */
1598
1904
  interface MessagingTestMessageResponse {
1599
1905
  ok: true;
@@ -2144,6 +2450,12 @@ interface UsageModelSeries extends UsageEntitySeriesCounts {
2144
2450
  provider: string;
2145
2451
  modelId: string;
2146
2452
  }
2453
+ /**
2454
+ * The two categories an error record is filed under: `unexpected` is a 500 or an unforeseen
2455
+ * runtime exception, `expected` an HttpError / business 4xx. The panel separates them by
2456
+ * colour, and `GET /usage/errors` takes one of them as its `kind` filter.
2457
+ */
2458
+ type UsageErrorKind = "unexpected" | "expected";
2147
2459
  /** Occurrence count of an error for a given source · code (the "most common" metric in the stats center's error panel). */
2148
2460
  interface UsageErrorCount {
2149
2461
  source: string;
@@ -2180,7 +2492,10 @@ interface UsageErrors {
2180
2492
  * GET /api/projects/:projectId/usage/errors — one page of the error detail table, newest
2181
2493
  * first. The dashboard response above already carries the first page; this exists so
2182
2494
  * "show me earlier ones" does not have to refetch the whole aggregate. It takes the same
2183
- * date/agent filter as the dashboard, so a page never widens what the summary counted.
2495
+ * date/agent filter as the dashboard, so a page never widens what the summary counted, plus
2496
+ * an optional `kind` ({@link UsageErrorKind}) narrowing to one of the two categories — which
2497
+ * is how the cost-center badge asks "are there unexpected errors, and how new is the newest"
2498
+ * with `limit=1` instead of pulling the whole dashboard aggregate.
2184
2499
  */
2185
2500
  interface UsageErrorsPage {
2186
2501
  items: UsageErrorItem[];
@@ -2491,5 +2806,61 @@ interface UpdateRunResponse {
2491
2806
  /** True when the install changed (or was already current): restart the service to run the new version. */
2492
2807
  needsRestart: boolean;
2493
2808
  }
2809
+ /** One `Host` entry of the server's `~/.ssh/config`, as the Machines page lists it. */
2810
+ interface MachineInfo {
2811
+ /** `ssh:<alias>` — the id the install route is asked for. */
2812
+ id: string;
2813
+ /**
2814
+ * The alias exactly as written in the config. The list is the config text and nothing
2815
+ * else — no `ssh -G`, no processes, no network — so a config declaring hundreds of hosts
2816
+ * costs one file read; an alias is resolved only when it is actually installed to.
2817
+ */
2818
+ alias: string;
2819
+ /**
2820
+ * The last install THIS server carried out there, remembered on disk so it survives a
2821
+ * restart, a hot push, and installing on some other machine. Null when this server has
2822
+ * never installed there.
2823
+ *
2824
+ * A record of what was done, not a survey of the far side: a machine wiped by hand still
2825
+ * reads as installed until the next install probes it and corrects the record. Asking the
2826
+ * remote instead would cost an ssh round trip per host at page load, which is the price
2827
+ * the config-text list exists to avoid.
2828
+ */
2829
+ installed: {
2830
+ version: string;
2831
+ at: string;
2832
+ } | null;
2833
+ }
2834
+ /**
2835
+ * The running or last install, polled by GET /api/machines while one runs. `log` carries the
2836
+ * far side's own words where there are any: ssh's diagnostics and the remote installer's
2837
+ * output say more about a refused key or an unusable Node than a paraphrase would.
2838
+ */
2839
+ interface MachineInstallJob {
2840
+ machineId: string;
2841
+ alias: string;
2842
+ running: boolean;
2843
+ log: string[];
2844
+ result: null | {
2845
+ ok: true;
2846
+ kind: "installed" | "already-installed";
2847
+ version: string | null;
2848
+ } | {
2849
+ ok: false;
2850
+ step: string;
2851
+ message: string;
2852
+ };
2853
+ }
2854
+ /** GET /api/machines, and the 202 body of POST /api/machines/:machineId/install. */
2855
+ interface MachinesResponse {
2856
+ machines: MachineInfo[];
2857
+ /**
2858
+ * The version an install would leave on the remote — the base release, plus a `+hmr.<sha>`
2859
+ * suffix when this server carries a pushed version to replicate. Null for a development
2860
+ * checkout, which stands on no release the remote could download.
2861
+ */
2862
+ imageVersion: string | null;
2863
+ job: MachineInstallJob | null;
2864
+ }
2494
2865
 
2495
- export type { AdminPasswordResetRequest, AdminUserCreateRequest, AdminUserCreateResponse, AdminUsersResponse, AgentCompactionConfigDto, AgentConfigDto, AgentConfigResponse, AgentConfigUpdateRequest, AgentCreateRequest, AgentCreateResponse, AgentImportRequest, AgentImportResponse, AgentKernelUpdateResponse, AgentMemoryConfigDto, AgentModelConfigDto, AgentSchedulesConfigDto, AgentSkillsConfigDto, AgentSkillsResponse, AgentSummary, AgentTraceDateGroup, AgentTraceFileRef, AgentTraceSessionEntry, AgentTraceSessionFile, AgentTraceSessionGroup, AgentTracesResponse, AgentVaultConfigDto, AgentsResponse, ApprovalDecisionRequest, ApprovalMode, AuthLoginRequest, AuthResponse, BenchmarkCaseScore, BenchmarkCaseSummary, BenchmarkCasesResponse, BenchmarkEvaluation, BenchmarkRunScore, BenchmarkSummary, BenchmarksResponse, CaseMaterial, ChatDefaultsDto, CommandPolicyDto, CommandPolicyRuleDto, ContextToolShare, CredentialInfo, DefaultModelResponse, DefaultModelUpdateRequest, DesktopUpdateStatus, DesktopUpdateStatusResponse, DesktopUpdaterCommandMessage, DesktopUpdaterStatusMessage, DirEntryInfo, DirListResponse, DirectorySkillItem, DirectorySkillsResponse, DraftShortcut, EndpointModelListRequest, EndpointModelListResponse, ErrorBody, FeishuBindingInfo, FeishuBindingPutRequest, FeishuBindingResponse, FeishuTestRequest, FeishuTestResponse, FilesStatRequest, FilesStatResponse, GoalResponse, GoalServerEvent, GoalStateView, HistoryMessage, McpServerTestResponse, MeResponse, MemberAddRequest, MemberAddResponse, MemberInfo, MembersResponse, MemoryFileInfo, MemoryFileResponse, MemoryFilesResponse, MemoryImportMode, MemoryImportRequest, MemoryImportResponse, MemoryOverviewResponse, MemoryScopeExport, MemoryScopeInfo, MemoryTransferFile, MessagesLiveTail, MessagesPageInfo, MessagesResponse, MessagingBindingInfo, MessagingBindingStateRequest, MessagingBindingsResponse, MessagingChannel, MessagingChannelState, MessagingRuntimeState, MessagingRuntimeStatus, MessagingTestMessageResponse, ModelInfo, ModelOAuthCodeRequest, ModelOAuthCodeResponse, ModelOAuthErrorCode, ModelOAuthMode, ModelOAuthStartRequest, ModelOAuthStartResponse, ModelOAuthStatusResponse, ModelPricingDto, ModelProtocolDetectRequest, ModelProtocolDetectResponse, ModelProtocolProbeDto, ModelRefDto, ModelTestRequest, ModelTestResponse, ModelUpdateEntry, ModelVisionDetectRequest, ModelVisionDetectResponse, ModelVisionOutcome, ModelsResponse, ModelsUpdateRequest, PasswordChangeRequest, PendingFollowUpInfo, PendingSteeringInfo, PrefsResponse, ProjectCreateRequest, ProjectCreateResponse, ProjectRole, ProjectSummary, ProjectUpdateRequest, ProjectUpdateResponse, ProjectsResponse, ProtocolProbeOutcome, RecalledMessageResponse, RequestSpan, RetryNowResponse, ScheduleItem, ScheduleServerEvent, ScheduleStatus, ScheduleUpsertRequest, SchedulesResponse, ServerEvent, ServerSettings, ServerSettingsResponse, ServerSettingsUpdateRequest, SessionCategory, SessionCategoryCounts, SessionContextParts, SessionContextResponse, SessionCreateRequest, SessionCreateResponse, SessionForkRequest, SessionForkResponse, SessionInfo, SessionPatchRequest, SessionProcessInfo, SessionProcessesResponse, SessionResponse, SessionSource, SessionStatus, SessionTracesResponse, SessionsResponse, SkillArchiveInstallRequest, SkillGroupItem, SkillInstallRequest, SkillLibraryResponse, SkillMetadataItem, SteerRequest, SubagentMessageResponse, SubagentRuntimeInfo, TaskCreateRequest, TaskCreateResponse, TaskInputPart, TelegramBindingInfo, TelegramBindingPutRequest, TelegramBindingResponse, TelegramTestRequest, TelegramTestResponse, ToolCallSpan, TraceAnalysisResponse, TraceEventsResponse, TraceFileInfo, TraceImportRequest, TraceImportResponse, TraceModelSegment, TraceOtherSpan, TracePosition, TraceTaskStats, TraceToolSpan, UiPrefs, UpdateCheckResponse, UpdateRunResponse, UploadLimits, UsageAgentSeries, UsageBucket, UsageEntitySeriesCounts, UsageErrorCount, UsageErrorItem, UsageErrors, UsageErrorsPage, UsageGranularity, UsageGroupBy, UsageGroupRow, UsageModelSeries, UsageResponse, UsageSeriesPoint, UsageTrendPointInTrace, UserInfo, VaultEntryInfo, VaultEntryUpdate, VaultResponse, VaultUpdateRequest, VersionResponse, WorkspaceFileEntry, WorkspaceFilesResponse };
2866
+ export type { AdminPasswordResetRequest, AdminUserCreateRequest, AdminUserCreateResponse, AdminUsersResponse, AgentCompactionConfigDto, AgentConfigDto, AgentConfigResponse, AgentConfigUpdateRequest, AgentCreateRequest, AgentCreateResponse, AgentImportRequest, AgentImportResponse, AgentKernelUpdateResponse, AgentMemoryConfigDto, AgentModelConfigDto, AgentSchedulesConfigDto, AgentSkillsConfigDto, AgentSkillsResponse, AgentSummary, AgentTraceDateGroup, AgentTraceFileRef, AgentTraceSessionEntry, AgentTraceSessionFile, AgentTraceSessionGroup, AgentTracesResponse, AgentVaultConfigDto, AgentsResponse, ApprovalDecisionRequest, ApprovalMode, AuthLoginRequest, AuthResponse, BenchmarkCaseScore, BenchmarkCaseSummary, BenchmarkCasesResponse, BenchmarkEvaluation, BenchmarkRunScore, BenchmarkSummary, BenchmarksResponse, CaseMaterial, ChatDefaultsDto, CommandPolicyDto, CommandPolicyRuleDto, ContextToolShare, CredentialInfo, DefaultModelResponse, DefaultModelUpdateRequest, DesktopUpdateStatus, DesktopUpdateStatusResponse, DesktopUpdaterCommandMessage, DesktopUpdaterStatusMessage, DirEntryInfo, DirListResponse, DirectorySkillItem, DirectorySkillsResponse, DraftShortcut, EndpointModelListRequest, EndpointModelListResponse, ErrorBody, FeishuBindingInfo, FeishuBindingPutRequest, FeishuBindingResponse, FeishuTestRequest, FeishuTestResponse, FilesStatRequest, FilesStatResponse, GoalResponse, GoalServerEvent, GoalStateView, HistoryMessage, InstallResponse, MachineInfo, MachineInstallJob, MachinesResponse, McpServerTestResponse, MeResponse, MemberAddRequest, MemberAddResponse, MemberInfo, MembersResponse, MemoryFileInfo, MemoryFileResponse, MemoryFilesResponse, MemoryImportMode, MemoryImportRequest, MemoryImportResponse, MemoryOverviewResponse, MemoryScopeExport, MemoryScopeInfo, MemoryTransferFile, MessagesLiveTail, MessagesPageInfo, MessagesResponse, MessagingBindingInfo, MessagingBindingStateRequest, MessagingBindingsResponse, MessagingChannel, MessagingChannelState, MessagingDeliveryError, MessagingRuntimeState, MessagingRuntimeStatus, MessagingTestMessageResponse, ModelInfo, ModelOAuthCodeRequest, ModelOAuthCodeResponse, ModelOAuthErrorCode, ModelOAuthMode, ModelOAuthStartRequest, ModelOAuthStartResponse, ModelOAuthStatusResponse, ModelPricingDto, ModelProtocolDetectRequest, ModelProtocolDetectResponse, ModelProtocolProbeDto, ModelRefDto, ModelTestRequest, ModelTestResponse, ModelUpdateEntry, ModelVisionDetectRequest, ModelVisionDetectResponse, ModelVisionOutcome, ModelsResponse, ModelsUpdateRequest, PasswordChangeRequest, PendingFollowUpInfo, PendingSteeringInfo, PrefsResponse, ProjectCreateRequest, ProjectCreateResponse, ProjectRole, ProjectSummary, ProjectUpdateRequest, ProjectUpdateResponse, ProjectsResponse, ProtocolProbeOutcome, QQBindingInfo, QQBindingPutRequest, QQBindingResponse, QQScanPollResponse, QQScanStartResponse, QQTestRequest, QQTestResponse, RecalledMessageResponse, RequestSpan, RetryNowResponse, ScheduleItem, ScheduleServerEvent, ScheduleStatus, ScheduleUpsertRequest, SchedulesResponse, ServerEvent, ServerSettings, ServerSettingsResponse, ServerSettingsUpdateRequest, SessionCategory, SessionCategoryCounts, SessionContextParts, SessionContextResponse, SessionCreateRequest, SessionCreateResponse, SessionForkRequest, SessionForkResponse, SessionInfo, SessionPatchRequest, SessionProcessInfo, SessionProcessesResponse, SessionResponse, SessionSource, SessionStatus, SessionTracesResponse, SessionsResponse, SkillArchiveInstallRequest, SkillGroupItem, SkillInstallRequest, SkillLibraryResponse, SkillMetadataItem, SkillUpdateRef, SteerRequest, SubagentMessageResponse, SubagentRuntimeInfo, TaskCreateRequest, TaskCreateResponse, TaskInputPart, TelegramBindingInfo, TelegramBindingPutRequest, TelegramBindingResponse, TelegramTestRequest, TelegramTestResponse, ToolCallSpan, TraceAnalysisResponse, TraceEventsResponse, TraceFileInfo, TraceImportRequest, TraceImportResponse, TraceModelSegment, TraceOtherSpan, TracePosition, TraceTaskStats, TraceToolSpan, UiPrefs, UpdateCheckResponse, UpdateRunResponse, UploadLimits, UsageAgentSeries, UsageBucket, UsageEntitySeriesCounts, UsageErrorCount, UsageErrorItem, UsageErrorKind, UsageErrors, UsageErrorsPage, UsageGranularity, UsageGroupBy, UsageGroupRow, UsageModelSeries, UsageResponse, UsageSeriesPoint, UsageTrendPointInTrace, UserInfo, VaultEntryInfo, VaultEntryUpdate, VaultResponse, VaultUpdateRequest, VersionResponse, WorkspaceFileEntry, WorkspaceFilesResponse };
@@ -0,0 +1,29 @@
1
+ /** An hour: long enough for a controller to finish, short enough to forget. */
2
+ declare const CLI_TOKEN_TTL_MS: number;
3
+ /**
4
+ * Ceiling on a caller-supplied `--ttl-seconds`, matching the default session TTL (config.ts):
5
+ * a token that lives in a file must not outlive an ordinary browser session.
6
+ */
7
+ declare const CLI_TOKEN_MAX_TTL_MS: number;
8
+ type MintTokenResult = {
9
+ outcome: "minted";
10
+ token: string;
11
+ userId: string;
12
+ expiresAt: string;
13
+ }
14
+ /** No web.db on this root — the server has never run here, so there is no account to mint for. */
15
+ | {
16
+ outcome: "no_server";
17
+ } | {
18
+ outcome: "failed";
19
+ detail: string;
20
+ };
21
+ /** Issues an API session token for the data root at `root`. */
22
+ declare function mintApiToken(root: string, opts?: {
23
+ userId?: string;
24
+ ttlMs?: number;
25
+ dbPath?: string;
26
+ now?: Date;
27
+ }): MintTokenResult;
28
+
29
+ export { CLI_TOKEN_MAX_TTL_MS, CLI_TOKEN_TTL_MS, type MintTokenResult, mintApiToken };