@biaoo/tiangong-wiki 0.2.1 → 0.2.3
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/README.md +27 -3
- package/README.zh-CN.md +27 -3
- package/dist/core/cli-env.js +34 -5
- package/dist/core/codex-workflow.js +37 -15
- package/dist/core/global-config.js +61 -0
- package/dist/core/onboarding.js +63 -4
- package/dist/core/paths.js +9 -0
- package/dist/core/workflow-result.js +5 -0
- package/dist/index.js +32 -1
- package/package.json +1 -1
- package/references/cli-interface.md +10 -1
- package/references/troubleshooting.md +7 -0
package/README.md
CHANGED
|
@@ -24,6 +24,21 @@
|
|
|
24
24
|
npm install -g @biaoo/tiangong-wiki
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
## Update
|
|
28
|
+
|
|
29
|
+
Upgrade the npm package itself:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install -g @biaoo/tiangong-wiki@latest
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Refresh workspace-local managed skills after upgrading the CLI or when upstream skill content changes:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
tiangong-wiki skill status
|
|
39
|
+
tiangong-wiki skill update --all
|
|
40
|
+
```
|
|
41
|
+
|
|
27
42
|
<details>
|
|
28
43
|
<summary><strong>Use as an AI Agent Skill</strong></summary>
|
|
29
44
|
|
|
@@ -47,6 +62,7 @@ To manage workspace-local skills from arbitrary repo/path sources after setup:
|
|
|
47
62
|
tiangong-wiki skill add ../my-skills --skill notes
|
|
48
63
|
tiangong-wiki skill status
|
|
49
64
|
tiangong-wiki skill update notes
|
|
65
|
+
tiangong-wiki skill update --all
|
|
50
66
|
```
|
|
51
67
|
|
|
52
68
|
</details>
|
|
@@ -61,7 +77,14 @@ tiangong-wiki init # initialize workspace
|
|
|
61
77
|
tiangong-wiki sync # index Markdown pages
|
|
62
78
|
```
|
|
63
79
|
|
|
64
|
-
`tiangong-wiki setup` creates `.wiki.env
|
|
80
|
+
`tiangong-wiki setup` creates a workspace-local `.wiki.env` and records it as your default workspace config. Command resolution now follows this order:
|
|
81
|
+
|
|
82
|
+
1. `--env-file <path>`
|
|
83
|
+
2. `WIKI_ENV_FILE`
|
|
84
|
+
3. The nearest `.wiki.env` found by walking upward from your current directory
|
|
85
|
+
4. The global default workspace config written by `tiangong-wiki setup`
|
|
86
|
+
|
|
87
|
+
That means commands still work best from inside a workspace, but they can also run from outside the workspace after setup, or target a specific workspace explicitly with `--env-file`.
|
|
65
88
|
|
|
66
89
|
```bash
|
|
67
90
|
tiangong-wiki find --type concept --status active # structured query
|
|
@@ -71,11 +94,12 @@ tiangong-wiki graph bayes-theorem --depth 2 # graph traversal
|
|
|
71
94
|
```
|
|
72
95
|
|
|
73
96
|
```bash
|
|
74
|
-
tiangong-wiki daemon
|
|
97
|
+
tiangong-wiki daemon start # start the daemon in the background
|
|
75
98
|
tiangong-wiki dashboard # open dashboard in browser
|
|
99
|
+
# or: tiangong-wiki daemon run # run the daemon in the foreground for debugging
|
|
76
100
|
```
|
|
77
101
|
|
|
78
|
-
> Environment variables are managed via `.wiki.env` (created by `tiangong-wiki setup`). The CLI
|
|
102
|
+
> Environment variables are managed via `.wiki.env` (created by `tiangong-wiki setup`). The CLI prefers the nearest local `.wiki.env`, then falls back to the global default workspace config. See [references/troubleshooting.md](./references/troubleshooting.md) for the full reference.
|
|
79
103
|
|
|
80
104
|
## CLI
|
|
81
105
|
|
package/README.zh-CN.md
CHANGED
|
@@ -24,6 +24,21 @@
|
|
|
24
24
|
npm install -g @biaoo/tiangong-wiki
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
## 更新
|
|
28
|
+
|
|
29
|
+
升级 npm 包本身:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install -g @biaoo/tiangong-wiki@latest
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
升级 CLI 后,或上游 skill 内容有更新时,刷新工作区本地 managed skills:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
tiangong-wiki skill status
|
|
39
|
+
tiangong-wiki skill update --all
|
|
40
|
+
```
|
|
41
|
+
|
|
27
42
|
<details>
|
|
28
43
|
<summary><strong>作为 AI Agent Skill 使用</strong></summary>
|
|
29
44
|
|
|
@@ -47,6 +62,7 @@ tiangong-wiki setup
|
|
|
47
62
|
tiangong-wiki skill add ../my-skills --skill notes
|
|
48
63
|
tiangong-wiki skill status
|
|
49
64
|
tiangong-wiki skill update notes
|
|
65
|
+
tiangong-wiki skill update --all
|
|
50
66
|
```
|
|
51
67
|
|
|
52
68
|
</details>
|
|
@@ -61,7 +77,14 @@ tiangong-wiki init # 初始化工作区
|
|
|
61
77
|
tiangong-wiki sync # 索引 Markdown 文件
|
|
62
78
|
```
|
|
63
79
|
|
|
64
|
-
`tiangong-wiki setup`
|
|
80
|
+
`tiangong-wiki setup` 会创建工作区本地 `.wiki.env`,并将其记录为默认工作区配置。CLI 的配置解析优先级如下:
|
|
81
|
+
|
|
82
|
+
1. `--env-file <path>`
|
|
83
|
+
2. `WIKI_ENV_FILE`
|
|
84
|
+
3. 从当前目录向上查找最近的 `.wiki.env`
|
|
85
|
+
4. `tiangong-wiki setup` 写入的全局默认工作区配置
|
|
86
|
+
|
|
87
|
+
这意味着命令仍然最适合在 workspace 内执行;但 setup 之后,即使在 workspace 外运行,也可以通过默认配置正常工作,或者通过 `--env-file` 显式指定目标工作区。
|
|
65
88
|
|
|
66
89
|
```bash
|
|
67
90
|
tiangong-wiki find --type concept --status active # 结构化查询
|
|
@@ -71,11 +94,12 @@ tiangong-wiki graph bayes-theorem --depth 2 # 图遍历
|
|
|
71
94
|
```
|
|
72
95
|
|
|
73
96
|
```bash
|
|
74
|
-
tiangong-wiki daemon
|
|
97
|
+
tiangong-wiki daemon start # 后台启动 daemon
|
|
75
98
|
tiangong-wiki dashboard # 在浏览器中打开仪表盘
|
|
99
|
+
# 或者:tiangong-wiki daemon run # 前台运行 daemon,适合调试
|
|
76
100
|
```
|
|
77
101
|
|
|
78
|
-
> 环境变量通过 `.wiki.env` 管理(由 `tiangong-wiki setup` 创建)。CLI
|
|
102
|
+
> 环境变量通过 `.wiki.env` 管理(由 `tiangong-wiki setup` 创建)。CLI 会优先使用最近的本地 `.wiki.env`,找不到时再 fallback 到全局默认工作区配置。完整参考见 [references/troubleshooting.md](./references/troubleshooting.md)。
|
|
79
103
|
|
|
80
104
|
## CLI
|
|
81
105
|
|
package/dist/core/cli-env.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { readGlobalConfig } from "./global-config.js";
|
|
2
3
|
import { pathExistsSync, readTextFileSync } from "../utils/fs.js";
|
|
3
4
|
export const DEFAULT_WIKI_ENV_FILE = ".wiki.env";
|
|
4
5
|
const EMPTY_INFO = {
|
|
@@ -6,6 +7,10 @@ const EMPTY_INFO = {
|
|
|
6
7
|
loadedPath: null,
|
|
7
8
|
autoDiscovered: false,
|
|
8
9
|
missingRequestedPath: false,
|
|
10
|
+
missingDefaultPath: false,
|
|
11
|
+
source: "none",
|
|
12
|
+
globalConfigPath: null,
|
|
13
|
+
defaultPath: null,
|
|
9
14
|
loadedKeys: [],
|
|
10
15
|
};
|
|
11
16
|
let lastCliEnvInfo = EMPTY_INFO;
|
|
@@ -86,20 +91,40 @@ export function applyCliEnvironment(targetEnv = process.env, cwd = process.cwd()
|
|
|
86
91
|
const requestedEnvFile = targetEnv.WIKI_ENV_FILE?.trim();
|
|
87
92
|
const requestedPath = requestedEnvFile ? path.resolve(cwd, requestedEnvFile) : null;
|
|
88
93
|
if (!requestedPath && hasExplicitCoreRuntimeEnv(targetEnv)) {
|
|
89
|
-
lastCliEnvInfo = { ...EMPTY_INFO };
|
|
94
|
+
lastCliEnvInfo = { ...EMPTY_INFO, source: "process-env" };
|
|
90
95
|
return lastCliEnvInfo;
|
|
91
96
|
}
|
|
92
|
-
const
|
|
97
|
+
const nearestPath = requestedPath ? null : findNearestEnvFile(cwd);
|
|
98
|
+
const globalConfig = requestedPath || nearestPath ? null : readGlobalConfig(targetEnv);
|
|
99
|
+
const defaultPath = globalConfig ? path.resolve(globalConfig.defaultEnvFile) : null;
|
|
100
|
+
const candidatePath = requestedPath ?? nearestPath ?? defaultPath;
|
|
101
|
+
const source = requestedPath
|
|
102
|
+
? "explicit-env-file"
|
|
103
|
+
: nearestPath
|
|
104
|
+
? "nearest-env-file"
|
|
105
|
+
: defaultPath
|
|
106
|
+
? "global-default-env-file"
|
|
107
|
+
: "none";
|
|
93
108
|
if (!candidatePath) {
|
|
94
|
-
lastCliEnvInfo = {
|
|
109
|
+
lastCliEnvInfo = {
|
|
110
|
+
...EMPTY_INFO,
|
|
111
|
+
requestedPath,
|
|
112
|
+
source,
|
|
113
|
+
globalConfigPath: globalConfig?.configPath ?? null,
|
|
114
|
+
defaultPath,
|
|
115
|
+
};
|
|
95
116
|
return lastCliEnvInfo;
|
|
96
117
|
}
|
|
97
118
|
if (!pathExistsSync(candidatePath)) {
|
|
98
119
|
lastCliEnvInfo = {
|
|
99
120
|
requestedPath: candidatePath,
|
|
100
121
|
loadedPath: null,
|
|
101
|
-
autoDiscovered:
|
|
122
|
+
autoDiscovered: source === "nearest-env-file",
|
|
102
123
|
missingRequestedPath: requestedPath !== null,
|
|
124
|
+
missingDefaultPath: requestedPath === null && source === "global-default-env-file",
|
|
125
|
+
source,
|
|
126
|
+
globalConfigPath: globalConfig?.configPath ?? null,
|
|
127
|
+
defaultPath,
|
|
103
128
|
loadedKeys: [],
|
|
104
129
|
};
|
|
105
130
|
return lastCliEnvInfo;
|
|
@@ -118,8 +143,12 @@ export function applyCliEnvironment(targetEnv = process.env, cwd = process.cwd()
|
|
|
118
143
|
lastCliEnvInfo = {
|
|
119
144
|
requestedPath,
|
|
120
145
|
loadedPath: candidatePath,
|
|
121
|
-
autoDiscovered:
|
|
146
|
+
autoDiscovered: source === "nearest-env-file",
|
|
122
147
|
missingRequestedPath: false,
|
|
148
|
+
missingDefaultPath: false,
|
|
149
|
+
source,
|
|
150
|
+
globalConfigPath: globalConfig?.configPath ?? null,
|
|
151
|
+
defaultPath,
|
|
123
152
|
loadedKeys,
|
|
124
153
|
};
|
|
125
154
|
return lastCliEnvInfo;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { Codex } from "@openai/codex-sdk";
|
|
3
3
|
import { readWorkflowResult } from "./workflow-result.js";
|
|
4
|
+
import { resolveAgentSettings } from "./paths.js";
|
|
4
5
|
import { readTextFileSync, writeTextFileSync } from "../utils/fs.js";
|
|
5
6
|
import { AppError } from "../utils/errors.js";
|
|
6
7
|
export const CODEX_WORKFLOW_VERSION = "2026-04-07";
|
|
@@ -85,16 +86,10 @@ async function runThread(thread, input) {
|
|
|
85
86
|
continue;
|
|
86
87
|
}
|
|
87
88
|
if (event.type === "turn.failed") {
|
|
88
|
-
throw
|
|
89
|
-
cause: event.error.message,
|
|
90
|
-
threadId: activeThreadId,
|
|
91
|
-
});
|
|
89
|
+
throw classifyWorkflowRuntimeError("Codex workflow turn failed", event.error.message, activeThreadId);
|
|
92
90
|
}
|
|
93
91
|
if (event.type === "error") {
|
|
94
|
-
throw
|
|
95
|
-
cause: event.message,
|
|
96
|
-
threadId: activeThreadId,
|
|
97
|
-
});
|
|
92
|
+
throw classifyWorkflowRuntimeError("Codex workflow stream failed", event.message, activeThreadId);
|
|
98
93
|
}
|
|
99
94
|
}
|
|
100
95
|
}
|
|
@@ -103,10 +98,7 @@ async function runThread(thread, input) {
|
|
|
103
98
|
throw error;
|
|
104
99
|
}
|
|
105
100
|
const message = error instanceof Error ? error.message : String(error);
|
|
106
|
-
throw
|
|
107
|
-
cause: message,
|
|
108
|
-
threadId: activeThreadId,
|
|
109
|
-
});
|
|
101
|
+
throw classifyWorkflowRuntimeError("Codex workflow turn failed", message, activeThreadId);
|
|
110
102
|
}
|
|
111
103
|
if (!activeThreadId && thread.id) {
|
|
112
104
|
activeThreadId = thread.id;
|
|
@@ -118,6 +110,10 @@ async function runThread(thread, input) {
|
|
|
118
110
|
return activeThreadId;
|
|
119
111
|
}
|
|
120
112
|
export class CodexSdkWorkflowRunner {
|
|
113
|
+
options;
|
|
114
|
+
constructor(options = {}) {
|
|
115
|
+
this.options = options;
|
|
116
|
+
}
|
|
121
117
|
// The SDK can only continue a thread by sending a new input, so queue retries
|
|
122
118
|
// must not automatically resume real workflow threads inline.
|
|
123
119
|
async startWorkflow(input) {
|
|
@@ -127,7 +123,7 @@ export class CodexSdkWorkflowRunner {
|
|
|
127
123
|
modelReasoningEffort: "low",
|
|
128
124
|
workingDirectory: input.workspaceRoot,
|
|
129
125
|
skipGitRepoCheck: true,
|
|
130
|
-
sandboxMode: "
|
|
126
|
+
sandboxMode: this.options.sandboxMode ?? "danger-full-access",
|
|
131
127
|
networkAccessEnabled: true,
|
|
132
128
|
approvalPolicy: "never",
|
|
133
129
|
webSearchMode: "disabled",
|
|
@@ -143,7 +139,7 @@ export class CodexSdkWorkflowRunner {
|
|
|
143
139
|
modelReasoningEffort: "low",
|
|
144
140
|
workingDirectory: input.workspaceRoot,
|
|
145
141
|
skipGitRepoCheck: true,
|
|
146
|
-
sandboxMode: "
|
|
142
|
+
sandboxMode: this.options.sandboxMode ?? "danger-full-access",
|
|
147
143
|
networkAccessEnabled: true,
|
|
148
144
|
approvalPolicy: "never",
|
|
149
145
|
webSearchMode: "disabled",
|
|
@@ -229,5 +225,31 @@ export function createDefaultWorkflowRunner(env = process.env) {
|
|
|
229
225
|
const delayMs = Number.parseInt(env.WIKI_TEST_FAKE_WORKFLOW_DELAY_MS ?? "0", 10) || 0;
|
|
230
226
|
return createSkipOnlyTestWorkflowRunner({ delayMs, mode: "delay-skip" });
|
|
231
227
|
}
|
|
232
|
-
return new CodexSdkWorkflowRunner(
|
|
228
|
+
return new CodexSdkWorkflowRunner({
|
|
229
|
+
sandboxMode: resolveAgentSettings(env).sandboxMode,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
function isSandboxStartupFailure(message) {
|
|
233
|
+
const normalized = message.toLowerCase();
|
|
234
|
+
return (normalized.includes("bwrap") ||
|
|
235
|
+
normalized.includes("bubblewrap") ||
|
|
236
|
+
normalized.includes("uid map") ||
|
|
237
|
+
normalized.includes("uid_map") ||
|
|
238
|
+
normalized.includes("gid map") ||
|
|
239
|
+
normalized.includes("gid_map") ||
|
|
240
|
+
normalized.includes("unshare") ||
|
|
241
|
+
normalized.includes("operation not permitted"));
|
|
242
|
+
}
|
|
243
|
+
function classifyWorkflowRuntimeError(baseMessage, cause, threadId) {
|
|
244
|
+
if (isSandboxStartupFailure(cause)) {
|
|
245
|
+
return new AppError("Codex workflow sandbox failed to initialize", "runtime", {
|
|
246
|
+
cause,
|
|
247
|
+
threadId,
|
|
248
|
+
phase: "sandbox",
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
return new AppError(baseMessage, "runtime", {
|
|
252
|
+
cause,
|
|
253
|
+
threadId,
|
|
254
|
+
});
|
|
233
255
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { AppError } from "../utils/errors.js";
|
|
4
|
+
import { ensureDirSync, pathExistsSync, readTextFileSync, writeTextFileSync } from "../utils/fs.js";
|
|
5
|
+
export const GLOBAL_CONFIG_DIRNAME = "tiangong-wiki";
|
|
6
|
+
export const GLOBAL_CONFIG_FILENAME = "config.json";
|
|
7
|
+
function resolveConfigBaseDir(env = process.env) {
|
|
8
|
+
const xdgConfigHome = env.XDG_CONFIG_HOME?.trim();
|
|
9
|
+
if (xdgConfigHome) {
|
|
10
|
+
return path.resolve(xdgConfigHome);
|
|
11
|
+
}
|
|
12
|
+
const homeDir = env.HOME?.trim() || os.homedir();
|
|
13
|
+
return path.join(homeDir, ".config");
|
|
14
|
+
}
|
|
15
|
+
export function resolveGlobalConfigPath(env = process.env) {
|
|
16
|
+
return path.join(resolveConfigBaseDir(env), GLOBAL_CONFIG_DIRNAME, GLOBAL_CONFIG_FILENAME);
|
|
17
|
+
}
|
|
18
|
+
export function readGlobalConfig(env = process.env) {
|
|
19
|
+
const configPath = resolveGlobalConfigPath(env);
|
|
20
|
+
if (!pathExistsSync(configPath)) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
let parsed;
|
|
24
|
+
try {
|
|
25
|
+
parsed = JSON.parse(readTextFileSync(configPath));
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
throw new AppError(`Failed to parse global CLI config JSON: ${configPath}`, "config", {
|
|
29
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
33
|
+
throw new AppError(`Global CLI config must be an object: ${configPath}`, "config");
|
|
34
|
+
}
|
|
35
|
+
const schemaVersion = parsed.schemaVersion;
|
|
36
|
+
if (!Number.isInteger(schemaVersion) || Number(schemaVersion) < 1) {
|
|
37
|
+
throw new AppError(`Global CLI config schemaVersion must be a positive integer: ${configPath}`, "config");
|
|
38
|
+
}
|
|
39
|
+
const defaultEnvFile = parsed.defaultEnvFile;
|
|
40
|
+
if (typeof defaultEnvFile !== "string" || defaultEnvFile.trim().length === 0) {
|
|
41
|
+
throw new AppError(`Global CLI config defaultEnvFile must be a non-empty string: ${configPath}`, "config");
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
configPath,
|
|
45
|
+
defaultEnvFile: path.resolve(defaultEnvFile),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function writeGlobalConfig(defaultEnvFile, env = process.env) {
|
|
49
|
+
const configPath = resolveGlobalConfigPath(env);
|
|
50
|
+
const normalizedEnvFile = path.resolve(defaultEnvFile);
|
|
51
|
+
const payload = {
|
|
52
|
+
schemaVersion: 1,
|
|
53
|
+
defaultEnvFile: normalizedEnvFile,
|
|
54
|
+
};
|
|
55
|
+
ensureDirSync(path.dirname(configPath));
|
|
56
|
+
writeTextFileSync(configPath, `${JSON.stringify(payload, null, 2)}\n`);
|
|
57
|
+
return {
|
|
58
|
+
configPath,
|
|
59
|
+
defaultEnvFile: normalizedEnvFile,
|
|
60
|
+
};
|
|
61
|
+
}
|
package/dist/core/onboarding.js
CHANGED
|
@@ -5,7 +5,8 @@ import { confirm, input, password, select } from "@inquirer/prompts";
|
|
|
5
5
|
import { DEFAULT_WIKI_ENV_FILE, getCliEnvironmentInfo, parseEnvFile, serializeEnvEntries } from "./cli-env.js";
|
|
6
6
|
import { resolveTemplateFilePath, loadConfig } from "./config.js";
|
|
7
7
|
import { EmbeddingClient } from "./embedding.js";
|
|
8
|
-
import {
|
|
8
|
+
import { writeGlobalConfig } from "./global-config.js";
|
|
9
|
+
import { parseVaultHashMode, parseWikiAgentSandboxMode, resolveAgentSettings } from "./paths.js";
|
|
9
10
|
import { loadSynologyConfigFromEnv, normalizeSynologyRemotePath, withSynologyClient } from "./synology.js";
|
|
10
11
|
import { ensureWikiSkillInstall, formatParserSkills, inspectSkillInstall, installParserSkill, OPTIONAL_PARSER_SKILLS, parseParserSkillSelection, parseParserSkills, resolveWorkspaceRootFromWikiPath, resolveWorkspaceSkillPath, resolveWorkspaceSkillPaths, } from "./workspace-skills.js";
|
|
11
12
|
import { scaffoldWorkspaceAssets } from "./workspace-bootstrap.js";
|
|
@@ -35,11 +36,16 @@ const MANAGED_ENV_KEYS = new Set([
|
|
|
35
36
|
"WIKI_AGENT_API_KEY",
|
|
36
37
|
"WIKI_AGENT_MODEL",
|
|
37
38
|
"WIKI_AGENT_BATCH_SIZE",
|
|
39
|
+
"WIKI_AGENT_SANDBOX_MODE",
|
|
38
40
|
"WIKI_PARSER_SKILLS",
|
|
39
41
|
]);
|
|
40
42
|
function writeSection(output, title) {
|
|
41
43
|
output.write(`\n${title}\n`);
|
|
42
44
|
}
|
|
45
|
+
function writeWarning(output, message) {
|
|
46
|
+
const isTty = "isTTY" in output && output.isTTY;
|
|
47
|
+
output.write(isTty ? `\x1b[31m${message}\x1b[0m\n` : `${message}\n`);
|
|
48
|
+
}
|
|
43
49
|
function resolvePackageRoot(packageRoot) {
|
|
44
50
|
return packageRoot ?? path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
45
51
|
}
|
|
@@ -72,6 +78,14 @@ function safeVaultHashMode(rawValue, defaultValue) {
|
|
|
72
78
|
return defaultValue;
|
|
73
79
|
}
|
|
74
80
|
}
|
|
81
|
+
function safeAgentSandboxMode(rawValue) {
|
|
82
|
+
try {
|
|
83
|
+
return parseWikiAgentSandboxMode(rawValue);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return "danger-full-access";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
75
89
|
function safeBooleanFlag(rawValue, defaultValue) {
|
|
76
90
|
if (rawValue === undefined || rawValue.trim().length === 0) {
|
|
77
91
|
return defaultValue;
|
|
@@ -380,6 +394,7 @@ function getPathDefaults(env, cwd) {
|
|
|
380
394
|
agentApiKey: env.WIKI_AGENT_API_KEY ?? null,
|
|
381
395
|
agentModel: env.WIKI_AGENT_MODEL ?? null,
|
|
382
396
|
agentBatchSize: env.WIKI_AGENT_BATCH_SIZE ?? "5",
|
|
397
|
+
agentSandboxMode: safeAgentSandboxMode(env.WIKI_AGENT_SANDBOX_MODE),
|
|
383
398
|
parserSkills: parseParserSkills(env.WIKI_PARSER_SKILLS, { strict: false }),
|
|
384
399
|
};
|
|
385
400
|
}
|
|
@@ -446,14 +461,32 @@ async function collectAgentSettings(driver, ctx, defaults) {
|
|
|
446
461
|
agentApiKey: null,
|
|
447
462
|
agentModel: null,
|
|
448
463
|
agentBatchSize: null,
|
|
464
|
+
agentSandboxMode: null,
|
|
449
465
|
};
|
|
450
466
|
}
|
|
467
|
+
writeWarning(ctx.output, "Warning: danger-full-access grants full access to the runtime workspace.");
|
|
451
468
|
return {
|
|
452
469
|
agentEnabled: true,
|
|
453
470
|
agentBaseUrl: await promptText(driver, "WIKI_AGENT_BASE_URL", defaults.agentBaseUrl ?? "https://api.openai.com/v1", { validator: (value) => validateUrl(value, "WIKI_AGENT_BASE_URL") }),
|
|
454
471
|
agentApiKey: await promptPassword(driver, "WIKI_AGENT_API_KEY", defaults.agentApiKey ?? "", { required: true }),
|
|
455
472
|
agentModel: await promptText(driver, "WIKI_AGENT_MODEL", defaults.agentModel ?? "", { required: true }),
|
|
456
473
|
agentBatchSize: await promptText(driver, "WIKI_AGENT_BATCH_SIZE", defaults.agentBatchSize ?? "5", { validator: (value) => validateNonNegativeInteger(value, "WIKI_AGENT_BATCH_SIZE") }),
|
|
474
|
+
agentSandboxMode: await driver.select({
|
|
475
|
+
message: "WIKI_AGENT_SANDBOX_MODE",
|
|
476
|
+
defaultValue: defaults.agentSandboxMode ?? "danger-full-access",
|
|
477
|
+
choices: [
|
|
478
|
+
{
|
|
479
|
+
value: "danger-full-access",
|
|
480
|
+
label: "danger-full-access",
|
|
481
|
+
description: "Full access to the runtime workspace. Default.",
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
value: "workspace-write",
|
|
485
|
+
label: "workspace-write",
|
|
486
|
+
description: "Use Codex workspace-write sandbox when the host supports it.",
|
|
487
|
+
},
|
|
488
|
+
],
|
|
489
|
+
}),
|
|
457
490
|
};
|
|
458
491
|
}
|
|
459
492
|
async function collectSynologySettings(driver, ctx, defaults) {
|
|
@@ -531,6 +564,7 @@ function buildSetupSummary(values) {
|
|
|
531
564
|
lines.push(` WIKI_AGENT_BASE_URL: ${values.agentBaseUrl}`);
|
|
532
565
|
lines.push(` WIKI_AGENT_MODEL: ${values.agentModel}`);
|
|
533
566
|
lines.push(` WIKI_AGENT_BATCH_SIZE: ${values.agentBatchSize}`);
|
|
567
|
+
lines.push(` WIKI_AGENT_SANDBOX_MODE: ${values.agentSandboxMode}`);
|
|
534
568
|
}
|
|
535
569
|
if (values.vaultSource === "synology") {
|
|
536
570
|
lines.push(` SYNOLOGY_BASE_URL: ${values.synologyBaseUrl}`);
|
|
@@ -568,6 +602,7 @@ function writeSetupEnvFile(values) {
|
|
|
568
602
|
["WIKI_AGENT_API_KEY", values.agentEnabled ? values.agentApiKey : null],
|
|
569
603
|
["WIKI_AGENT_MODEL", values.agentEnabled ? values.agentModel : null],
|
|
570
604
|
["WIKI_AGENT_BATCH_SIZE", values.agentEnabled ? values.agentBatchSize : null],
|
|
605
|
+
["WIKI_AGENT_SANDBOX_MODE", values.agentEnabled ? values.agentSandboxMode : null],
|
|
571
606
|
["WIKI_PARSER_SKILLS", formatParserSkills(values.parserSkills)],
|
|
572
607
|
];
|
|
573
608
|
const body = [
|
|
@@ -672,9 +707,11 @@ export async function runSetupWizard(env = process.env, options = {}) {
|
|
|
672
707
|
output,
|
|
673
708
|
}));
|
|
674
709
|
writeSetupEnvFile(values);
|
|
710
|
+
const globalConfig = writeGlobalConfig(values.envFilePath, env);
|
|
675
711
|
output.write([
|
|
676
712
|
"\ntiangong-wiki setup complete",
|
|
677
713
|
`configuration file: ${values.envFilePath}`,
|
|
714
|
+
`default workspace config: ${globalConfig.configPath}`,
|
|
678
715
|
`workspace root: ${workspaceRoot}`,
|
|
679
716
|
`skills root: ${skillsRoot}`,
|
|
680
717
|
`tiangong-wiki-skill: ${wikiSkillInstall.status}`,
|
|
@@ -687,8 +724,10 @@ export async function runSetupWizard(env = process.env, options = {}) {
|
|
|
687
724
|
: []),
|
|
688
725
|
"",
|
|
689
726
|
"Next steps:",
|
|
690
|
-
`-
|
|
727
|
+
`- Commands inside ${JSON.stringify(workspaceRoot)} will auto-discover the local .wiki.env first.`,
|
|
728
|
+
`- Commands outside the workspace will fall back to the default workspace config at ${globalConfig.configPath}.`,
|
|
691
729
|
`- Example: cd ${JSON.stringify(workspaceRoot)} && tiangong-wiki doctor`,
|
|
730
|
+
`- Example: tiangong-wiki --env-file ${JSON.stringify(values.envFilePath)} doctor`,
|
|
692
731
|
`- Example: cd ${JSON.stringify(workspaceRoot)} && tiangong-wiki init`,
|
|
693
732
|
"- Run `tiangong-wiki doctor` to validate the generated configuration.",
|
|
694
733
|
"- Run `tiangong-wiki init` to create index.db and perform the first sync.",
|
|
@@ -701,6 +740,7 @@ export async function runSetupWizard(env = process.env, options = {}) {
|
|
|
701
740
|
].join("\n"));
|
|
702
741
|
return {
|
|
703
742
|
envFilePath: values.envFilePath,
|
|
743
|
+
globalConfigPath: globalConfig.configPath,
|
|
704
744
|
createdDirectories: bootstrap.createdDirectories,
|
|
705
745
|
copiedConfig: bootstrap.copiedConfig,
|
|
706
746
|
copiedTemplates: bootstrap.copiedTemplates,
|
|
@@ -1017,11 +1057,26 @@ export async function buildDoctorReport(env = process.env, options = {}) {
|
|
|
1017
1057
|
if (envFile.missingRequestedPath && envFile.requestedPath) {
|
|
1018
1058
|
collectDoctorCheck(checks, "error", "env-file", `Requested env file does not exist: ${envFile.requestedPath}`, "Create the env file or rerun `tiangong-wiki setup`.");
|
|
1019
1059
|
}
|
|
1060
|
+
else if (envFile.missingDefaultPath && envFile.defaultPath) {
|
|
1061
|
+
collectDoctorCheck(checks, "error", "env-file", `The default workspace config points to a missing env file: ${envFile.defaultPath}`, envFile.globalConfigPath
|
|
1062
|
+
? `Fix or remove ${envFile.globalConfigPath}, rerun \`tiangong-wiki setup\`, or pass \`--env-file\` explicitly.`
|
|
1063
|
+
: "Fix the default workspace config, rerun `tiangong-wiki setup`, or pass `--env-file` explicitly.");
|
|
1064
|
+
}
|
|
1020
1065
|
else if (envFile.loadedPath) {
|
|
1021
|
-
|
|
1066
|
+
const sourceLabel = envFile.source === "explicit-env-file"
|
|
1067
|
+
? "from --env-file or WIKI_ENV_FILE."
|
|
1068
|
+
: envFile.source === "nearest-env-file"
|
|
1069
|
+
? "from the current workspace (auto-discovered)."
|
|
1070
|
+
: envFile.source === "global-default-env-file"
|
|
1071
|
+
? "from the global default workspace config."
|
|
1072
|
+
: ".";
|
|
1073
|
+
collectDoctorCheck(checks, "ok", "env-file", `Loaded configuration from ${envFile.loadedPath} ${sourceLabel}`);
|
|
1074
|
+
}
|
|
1075
|
+
else if (envFile.source === "process-env") {
|
|
1076
|
+
collectDoctorCheck(checks, "ok", "env-file", "Using runtime paths provided directly via process.env; no .wiki.env file was loaded.");
|
|
1022
1077
|
}
|
|
1023
1078
|
else {
|
|
1024
|
-
collectDoctorCheck(checks, "warn", "env-file", "No
|
|
1079
|
+
collectDoctorCheck(checks, "warn", "env-file", "No workspace configuration was found from --env-file, WIKI_ENV_FILE, the current directory, or the global default workspace config.", "Run `tiangong-wiki setup`, set `WIKI_ENV_FILE`, or pass `--env-file` to point at a workspace explicitly.");
|
|
1025
1080
|
}
|
|
1026
1081
|
const wikiPath = env.WIKI_PATH ? path.resolve(env.WIKI_PATH) : null;
|
|
1027
1082
|
const wikiRoot = wikiPath ? path.resolve(wikiPath, "..") : null;
|
|
@@ -1055,6 +1110,10 @@ export async function buildDoctorReport(env = process.env, options = {}) {
|
|
|
1055
1110
|
loadedPath: envFile.loadedPath,
|
|
1056
1111
|
autoDiscovered: envFile.autoDiscovered,
|
|
1057
1112
|
missingRequestedPath: envFile.missingRequestedPath,
|
|
1113
|
+
missingDefaultPath: envFile.missingDefaultPath,
|
|
1114
|
+
source: envFile.source,
|
|
1115
|
+
globalConfigPath: envFile.globalConfigPath,
|
|
1116
|
+
defaultPath: envFile.defaultPath,
|
|
1058
1117
|
},
|
|
1059
1118
|
effectivePaths: {
|
|
1060
1119
|
wikiPath,
|
package/dist/core/paths.js
CHANGED
|
@@ -89,12 +89,20 @@ export function parseWikiAgentBackend(raw) {
|
|
|
89
89
|
}
|
|
90
90
|
throw new AppError(`WIKI_AGENT_BACKEND must be "codex-workflow", got ${raw}`, "config");
|
|
91
91
|
}
|
|
92
|
+
export function parseWikiAgentSandboxMode(raw) {
|
|
93
|
+
const value = (raw ?? "danger-full-access").trim().toLowerCase();
|
|
94
|
+
if (value === "danger-full-access" || value === "workspace-write") {
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
throw new AppError(`WIKI_AGENT_SANDBOX_MODE must be "danger-full-access" or "workspace-write", got ${raw}`, "config");
|
|
98
|
+
}
|
|
92
99
|
export function resolveAgentSettings(env = process.env, options = {}) {
|
|
93
100
|
const enabled = parseBooleanFlag("WIKI_AGENT_ENABLED", env.WIKI_AGENT_ENABLED, false);
|
|
94
101
|
const baseUrl = normalizeOptionalUrl(env.WIKI_AGENT_BASE_URL);
|
|
95
102
|
const apiKey = env.WIKI_AGENT_API_KEY?.trim() || null;
|
|
96
103
|
const model = env.WIKI_AGENT_MODEL?.trim() || null;
|
|
97
104
|
const batchSize = parseNonNegativeInteger(env.WIKI_AGENT_BATCH_SIZE, 5, "WIKI_AGENT_BATCH_SIZE");
|
|
105
|
+
const sandboxMode = parseWikiAgentSandboxMode(env.WIKI_AGENT_SANDBOX_MODE);
|
|
98
106
|
const workflowTimeoutSeconds = parsePositiveInteger(env.WIKI_WORKFLOW_TIMEOUT, 600, "WIKI_WORKFLOW_TIMEOUT");
|
|
99
107
|
const missing = [];
|
|
100
108
|
if (enabled) {
|
|
@@ -114,6 +122,7 @@ export function resolveAgentSettings(env = process.env, options = {}) {
|
|
|
114
122
|
apiKey,
|
|
115
123
|
model,
|
|
116
124
|
batchSize,
|
|
125
|
+
sandboxMode,
|
|
117
126
|
workflowTimeoutSeconds,
|
|
118
127
|
configured: enabled && missing.length === 0,
|
|
119
128
|
missing,
|
|
@@ -148,6 +148,11 @@ export function readWorkflowResult(resultPath) {
|
|
|
148
148
|
fail(`Workflow result not found: ${resultPath}`);
|
|
149
149
|
}
|
|
150
150
|
const rawText = readTextFileSync(resultPath);
|
|
151
|
+
if (!rawText.trim()) {
|
|
152
|
+
fail("Workflow result is empty", {
|
|
153
|
+
resultPath,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
151
156
|
let parsed;
|
|
152
157
|
try {
|
|
153
158
|
parsed = JSON.parse(rawText);
|
package/dist/index.js
CHANGED
|
@@ -29,12 +29,39 @@ import { loadRuntimeConfig } from "./core/runtime.js";
|
|
|
29
29
|
import { embedPendingPages } from "./core/sync.js";
|
|
30
30
|
import { processVaultQueueBatch } from "./core/vault-processing.js";
|
|
31
31
|
import { handleCliError, writeJson } from "./utils/output.js";
|
|
32
|
+
function extractEnvFileOption(argv) {
|
|
33
|
+
const nextArgv = argv.slice(0, 2);
|
|
34
|
+
let envFile = null;
|
|
35
|
+
for (let index = 2; index < argv.length; index += 1) {
|
|
36
|
+
const arg = argv[index];
|
|
37
|
+
if (arg === "--env-file") {
|
|
38
|
+
const value = argv[index + 1];
|
|
39
|
+
if (!value || value.startsWith("-")) {
|
|
40
|
+
throw new Error("--env-file requires a value");
|
|
41
|
+
}
|
|
42
|
+
envFile = value;
|
|
43
|
+
index += 1;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (arg.startsWith("--env-file=")) {
|
|
47
|
+
const value = arg.slice("--env-file=".length);
|
|
48
|
+
if (!value) {
|
|
49
|
+
throw new Error("--env-file requires a value");
|
|
50
|
+
}
|
|
51
|
+
envFile = value;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
nextArgv.push(arg);
|
|
55
|
+
}
|
|
56
|
+
return { envFile, argv: nextArgv };
|
|
57
|
+
}
|
|
32
58
|
function buildProgram() {
|
|
33
59
|
const program = new Command();
|
|
34
60
|
program
|
|
35
61
|
.name("tiangong-wiki")
|
|
36
62
|
.description("Tiangong Wiki — local-first indexing and query CLI")
|
|
37
63
|
.version(packageJson.version)
|
|
64
|
+
.option("--env-file <path>", "Load runtime environment from a specific .wiki.env file")
|
|
38
65
|
.showHelpAfterError();
|
|
39
66
|
let runtimeConfig;
|
|
40
67
|
try {
|
|
@@ -81,9 +108,13 @@ function buildProgram() {
|
|
|
81
108
|
return program;
|
|
82
109
|
}
|
|
83
110
|
try {
|
|
111
|
+
const { envFile, argv } = extractEnvFileOption(process.argv);
|
|
112
|
+
if (envFile) {
|
|
113
|
+
process.env.WIKI_ENV_FILE = envFile;
|
|
114
|
+
}
|
|
84
115
|
applyCliEnvironment(process.env, process.cwd());
|
|
85
116
|
const program = buildProgram();
|
|
86
|
-
await program.parseAsync(
|
|
117
|
+
await program.parseAsync(argv);
|
|
87
118
|
}
|
|
88
119
|
catch (error) {
|
|
89
120
|
handleCliError(error);
|
package/package.json
CHANGED
|
@@ -13,6 +13,13 @@ npx @biaoo/tiangong-wiki <command> [options]
|
|
|
13
13
|
npm run dev -- <command> [options]
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
Global workspace resolution priority:
|
|
17
|
+
|
|
18
|
+
1. `--env-file <path>`
|
|
19
|
+
2. `WIKI_ENV_FILE`
|
|
20
|
+
3. nearest `.wiki.env` found by walking upward from the current directory
|
|
21
|
+
4. the global default workspace config written by `tiangong-wiki setup`
|
|
22
|
+
|
|
16
23
|
---
|
|
17
24
|
|
|
18
25
|
## Command Overview
|
|
@@ -57,6 +64,7 @@ Interactive step-by-step wizard that:
|
|
|
57
64
|
- Records `WIKI_PATH`, `VAULT_PATH`, `WIKI_DB_PATH`, `WIKI_CONFIG_PATH`, `WIKI_TEMPLATES_PATH`
|
|
58
65
|
- Optionally configures `EMBEDDING_*` and Synology vault settings
|
|
59
66
|
- Writes `.wiki.env` in the current working directory
|
|
67
|
+
- Writes a global default workspace config pointing to that `.wiki.env`
|
|
60
68
|
- Scaffolds `wiki/pages/`, `vault/`, `wiki.config.json`, and `templates/`
|
|
61
69
|
|
|
62
70
|
After setup, run `tiangong-wiki doctor` then `tiangong-wiki init` to complete initialization.
|
|
@@ -64,11 +72,12 @@ After setup, run `tiangong-wiki doctor` then `tiangong-wiki init` to complete in
|
|
|
64
72
|
### doctor
|
|
65
73
|
|
|
66
74
|
```
|
|
67
|
-
tiangong-wiki doctor [--probe] [--format text|json]
|
|
75
|
+
tiangong-wiki doctor [--env-file <path>] [--probe] [--format text|json]
|
|
68
76
|
```
|
|
69
77
|
|
|
70
78
|
| Option | Description |
|
|
71
79
|
| --- | --- |
|
|
80
|
+
| `--env-file` | Load a specific `.wiki.env` before running the command |
|
|
72
81
|
| `--probe` | Additionally test remote services (embedding endpoint, Synology NAS) |
|
|
73
82
|
| `--format` | Output format: `text` (default) or `json` |
|
|
74
83
|
|
|
@@ -73,8 +73,11 @@ The agent uses [Codex SDK](https://www.npmjs.com/package/@openai/codex-sdk) to p
|
|
|
73
73
|
| `WIKI_AGENT_API_KEY` | If enabled | API key for the LLM provider |
|
|
74
74
|
| `WIKI_AGENT_MODEL` | No | Model name (e.g. `gpt-5.4`, `Qwen/Qwen3.5-397B-A17B-GPTQ-Int4`) |
|
|
75
75
|
| `WIKI_AGENT_BATCH_SIZE` | No | Max concurrent vault items per batch (default: `5`) |
|
|
76
|
+
| `WIKI_AGENT_SANDBOX_MODE` | No | Codex sandbox mode: `danger-full-access` (default) or `workspace-write` |
|
|
76
77
|
| `WIKI_PARSER_SKILLS` | No | Comma-separated parser skill list (e.g. `pdf,docx,pptx,xlsx`) |
|
|
77
78
|
|
|
79
|
+
`tiangong-wiki setup` now prompts for `WIKI_AGENT_SANDBOX_MODE` when automatic vault processing is enabled. The default is `danger-full-access`, and the setup wizard highlights that this mode grants full runtime access.
|
|
80
|
+
|
|
78
81
|
---
|
|
79
82
|
|
|
80
83
|
## Common Issues
|
|
@@ -102,6 +105,10 @@ Always run `tiangong-wiki lint --path <page-id> --format json` after mutations.
|
|
|
102
105
|
|
|
103
106
|
Parser skills must be installed under `<workspace-root>/.agents/skills/`. Run `tiangong-wiki skill` to inspect installed skills. Use `tiangong-wiki skill update --all` to update.
|
|
104
107
|
|
|
108
|
+
### Codex workflow sandbox fails to initialize
|
|
109
|
+
|
|
110
|
+
If the agent workflow fails with `bwrap`, `unshare`, `uid_map`, or similar sandbox startup errors, switch `WIKI_AGENT_SANDBOX_MODE` to `danger-full-access`. Use `workspace-write` only when you explicitly want that sandbox mode and know the host supports it.
|
|
111
|
+
|
|
105
112
|
---
|
|
106
113
|
|
|
107
114
|
## LLM Provider Setup
|