@hiai-gg/docsmint 0.3.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 (57) hide show
  1. package/LICENSE +171 -0
  2. package/README.md +348 -0
  3. package/backend/src/lib/logger.ts +18 -0
  4. package/backend/src/lib/redis-factory.ts +40 -0
  5. package/backend/src/lib/storage-factory.ts +56 -0
  6. package/frontend/src/lib/components/editor/shared-document.ts +237 -0
  7. package/frontend/src/lib/extensions/context.ts +60 -0
  8. package/frontend/src/lib/extensions/doc-tabs.ts +18 -0
  9. package/frontend/src/lib/extensions/resolve.ts +48 -0
  10. package/frontend/src/lib/extensions/types.ts +202 -0
  11. package/frontend/src/lib/hosts/DocsmintSharedDocumentHost.svelte +65 -0
  12. package/frontend/src/lib/hosts/HiaiDocsDashboardHost.svelte +1007 -0
  13. package/frontend/src/lib/hosts/HiaiDocsExtensionProvider.svelte +20 -0
  14. package/frontend/src/lib/hosts/HiaiDocsSearchHost.svelte +996 -0
  15. package/frontend/src/lib/hosts/index.ts +25 -0
  16. package/frontend/src/lib/index.ts +65 -0
  17. package/frontend/src/lib/stores/doc-tab-registry.svelte.ts +68 -0
  18. package/package.json +178 -0
  19. package/packages/cli/src/client.ts +271 -0
  20. package/packages/cli/src/commands/config.ts +47 -0
  21. package/packages/cli/src/commands/create.ts +35 -0
  22. package/packages/cli/src/commands/delete.ts +37 -0
  23. package/packages/cli/src/commands/export.ts +36 -0
  24. package/packages/cli/src/commands/folders.ts +88 -0
  25. package/packages/cli/src/commands/history.ts +55 -0
  26. package/packages/cli/src/commands/list.ts +61 -0
  27. package/packages/cli/src/commands/read.ts +38 -0
  28. package/packages/cli/src/commands/restore.ts +30 -0
  29. package/packages/cli/src/commands/search.ts +56 -0
  30. package/packages/cli/src/commands/snapshot.ts +35 -0
  31. package/packages/cli/src/commands/update.ts +54 -0
  32. package/packages/cli/src/config.ts +83 -0
  33. package/packages/cli/src/format.ts +153 -0
  34. package/packages/cli/src/index.ts +73 -0
  35. package/packages/db/src/client.ts +20 -0
  36. package/packages/db/src/index.ts +5 -0
  37. package/packages/db/src/schema.ts +692 -0
  38. package/packages/db/src/with-tenant.ts +75 -0
  39. package/packages/mcp-server/src/client.ts +172 -0
  40. package/packages/mcp-server/src/index.ts +109 -0
  41. package/packages/mcp-server/src/tools/create-document.ts +32 -0
  42. package/packages/mcp-server/src/tools/create-folder.ts +24 -0
  43. package/packages/mcp-server/src/tools/create-snapshot.ts +30 -0
  44. package/packages/mcp-server/src/tools/export-document.ts +22 -0
  45. package/packages/mcp-server/src/tools/get-document.ts +20 -0
  46. package/packages/mcp-server/src/tools/list-documents.ts +42 -0
  47. package/packages/mcp-server/src/tools/list-folders.ts +25 -0
  48. package/packages/mcp-server/src/tools/search.ts +42 -0
  49. package/packages/mcp-server/src/tools/update-document.ts +30 -0
  50. package/packages/mcp-server/src/tools/version-history.ts +32 -0
  51. package/packages/mcp-server/src/types.ts +126 -0
  52. package/packages/sdk/dist/client.d.ts +187 -0
  53. package/packages/sdk/dist/client.js +568 -0
  54. package/packages/sdk/dist/index.d.ts +3 -0
  55. package/packages/sdk/dist/index.js +1 -0
  56. package/packages/sdk/dist/types.d.ts +391 -0
  57. package/packages/sdk/dist/types.js +8 -0
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Public application hosts.
3
+ *
4
+ * These components preserve the standalone HiAi-Docs routes while allowing a
5
+ * product build to mount typed, additive frontend extensions.
6
+ */
7
+
8
+ export { default as DocsmintSharedDocumentHost } from "./DocsmintSharedDocumentHost.svelte";
9
+ export type { HiaiDocsDashboardData } from "./HiaiDocsDashboardHost.svelte";
10
+ /** @deprecated Use DocsmintDashboardHost. */
11
+ export {
12
+ default as HiaiDocsDashboardHost,
13
+ default as DocsmintDashboardHost,
14
+ } from "./HiaiDocsDashboardHost.svelte";
15
+ /** Canonical extension provider. */
16
+ export {
17
+ default as HiaiDocsExtensionProvider,
18
+ default as DocsmintExtensionProvider,
19
+ } from "./HiaiDocsExtensionProvider.svelte";
20
+ export type { HiaiDocsSearchData } from "./HiaiDocsSearchHost.svelte";
21
+ /** @deprecated Use DocsmintSearchHost. */
22
+ export {
23
+ default as HiaiDocsSearchHost,
24
+ default as DocsmintSearchHost,
25
+ } from "./HiaiDocsSearchHost.svelte";
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Public entrypoint for the hiai-docs frontend extension package.
3
+ *
4
+ * Keep this barrel limited to SSR-safe contracts and pure helpers. Hosts such
5
+ * as DocsMint import this path from the published package; route modules and
6
+ * mutable app singletons are intentionally not part of the public surface.
7
+ */
8
+
9
+ export type {
10
+ ProseMirrorDoc,
11
+ ProseMirrorNode,
12
+ SharedAttachmentObjectUrls,
13
+ } from "./components/editor/shared-document";
14
+ export {
15
+ hydrateSharedAttachmentImages,
16
+ renderSharedDocument,
17
+ sharedAttachmentHeaders,
18
+ } from "./components/editor/shared-document";
19
+ export {
20
+ createFrontendExtensions,
21
+ getFrontendExtensions,
22
+ getHiaiDocsExtensions,
23
+ provideFrontendExtensions,
24
+ setFrontendExtensions,
25
+ setHiaiDocsExtensions,
26
+ } from "./extensions/context";
27
+ export type {
28
+ DocTabDefinition,
29
+ DocTabIcon,
30
+ DocTabPanelProps,
31
+ } from "./extensions/doc-tabs";
32
+ export type {
33
+ CommandPaletteAction,
34
+ CommandPaletteActionContext,
35
+ CommandPaletteActionExtension,
36
+ DashboardWidgetExtension,
37
+ DashboardWidgetProps,
38
+ DocumentMenuAction,
39
+ DocumentMenuActionContext,
40
+ DocumentMenuActionExtension,
41
+ EditorActionContext,
42
+ EditorActionExtension,
43
+ ExtensionAction,
44
+ ExtensionIcon,
45
+ ExtensionVisibility,
46
+ ExtensionVisibilityContext,
47
+ FrontendExtensions,
48
+ HiaiDocsFrontendExtensions,
49
+ NavigationExtension,
50
+ SearchWidgetExtension,
51
+ SearchWidgetProps,
52
+ SettingsSectionExtension,
53
+ SettingsSectionProps,
54
+ SharedDocumentExtension,
55
+ SharedDocumentExtensionContext,
56
+ } from "./extensions/types";
57
+ export {
58
+ DocsmintDashboardHost,
59
+ DocsmintExtensionProvider,
60
+ DocsmintSearchHost,
61
+ DocsmintSharedDocumentHost,
62
+ HiaiDocsDashboardHost,
63
+ HiaiDocsExtensionProvider,
64
+ HiaiDocsSearchHost,
65
+ } from "./hosts";
@@ -0,0 +1,68 @@
1
+ /**
2
+ * doc-tab-registry.svelte.ts
3
+ *
4
+ * Lightweight open registry for document-page tabs.
5
+ *
6
+ * hiai-docs ships this file empty - no tabs are registered out of the box.
7
+ * External projects (e.g. hiai-admin, commercial forks) call registerDocTab()
8
+ * from their own +layout.svelte to inject custom tabs alongside the built-in
9
+ * editor without modifying any hiai-docs core files.
10
+ *
11
+ * Usage in an external project's layout:
12
+ * import { registerDocTab } from "$lib/stores/doc-tab-registry.svelte";
13
+ * import HtmlRenditionPanel from "./HtmlRenditionPanel.svelte";
14
+ * registerDocTab({ id: "html-rendition", label: "HTML Preview", component: HtmlRenditionPanel });
15
+ *
16
+ * STABILITY NOTICE:
17
+ * The interfaces `DocTabPanelProps` and `DocTabDefinition` and functions/states
18
+ * `registerDocTab` and `docTabRegistry` are considered stable public APIs.
19
+ * Breaking changes to these will be announced as major version bumps.
20
+ */
21
+
22
+ import type { DocTabDefinition } from "../extensions/doc-tabs";
23
+
24
+ export type {
25
+ DocTabDefinition,
26
+ DocTabIcon,
27
+ DocTabPanelProps,
28
+ } from "../extensions/doc-tabs";
29
+
30
+ /**
31
+ * Reactive array of registered doc tabs.
32
+ * Read by the document page to render the tab bar and panels.
33
+ * Mutate only via registerDocTab() to guarantee idempotency.
34
+ */
35
+ export const docTabRegistry: DocTabDefinition[] = $state([]);
36
+
37
+ /**
38
+ * Create an isolated tab collection for a host-provided extension manifest.
39
+ * The legacy `docTabRegistry` export remains available for existing clients,
40
+ * while new app-shell integrations should keep their tabs request-scoped.
41
+ */
42
+ export function createDocTabRegistry(
43
+ initial: readonly DocTabDefinition[] = [],
44
+ ): DocTabDefinition[] {
45
+ return [...initial];
46
+ }
47
+
48
+ /** Register a tab in an isolated collection, preserving idempotency. */
49
+ export function registerDocTabIn(
50
+ registry: DocTabDefinition[],
51
+ tab: DocTabDefinition,
52
+ ): void {
53
+ if (!registry.find((existing) => existing.id === tab.id)) {
54
+ registry.push(tab);
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Register a custom document tab.
60
+ *
61
+ * Safe to call multiple times (e.g. across HMR reloads) - duplicate ids
62
+ * are silently ignored so layout-level registrations do not stack up.
63
+ *
64
+ * @param tab - Tab definition to register.
65
+ */
66
+ export function registerDocTab(tab: DocTabDefinition): void {
67
+ registerDocTabIn(docTabRegistry, tab);
68
+ }
package/package.json ADDED
@@ -0,0 +1,178 @@
1
+ {
2
+ "name": "@hiai-gg/docsmint",
3
+ "version": "0.3.0",
4
+ "type": "module",
5
+ "license": "Apache-2.0",
6
+ "description": "The lightweight, AI-native self-hosted knowledge base — built-in RAG, hybrid semantic search, and a clean REST API for AI agents.",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/HiAi-gg/docsmint"
10
+ },
11
+ "homepage": "https://github.com/hiai-gg/docsmint#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/hiai-gg/docsmint/issues"
14
+ },
15
+ "keywords": [
16
+ "knowledge-base",
17
+ "ai-native",
18
+ "wiki",
19
+ "markdown",
20
+ "ai-embeddings",
21
+ "rag",
22
+ "semantic-search",
23
+ "self-hosted",
24
+ "pgvector",
25
+ "ollama",
26
+ "local-llm",
27
+ "pwa",
28
+ "progressive-web-app",
29
+ "offline-knowledge-base",
30
+ "docsmint",
31
+ "outline-alternative",
32
+ "docmost-alternative"
33
+ ],
34
+ "main": "./packages/sdk/dist/index.js",
35
+ "types": "./packages/sdk/dist/index.d.ts",
36
+ "exports": {
37
+ ".": {
38
+ "import": "./packages/sdk/dist/index.js",
39
+ "types": "./packages/sdk/dist/index.d.ts"
40
+ },
41
+ "./sdk": {
42
+ "import": "./packages/sdk/dist/index.js",
43
+ "types": "./packages/sdk/dist/index.d.ts"
44
+ },
45
+ "./frontend": {
46
+ "import": "./frontend/src/lib/index.ts",
47
+ "types": "./frontend/src/lib/index.ts"
48
+ },
49
+ "./frontend/shared-document": {
50
+ "import": "./frontend/src/lib/components/editor/shared-document.ts",
51
+ "types": "./frontend/src/lib/components/editor/shared-document.ts"
52
+ },
53
+ "./frontend/extensions": {
54
+ "import": "./frontend/src/lib/extensions/types.ts",
55
+ "types": "./frontend/src/lib/extensions/types.ts"
56
+ },
57
+ "./frontend/hosts": {
58
+ "import": "./frontend/src/lib/hosts/index.ts",
59
+ "types": "./frontend/src/lib/hosts/index.ts"
60
+ },
61
+ "./frontend/legacy/doc-tab-registry": {
62
+ "import": "./frontend/src/lib/stores/doc-tab-registry.svelte.ts",
63
+ "types": "./frontend/src/lib/stores/doc-tab-registry.svelte.ts"
64
+ },
65
+ "./schema": {
66
+ "import": "./packages/db/src/schema.ts",
67
+ "types": "./packages/db/src/schema.ts"
68
+ },
69
+ "./db": {
70
+ "import": "./packages/db/src/index.ts",
71
+ "types": "./packages/db/src/index.ts"
72
+ },
73
+ "./db/client": {
74
+ "import": "./packages/db/src/client.ts",
75
+ "types": "./packages/db/src/client.ts"
76
+ },
77
+ "./db/with-tenant": {
78
+ "import": "./packages/db/src/with-tenant.ts",
79
+ "types": "./packages/db/src/with-tenant.ts"
80
+ },
81
+ "./backend/lib/redis": {
82
+ "import": "./backend/src/lib/redis-factory.ts",
83
+ "types": "./backend/src/lib/redis-factory.ts"
84
+ },
85
+ "./backend/lib/storage": {
86
+ "import": "./backend/src/lib/storage-factory.ts",
87
+ "types": "./backend/src/lib/storage-factory.ts"
88
+ },
89
+ "./backend/lib/logger": {
90
+ "import": "./backend/src/lib/logger.ts",
91
+ "types": "./backend/src/lib/logger.ts"
92
+ }
93
+ },
94
+ "bin": {
95
+ "docsmint": "./packages/cli/src/index.ts",
96
+ "docsmint-mcp": "./packages/mcp-server/src/index.ts",
97
+ "hiai-docs": "./packages/cli/src/index.ts",
98
+ "hiai-docs-mcp": "./packages/mcp-server/src/index.ts"
99
+ },
100
+ "files": [
101
+ "packages/sdk/dist/index.js",
102
+ "packages/sdk/dist/index.d.ts",
103
+ "packages/sdk/dist/client.js",
104
+ "packages/sdk/dist/client.d.ts",
105
+ "packages/sdk/dist/types.js",
106
+ "packages/sdk/dist/types.d.ts",
107
+ "packages/db/src/index.ts",
108
+ "packages/db/src/client.ts",
109
+ "packages/db/src/schema.ts",
110
+ "packages/db/src/with-tenant.ts",
111
+ "packages/cli/src/index.ts",
112
+ "packages/cli/src/client.ts",
113
+ "packages/cli/src/config.ts",
114
+ "packages/cli/src/format.ts",
115
+ "packages/cli/src/commands",
116
+ "packages/mcp-server/src/index.ts",
117
+ "packages/mcp-server/src/client.ts",
118
+ "packages/mcp-server/src/types.ts",
119
+ "packages/mcp-server/src/tools",
120
+ "backend/src/lib/redis-factory.ts",
121
+ "backend/src/lib/storage-factory.ts",
122
+ "backend/src/lib/logger.ts",
123
+ "frontend/src/lib/index.ts",
124
+ "frontend/src/lib/extensions/context.ts",
125
+ "frontend/src/lib/extensions/doc-tabs.ts",
126
+ "frontend/src/lib/extensions/resolve.ts",
127
+ "frontend/src/lib/extensions/types.ts",
128
+ "frontend/src/lib/hosts",
129
+ "frontend/src/lib/stores/doc-tab-registry.svelte.ts",
130
+ "frontend/src/lib/components/editor/shared-document.ts",
131
+ "README.md",
132
+ "LICENSE"
133
+ ],
134
+ "engines": {
135
+ "bun": ">= 1.3.14"
136
+ },
137
+ "publishConfig": {
138
+ "access": "public"
139
+ },
140
+ "scripts": {
141
+ "dev": "docker compose up -d",
142
+ "build": "docker compose build",
143
+ "start": "docker compose up -d",
144
+ "stop": "docker compose down",
145
+ "build:sdk": "cd packages/sdk && bun run build",
146
+ "prepublishOnly": "cd packages/sdk && bun run build"
147
+ },
148
+ "dependencies": {
149
+ "@modelcontextprotocol/sdk": "^1.12.1",
150
+ "commander": "^13.1.0",
151
+ "zod": "^4.0.0"
152
+ },
153
+ "peerDependencies": {
154
+ "drizzle-orm": "^0.45.2",
155
+ "postgres": "^3.4.9",
156
+ "ioredis": "^5.4.0",
157
+ "@aws-sdk/client-s3": "^3.0.0",
158
+ "pino": "^10.0.0",
159
+ "svelte": "^5.56.4"
160
+ },
161
+ "peerDependenciesMeta": {
162
+ "drizzle-orm": {
163
+ "optional": true
164
+ },
165
+ "postgres": {
166
+ "optional": true
167
+ },
168
+ "ioredis": {
169
+ "optional": true
170
+ },
171
+ "@aws-sdk/client-s3": {
172
+ "optional": true
173
+ },
174
+ "pino": {
175
+ "optional": true
176
+ }
177
+ }
178
+ }
@@ -0,0 +1,271 @@
1
+ /**
2
+ * REST client for hiai-docs.
3
+ *
4
+ * Pattern mirrors @hiai-docs/mcp-server/src/client.ts: one
5
+ * `request()` helper handles auth headers, query encoding, and
6
+ * error unwrapping; the exported `client` object groups all
7
+ * endpoints into typed methods.
8
+ *
9
+ * Configuration comes from `./config.ts` (file + env), which
10
+ * differs from the MCP server's env-only approach because the CLI
11
+ * is interactive and benefits from a persistent config file.
12
+ *
13
+ * Bun-native. Uses global `fetch`. Throws `HiaiDocsError` on
14
+ * non-2xx responses with the error message extracted from the
15
+ * response body when possible.
16
+ */
17
+
18
+ import { loadConfig, type Config } from "./config.js";
19
+
20
+ export class HiaiDocsError extends Error {
21
+ constructor(
22
+ message: string,
23
+ readonly status: number,
24
+ readonly body: unknown,
25
+ ) {
26
+ super(message);
27
+ this.name = "HiaiDocsError";
28
+ }
29
+ }
30
+
31
+ interface RequestOptions {
32
+ query?: Record<string, string | number | boolean | string[] | undefined>;
33
+ body?: unknown;
34
+ headers?: Record<string, string>;
35
+ accept?: string;
36
+ }
37
+
38
+ async function request<T>(
39
+ method: string,
40
+ path: string,
41
+ options: RequestOptions = {},
42
+ ): Promise<T> {
43
+ const cfg = loadConfig();
44
+ const baseUrl = cfg.url.replace(/\/+$/, "");
45
+ const url = new URL(path.startsWith("/") ? path : `/${path}`, `${baseUrl}/`);
46
+
47
+ if (options.query) {
48
+ for (const [key, value] of Object.entries(options.query)) {
49
+ if (value === undefined) continue;
50
+ if (Array.isArray(value)) {
51
+ if (value.length > 0) url.searchParams.set(key, value.join(","));
52
+ } else {
53
+ url.searchParams.set(key, String(value));
54
+ }
55
+ }
56
+ }
57
+
58
+ const headers: Record<string, string> = {
59
+ Accept: options.accept ?? "application/json",
60
+ ...options.headers,
61
+ };
62
+ if (cfg.apiKey) {
63
+ headers["Authorization"] = `Bearer ${cfg.apiKey}`;
64
+ }
65
+
66
+ let body: BodyInit | undefined;
67
+ if (options.body !== undefined) {
68
+ headers["Content-Type"] = "application/json";
69
+ body = JSON.stringify(options.body);
70
+ }
71
+
72
+ const response = await fetch(url, { method, headers, body });
73
+
74
+ const contentType = response.headers.get("content-type") ?? "";
75
+ const isJson = contentType.includes("application/json");
76
+ const payload: unknown = isJson
77
+ ? await response.json().catch(() => null)
78
+ : await response.text().catch(() => null);
79
+
80
+ if (!response.ok) {
81
+ const message =
82
+ (isJson &&
83
+ payload &&
84
+ typeof payload === "object" &&
85
+ "error" in payload
86
+ ? String((payload as { error: unknown }).error)
87
+ : typeof payload === "string" && payload.length > 0
88
+ ? payload
89
+ : `HTTP ${response.status} ${response.statusText}`) ||
90
+ `HTTP ${response.status}`;
91
+ throw new HiaiDocsError(message, response.status, payload);
92
+ }
93
+
94
+ return payload as T;
95
+ }
96
+
97
+ function joinId(...segments: string[]): string {
98
+ return segments.map((s) => encodeURIComponent(s)).join("/");
99
+ }
100
+
101
+ // --- Response types ----------------------------------------------------------
102
+
103
+ export interface DocumentSummary {
104
+ id: string;
105
+ title: string;
106
+ content?: string | null;
107
+ folderId?: string | null;
108
+ folderName?: string | null;
109
+ createdAt: string;
110
+ updatedAt: string;
111
+ tags?: Array<{ id: string; name: string; color: string | null }>;
112
+ }
113
+
114
+ export interface DocumentDetail extends DocumentSummary {
115
+ ownerId: string;
116
+ content?: string | null;
117
+ contentJson?: unknown;
118
+ metadata?: unknown;
119
+ }
120
+
121
+ export interface Folder {
122
+ id: string;
123
+ ownerId?: string;
124
+ name: string;
125
+ parentId?: string | null;
126
+ createdAt?: string;
127
+ updatedAt?: string;
128
+ }
129
+
130
+ export interface VersionRow {
131
+ id: string;
132
+ documentId: string;
133
+ createdBy: string;
134
+ createdAt: string;
135
+ label?: string | null;
136
+ description?: string | null;
137
+ isSnapshot: boolean;
138
+ restoredFrom?: string | null;
139
+ }
140
+
141
+ export interface SnapshotRow extends VersionRow {
142
+ content?: string | null;
143
+ contentJson?: unknown;
144
+ }
145
+
146
+ export interface SearchItem {
147
+ id: string;
148
+ title: string;
149
+ snippet: string;
150
+ score: number;
151
+ folder_id?: string | null;
152
+ folder_name?: string | null;
153
+ created_at: string;
154
+ updated_at: string;
155
+ tags?: Array<{ id: string; name: string; color: string | null }>;
156
+ }
157
+
158
+ export interface SearchResponse {
159
+ items: SearchItem[];
160
+ total: number;
161
+ page: number;
162
+ limit: number;
163
+ }
164
+
165
+ export interface ListDocumentsResponse {
166
+ items: DocumentSummary[];
167
+ total: number;
168
+ page: number;
169
+ limit: number;
170
+ }
171
+
172
+ // --- Client ----------------------------------------------------------------
173
+
174
+ export const client = {
175
+ search(params: {
176
+ query: string;
177
+ folder?: string;
178
+ tags?: string[];
179
+ limit?: number;
180
+ }): Promise<SearchResponse> {
181
+ return request("GET", "/api/search", {
182
+ query: {
183
+ q: params.query,
184
+ folder: params.folder,
185
+ tags: params.tags,
186
+ limit: params.limit,
187
+ },
188
+ });
189
+ },
190
+
191
+ listDocuments(params: {
192
+ folderId?: string;
193
+ tag?: string;
194
+ page?: number;
195
+ limit?: number;
196
+ }): Promise<ListDocumentsResponse> {
197
+ return request("GET", "/api/documents", { query: params });
198
+ },
199
+
200
+ getDocument(id: string): Promise<DocumentDetail> {
201
+ return request("GET", `/api/${joinId("documents", id)}`);
202
+ },
203
+
204
+ createDocument(input: {
205
+ title: string;
206
+ content?: string;
207
+ folderId?: string;
208
+ }): Promise<DocumentDetail> {
209
+ return request("POST", "/api/documents", { body: input });
210
+ },
211
+
212
+ updateDocument(
213
+ id: string,
214
+ input: { title?: string; content?: string; folderId?: string | null },
215
+ ): Promise<DocumentDetail> {
216
+ return request("PATCH", `/api/${joinId("documents", id)}`, { body: input });
217
+ },
218
+
219
+ deleteDocument(id: string): Promise<{ success: boolean }> {
220
+ return request("DELETE", `/api/${joinId("documents", id)}`);
221
+ },
222
+
223
+ exportDocument(id: string): Promise<string> {
224
+ return request("GET", `/api/${joinId("documents", id, "export")}`, {
225
+ accept: "text/markdown",
226
+ });
227
+ },
228
+
229
+ createSnapshot(
230
+ documentId: string,
231
+ input: { label: string; description?: string },
232
+ ): Promise<SnapshotRow> {
233
+ // The backend exposes snapshots via POST /api/documents/:id/versions
234
+ // with isSnapshot=true (see backend/src/api/routes/versions.ts).
235
+ return request(
236
+ "POST",
237
+ `/api/${joinId("documents", documentId, "versions")}`,
238
+ { body: input },
239
+ );
240
+ },
241
+
242
+ listVersions(
243
+ documentId: string,
244
+ onlySnapshots?: boolean,
245
+ ): Promise<VersionRow[]> {
246
+ return request(
247
+ "GET",
248
+ `/api/${joinId("documents", documentId, "versions")}`,
249
+ { query: { onlySnapshots } },
250
+ );
251
+ },
252
+
253
+ restoreVersion(documentId: string, versionId: string): Promise<DocumentDetail> {
254
+ return request(
255
+ "POST",
256
+ `/api/${joinId("documents", documentId, "versions", versionId, "restore")}`,
257
+ );
258
+ },
259
+
260
+ listFolders(params: { parentId?: string }): Promise<Folder[]> {
261
+ return request("GET", "/api/folders", { query: { parentId: params.parentId } });
262
+ },
263
+
264
+ createFolder(input: { name: string; parentId?: string }): Promise<Folder> {
265
+ return request("POST", "/api/folders", { body: input });
266
+ },
267
+ };
268
+
269
+ export type HiaiDocsClient = typeof client;
270
+
271
+ export type { Config };
@@ -0,0 +1,47 @@
1
+ /**
2
+ * `hiai-docs config` — save or update the CLI config file.
3
+ *
4
+ * Both --url and --key are optional: with neither supplied the
5
+ * command just prints the current configuration. With either
6
+ * supplied, only the provided keys are updated (the other is
7
+ * preserved from the existing file or env var).
8
+ */
9
+
10
+ import type { Command } from "commander";
11
+ import { loadConfig, saveConfig } from "../config.js";
12
+ import { dim, formatError, green } from "../format.js";
13
+
14
+ export function registerConfig(program: Command) {
15
+ program
16
+ .command("config")
17
+ .description("View or update CLI configuration")
18
+ .option("--url <url>", "API base URL")
19
+ .option("--key <key>", "API key (Bearer token)")
20
+ .option("--show", "Print the resolved configuration and exit")
21
+ .action(
22
+ async (opts: { url?: string; key?: string; show?: boolean }) => {
23
+ try {
24
+ const current = loadConfig();
25
+ if (opts.show || (opts.url === undefined && opts.key === undefined)) {
26
+ process.stdout.write(`url: ${current.url}\n`);
27
+ process.stdout.write(
28
+ `key: ${current.apiKey ? `${current.apiKey.slice(0, 4)}…(redacted)` : "(unset)"}\n`,
29
+ );
30
+ process.stdout.write(
31
+ `${dim("Env vars HIAI_DOCS_URL / HIAI_DOCS_API_KEY override the file.")}\n`,
32
+ );
33
+ return;
34
+ }
35
+ const next = {
36
+ url: opts.url ?? current.url,
37
+ apiKey: opts.key ?? current.apiKey,
38
+ };
39
+ saveConfig(next);
40
+ process.stdout.write(`${green("✓")} Configuration saved.\n`);
41
+ } catch (err) {
42
+ process.stderr.write(`${formatError(err)}\n`);
43
+ process.exitCode = 1;
44
+ }
45
+ },
46
+ );
47
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * `hiai-docs create` — create a new document.
3
+ *
4
+ * Optional `--content` lets you inline markdown from the CLI; if you
5
+ * pipe a file (`cat spec.md | hiai-docs create --title "Spec"` —
6
+ * reserved for a future stdin flag) you'd extend this command.
7
+ */
8
+
9
+ import type { Command } from "commander";
10
+ import { client, type HiaiDocsClient } from "../client.js";
11
+ import { formatError } from "../format.js";
12
+
13
+ export function registerCreate(program: Command, _getClient: () => HiaiDocsClient) {
14
+ program
15
+ .command("create")
16
+ .description("Create a new document")
17
+ .requiredOption("--title <title>", "Document title")
18
+ .option("-c, --content <markdown>", "Initial markdown content")
19
+ .option("-f, --folder <uuid>", "Place in folder")
20
+ .action(
21
+ async (opts: { title: string; content?: string; folder?: string }) => {
22
+ try {
23
+ const doc = await client.createDocument({
24
+ title: opts.title,
25
+ content: opts.content,
26
+ folderId: opts.folder,
27
+ });
28
+ process.stdout.write(`${doc.id}\n`);
29
+ } catch (err) {
30
+ process.stderr.write(`${formatError(err)}\n`);
31
+ process.exitCode = 1;
32
+ }
33
+ },
34
+ );
35
+ }