@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.
- package/LICENSE +171 -0
- package/README.md +348 -0
- package/backend/src/lib/logger.ts +18 -0
- package/backend/src/lib/redis-factory.ts +40 -0
- package/backend/src/lib/storage-factory.ts +56 -0
- package/frontend/src/lib/components/editor/shared-document.ts +237 -0
- package/frontend/src/lib/extensions/context.ts +60 -0
- package/frontend/src/lib/extensions/doc-tabs.ts +18 -0
- package/frontend/src/lib/extensions/resolve.ts +48 -0
- package/frontend/src/lib/extensions/types.ts +202 -0
- package/frontend/src/lib/hosts/DocsmintSharedDocumentHost.svelte +65 -0
- package/frontend/src/lib/hosts/HiaiDocsDashboardHost.svelte +1007 -0
- package/frontend/src/lib/hosts/HiaiDocsExtensionProvider.svelte +20 -0
- package/frontend/src/lib/hosts/HiaiDocsSearchHost.svelte +996 -0
- package/frontend/src/lib/hosts/index.ts +25 -0
- package/frontend/src/lib/index.ts +65 -0
- package/frontend/src/lib/stores/doc-tab-registry.svelte.ts +68 -0
- package/package.json +178 -0
- package/packages/cli/src/client.ts +271 -0
- package/packages/cli/src/commands/config.ts +47 -0
- package/packages/cli/src/commands/create.ts +35 -0
- package/packages/cli/src/commands/delete.ts +37 -0
- package/packages/cli/src/commands/export.ts +36 -0
- package/packages/cli/src/commands/folders.ts +88 -0
- package/packages/cli/src/commands/history.ts +55 -0
- package/packages/cli/src/commands/list.ts +61 -0
- package/packages/cli/src/commands/read.ts +38 -0
- package/packages/cli/src/commands/restore.ts +30 -0
- package/packages/cli/src/commands/search.ts +56 -0
- package/packages/cli/src/commands/snapshot.ts +35 -0
- package/packages/cli/src/commands/update.ts +54 -0
- package/packages/cli/src/config.ts +83 -0
- package/packages/cli/src/format.ts +153 -0
- package/packages/cli/src/index.ts +73 -0
- package/packages/db/src/client.ts +20 -0
- package/packages/db/src/index.ts +5 -0
- package/packages/db/src/schema.ts +692 -0
- package/packages/db/src/with-tenant.ts +75 -0
- package/packages/mcp-server/src/client.ts +172 -0
- package/packages/mcp-server/src/index.ts +109 -0
- package/packages/mcp-server/src/tools/create-document.ts +32 -0
- package/packages/mcp-server/src/tools/create-folder.ts +24 -0
- package/packages/mcp-server/src/tools/create-snapshot.ts +30 -0
- package/packages/mcp-server/src/tools/export-document.ts +22 -0
- package/packages/mcp-server/src/tools/get-document.ts +20 -0
- package/packages/mcp-server/src/tools/list-documents.ts +42 -0
- package/packages/mcp-server/src/tools/list-folders.ts +25 -0
- package/packages/mcp-server/src/tools/search.ts +42 -0
- package/packages/mcp-server/src/tools/update-document.ts +30 -0
- package/packages/mcp-server/src/tools/version-history.ts +32 -0
- package/packages/mcp-server/src/types.ts +126 -0
- package/packages/sdk/dist/client.d.ts +187 -0
- package/packages/sdk/dist/client.js +568 -0
- package/packages/sdk/dist/index.d.ts +3 -0
- package/packages/sdk/dist/index.js +1 -0
- package/packages/sdk/dist/types.d.ts +391 -0
- package/packages/sdk/dist/types.js +8 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output formatting helpers.
|
|
3
|
+
*
|
|
4
|
+
* Goals:
|
|
5
|
+
* - Clean, scannable tables for human use
|
|
6
|
+
* - Pipe-friendly: ANSI codes disabled when stdout is not a TTY
|
|
7
|
+
* - No third-party chalk/table dependencies — keep the runtime small
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { isatty } from "node:tty";
|
|
11
|
+
|
|
12
|
+
const useColor = isatty(1);
|
|
13
|
+
|
|
14
|
+
function wrap(open: string, close: string): (s: string) => string {
|
|
15
|
+
if (!useColor) return (s) => s;
|
|
16
|
+
return (s) => `${open}${s}${close}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const dim = wrap("\x1b[2m", "\x1b[22m");
|
|
20
|
+
export const bold = wrap("\x1b[1m", "\x1b[22m");
|
|
21
|
+
export const red = wrap("\x1b[31m", "\x1b[39m");
|
|
22
|
+
export const green = wrap("\x1b[32m", "\x1b[39m");
|
|
23
|
+
export const yellow = wrap("\x1b[33m", "\x1b[39m");
|
|
24
|
+
export const cyan = wrap("\x1b[36m", "\x1b[39m");
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Truncate a string to `max` characters with an ellipsis suffix.
|
|
28
|
+
* Used to keep wide table cells from wrapping in terminals.
|
|
29
|
+
*/
|
|
30
|
+
export function truncate(value: string, max: number): string {
|
|
31
|
+
if (max <= 0) return "";
|
|
32
|
+
if (value.length <= max) return value;
|
|
33
|
+
if (max <= 1) return value.slice(0, max);
|
|
34
|
+
return `${value.slice(0, max - 1)}…`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Strip trailing whitespace and collapse internal newlines into single
|
|
39
|
+
* spaces — keeps snippet-style cells to one row in a table.
|
|
40
|
+
*/
|
|
41
|
+
export function oneLine(value: string, max = 200): string {
|
|
42
|
+
const flat = value.replace(/\s+/g, " ").trim();
|
|
43
|
+
return truncate(flat, max);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface Column<T> {
|
|
47
|
+
header: string;
|
|
48
|
+
width: number;
|
|
49
|
+
get: (row: T) => string;
|
|
50
|
+
align?: "left" | "right";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function pad(s: string, width: number, align: "left" | "right" = "left"): string {
|
|
54
|
+
// Account for visible width — assumes no double-width chars for now.
|
|
55
|
+
const visible = s.length;
|
|
56
|
+
if (visible >= width) return s;
|
|
57
|
+
const filler = " ".repeat(width - visible);
|
|
58
|
+
return align === "right" ? `${filler}${s}` : `${s}${filler}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Render an array of rows as an ASCII table. Returns "" for an empty input.
|
|
63
|
+
*/
|
|
64
|
+
export function renderTable<T>(
|
|
65
|
+
rows: T[],
|
|
66
|
+
columns: Array<Column<T>>,
|
|
67
|
+
): string {
|
|
68
|
+
if (rows.length === 0) return "";
|
|
69
|
+
const headerLine = columns
|
|
70
|
+
.map((c) => bold(pad(c.header, c.width, c.align ?? "left")))
|
|
71
|
+
.join(" ");
|
|
72
|
+
const sepLine = columns.map((c) => dim("-".repeat(c.width))).join(" ");
|
|
73
|
+
const body = rows.map((row) =>
|
|
74
|
+
columns
|
|
75
|
+
.map((c) => {
|
|
76
|
+
const raw = c.get(row) ?? "";
|
|
77
|
+
// Truncate *before* padding so columns stay aligned.
|
|
78
|
+
const truncated = truncate(raw, c.width);
|
|
79
|
+
return pad(truncated, c.width, c.align ?? "left");
|
|
80
|
+
})
|
|
81
|
+
.join(" "),
|
|
82
|
+
);
|
|
83
|
+
return [headerLine, sepLine, ...body].join("\n");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Render a tree of folders given a flat parent → children mapping.
|
|
88
|
+
* Each level is indented by `depth * 2` spaces. Root entries use the
|
|
89
|
+
* tree glyph "▸"; deeper entries use "└─".
|
|
90
|
+
*/
|
|
91
|
+
export function renderFolderTree(
|
|
92
|
+
folders: Array<{ id: string; name: string; parentId?: string | null }>,
|
|
93
|
+
options: { parentId?: string | null; depth?: number } = {},
|
|
94
|
+
): string {
|
|
95
|
+
const depth = options.depth ?? 0;
|
|
96
|
+
const indent = " ".repeat(depth);
|
|
97
|
+
const childIndent = " ".repeat(depth + 1);
|
|
98
|
+
const matches = folders.filter((f) => {
|
|
99
|
+
if (options.parentId === undefined) {
|
|
100
|
+
return f.parentId === null || f.parentId === undefined;
|
|
101
|
+
}
|
|
102
|
+
if (options.parentId === null) {
|
|
103
|
+
return f.parentId === null || f.parentId === undefined;
|
|
104
|
+
}
|
|
105
|
+
return f.parentId === options.parentId;
|
|
106
|
+
});
|
|
107
|
+
if (matches.length === 0) return "";
|
|
108
|
+
const lines: string[] = [];
|
|
109
|
+
for (const folder of matches) {
|
|
110
|
+
const prefix = depth === 0 ? "▸" : "└─";
|
|
111
|
+
lines.push(`${indent}${prefix} ${bold(folder.name)} ${dim(`(${folder.id})`)}`);
|
|
112
|
+
const sub = renderFolderTree(folders, {
|
|
113
|
+
parentId: folder.id,
|
|
114
|
+
depth: depth + 1,
|
|
115
|
+
});
|
|
116
|
+
if (sub) lines.push(sub);
|
|
117
|
+
}
|
|
118
|
+
// The childIndent is only meaningful for readability of the produced
|
|
119
|
+
// string — callers may chain it. Keeping it defined avoids an unused
|
|
120
|
+
// warning under noUnusedLocals if we ever enable it.
|
|
121
|
+
void childIndent;
|
|
122
|
+
return lines.join("\n");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Confirmation prompt using readline. Returns true only when the user
|
|
127
|
+
* explicitly types y/yes. Any other input (including empty) returns false.
|
|
128
|
+
*
|
|
129
|
+
* Pass `--yes`/`-y` in the command to bypass the prompt programmatically.
|
|
130
|
+
*/
|
|
131
|
+
export async function confirm(message: string): Promise<boolean> {
|
|
132
|
+
const { createInterface } = await import("node:readline/promises");
|
|
133
|
+
const { stdin, stdout } = await import("node:process");
|
|
134
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
135
|
+
try {
|
|
136
|
+
const answer = (await rl.question(`${message} [y/N]: `)).trim().toLowerCase();
|
|
137
|
+
return answer === "y" || answer === "yes";
|
|
138
|
+
} finally {
|
|
139
|
+
rl.close();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function formatError(err: unknown): string {
|
|
144
|
+
if (err instanceof HiaiDocsError) {
|
|
145
|
+
if (err.status === 401) return `${red("Error:")} Unauthorized — check your API key.`;
|
|
146
|
+
if (err.status === 404) return `${red("Error:")} ${err.message}`;
|
|
147
|
+
return `${red(`Error (${err.status}):`)} ${err.message}`;
|
|
148
|
+
}
|
|
149
|
+
if (err instanceof Error) return `${red("Error:")} ${err.message}`;
|
|
150
|
+
return `${red("Error:")} ${String(err)}`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
import { HiaiDocsError } from "./client.js";
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* `hiai-docs` — terminal CLI for the hiai-docs knowledge base.
|
|
4
|
+
*
|
|
5
|
+
* Built on commander. Each command is a self-contained module under
|
|
6
|
+
* `./commands/`; this file is just the registration table.
|
|
7
|
+
*
|
|
8
|
+
* Bun-native. ESM-only. No external color/formatting libraries.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Command } from "commander";
|
|
12
|
+
import { configFilePath, loadConfig, saveConfig } from "./config.js";
|
|
13
|
+
import { client, type HiaiDocsClient } from "./client.js";
|
|
14
|
+
import { registerConfig } from "./commands/config.js";
|
|
15
|
+
import { registerCreate } from "./commands/create.js";
|
|
16
|
+
import { registerDelete } from "./commands/delete.js";
|
|
17
|
+
import { registerExport } from "./commands/export.js";
|
|
18
|
+
import { registerFolders } from "./commands/folders.js";
|
|
19
|
+
import { registerHistory } from "./commands/history.js";
|
|
20
|
+
import { registerList } from "./commands/list.js";
|
|
21
|
+
import { registerRead } from "./commands/read.js";
|
|
22
|
+
import { registerRestore } from "./commands/restore.js";
|
|
23
|
+
import { registerSearch } from "./commands/search.js";
|
|
24
|
+
import { registerSnapshot } from "./commands/snapshot.js";
|
|
25
|
+
import { registerUpdate } from "./commands/update.js";
|
|
26
|
+
|
|
27
|
+
const VERSION = "0.3.0";
|
|
28
|
+
|
|
29
|
+
const program = new Command();
|
|
30
|
+
program
|
|
31
|
+
.name("hiai-docs")
|
|
32
|
+
.description("CLI for the hiai-docs knowledge base")
|
|
33
|
+
.version(VERSION);
|
|
34
|
+
|
|
35
|
+
const getClient = (): HiaiDocsClient => client;
|
|
36
|
+
|
|
37
|
+
// Bootstrap command — interactive first-run helper.
|
|
38
|
+
program
|
|
39
|
+
.command("init")
|
|
40
|
+
.description("Initialize the CLI (writes ~/.hiai-docs/config.json)")
|
|
41
|
+
.option("--url <url>", "API base URL")
|
|
42
|
+
.option("--key <key>", "API key")
|
|
43
|
+
.action(async (opts: { url?: string; key?: string }) => {
|
|
44
|
+
const current = loadConfig();
|
|
45
|
+
const next = {
|
|
46
|
+
url: opts.url ?? current.url,
|
|
47
|
+
apiKey: opts.key ?? current.apiKey,
|
|
48
|
+
};
|
|
49
|
+
saveConfig(next);
|
|
50
|
+
process.stdout.write(`Config written to ${configFilePath()}\n`);
|
|
51
|
+
process.stdout.write(`url: ${next.url}\n`);
|
|
52
|
+
if (next.apiKey) {
|
|
53
|
+
process.stdout.write(`key: ${next.apiKey.slice(0, 4)}…(redacted)\n`);
|
|
54
|
+
} else {
|
|
55
|
+
process.stdout.write("key: (unset)\n");
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Register the spec'd commands.
|
|
60
|
+
registerSearch(program, getClient);
|
|
61
|
+
registerList(program, getClient);
|
|
62
|
+
registerRead(program, getClient);
|
|
63
|
+
registerCreate(program, getClient);
|
|
64
|
+
registerUpdate(program, getClient);
|
|
65
|
+
registerDelete(program, getClient);
|
|
66
|
+
registerSnapshot(program, getClient);
|
|
67
|
+
registerHistory(program, getClient);
|
|
68
|
+
registerRestore(program, getClient);
|
|
69
|
+
registerExport(program, getClient);
|
|
70
|
+
registerFolders(program, getClient);
|
|
71
|
+
registerConfig(program);
|
|
72
|
+
|
|
73
|
+
await program.parseAsync(process.argv);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as schema from "./schema";
|
|
2
|
+
import { drizzle, type PostgresJsDatabase } from "drizzle-orm/postgres-js";
|
|
3
|
+
import postgres from "postgres";
|
|
4
|
+
|
|
5
|
+
export type Schema = typeof schema;
|
|
6
|
+
export type Database = PostgresJsDatabase<Schema>;
|
|
7
|
+
|
|
8
|
+
const databaseUrl =
|
|
9
|
+
process.env.DATABASE_URL ||
|
|
10
|
+
"postgresql://hiai_app:changeme@localhost:5437/hiai_docs";
|
|
11
|
+
|
|
12
|
+
const client = postgres(databaseUrl, {
|
|
13
|
+
max: 20,
|
|
14
|
+
idle_timeout: 30,
|
|
15
|
+
connect_timeout: 10,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const db: Database = drizzle(client, { schema });
|
|
19
|
+
|
|
20
|
+
export { client };
|