@book.dev/sdk 1.60.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.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +23 -0
  3. package/dist/account.d.ts +57 -0
  4. package/dist/account.js +104 -0
  5. package/dist/account.js.map +1 -0
  6. package/dist/ai.d.ts +264 -0
  7. package/dist/ai.js +36 -0
  8. package/dist/ai.js.map +1 -0
  9. package/dist/backup.d.ts +52 -0
  10. package/dist/backup.js +35 -0
  11. package/dist/backup.js.map +1 -0
  12. package/dist/bookFolder.d.ts +44 -0
  13. package/dist/bookFolder.js +98 -0
  14. package/dist/bookFolder.js.map +1 -0
  15. package/dist/bookfile.d.ts +42 -0
  16. package/dist/bookfile.js +159 -0
  17. package/dist/bookfile.js.map +1 -0
  18. package/dist/client.d.ts +279 -0
  19. package/dist/client.js +502 -0
  20. package/dist/client.js.map +1 -0
  21. package/dist/connection.d.ts +20 -0
  22. package/dist/connection.js +77 -0
  23. package/dist/connection.js.map +1 -0
  24. package/dist/content.d.ts +38 -0
  25. package/dist/content.js +107 -0
  26. package/dist/content.js.map +1 -0
  27. package/dist/database.d.ts +687 -0
  28. package/dist/database.js +1082 -0
  29. package/dist/database.js.map +1 -0
  30. package/dist/formula.d.ts +41 -0
  31. package/dist/formula.js +469 -0
  32. package/dist/formula.js.map +1 -0
  33. package/dist/forwarding/challenge.d.ts +35 -0
  34. package/dist/forwarding/challenge.js +45 -0
  35. package/dist/forwarding/challenge.js.map +1 -0
  36. package/dist/forwarding/encoding.d.ts +14 -0
  37. package/dist/forwarding/encoding.js +54 -0
  38. package/dist/forwarding/encoding.js.map +1 -0
  39. package/dist/forwarding/forwardingClient.d.ts +76 -0
  40. package/dist/forwarding/forwardingClient.js +128 -0
  41. package/dist/forwarding/forwardingClient.js.map +1 -0
  42. package/dist/forwarding/index.d.ts +5 -0
  43. package/dist/forwarding/index.js +14 -0
  44. package/dist/forwarding/index.js.map +1 -0
  45. package/dist/forwarding/siteKey.d.ts +12 -0
  46. package/dist/forwarding/siteKey.js +36 -0
  47. package/dist/forwarding/siteKey.js.map +1 -0
  48. package/dist/forwarding/tunnelClient.d.ts +56 -0
  49. package/dist/forwarding/tunnelClient.js +206 -0
  50. package/dist/forwarding/tunnelClient.js.map +1 -0
  51. package/dist/forwarding/tunnelProtocol.d.ts +45 -0
  52. package/dist/forwarding/tunnelProtocol.js +29 -0
  53. package/dist/forwarding/tunnelProtocol.js.map +1 -0
  54. package/dist/index.d.ts +23 -0
  55. package/dist/index.js +19 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/mtime.d.ts +51 -0
  58. package/dist/mtime.js +84 -0
  59. package/dist/mtime.js.map +1 -0
  60. package/dist/pageProperties.d.ts +80 -0
  61. package/dist/pageProperties.js +97 -0
  62. package/dist/pageProperties.js.map +1 -0
  63. package/dist/plugins.d.ts +110 -0
  64. package/dist/plugins.js +113 -0
  65. package/dist/plugins.js.map +1 -0
  66. package/dist/routes.d.ts +89 -0
  67. package/dist/routes.js +89 -0
  68. package/dist/routes.js.map +1 -0
  69. package/dist/sampleDocument.d.ts +25 -0
  70. package/dist/sampleDocument.js +67 -0
  71. package/dist/sampleDocument.js.map +1 -0
  72. package/dist/suggestions.d.ts +126 -0
  73. package/dist/suggestions.js +21 -0
  74. package/dist/suggestions.js.map +1 -0
  75. package/dist/templates.d.ts +35 -0
  76. package/dist/templates.js +555 -0
  77. package/dist/templates.js.map +1 -0
  78. package/dist/types.d.ts +146 -0
  79. package/dist/types.js +16 -0
  80. package/dist/types.js.map +1 -0
  81. package/package.json +33 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Eliot Lim
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # @book.dev/sdk
2
+
3
+ The shared contract for OpenBook — imported by the server, the desktop app, and
4
+ the web shell so types and the HTTP API can never drift between them.
5
+
6
+ Exports:
7
+
8
+ - **Types** — `Page`/`StoredPage`, `PageMeta`, `PageInput`, `PageSnapshot`,
9
+ `ServerInfo`, plus `emptyPageSnapshot()`.
10
+ - **`API`** — the route paths (`/api/pages`, `/api/pages/:id`, `/health`). The
11
+ server's router and the client both build URLs from these.
12
+ - **`DataClient`** — the storage-agnostic interface the document UI depends on.
13
+ - **`HttpDataClient`** — an isomorphic `fetch`-based implementation. The desktop
14
+ points it at its bundled local server; the web shell at a remote one.
15
+
16
+ No React or Node dependencies — safe to import from any layer.
17
+
18
+ ```ts
19
+ import {HttpDataClient, type PageInput} from '@book.dev/sdk';
20
+
21
+ const client = new HttpDataClient('http://127.0.0.1:4319');
22
+ const page = await client.savePage({data: {editorjs: {blocks: []}, values: [], names: []}});
23
+ ```
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Client for the OpenBook account service (account.book.pub): the deep-link
3
+ * sign-in URL plus the settings-sync API. Identity + settings sync live in a
4
+ * service separate from the (single-tenant) data server, so this is its own
5
+ * small client — independent of {@link HttpDataClient}, authed by a bearer
6
+ * "device token" rather than the cookieless data API.
7
+ */
8
+ /** Where the account service lives when nothing overrides it. */
9
+ export declare const DEFAULT_ACCOUNT_URL = "https://account.book.pub";
10
+ /** A dev/self-host override for the account base URL (localStorage), or null. */
11
+ export declare function getAccountUrlOverride(): string | null;
12
+ /** Set (or clear, with `null`) the account base URL override. */
13
+ export declare function setAccountUrlOverride(url: string | null): void;
14
+ /** The effective account base URL (override, else the production default). */
15
+ export declare function resolveAccountUrl(): string;
16
+ /** The user's synced blob plus the server's last-write timestamp. */
17
+ export interface AccountSettings {
18
+ /** Whatever the app stored, or `{}` when nothing is synced yet. */
19
+ settings: Record<string, unknown>;
20
+ /** ISO timestamp of the last server write, or null if never written. */
21
+ updatedAt: string | null;
22
+ }
23
+ /** Thrown on a non-OK account response, so callers can branch on `status` (401). */
24
+ export declare class AccountError extends Error {
25
+ readonly status: number;
26
+ constructor(status: number, message: string);
27
+ }
28
+ /**
29
+ * Talks to the account service's `/api/connect` (deep-link sign-in) and
30
+ * `/api/settings` (bearer-authed settings sync). Stateless: the caller holds the
31
+ * device token and passes it per request.
32
+ */
33
+ export declare class AccountClient {
34
+ private readonly baseUrl;
35
+ constructor(baseUrl?: string);
36
+ /** The base URL this client targets (already trimmed). */
37
+ get origin(): string;
38
+ /**
39
+ * The browser URL that starts deep-link sign-in: it runs OAuth (if needed),
40
+ * mints a one-shot device token, and redirects to
41
+ * `redirectUri#token=<token>&state=<state>`. Open it in the system browser.
42
+ */
43
+ connectUrl(opts: {
44
+ redirectUri: string;
45
+ state: string;
46
+ name?: string;
47
+ }): string;
48
+ /** Pull the synced settings blob. Throws `AccountError(401)` if the token is
49
+ * invalid or revoked. */
50
+ getSettings(token: string): Promise<AccountSettings>;
51
+ /** Push the settings blob; returns the new server timestamp for reconciliation. */
52
+ putSettings(token: string, settings: Record<string, unknown>): Promise<{
53
+ updatedAt: string;
54
+ }>;
55
+ /** Cheap token check (a settings GET): true if accepted, false on 401. */
56
+ validate(token: string): Promise<boolean>;
57
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Client for the OpenBook account service (account.book.pub): the deep-link
3
+ * sign-in URL plus the settings-sync API. Identity + settings sync live in a
4
+ * service separate from the (single-tenant) data server, so this is its own
5
+ * small client — independent of {@link HttpDataClient}, authed by a bearer
6
+ * "device token" rather than the cookieless data API.
7
+ */
8
+ /** Where the account service lives when nothing overrides it. */
9
+ export const DEFAULT_ACCOUNT_URL = 'https://account.book.pub';
10
+ const ACCOUNT_URL_KEY = 'openbook.accountUrl';
11
+ const trimUrl = (u) => u.trim().replace(/\/+$/, '');
12
+ /** A dev/self-host override for the account base URL (localStorage), or null. */
13
+ export function getAccountUrlOverride() {
14
+ if (typeof localStorage === 'undefined')
15
+ return null;
16
+ const v = localStorage.getItem(ACCOUNT_URL_KEY);
17
+ return v && v.trim() ? trimUrl(v) : null;
18
+ }
19
+ /** Set (or clear, with `null`) the account base URL override. */
20
+ export function setAccountUrlOverride(url) {
21
+ if (typeof localStorage === 'undefined')
22
+ return;
23
+ if (url && url.trim())
24
+ localStorage.setItem(ACCOUNT_URL_KEY, trimUrl(url));
25
+ else
26
+ localStorage.removeItem(ACCOUNT_URL_KEY);
27
+ }
28
+ /** The effective account base URL (override, else the production default). */
29
+ export function resolveAccountUrl() {
30
+ return getAccountUrlOverride() ?? DEFAULT_ACCOUNT_URL;
31
+ }
32
+ /** Thrown on a non-OK account response, so callers can branch on `status` (401). */
33
+ export class AccountError extends Error {
34
+ constructor(status, message) {
35
+ super(message);
36
+ this.status = status;
37
+ this.name = 'AccountError';
38
+ }
39
+ }
40
+ /**
41
+ * Talks to the account service's `/api/connect` (deep-link sign-in) and
42
+ * `/api/settings` (bearer-authed settings sync). Stateless: the caller holds the
43
+ * device token and passes it per request.
44
+ */
45
+ export class AccountClient {
46
+ constructor(baseUrl = resolveAccountUrl()) {
47
+ this.baseUrl = trimUrl(baseUrl);
48
+ }
49
+ /** The base URL this client targets (already trimmed). */
50
+ get origin() {
51
+ return this.baseUrl;
52
+ }
53
+ /**
54
+ * The browser URL that starts deep-link sign-in: it runs OAuth (if needed),
55
+ * mints a one-shot device token, and redirects to
56
+ * `redirectUri#token=<token>&state=<state>`. Open it in the system browser.
57
+ */
58
+ connectUrl(opts) {
59
+ const u = new URL('/api/connect', this.baseUrl + '/');
60
+ u.searchParams.set('redirect_uri', opts.redirectUri);
61
+ u.searchParams.set('state', opts.state);
62
+ if (opts.name)
63
+ u.searchParams.set('name', opts.name);
64
+ return u.toString();
65
+ }
66
+ /** Pull the synced settings blob. Throws `AccountError(401)` if the token is
67
+ * invalid or revoked. */
68
+ async getSettings(token) {
69
+ const res = await fetch(new URL('/api/settings', this.baseUrl + '/'), {
70
+ headers: { authorization: `Bearer ${token}` },
71
+ cache: 'no-store',
72
+ });
73
+ if (!res.ok)
74
+ throw new AccountError(res.status, `account settings GET failed (${res.status})`);
75
+ const body = (await res.json());
76
+ return { settings: body.settings ?? {}, updatedAt: body.updatedAt ?? null };
77
+ }
78
+ /** Push the settings blob; returns the new server timestamp for reconciliation. */
79
+ async putSettings(token, settings) {
80
+ const res = await fetch(new URL('/api/settings', this.baseUrl + '/'), {
81
+ method: 'PUT',
82
+ headers: { authorization: `Bearer ${token}`, 'content-type': 'application/json' },
83
+ body: JSON.stringify({ settings }),
84
+ cache: 'no-store',
85
+ });
86
+ if (!res.ok)
87
+ throw new AccountError(res.status, `account settings PUT failed (${res.status})`);
88
+ const body = (await res.json());
89
+ return { updatedAt: body.updatedAt };
90
+ }
91
+ /** Cheap token check (a settings GET): true if accepted, false on 401. */
92
+ async validate(token) {
93
+ try {
94
+ await this.getSettings(token);
95
+ return true;
96
+ }
97
+ catch (err) {
98
+ if (err instanceof AccountError && err.status === 401)
99
+ return false;
100
+ throw err;
101
+ }
102
+ }
103
+ }
104
+ //# sourceMappingURL=account.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account.js","sourceRoot":"","sources":["../src/account.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,iEAAiE;AACjE,MAAM,CAAC,MAAM,mBAAmB,GAAG,0BAA0B,CAAC;AAE9D,MAAM,eAAe,GAAG,qBAAqB,CAAC;AAE9C,MAAM,OAAO,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAEpE,iFAAiF;AACjF,MAAM,UAAU,qBAAqB;IACnC,IAAI,OAAO,YAAY,KAAK,WAAW;QAAE,OAAO,IAAI,CAAC;IACrD,MAAM,CAAC,GAAG,YAAY,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAChD,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC3C,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,qBAAqB,CAAC,GAAkB;IACtD,IAAI,OAAO,YAAY,KAAK,WAAW;QAAE,OAAO;IAChD,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE;QAAE,YAAY,CAAC,OAAO,CAAC,eAAe,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;;QACtE,YAAY,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;AAChD,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,iBAAiB;IAC/B,OAAO,qBAAqB,EAAE,IAAI,mBAAmB,CAAC;AACxD,CAAC;AAUD,oFAAoF;AACpF,MAAM,OAAO,YAAa,SAAQ,KAAK;IACrC,YACkB,MAAc,EAC9B,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,WAAM,GAAN,MAAM,CAAQ;QAI9B,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,OAAO,aAAa;IAGxB,YAAY,UAAkB,iBAAiB,EAAE;QAC/C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClC,CAAC;IAED,0DAA0D;IAC1D,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED;;;;OAIG;IACH,UAAU,CAAC,IAAyD;QAClE,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,CAAC;QACtD,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QACrD,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,IAAI,CAAC,IAAI;YAAE,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACrD,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;IACtB,CAAC;IAED;8BAC0B;IAC1B,KAAK,CAAC,WAAW,CAAC,KAAa;QAC7B,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,GAAG,CAAC,eAAe,EAAE,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,EAAE;YACpE,OAAO,EAAE,EAAC,aAAa,EAAE,UAAU,KAAK,EAAE,EAAC;YAC3C,KAAK,EAAE,UAAU;SAClB,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,MAAM,IAAI,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,gCAAgC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;QAC/F,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA6B,CAAC;QAC5D,OAAO,EAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,IAAI,EAAC,CAAC;IAC5E,CAAC;IAED,mFAAmF;IACnF,KAAK,CAAC,WAAW,CAAC,KAAa,EAAE,QAAiC;QAChE,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,GAAG,CAAC,eAAe,EAAE,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,EAAE;YACpE,MAAM,EAAE,KAAK;YACb,OAAO,EAAE,EAAC,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAC;YAC/E,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAC,QAAQ,EAAC,CAAC;YAChC,KAAK,EAAE,UAAU;SAClB,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,MAAM,IAAI,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,gCAAgC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;QAC/F,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAwB,CAAC;QACvD,OAAO,EAAC,SAAS,EAAE,IAAI,CAAC,SAAS,EAAC,CAAC;IACrC,CAAC;IAED,0EAA0E;IAC1E,KAAK,CAAC,QAAQ,CAAC,KAAa;QAC1B,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YAC9B,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,YAAY,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;gBAAE,OAAO,KAAK,CAAC;YACpE,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;CACF"}
package/dist/ai.d.ts ADDED
@@ -0,0 +1,264 @@
1
+ /**
2
+ * The optional local-AI subsystem's shared contract. The server hosts a
3
+ * pluggable inference engine; these types describe its configuration,
4
+ * status, and request/response shapes. Everything degrades gracefully:
5
+ * with the engine off, lexical (BM25) note search still works and the AI
6
+ * editor affordances simply hide.
7
+ *
8
+ * Providers:
9
+ * - `off` — disabled (default).
10
+ * - `mock` — deterministic in-process engine (tests, demos).
11
+ * - `llama` — llama.cpp in-process via node-llama-cpp (GGUF models,
12
+ * cross-platform: Metal/CUDA/Vulkan/CPU). The model file is
13
+ * downloaded on demand into the server's models directory.
14
+ * - `mlx` — Apple-Silicon MLX through `mlx_lm.server`'s OpenAI-compatible
15
+ * API (optionally auto-started by the server).
16
+ * - `openai` — any OpenAI-compatible local endpoint (Ollama, LM Studio,
17
+ * llama-server, vLLM…).
18
+ * - `claude` — Anthropic's hosted Claude API (cloud; needs an API key). The
19
+ * only provider that sends content off the machine.
20
+ */
21
+ import type { StoredSuggestion } from './suggestions';
22
+ export type AiProvider = 'off' | 'mock' | 'llama' | 'mlx' | 'openai' | 'claude';
23
+ /**
24
+ * How hard the agent works on a turn. One knob maps (server-side, in one
25
+ * place — `ai/effort.ts`) to a thinking-token budget, sampling temperature,
26
+ * answer-token cap, and the agent's max tool-call steps.
27
+ */
28
+ export type AiEffort = 'low' | 'med' | 'high';
29
+ /** Per-provider connection settings. Every provider is configured independently
30
+ * (in `AiConfig.providers`), so a workspace can have llama, mlx, openai and
31
+ * claude all set up at once and switch between them per agent run. */
32
+ export interface AiProviderSettings {
33
+ /** Model identifier: a GGUF filename (llama), an MLX model id (mlx), a served
34
+ * model name (openai), or a Claude model id (e.g. `claude-sonnet-4-6`). */
35
+ model?: string;
36
+ /** Base URL for `mlx` / `openai` / `claude`. Defaults: mlx
37
+ * http://127.0.0.1:8080, openai http://127.0.0.1:11434, claude
38
+ * https://api.anthropic.com (override for a proxy/gateway). */
39
+ baseUrl?: string;
40
+ /** `claude` only: the Anthropic API key. */
41
+ apiKey?: string;
42
+ /** `mlx` only: spawn `mlx_lm.server` automatically when possible. */
43
+ autoStart?: boolean;
44
+ }
45
+ export interface AiConfig {
46
+ /** The default provider — used unless an agent run overrides it. */
47
+ provider: AiProvider;
48
+ /** Per-provider settings, so every provider can be configured at once. */
49
+ providers?: Partial<Record<AiProvider, AiProviderSettings>>;
50
+ /** Default agent effort (low/med/high). Falls back to 'med'. */
51
+ effort?: AiEffort;
52
+ /** Whether the agent surfaces its reasoning (collapsible). Default true. */
53
+ thinking?: boolean;
54
+ /** @deprecated use `providers[provider].model` */ model?: string;
55
+ /** @deprecated use `providers[provider].baseUrl` */ baseUrl?: string;
56
+ /** @deprecated use `providers[provider].apiKey` */ apiKey?: string;
57
+ /** @deprecated use `providers[provider].autoStart` */ autoStart?: boolean;
58
+ }
59
+ /**
60
+ * The effective settings for one provider: its `providers` entry, or — for a
61
+ * legacy config saved before per-provider settings existed — the flat top-level
62
+ * fields (which belonged to the then-active provider). Server engine creation
63
+ * and both UIs read settings through this, so old configs keep working.
64
+ */
65
+ export declare function providerSettings(config: AiConfig, provider: AiProvider): AiProviderSettings;
66
+ export interface AiStatus {
67
+ config: AiConfig;
68
+ /** The engine can generate text right now. */
69
+ ready: boolean;
70
+ /** The engine can embed text (semantic search reranking). */
71
+ embeddings: boolean;
72
+ /** Human-readable detail when not ready (missing model, endpoint down…). */
73
+ detail?: string;
74
+ /** Lexical search index state (always available, even with AI off). */
75
+ index: {
76
+ pages: number;
77
+ builtAt: string | null;
78
+ };
79
+ /** In-flight model download, when one is running. */
80
+ download?: {
81
+ url: string;
82
+ received: number;
83
+ total: number | null;
84
+ done: boolean;
85
+ error?: string;
86
+ };
87
+ }
88
+ export interface AiSearchResult {
89
+ pageId: string;
90
+ title: string;
91
+ /** Best-matching snippet of the page's text. */
92
+ snippet: string;
93
+ score: number;
94
+ }
95
+ export interface AiSearchResponse {
96
+ results: AiSearchResult[];
97
+ /** 'lexical' (BM25 only) or 'hybrid' (BM25 + embedding rerank). */
98
+ mode: 'lexical' | 'hybrid';
99
+ }
100
+ export interface AiTasksResponse {
101
+ tasks: string[];
102
+ }
103
+ /**
104
+ * Server-sent chunk of a streaming generation. `token` carries answer text;
105
+ * `reasoning` carries a model's thinking (from `<think>…</think>` or a
106
+ * scratchpad) routed to a separate channel so the UI renders it as a
107
+ * collapsible block and never as document content.
108
+ */
109
+ export interface AiStreamEvent {
110
+ token?: string;
111
+ /** A reasoning/thinking token (kept out of the document). */
112
+ reasoning?: string;
113
+ done?: boolean;
114
+ error?: string;
115
+ }
116
+ export interface AgentChatMessage {
117
+ role: 'user' | 'assistant';
118
+ content: string;
119
+ }
120
+ /**
121
+ * A single change the agent's write tools describe. Internal to the agent
122
+ * harness: a write tool builds one of these, the runner persists it as a
123
+ * {@link StoredSuggestion} (see `./suggestions`), and the suggestion — not this
124
+ * proposal — is what reaches the UI. Retained because the persisted
125
+ * suggestion's `payload` carries this `kind` (as `applyKind`), which the editor
126
+ * bridge replays to apply the change when a human accepts it.
127
+ */
128
+ export interface AgentProposal {
129
+ /** Stable id within the turn's change set. */
130
+ id: string;
131
+ /** Which write tool produced it (drives how the bridge applies it). */
132
+ kind: 'set_kit_value' | 'set_db_cell' | 'update_block' | 'append_blocks' | 'set_page_theme' | 'delete_block' | 'set_block_props';
133
+ /** One-line human summary, e.g. `Set "budget" = 1200`. */
134
+ summary: string;
135
+ /** The page this change targets (for block/kit writes). */
136
+ pageId?: string;
137
+ /** Prior value, rendered for the diff card (optional). */
138
+ before?: string;
139
+ /** New value, rendered for the diff card. */
140
+ after?: string;
141
+ /** Structured payload the client bridge replays to mutate the CRDT/DB. */
142
+ payload: Record<string, unknown>;
143
+ }
144
+ /**
145
+ * One step of a multi-step interview the agent asks the user (the `ask_user`
146
+ * tool). Each step is one question; the user answers all steps, and the answers
147
+ * return to the agent as their next message.
148
+ */
149
+ export interface InterviewStep {
150
+ id: string;
151
+ /** The question text. */
152
+ question: string;
153
+ /** Choices to pick from. Omit (or set `freeText`) for a typed answer. */
154
+ options?: Array<{
155
+ label: string;
156
+ value: string;
157
+ }>;
158
+ /** Allow selecting more than one option. */
159
+ multiple?: boolean;
160
+ /** Allow a typed answer (on its own, or in addition to the options). */
161
+ freeText?: boolean;
162
+ }
163
+ /** One streamed step of an agent run. */
164
+ export type AgentChatEvent = {
165
+ type: 'tool';
166
+ name: string;
167
+ args: Record<string, unknown>;
168
+ } | {
169
+ type: 'tool_result';
170
+ name: string;
171
+ result: string;
172
+ }
173
+ /**
174
+ * The agent is asking to apply its edits DIRECTLY (without the review pane).
175
+ * The UI shows an allow/keep-reviewing prompt; granting it makes subsequent
176
+ * edits in this conversation apply immediately (see {@link apply}).
177
+ */
178
+ | {
179
+ type: 'permission_request';
180
+ summary: string;
181
+ }
182
+ /** The agent is asking the user a multi-step interview; answers return as the
183
+ * user's next message. */
184
+ | {
185
+ type: 'interview';
186
+ title?: string;
187
+ steps: InterviewStep[];
188
+ }
189
+ /**
190
+ * Edits the agent applied DIRECTLY (the user granted edit access). The UI
191
+ * replays them through the editor bridge — the same path an accepted
192
+ * suggestion takes — and shows a short "applied" summary instead of a review
193
+ * card.
194
+ */
195
+ | {
196
+ type: 'apply';
197
+ proposals: AgentProposal[];
198
+ }
199
+ /**
200
+ * A chunk of the assistant's answer, streamed live as the model writes it
201
+ * (engines that support native tool-calling only; the JSON-protocol fallback
202
+ * surfaces the answer once, via {@link final}). The UI appends these to the
203
+ * in-progress answer bubble; the matching {@link final} carries the complete,
204
+ * authoritative text.
205
+ */
206
+ | {
207
+ type: 'token';
208
+ text: string;
209
+ } | {
210
+ type: 'reasoning';
211
+ text: string;
212
+ }
213
+ /**
214
+ * The agent's write tools persisted these suggestions for review (NOT
215
+ * applied). The UI shows a "proposed N suggestions — Review" card linking to
216
+ * the Review side pane; a human accepts/rejects each there.
217
+ */
218
+ | {
219
+ type: 'suggestions';
220
+ suggestions: StoredSuggestion[];
221
+ } | {
222
+ type: 'final';
223
+ text: string;
224
+ } | {
225
+ type: 'error';
226
+ error: string;
227
+ };
228
+ /** Options for one agent run. */
229
+ export interface AgentChatOptions {
230
+ signal?: AbortSignal;
231
+ /** Override the default provider for this run (else the configured default). */
232
+ provider?: AiProvider;
233
+ /** Override the model for this run (else the provider's configured model). */
234
+ model?: string;
235
+ /** Override the configured default effort for this run. */
236
+ effort?: AiEffort;
237
+ /** Override whether reasoning is surfaced for this run. */
238
+ thinking?: boolean;
239
+ /** Names of prompt/recipe skills to inline into the system prompt. */
240
+ skills?: string[];
241
+ /** The page the user is currently viewing — its content is added as context. */
242
+ pageId?: string;
243
+ /** The user's current text selection — added as context on top of the message. */
244
+ selection?: string;
245
+ /** When true (the user granted edit access), the agent's edits apply directly
246
+ * via an {@link AgentChatEvent.apply} event instead of becoming review
247
+ * suggestions. Sticky for the conversation once granted. */
248
+ allowDirectEdits?: boolean;
249
+ }
250
+ /**
251
+ * A user-authored prompt/recipe skill: markdown instructions the agent can
252
+ * inline into its system prompt. No code — pure prompt engineering, editable
253
+ * by the user. Stored per-workspace (in the `settings` table under `ai.skills`;
254
+ * see `ai/skills.ts`).
255
+ */
256
+ export interface AiSkill {
257
+ /** Stable slug (lowercase, hyphenated), unique per workspace. */
258
+ name: string;
259
+ /** Short one-line description shown in the catalogue. */
260
+ description: string;
261
+ /** The instructions inlined when the skill is invoked (markdown). */
262
+ instructions: string;
263
+ updatedAt?: string;
264
+ }
package/dist/ai.js ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The optional local-AI subsystem's shared contract. The server hosts a
3
+ * pluggable inference engine; these types describe its configuration,
4
+ * status, and request/response shapes. Everything degrades gracefully:
5
+ * with the engine off, lexical (BM25) note search still works and the AI
6
+ * editor affordances simply hide.
7
+ *
8
+ * Providers:
9
+ * - `off` — disabled (default).
10
+ * - `mock` — deterministic in-process engine (tests, demos).
11
+ * - `llama` — llama.cpp in-process via node-llama-cpp (GGUF models,
12
+ * cross-platform: Metal/CUDA/Vulkan/CPU). The model file is
13
+ * downloaded on demand into the server's models directory.
14
+ * - `mlx` — Apple-Silicon MLX through `mlx_lm.server`'s OpenAI-compatible
15
+ * API (optionally auto-started by the server).
16
+ * - `openai` — any OpenAI-compatible local endpoint (Ollama, LM Studio,
17
+ * llama-server, vLLM…).
18
+ * - `claude` — Anthropic's hosted Claude API (cloud; needs an API key). The
19
+ * only provider that sends content off the machine.
20
+ */
21
+ /**
22
+ * The effective settings for one provider: its `providers` entry, or — for a
23
+ * legacy config saved before per-provider settings existed — the flat top-level
24
+ * fields (which belonged to the then-active provider). Server engine creation
25
+ * and both UIs read settings through this, so old configs keep working.
26
+ */
27
+ export function providerSettings(config, provider) {
28
+ const entry = config.providers?.[provider];
29
+ if (entry)
30
+ return entry;
31
+ if (provider === config.provider) {
32
+ return { model: config.model, baseUrl: config.baseUrl, apiKey: config.apiKey, autoStart: config.autoStart };
33
+ }
34
+ return {};
35
+ }
36
+ //# sourceMappingURL=ai.js.map
package/dist/ai.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai.js","sourceRoot":"","sources":["../src/ai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAgDH;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAgB,EAAE,QAAoB;IACrE,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC,QAAQ,CAAC,CAAC;IAC3C,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,QAAQ,KAAK,MAAM,CAAC,QAAQ,EAAE,CAAC;QACjC,OAAO,EAAC,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAC,CAAC;IAC5G,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Whole-space backup & restore contract. A backup is one JSON bundle of every
3
+ * live page (full data, nesting, database membership, properties) plus every
4
+ * database; emoji `icons` are added client-side (they live in localStorage).
5
+ *
6
+ * Restore has two modes:
7
+ * - `copy` (default): import as new pages (fresh ids); names that clash with an
8
+ * existing live page get a `" (imported)"` suffix. Never clobbers.
9
+ * - `overwrite`: restore in place by id, replacing existing pages/databases. The
10
+ * UI double-confirms, quoting how many existing pages will be replaced.
11
+ */
12
+ import type { StoredPage } from './types';
13
+ import type { StoredDatabase } from './database';
14
+ export declare const BACKUP_VERSION = 1;
15
+ export interface SpaceBackup {
16
+ version: number;
17
+ exportedAt: string;
18
+ pages: StoredPage[];
19
+ databases: StoredDatabase[];
20
+ /** pageId → emoji icon (added client-side; ignored by the server). */
21
+ icons?: Record<string, string>;
22
+ }
23
+ export type ImportMode = 'copy' | 'overwrite';
24
+ /** What the client sends to restore: the (already-selected) pages/databases + mode. */
25
+ export interface ImportRequest {
26
+ pages: StoredPage[];
27
+ databases: StoredDatabase[];
28
+ mode: ImportMode;
29
+ }
30
+ export interface ImportResult {
31
+ /** New pages created (copy mode, or overwrite of a not-yet-existing id). */
32
+ created: number;
33
+ /** Existing pages replaced (overwrite mode). */
34
+ overwritten: number;
35
+ /** Pages whose name was suffixed to avoid a clash (copy mode). */
36
+ renamed: number;
37
+ /** old page id → new page id (copy mode; identity in overwrite). */
38
+ idMap: Record<string, string>;
39
+ }
40
+ /**
41
+ * Pure: re-key a bundle for copy-mode import. Mints a fresh id for every page and
42
+ * database, remaps every internal reference (`parentId`, `databaseId`,
43
+ * `hostedDatabaseId`, a database's `pageId`, and `@`-mention `data-page-id`s
44
+ * embedded in block HTML), and returns the rewritten pages/databases plus the
45
+ * `oldId → newId` map. References to pages outside the bundle are left as-is.
46
+ * Unit-tested; the store layer adds DB-aware name de-duplication on top.
47
+ */
48
+ export declare function remapBundle(pages: StoredPage[], databases: StoredDatabase[], newId: () => string): {
49
+ pages: StoredPage[];
50
+ databases: StoredDatabase[];
51
+ idMap: Record<string, string>;
52
+ };
package/dist/backup.js ADDED
@@ -0,0 +1,35 @@
1
+ export const BACKUP_VERSION = 1;
2
+ /**
3
+ * Pure: re-key a bundle for copy-mode import. Mints a fresh id for every page and
4
+ * database, remaps every internal reference (`parentId`, `databaseId`,
5
+ * `hostedDatabaseId`, a database's `pageId`, and `@`-mention `data-page-id`s
6
+ * embedded in block HTML), and returns the rewritten pages/databases plus the
7
+ * `oldId → newId` map. References to pages outside the bundle are left as-is.
8
+ * Unit-tested; the store layer adds DB-aware name de-duplication on top.
9
+ */
10
+ export function remapBundle(pages, databases, newId) {
11
+ const idMap = {};
12
+ for (const p of pages)
13
+ idMap[p.id] = newId();
14
+ const dbMap = {};
15
+ for (const d of databases)
16
+ dbMap[d.id] = newId();
17
+ const remapMentions = (data) => {
18
+ let json = JSON.stringify(data);
19
+ for (const [oldId, nid] of Object.entries(idMap)) {
20
+ json = json.split(`data-page-id=\\"${oldId}\\"`).join(`data-page-id=\\"${nid}\\"`);
21
+ }
22
+ return JSON.parse(json);
23
+ };
24
+ const remappedPages = pages.map((p) => ({
25
+ ...p,
26
+ id: idMap[p.id],
27
+ parentId: p.parentId && idMap[p.parentId] ? idMap[p.parentId] : null,
28
+ databaseId: p.databaseId && dbMap[p.databaseId] ? dbMap[p.databaseId] : null,
29
+ hostedDatabaseId: p.hostedDatabaseId && dbMap[p.hostedDatabaseId] ? dbMap[p.hostedDatabaseId] : null,
30
+ data: remapMentions(p.data),
31
+ }));
32
+ const remappedDbs = databases.map((d) => ({ ...d, id: dbMap[d.id], pageId: idMap[d.pageId] ?? d.pageId }));
33
+ return { pages: remappedPages, databases: remappedDbs, idMap };
34
+ }
35
+ //# sourceMappingURL=backup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup.js","sourceRoot":"","sources":["../src/backup.ts"],"names":[],"mappings":"AAcA,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AA+BhC;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CACzB,KAAmB,EACnB,SAA2B,EAC3B,KAAmB;IAEnB,MAAM,KAAK,GAA2B,EAAE,CAAC;IACzC,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC;IAC7C,MAAM,KAAK,GAA2B,EAAE,CAAC;IACzC,KAAK,MAAM,CAAC,IAAI,SAAS;QAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC;IAEjD,MAAM,aAAa,GAAG,CAAC,IAAwB,EAAsB,EAAE;QACrE,IAAI,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAChC,KAAK,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACjD,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,mBAAmB,KAAK,KAAK,CAAC,CAAC,IAAI,CAAC,mBAAmB,GAAG,KAAK,CAAC,CAAC;QACrF,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAuB,CAAC;IAChD,CAAC,CAAC;IAEF,MAAM,aAAa,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACtC,GAAG,CAAC;QACJ,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QACf,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI;QACpE,UAAU,EAAE,CAAC,CAAC,UAAU,IAAI,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI;QAC5E,gBAAgB,EAAE,CAAC,CAAC,gBAAgB,IAAI,KAAK,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,IAAI;QACpG,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC;KAC5B,CAAC,CAAC,CAAC;IACJ,MAAM,WAAW,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAC,GAAG,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,EAAC,CAAC,CAAC,CAAC;IACzG,OAAO,EAAC,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,WAAW,EAAE,KAAK,EAAC,CAAC;AAC/D,CAAC"}
@@ -0,0 +1,44 @@
1
+ import type { StoredPage } from './types';
2
+ import type { StoredDatabase } from './database';
3
+ /**
4
+ * Whole-space → folder-of-files serialisation, shared by every "dump my books
5
+ * to a folder" surface: the desktop's native folder export and the web app's
6
+ * File System Access export both call {@link spaceToBookFiles}, and the layout
7
+ * is byte-compatible with the server's on-disk {@link BookMirror} (OB-134) so a
8
+ * folder written by one can be re-imported by the other.
9
+ *
10
+ * The layout is exactly two levels deep — `<book-folder>/<page>.html` — where
11
+ * the book folder is named from the page's *root* ancestor. Each page renders to
12
+ * the human-readable `.book.html` format (`pageToBookHtml`); alongside them a
13
+ * single {@link SPACE_BUNDLE_FILE} carries the full structured bundle so an
14
+ * import is lossless (parent/position/properties and databases survive, which
15
+ * the flat HTML files alone don't capture).
16
+ */
17
+ /** A relative file within the chosen folder (POSIX `/` separators). */
18
+ export interface BookFolderFile {
19
+ path: string;
20
+ contents: string;
21
+ }
22
+ /** Everything in a space, as returned by `DataClient.exportSpace`. */
23
+ export interface SpaceSnapshot {
24
+ pages: StoredPage[];
25
+ databases: StoredDatabase[];
26
+ }
27
+ /** Lossless structured sidecar, parsed back by {@link parseBookFolder}. */
28
+ export declare const SPACE_BUNDLE_FILE = "openbook.space.json";
29
+ /**
30
+ * Serialise a space to its on-disk files. By default includes the lossless
31
+ * {@link SPACE_BUNDLE_FILE}; pass `includeBundle: false` for the human-readable
32
+ * HTML files only.
33
+ */
34
+ export declare function spaceToBookFiles(snapshot: SpaceSnapshot, opts?: {
35
+ includeBundle?: boolean;
36
+ }): BookFolderFile[];
37
+ /**
38
+ * Reconstruct a space from a folder's files. Prefers the lossless
39
+ * {@link SPACE_BUNDLE_FILE} when present; otherwise falls back to parsing the
40
+ * `.html` files into a flat list of pages (no databases, no nesting — the most
41
+ * a human-readable folder can recover). Returns `null` if nothing parseable was
42
+ * found, so the caller can surface "not an OpenBook folder".
43
+ */
44
+ export declare function parseBookFolder(files: BookFolderFile[]): SpaceSnapshot | null;