@biaoo/tiangong-wiki 0.2.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 +21 -0
- package/README.md +167 -0
- package/README.zh-CN.md +167 -0
- package/SKILL.md +116 -0
- package/agents/openai.yaml +4 -0
- package/assets/config.example.env +18 -0
- package/assets/templates/achievement.md +32 -0
- package/assets/templates/bridge.md +33 -0
- package/assets/templates/concept.md +47 -0
- package/assets/templates/faq.md +31 -0
- package/assets/templates/lesson.md +31 -0
- package/assets/templates/method.md +31 -0
- package/assets/templates/misconception.md +35 -0
- package/assets/templates/person.md +31 -0
- package/assets/templates/research-note.md +34 -0
- package/assets/templates/resume.md +34 -0
- package/assets/templates/source-summary.md +35 -0
- package/assets/vllm/qwen3_5_openai_developer.jinja +182 -0
- package/assets/wiki.config.default.json +193 -0
- package/dist/commands/check-config.js +77 -0
- package/dist/commands/create.js +32 -0
- package/dist/commands/daemon.js +186 -0
- package/dist/commands/dashboard.js +112 -0
- package/dist/commands/doctor.js +22 -0
- package/dist/commands/export-graph.js +28 -0
- package/dist/commands/export-index.js +31 -0
- package/dist/commands/find.js +36 -0
- package/dist/commands/fts.js +32 -0
- package/dist/commands/graph.js +35 -0
- package/dist/commands/init.js +48 -0
- package/dist/commands/lint.js +35 -0
- package/dist/commands/list.js +28 -0
- package/dist/commands/page-info.js +24 -0
- package/dist/commands/search.js +32 -0
- package/dist/commands/setup.js +15 -0
- package/dist/commands/stat.js +20 -0
- package/dist/commands/sync.js +38 -0
- package/dist/commands/template.js +71 -0
- package/dist/commands/type.js +88 -0
- package/dist/commands/vault.js +64 -0
- package/dist/core/agent.js +201 -0
- package/dist/core/cli-env.js +129 -0
- package/dist/core/codex-workflow.js +233 -0
- package/dist/core/config.js +126 -0
- package/dist/core/db.js +292 -0
- package/dist/core/embedding.js +104 -0
- package/dist/core/frontmatter.js +287 -0
- package/dist/core/indexer.js +241 -0
- package/dist/core/onboarding.js +967 -0
- package/dist/core/page-files.js +91 -0
- package/dist/core/paths.js +161 -0
- package/dist/core/presenters.js +23 -0
- package/dist/core/query.js +58 -0
- package/dist/core/runtime.js +20 -0
- package/dist/core/sync.js +235 -0
- package/dist/core/synology.js +412 -0
- package/dist/core/template-evolution.js +38 -0
- package/dist/core/vault-processing.js +742 -0
- package/dist/core/vault.js +594 -0
- package/dist/core/workflow-context.js +188 -0
- package/dist/core/workflow-result.js +162 -0
- package/dist/core/workspace-bootstrap.js +30 -0
- package/dist/core/workspace-skills.js +220 -0
- package/dist/daemon/client.js +147 -0
- package/dist/daemon/server.js +807 -0
- package/dist/daemon/state.js +53 -0
- package/dist/dashboard/assets/index-1FgAUZ28.css +1 -0
- package/dist/dashboard/assets/index-6A0PWT4X.js +154 -0
- package/dist/dashboard/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/dashboard/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/dashboard/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
- package/dist/dashboard/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
- package/dist/dashboard/assets/jetbrains-mono-cyrillic-700-normal-BWTpRfYl.woff2 +0 -0
- package/dist/dashboard/assets/jetbrains-mono-cyrillic-700-normal-CEoEElIJ.woff +0 -0
- package/dist/dashboard/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/dashboard/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/dashboard/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
- package/dist/dashboard/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
- package/dist/dashboard/assets/jetbrains-mono-greek-700-normal-C6CZE3T8.woff2 +0 -0
- package/dist/dashboard/assets/jetbrains-mono-greek-700-normal-DEigVDxa.woff +0 -0
- package/dist/dashboard/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/dashboard/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/dashboard/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- package/dist/dashboard/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
- package/dist/dashboard/assets/jetbrains-mono-latin-700-normal-BYuf6tUa.woff2 +0 -0
- package/dist/dashboard/assets/jetbrains-mono-latin-700-normal-D3wTyLJW.woff +0 -0
- package/dist/dashboard/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/dashboard/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/dashboard/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
- package/dist/dashboard/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
- package/dist/dashboard/assets/jetbrains-mono-latin-ext-700-normal-CZipNAKV.woff2 +0 -0
- package/dist/dashboard/assets/jetbrains-mono-latin-ext-700-normal-CxPITLHs.woff +0 -0
- package/dist/dashboard/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/dashboard/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
- package/dist/dashboard/assets/jetbrains-mono-vietnamese-700-normal-BDLVIk2r.woff +0 -0
- package/dist/dashboard/assets/space-grotesk-latin-400-normal-BnQMeOim.woff +0 -0
- package/dist/dashboard/assets/space-grotesk-latin-400-normal-CJ-V5oYT.woff2 +0 -0
- package/dist/dashboard/assets/space-grotesk-latin-500-normal-CNSSEhBt.woff +0 -0
- package/dist/dashboard/assets/space-grotesk-latin-500-normal-lFbtlQH6.woff2 +0 -0
- package/dist/dashboard/assets/space-grotesk-latin-700-normal-CwsQ-cCU.woff +0 -0
- package/dist/dashboard/assets/space-grotesk-latin-700-normal-RjhwGPKo.woff2 +0 -0
- package/dist/dashboard/assets/space-grotesk-latin-ext-400-normal-CfP_5XZW.woff2 +0 -0
- package/dist/dashboard/assets/space-grotesk-latin-ext-400-normal-DRPE3kg4.woff +0 -0
- package/dist/dashboard/assets/space-grotesk-latin-ext-500-normal-3dgZTiw9.woff +0 -0
- package/dist/dashboard/assets/space-grotesk-latin-ext-500-normal-DUe3BAxM.woff2 +0 -0
- package/dist/dashboard/assets/space-grotesk-latin-ext-700-normal-BQnZhY3m.woff2 +0 -0
- package/dist/dashboard/assets/space-grotesk-latin-ext-700-normal-HVCqSBdx.woff +0 -0
- package/dist/dashboard/assets/space-grotesk-vietnamese-400-normal-B7xT_GF5.woff2 +0 -0
- package/dist/dashboard/assets/space-grotesk-vietnamese-400-normal-BIWiOVfw.woff +0 -0
- package/dist/dashboard/assets/space-grotesk-vietnamese-500-normal-BTqKIpxg.woff +0 -0
- package/dist/dashboard/assets/space-grotesk-vietnamese-500-normal-BmEvtly_.woff2 +0 -0
- package/dist/dashboard/assets/space-grotesk-vietnamese-700-normal-DMty7AZE.woff2 +0 -0
- package/dist/dashboard/assets/space-grotesk-vietnamese-700-normal-Duxec5Rn.woff +0 -0
- package/dist/dashboard/index.html +18 -0
- package/dist/index.js +86 -0
- package/dist/operations/dashboard.js +1231 -0
- package/dist/operations/export.js +110 -0
- package/dist/operations/query.js +649 -0
- package/dist/operations/type-template.js +210 -0
- package/dist/operations/write.js +143 -0
- package/dist/types/config.js +1 -0
- package/dist/types/page.js +1 -0
- package/dist/utils/case.js +22 -0
- package/dist/utils/errors.js +26 -0
- package/dist/utils/fs.js +77 -0
- package/dist/utils/output.js +33 -0
- package/dist/utils/process.js +60 -0
- package/dist/utils/segmenter.js +24 -0
- package/dist/utils/slug.js +10 -0
- package/dist/utils/time.js +24 -0
- package/package.json +64 -0
- package/references/cli-interface.md +312 -0
- package/references/env.md +122 -0
- package/references/template-design-guide.md +271 -0
- package/references/vault-to-wiki-instruction.md +110 -0
- package/references/wiki-maintenance-instruction.md +190 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getPackageRoot } from "../core/paths.js";
|
|
2
|
+
import { runSetupWizard } from "../core/onboarding.js";
|
|
3
|
+
export function registerSetupCommand(program) {
|
|
4
|
+
program
|
|
5
|
+
.command("setup")
|
|
6
|
+
.description("Run the step-by-step wiki configuration wizard")
|
|
7
|
+
.action(async () => {
|
|
8
|
+
await runSetupWizard(process.env, {
|
|
9
|
+
cwd: process.cwd(),
|
|
10
|
+
input: process.stdin,
|
|
11
|
+
output: process.stdout,
|
|
12
|
+
packageRoot: getPackageRoot(),
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { executeServerBackedOperation, requestDaemonJson } from "../daemon/client.js";
|
|
2
|
+
import { getWikiStat } from "../operations/query.js";
|
|
3
|
+
import { writeJson } from "../utils/output.js";
|
|
4
|
+
export function registerStatCommand(program) {
|
|
5
|
+
program
|
|
6
|
+
.command("stat")
|
|
7
|
+
.description("Show aggregate wiki index statistics")
|
|
8
|
+
.action(async () => {
|
|
9
|
+
const payload = await executeServerBackedOperation({
|
|
10
|
+
kind: "read",
|
|
11
|
+
local: () => getWikiStat(process.env),
|
|
12
|
+
remote: (endpoint) => requestDaemonJson({
|
|
13
|
+
endpoint,
|
|
14
|
+
method: "GET",
|
|
15
|
+
path: "/stat",
|
|
16
|
+
}),
|
|
17
|
+
});
|
|
18
|
+
writeJson(payload);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { executeServerBackedOperation, requestDaemonJson } from "../daemon/client.js";
|
|
2
|
+
import { runSyncCommand } from "../operations/write.js";
|
|
3
|
+
import { writeJson } from "../utils/output.js";
|
|
4
|
+
export function registerSyncCommand(program) {
|
|
5
|
+
program
|
|
6
|
+
.command("sync")
|
|
7
|
+
.description("Incrementally sync wiki pages, embeddings, and vault metadata")
|
|
8
|
+
.option("--path <pagePath>", "Only sync a single wiki page")
|
|
9
|
+
.option("--force", "Force a full rebuild of the index")
|
|
10
|
+
.option("--skip-embedding", "Skip embedding generation")
|
|
11
|
+
.option("--process", "Process vault queue items after sync")
|
|
12
|
+
.option("--vault-file <fileId>", "Only process one vault queue file_id (relative to VAULT_PATH)")
|
|
13
|
+
.action(async (options) => {
|
|
14
|
+
const result = await executeServerBackedOperation({
|
|
15
|
+
kind: "write",
|
|
16
|
+
local: () => runSyncCommand(process.env, {
|
|
17
|
+
targetPaths: options.path ? [options.path] : undefined,
|
|
18
|
+
force: options.force === true,
|
|
19
|
+
skipEmbedding: options.skipEmbedding === true,
|
|
20
|
+
process: options.process === true,
|
|
21
|
+
vaultFileId: options.vaultFile ?? undefined,
|
|
22
|
+
}),
|
|
23
|
+
remote: (endpoint) => requestDaemonJson({
|
|
24
|
+
endpoint,
|
|
25
|
+
method: "POST",
|
|
26
|
+
path: "/sync",
|
|
27
|
+
body: {
|
|
28
|
+
path: options.path ?? undefined,
|
|
29
|
+
force: options.force === true,
|
|
30
|
+
skipEmbedding: options.skipEmbedding === true,
|
|
31
|
+
process: options.process === true,
|
|
32
|
+
vaultFileId: options.vaultFile ?? undefined,
|
|
33
|
+
},
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
writeJson(result);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { executeServerBackedOperation, requestDaemonJson } from "../daemon/client.js";
|
|
2
|
+
import { createTemplate, listTemplates, showTemplate } from "../operations/type-template.js";
|
|
3
|
+
import { ensureTextOrJson, writeJson, writeText } from "../utils/output.js";
|
|
4
|
+
export function registerTemplateCommand(program) {
|
|
5
|
+
const template = program.command("template").description("List, show, or create wiki templates");
|
|
6
|
+
template
|
|
7
|
+
.command("list")
|
|
8
|
+
.option("--format <format>", "Output format: text or json", "text")
|
|
9
|
+
.action(async (options) => {
|
|
10
|
+
const format = ensureTextOrJson(options.format);
|
|
11
|
+
const payload = await executeServerBackedOperation({
|
|
12
|
+
kind: "read",
|
|
13
|
+
local: () => listTemplates(process.env),
|
|
14
|
+
remote: (endpoint) => requestDaemonJson({
|
|
15
|
+
endpoint,
|
|
16
|
+
method: "GET",
|
|
17
|
+
path: "/template/list",
|
|
18
|
+
}),
|
|
19
|
+
});
|
|
20
|
+
if (format === "json") {
|
|
21
|
+
writeJson(payload);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
writeText(payload.map((entry) => `${entry.pageType} -> ${entry.file}`).join("\n"));
|
|
25
|
+
});
|
|
26
|
+
template
|
|
27
|
+
.command("show")
|
|
28
|
+
.argument("<pageType>", "Registered pageType")
|
|
29
|
+
.option("--format <format>", "Output format: text or json", "text")
|
|
30
|
+
.action(async (pageType, options) => {
|
|
31
|
+
const format = ensureTextOrJson(options.format);
|
|
32
|
+
const payload = await executeServerBackedOperation({
|
|
33
|
+
kind: "read",
|
|
34
|
+
local: () => showTemplate(process.env, pageType),
|
|
35
|
+
remote: (endpoint) => requestDaemonJson({
|
|
36
|
+
endpoint,
|
|
37
|
+
method: "GET",
|
|
38
|
+
path: "/template/show",
|
|
39
|
+
query: { pageType },
|
|
40
|
+
}),
|
|
41
|
+
});
|
|
42
|
+
if (format === "json") {
|
|
43
|
+
writeJson(payload);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
writeText(String(payload.content ?? ""));
|
|
47
|
+
});
|
|
48
|
+
template
|
|
49
|
+
.command("create")
|
|
50
|
+
.requiredOption("--type <pageType>", "New pageType")
|
|
51
|
+
.requiredOption("--title <title>", "Human title used inside the template frontmatter")
|
|
52
|
+
.action(async (options) => {
|
|
53
|
+
const payload = await executeServerBackedOperation({
|
|
54
|
+
kind: "write",
|
|
55
|
+
local: () => Promise.resolve(createTemplate(process.env, {
|
|
56
|
+
type: options.type,
|
|
57
|
+
title: options.title,
|
|
58
|
+
})),
|
|
59
|
+
remote: (endpoint) => requestDaemonJson({
|
|
60
|
+
endpoint,
|
|
61
|
+
method: "POST",
|
|
62
|
+
path: "/template/create",
|
|
63
|
+
body: {
|
|
64
|
+
type: options.type,
|
|
65
|
+
title: options.title,
|
|
66
|
+
},
|
|
67
|
+
}),
|
|
68
|
+
});
|
|
69
|
+
writeJson(payload);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { executeServerBackedOperation, requestDaemonJson } from "../daemon/client.js";
|
|
2
|
+
import { listTypes, recommendTypes, showType } from "../operations/type-template.js";
|
|
3
|
+
import { ensureTextOrJson, writeJson, writeText } from "../utils/output.js";
|
|
4
|
+
export function registerTypeCommand(program) {
|
|
5
|
+
const typeCommand = program.command("type").description("Inspect and recommend wiki page types");
|
|
6
|
+
typeCommand
|
|
7
|
+
.command("list")
|
|
8
|
+
.option("--format <format>", "Output format: text or json", "text")
|
|
9
|
+
.action(async (options) => {
|
|
10
|
+
const format = ensureTextOrJson(options.format);
|
|
11
|
+
const payload = await executeServerBackedOperation({
|
|
12
|
+
kind: "read",
|
|
13
|
+
local: () => listTypes(process.env),
|
|
14
|
+
remote: (endpoint) => requestDaemonJson({
|
|
15
|
+
endpoint,
|
|
16
|
+
method: "GET",
|
|
17
|
+
path: "/type/list",
|
|
18
|
+
}),
|
|
19
|
+
});
|
|
20
|
+
if (format === "json") {
|
|
21
|
+
writeJson(payload);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
writeText(payload.map((entry) => `${entry.pageType} -> ${entry.file}`).join("\n"));
|
|
25
|
+
});
|
|
26
|
+
typeCommand
|
|
27
|
+
.command("show")
|
|
28
|
+
.argument("<pageType>", "Registered pageType")
|
|
29
|
+
.option("--format <format>", "Output format: text or json", "text")
|
|
30
|
+
.action(async (pageType, options) => {
|
|
31
|
+
const format = ensureTextOrJson(options.format);
|
|
32
|
+
const payload = await executeServerBackedOperation({
|
|
33
|
+
kind: "read",
|
|
34
|
+
local: () => showType(process.env, pageType),
|
|
35
|
+
remote: (endpoint) => requestDaemonJson({
|
|
36
|
+
endpoint,
|
|
37
|
+
method: "GET",
|
|
38
|
+
path: "/type/show",
|
|
39
|
+
query: { pageType },
|
|
40
|
+
}),
|
|
41
|
+
});
|
|
42
|
+
if (format === "json") {
|
|
43
|
+
writeJson(payload);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
writeText([
|
|
47
|
+
`pageType: ${payload.pageType}`,
|
|
48
|
+
`file: ${payload.file}`,
|
|
49
|
+
`columns: ${Object.keys(payload.columns ?? {}).join(", ") || "(none)"}`,
|
|
50
|
+
`edges: ${Object.keys(payload.edges ?? {}).join(", ") || "(none)"}`,
|
|
51
|
+
`summaryFields: ${payload.summaryFields.join(", ") || "(none)"}`,
|
|
52
|
+
].join("\n"));
|
|
53
|
+
});
|
|
54
|
+
typeCommand
|
|
55
|
+
.command("recommend")
|
|
56
|
+
.requiredOption("--text <text>", "Short summary or extracted content")
|
|
57
|
+
.option("--keywords <keywords>", "Comma-separated keywords")
|
|
58
|
+
.option("--limit <limit>", "Max number of recommendations", "5")
|
|
59
|
+
.option("--format <format>", "Output format: text or json", "text")
|
|
60
|
+
.action(async (options) => {
|
|
61
|
+
const format = ensureTextOrJson(options.format);
|
|
62
|
+
const payload = await executeServerBackedOperation({
|
|
63
|
+
kind: "read",
|
|
64
|
+
local: () => recommendTypes(process.env, {
|
|
65
|
+
text: String(options.text ?? ""),
|
|
66
|
+
keywords: options.keywords ?? undefined,
|
|
67
|
+
limit: options.limit ?? undefined,
|
|
68
|
+
}),
|
|
69
|
+
remote: (endpoint) => requestDaemonJson({
|
|
70
|
+
endpoint,
|
|
71
|
+
method: "POST",
|
|
72
|
+
path: "/type/recommend",
|
|
73
|
+
body: {
|
|
74
|
+
text: String(options.text ?? ""),
|
|
75
|
+
keywords: options.keywords ?? undefined,
|
|
76
|
+
limit: options.limit ?? undefined,
|
|
77
|
+
},
|
|
78
|
+
}),
|
|
79
|
+
});
|
|
80
|
+
if (format === "json") {
|
|
81
|
+
writeJson(payload);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
writeText(payload.recommendations
|
|
85
|
+
.map((entry) => `${entry.pageType} (${entry.score.toFixed(4)}) ${entry.signals.join(" | ")}`)
|
|
86
|
+
.join("\n"));
|
|
87
|
+
});
|
|
88
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { executeServerBackedOperation, requestDaemonJson } from "../daemon/client.js";
|
|
2
|
+
import { diffVaultFiles, getVaultQueue, listVaultFiles } from "../operations/query.js";
|
|
3
|
+
import { writeJson } from "../utils/output.js";
|
|
4
|
+
export function registerVaultCommand(program) {
|
|
5
|
+
const vault = program.command("vault").description("Inspect indexed vault files and changelog entries");
|
|
6
|
+
vault
|
|
7
|
+
.command("list")
|
|
8
|
+
.option("--path <prefix>", "Filter by relative path prefix")
|
|
9
|
+
.option("--ext <ext>", "Filter by file extension")
|
|
10
|
+
.action(async (options) => {
|
|
11
|
+
const payload = await executeServerBackedOperation({
|
|
12
|
+
kind: "read",
|
|
13
|
+
local: () => listVaultFiles(process.env, options),
|
|
14
|
+
remote: (endpoint) => requestDaemonJson({
|
|
15
|
+
endpoint,
|
|
16
|
+
method: "GET",
|
|
17
|
+
path: "/vault/list",
|
|
18
|
+
query: {
|
|
19
|
+
path: options.path ?? undefined,
|
|
20
|
+
ext: options.ext ?? undefined,
|
|
21
|
+
},
|
|
22
|
+
}),
|
|
23
|
+
});
|
|
24
|
+
writeJson(payload);
|
|
25
|
+
});
|
|
26
|
+
vault
|
|
27
|
+
.command("diff")
|
|
28
|
+
.option("--since <date>", "Show changes since a timestamp")
|
|
29
|
+
.option("--path <prefix>", "Filter by relative path prefix")
|
|
30
|
+
.action(async (options) => {
|
|
31
|
+
const payload = await executeServerBackedOperation({
|
|
32
|
+
kind: "read",
|
|
33
|
+
local: () => diffVaultFiles(process.env, options),
|
|
34
|
+
remote: (endpoint) => requestDaemonJson({
|
|
35
|
+
endpoint,
|
|
36
|
+
method: "GET",
|
|
37
|
+
path: "/vault/diff",
|
|
38
|
+
query: {
|
|
39
|
+
since: options.since ?? undefined,
|
|
40
|
+
path: options.path ?? undefined,
|
|
41
|
+
},
|
|
42
|
+
}),
|
|
43
|
+
});
|
|
44
|
+
writeJson(payload);
|
|
45
|
+
});
|
|
46
|
+
vault
|
|
47
|
+
.command("queue")
|
|
48
|
+
.option("--status <status>", "Filter queue items by status")
|
|
49
|
+
.action(async (options) => {
|
|
50
|
+
const payload = await executeServerBackedOperation({
|
|
51
|
+
kind: "read",
|
|
52
|
+
local: () => getVaultQueue(process.env, options),
|
|
53
|
+
remote: (endpoint) => requestDaemonJson({
|
|
54
|
+
endpoint,
|
|
55
|
+
method: "GET",
|
|
56
|
+
path: "/vault/queue",
|
|
57
|
+
query: {
|
|
58
|
+
status: options.status ?? undefined,
|
|
59
|
+
},
|
|
60
|
+
}),
|
|
61
|
+
});
|
|
62
|
+
writeJson(payload);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
import { AppError } from "../utils/errors.js";
|
|
3
|
+
function parseBoolean(value) {
|
|
4
|
+
return /^(1|true|yes|on)$/i.test((value ?? "").trim());
|
|
5
|
+
}
|
|
6
|
+
function parseBatchSize(value) {
|
|
7
|
+
const raw = value?.trim() || "5";
|
|
8
|
+
const parsed = Number.parseInt(raw, 10);
|
|
9
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
10
|
+
throw new AppError(`WIKI_AGENT_BATCH_SIZE must be a non-negative integer, got ${value}`, "config");
|
|
11
|
+
}
|
|
12
|
+
return parsed;
|
|
13
|
+
}
|
|
14
|
+
function normalizeBaseUrl(value) {
|
|
15
|
+
const trimmed = value?.trim();
|
|
16
|
+
if (!trimmed) {
|
|
17
|
+
return "https://api.openai.com/v1";
|
|
18
|
+
}
|
|
19
|
+
return trimmed.replace(/\/+$/g, "");
|
|
20
|
+
}
|
|
21
|
+
function normalizeMessageContent(value) {
|
|
22
|
+
if (typeof value === "string") {
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
if (!Array.isArray(value)) {
|
|
26
|
+
return "";
|
|
27
|
+
}
|
|
28
|
+
return value
|
|
29
|
+
.map((item) => item.text ?? "")
|
|
30
|
+
.join("")
|
|
31
|
+
.trim();
|
|
32
|
+
}
|
|
33
|
+
function normalizeStringArray(value) {
|
|
34
|
+
if (!Array.isArray(value)) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
return value
|
|
38
|
+
.map((item) => String(item ?? "").trim())
|
|
39
|
+
.filter(Boolean);
|
|
40
|
+
}
|
|
41
|
+
function parseDecisionPayload(raw) {
|
|
42
|
+
const cleaned = raw.trim().replace(/^```json\s*/i, "").replace(/^```\s*/i, "").replace(/\s*```$/i, "");
|
|
43
|
+
let parsed;
|
|
44
|
+
try {
|
|
45
|
+
parsed = JSON.parse(cleaned);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
throw new AppError("Wiki agent returned invalid JSON", "runtime", {
|
|
49
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
50
|
+
raw,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
54
|
+
throw new AppError("Wiki agent returned a non-object payload", "runtime", { raw });
|
|
55
|
+
}
|
|
56
|
+
const payload = parsed;
|
|
57
|
+
const action = String(payload.action ?? "").trim();
|
|
58
|
+
if (action !== "create" && action !== "update" && action !== "skip") {
|
|
59
|
+
throw new AppError("Wiki agent action must be create, update, or skip", "runtime", { raw });
|
|
60
|
+
}
|
|
61
|
+
const reason = String(payload.reason ?? "").trim();
|
|
62
|
+
if (!reason) {
|
|
63
|
+
throw new AppError("Wiki agent payload is missing reason", "runtime", { raw });
|
|
64
|
+
}
|
|
65
|
+
const decision = {
|
|
66
|
+
action,
|
|
67
|
+
reason,
|
|
68
|
+
keyFindings: normalizeStringArray(payload.keyFindings),
|
|
69
|
+
tags: normalizeStringArray(payload.tags),
|
|
70
|
+
relatedPages: normalizeStringArray(payload.relatedPages),
|
|
71
|
+
};
|
|
72
|
+
if (typeof payload.title === "string" && payload.title.trim()) {
|
|
73
|
+
decision.title = payload.title.trim();
|
|
74
|
+
}
|
|
75
|
+
if (typeof payload.bodyMarkdown === "string" && payload.bodyMarkdown.trim()) {
|
|
76
|
+
decision.bodyMarkdown = payload.bodyMarkdown.trim();
|
|
77
|
+
}
|
|
78
|
+
if ((action === "create" || action === "update") && !decision.bodyMarkdown) {
|
|
79
|
+
throw new AppError("Wiki agent payload must include bodyMarkdown for create/update", "runtime", {
|
|
80
|
+
raw,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
if (action === "create" && !decision.title) {
|
|
84
|
+
throw new AppError("Wiki agent payload must include title for create", "runtime", { raw });
|
|
85
|
+
}
|
|
86
|
+
return decision;
|
|
87
|
+
}
|
|
88
|
+
export function inspectWikiAgentSettings(env = process.env) {
|
|
89
|
+
const enabled = parseBoolean(env.WIKI_AGENT_ENABLED);
|
|
90
|
+
const baseUrl = normalizeBaseUrl(env.WIKI_AGENT_BASE_URL);
|
|
91
|
+
const model = env.WIKI_AGENT_MODEL?.trim() || null;
|
|
92
|
+
const errors = [];
|
|
93
|
+
const batchSize = parseBatchSize(env.WIKI_AGENT_BATCH_SIZE);
|
|
94
|
+
if (enabled) {
|
|
95
|
+
if (!(env.WIKI_AGENT_API_KEY ?? "").trim()) {
|
|
96
|
+
errors.push("WIKI_AGENT_API_KEY is required when WIKI_AGENT_ENABLED=true");
|
|
97
|
+
}
|
|
98
|
+
if (!model) {
|
|
99
|
+
errors.push("WIKI_AGENT_MODEL is required when WIKI_AGENT_ENABLED=true");
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
enabled,
|
|
104
|
+
configured: enabled ? errors.length === 0 : false,
|
|
105
|
+
baseUrl: enabled ? baseUrl : null,
|
|
106
|
+
model,
|
|
107
|
+
batchSize,
|
|
108
|
+
errors,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
export function resolveWikiAgentSettings(env = process.env) {
|
|
112
|
+
const inspected = inspectWikiAgentSettings(env);
|
|
113
|
+
if (!inspected.enabled) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
if (inspected.errors.length > 0) {
|
|
117
|
+
throw new AppError(inspected.errors.join("; "), "config");
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
enabled: true,
|
|
121
|
+
baseUrl: inspected.baseUrl ?? "https://api.openai.com/v1",
|
|
122
|
+
apiKey: (env.WIKI_AGENT_API_KEY ?? "").trim(),
|
|
123
|
+
model: inspected.model ?? "",
|
|
124
|
+
batchSize: inspected.batchSize,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
export class WikiAgentClient {
|
|
128
|
+
settings;
|
|
129
|
+
client;
|
|
130
|
+
constructor(settings) {
|
|
131
|
+
this.settings = settings;
|
|
132
|
+
this.client = new OpenAI({
|
|
133
|
+
apiKey: settings.apiKey,
|
|
134
|
+
baseURL: settings.baseUrl,
|
|
135
|
+
maxRetries: 0,
|
|
136
|
+
timeout: 60_000,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
async decideSourceAction(input) {
|
|
140
|
+
try {
|
|
141
|
+
const response = await this.client.chat.completions.create({
|
|
142
|
+
model: this.settings.model,
|
|
143
|
+
response_format: { type: "json_object" },
|
|
144
|
+
messages: [
|
|
145
|
+
{
|
|
146
|
+
role: "system",
|
|
147
|
+
content: "You are a local wiki ingestion agent. Decide whether a vault file should create, update, or skip a source-summary page. Return JSON only.",
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
role: "user",
|
|
151
|
+
content: JSON.stringify({
|
|
152
|
+
task: "Decide how to process one vault file into the local wiki.",
|
|
153
|
+
rules: [
|
|
154
|
+
"Prefer source-summary pages for durable source files.",
|
|
155
|
+
"Treat pdf, docx, pptx, xlsx, and md files with readable extracted text as durable by default unless they are clear duplicates or pure noise.",
|
|
156
|
+
"Skip transient, duplicate, or low-signal files.",
|
|
157
|
+
"If existingPageId is present, you may update that page instead of creating a new one.",
|
|
158
|
+
"Body markdown must be substantive and use the provided template headings.",
|
|
159
|
+
"bodyMarkdown must contain markdown sections only and must not include YAML frontmatter or code fences.",
|
|
160
|
+
"Do not invent unsupported page ids in relatedPages.",
|
|
161
|
+
],
|
|
162
|
+
file: {
|
|
163
|
+
fileId: input.fileId,
|
|
164
|
+
fileExt: input.fileExt,
|
|
165
|
+
sourceType: input.sourceType,
|
|
166
|
+
},
|
|
167
|
+
existingPageId: input.existingPageId ?? null,
|
|
168
|
+
existingPageContent: input.existingPageContent ?? null,
|
|
169
|
+
wikiStats: input.wikiStats,
|
|
170
|
+
sourceSummaryTemplate: input.templateText,
|
|
171
|
+
vaultToWikiInstruction: input.instructionText,
|
|
172
|
+
extractedText: input.extractedText,
|
|
173
|
+
outputSchema: {
|
|
174
|
+
action: "create | update | skip",
|
|
175
|
+
reason: "short explanation",
|
|
176
|
+
title: "required for create",
|
|
177
|
+
keyFindings: ["1-5 concise findings"],
|
|
178
|
+
tags: ["optional", "tags"],
|
|
179
|
+
relatedPages: ["optional/page-id.md"],
|
|
180
|
+
bodyMarkdown: "full markdown body using the source-summary template headings",
|
|
181
|
+
},
|
|
182
|
+
}, null, 2),
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
});
|
|
186
|
+
const content = normalizeMessageContent(response.choices?.[0]?.message?.content ?? undefined);
|
|
187
|
+
if (!content) {
|
|
188
|
+
throw new AppError("Wiki agent returned an empty response", "runtime");
|
|
189
|
+
}
|
|
190
|
+
return parseDecisionPayload(content);
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
if (error instanceof AppError) {
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
throw new AppError("Wiki agent request failed", "runtime", {
|
|
197
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { pathExistsSync, readTextFileSync } from "../utils/fs.js";
|
|
3
|
+
export const DEFAULT_WIKI_ENV_FILE = ".wiki.env";
|
|
4
|
+
const EMPTY_INFO = {
|
|
5
|
+
requestedPath: null,
|
|
6
|
+
loadedPath: null,
|
|
7
|
+
autoDiscovered: false,
|
|
8
|
+
missingRequestedPath: false,
|
|
9
|
+
loadedKeys: [],
|
|
10
|
+
};
|
|
11
|
+
let lastCliEnvInfo = EMPTY_INFO;
|
|
12
|
+
const CORE_RUNTIME_ENV_KEYS = [
|
|
13
|
+
"WIKI_PATH",
|
|
14
|
+
"VAULT_PATH",
|
|
15
|
+
"WIKI_DB_PATH",
|
|
16
|
+
"WIKI_CONFIG_PATH",
|
|
17
|
+
"WIKI_TEMPLATES_PATH",
|
|
18
|
+
];
|
|
19
|
+
function unquoteEnvValue(rawValue) {
|
|
20
|
+
const value = rawValue.trim();
|
|
21
|
+
if (value.length < 2) {
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
25
|
+
return value.slice(1, -1).replace(/\\n/g, "\n").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
26
|
+
}
|
|
27
|
+
if (value.startsWith("'") && value.endsWith("'")) {
|
|
28
|
+
return value.slice(1, -1);
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
function findNearestEnvFile(startDir) {
|
|
33
|
+
let current = path.resolve(startDir);
|
|
34
|
+
while (true) {
|
|
35
|
+
const candidate = path.join(current, DEFAULT_WIKI_ENV_FILE);
|
|
36
|
+
if (pathExistsSync(candidate)) {
|
|
37
|
+
return candidate;
|
|
38
|
+
}
|
|
39
|
+
const parent = path.dirname(current);
|
|
40
|
+
if (parent === current) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
current = parent;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function parseEnvFile(text) {
|
|
47
|
+
const entries = {};
|
|
48
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
49
|
+
const line = rawLine.trim();
|
|
50
|
+
if (!line || line.startsWith("#")) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const normalized = line.startsWith("export ") ? line.slice("export ".length).trim() : line;
|
|
54
|
+
const separatorIndex = normalized.indexOf("=");
|
|
55
|
+
if (separatorIndex <= 0) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const key = normalized.slice(0, separatorIndex).trim();
|
|
59
|
+
const value = normalized.slice(separatorIndex + 1);
|
|
60
|
+
if (!key) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
entries[key] = unquoteEnvValue(value);
|
|
64
|
+
}
|
|
65
|
+
return entries;
|
|
66
|
+
}
|
|
67
|
+
function hasExplicitCoreRuntimeEnv(targetEnv) {
|
|
68
|
+
return CORE_RUNTIME_ENV_KEYS.some((key) => {
|
|
69
|
+
const value = targetEnv[key];
|
|
70
|
+
return value !== undefined && value.trim().length > 0;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function quoteEnvValue(value) {
|
|
74
|
+
if (/^[A-Za-z0-9_./:@+-]+$/.test(value)) {
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
return JSON.stringify(value);
|
|
78
|
+
}
|
|
79
|
+
export function serializeEnvEntries(entries) {
|
|
80
|
+
return `${entries
|
|
81
|
+
.filter(([, value]) => value !== null && value !== undefined)
|
|
82
|
+
.map(([key, value]) => `${key}=${quoteEnvValue(String(value))}`)
|
|
83
|
+
.join("\n")}\n`;
|
|
84
|
+
}
|
|
85
|
+
export function applyCliEnvironment(targetEnv = process.env, cwd = process.cwd()) {
|
|
86
|
+
const requestedEnvFile = targetEnv.WIKI_ENV_FILE?.trim();
|
|
87
|
+
const requestedPath = requestedEnvFile ? path.resolve(cwd, requestedEnvFile) : null;
|
|
88
|
+
if (!requestedPath && hasExplicitCoreRuntimeEnv(targetEnv)) {
|
|
89
|
+
lastCliEnvInfo = { ...EMPTY_INFO };
|
|
90
|
+
return lastCliEnvInfo;
|
|
91
|
+
}
|
|
92
|
+
const candidatePath = requestedPath ?? findNearestEnvFile(cwd);
|
|
93
|
+
if (!candidatePath) {
|
|
94
|
+
lastCliEnvInfo = { ...EMPTY_INFO, requestedPath };
|
|
95
|
+
return lastCliEnvInfo;
|
|
96
|
+
}
|
|
97
|
+
if (!pathExistsSync(candidatePath)) {
|
|
98
|
+
lastCliEnvInfo = {
|
|
99
|
+
requestedPath: candidatePath,
|
|
100
|
+
loadedPath: null,
|
|
101
|
+
autoDiscovered: false,
|
|
102
|
+
missingRequestedPath: requestedPath !== null,
|
|
103
|
+
loadedKeys: [],
|
|
104
|
+
};
|
|
105
|
+
return lastCliEnvInfo;
|
|
106
|
+
}
|
|
107
|
+
const parsed = parseEnvFile(readTextFileSync(candidatePath));
|
|
108
|
+
const loadedKeys = [];
|
|
109
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
110
|
+
if (targetEnv[key] === undefined) {
|
|
111
|
+
targetEnv[key] = value;
|
|
112
|
+
loadedKeys.push(key);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (!targetEnv.WIKI_ENV_FILE) {
|
|
116
|
+
targetEnv.WIKI_ENV_FILE = candidatePath;
|
|
117
|
+
}
|
|
118
|
+
lastCliEnvInfo = {
|
|
119
|
+
requestedPath,
|
|
120
|
+
loadedPath: candidatePath,
|
|
121
|
+
autoDiscovered: requestedPath === null,
|
|
122
|
+
missingRequestedPath: false,
|
|
123
|
+
loadedKeys,
|
|
124
|
+
};
|
|
125
|
+
return lastCliEnvInfo;
|
|
126
|
+
}
|
|
127
|
+
export function getCliEnvironmentInfo() {
|
|
128
|
+
return lastCliEnvInfo;
|
|
129
|
+
}
|