@biaoo/tiangong-wiki 0.3.5 → 0.3.7

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 CHANGED
@@ -86,6 +86,8 @@ tiangong-wiki sync # index Markdown pages
86
86
 
87
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`.
88
88
 
89
+ For automatic vault processing, new setup runs default to `WIKI_AGENT_AUTH_MODE=codex-login`, a dedicated Codex home under the current user's home directory, and `WIKI_AGENT_MODEL=gpt-5.5`. Before enabling that mode, run `CODEX_HOME="$HOME/.codex-tiangong-wiki" codex login` on macOS/Linux, or set `$env:CODEX_HOME = "$env:USERPROFILE\.codex-tiangong-wiki"` before `codex login` on Windows PowerShell.
90
+
89
91
  ```bash
90
92
  tiangong-wiki find --type concept --status active # structured query
91
93
  tiangong-wiki fts "Bayesian" # full-text search
package/README.zh-CN.md CHANGED
@@ -86,6 +86,8 @@ tiangong-wiki sync # 索引 Markdown 文件
86
86
 
87
87
  这意味着命令仍然最适合在 workspace 内执行;但 setup 之后,即使在 workspace 外运行,也可以通过默认配置正常工作,或者通过 `--env-file` 显式指定目标工作区。
88
88
 
89
+ 如果启用自动 vault 处理,新的 setup 默认使用 `WIKI_AGENT_AUTH_MODE=codex-login`、当前用户 home 目录下的专用 Codex home 和 `WIKI_AGENT_MODEL=gpt-5.5`。启用前,macOS/Linux 执行 `CODEX_HOME="$HOME/.codex-tiangong-wiki" codex login`;Windows PowerShell 先设置 `$env:CODEX_HOME = "$env:USERPROFILE\.codex-tiangong-wiki"`,再执行 `codex login`。
90
+
89
91
  ```bash
90
92
  tiangong-wiki find --type concept --status active # 结构化查询
91
93
  tiangong-wiki fts "贝叶斯" # 全文搜索
package/SKILL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: tiangong-wiki-skill
3
- description: "Use when you need to retrieve historically accumulated knowledge, methods, or behavioral patterns before answering or acting; when a conversation or workflow produces durable insights worth preserving for future reuse; or when you discover existing wiki content that is outdated or incorrect and needs correction."
3
+ description: "Use when you need to retrieve historically accumulated knowledge, methods, or behavioral patterns before answering or acting; when a conversation or workflow produces durable insights worth preserving for future reuse; or when existing wiki content is outdated or incorrect. On Windows native shells or Codex automation, invoke the CLI as tiangong-wiki.cmd."
4
4
  ---
5
5
 
6
6
  # Wiki Skill
@@ -9,6 +9,20 @@ description: "Use when you need to retrieve historically accumulated knowledge,
9
9
 
10
10
  Use the local wiki as the **durable knowledge layer** — not just for the current conversation, but for all future work. Query first, then read or edit the Markdown files that remain the source of truth.
11
11
 
12
+ ## CLI Entrypoint
13
+
14
+ Use `tiangong-wiki <command>` on macOS, Linux, WSL, and Git Bash.
15
+
16
+ On Windows native shells such as PowerShell, Command Prompt, background daemon tasks, or Codex worker automation, use the npm command shim explicitly:
17
+
18
+ ```powershell
19
+ tiangong-wiki.cmd doctor
20
+ tiangong-wiki.cmd sync
21
+ tiangong-wiki.cmd lint --format json
22
+ ```
23
+
24
+ Do not invoke the suffixless `tiangong-wiki` executable from Windows native shells. npm also installs that shebang script for POSIX-like environments, but Windows may treat it as an unknown file and open the "choose an app" dialog instead of executing the CLI.
25
+
12
26
  ## When to Use
13
27
 
14
28
  Activate this skill in three scenarios:
@@ -8,7 +8,24 @@ WIKI_SYNC_INTERVAL=86400
8
8
  EMBEDDING_BASE_URL=https://api.openai.com/v1
9
9
  EMBEDDING_API_KEY=your-embedding-api-key
10
10
  EMBEDDING_MODEL=text-embedding-3-small
11
- EMBEDDING_DIMENSIONS=384
11
+ EMBEDDING_DIMENSIONS=1536
12
+
13
+ WIKI_AGENT_ENABLED=false
14
+ # For local Codex login auth, run on macOS/Linux:
15
+ # CODEX_HOME="$HOME/.codex-tiangong-wiki" codex login
16
+ # On Windows PowerShell:
17
+ # $env:CODEX_HOME = "$env:USERPROFILE\.codex-tiangong-wiki"; codex login
18
+ WIKI_AGENT_AUTH_MODE=codex-login
19
+ # Optional. Leave unset to use the current user's default: <home>/.codex-tiangong-wiki
20
+ # WIKI_AGENT_CODEX_HOME=/absolute/path/to/.codex-tiangong-wiki
21
+ #
22
+ # To use an API key instead of Codex login:
23
+ # WIKI_AGENT_AUTH_MODE=api-key
24
+ # WIKI_AGENT_BASE_URL=https://api.openai.com/v1
25
+ # WIKI_AGENT_API_KEY=sk-...
26
+ WIKI_AGENT_MODEL=gpt-5.5
27
+ WIKI_AGENT_BATCH_SIZE=5
28
+ WIKI_AGENT_SANDBOX_MODE=danger-full-access
12
29
 
13
30
  VAULT_SOURCE=local
14
31
  # When VAULT_SOURCE=synology, also set:
@@ -63,7 +63,9 @@ export function registerCheckConfigCommand(program) {
63
63
  embeddingConfigured: embeddingClient !== null,
64
64
  agentEnabled: wikiAgent.enabled,
65
65
  agentConfigured: wikiAgent.configured,
66
+ agentAuthMode: wikiAgent.authMode,
66
67
  agentBaseUrl: wikiAgent.baseUrl ?? "",
68
+ agentCodexHome: wikiAgent.codexHome ?? "",
67
69
  agentModel: wikiAgent.model ?? "",
68
70
  agentBatchSize: wikiAgent.batchSize,
69
71
  agentWorkflowTimeoutSeconds: wikiAgent.workflowTimeoutSeconds,
@@ -27,7 +27,27 @@ function unquoteEnvValue(rawValue) {
27
27
  return value;
28
28
  }
29
29
  if (value.startsWith('"') && value.endsWith('"')) {
30
- return value.slice(1, -1).replace(/\\n/g, "\n").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
30
+ const inner = value.slice(1, -1);
31
+ let output = "";
32
+ for (let index = 0; index < inner.length; index += 1) {
33
+ const current = inner[index];
34
+ if (current !== "\\" || index === inner.length - 1) {
35
+ output += current;
36
+ continue;
37
+ }
38
+ const next = inner[index + 1];
39
+ index += 1;
40
+ if (next === "n") {
41
+ output += "\n";
42
+ }
43
+ else if (next === '"' || next === "\\") {
44
+ output += next;
45
+ }
46
+ else {
47
+ output += `\\${next}`;
48
+ }
49
+ }
50
+ return output;
31
51
  }
32
52
  if (value.startsWith("'") && value.endsWith("'")) {
33
53
  return value.slice(1, -1);
@@ -1,28 +1,89 @@
1
1
  import path from "node:path";
2
- import { Codex } from "@openai/codex-sdk";
2
+ import { mkdirSync } from "node:fs";
3
+ import childProcess from "node:child_process";
4
+ import { syncBuiltinESMExports } from "node:module";
3
5
  import { readWorkflowResult } from "./workflow-result.js";
4
6
  import { resolveAgentSettings } from "./paths.js";
5
7
  import { readTextFileSync, writeTextFileSync } from "../utils/fs.js";
6
8
  import { AppError } from "../utils/errors.js";
7
9
  export const CODEX_WORKFLOW_VERSION = "2026-04-07";
10
+ const hiddenWindowsSpawnPatch = Symbol.for("tiangong-wiki.hiddenWindowsSpawnPatch");
11
+ function isSpawnOptions(value) {
12
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
13
+ }
14
+ function addHiddenWindowDefault(options) {
15
+ if (!isSpawnOptions(options)) {
16
+ return { windowsHide: true };
17
+ }
18
+ return { ...options, windowsHide: options.windowsHide ?? true };
19
+ }
20
+ export function createHiddenWindowsSpawn(spawnFn) {
21
+ return ((command, argsOrOptions, options) => {
22
+ if (Array.isArray(argsOrOptions)) {
23
+ return spawnFn(command, argsOrOptions, addHiddenWindowDefault(options));
24
+ }
25
+ if (isSpawnOptions(argsOrOptions) && options === undefined) {
26
+ return spawnFn(command, addHiddenWindowDefault(argsOrOptions));
27
+ }
28
+ if (argsOrOptions === undefined && options === undefined) {
29
+ return spawnFn(command, addHiddenWindowDefault(undefined));
30
+ }
31
+ return spawnFn(command, argsOrOptions, addHiddenWindowDefault(options));
32
+ });
33
+ }
34
+ function installHiddenWindowsSpawnPatch(platform = process.platform) {
35
+ if (platform !== "win32") {
36
+ return;
37
+ }
38
+ const currentSpawn = childProcess.spawn;
39
+ if (currentSpawn[hiddenWindowsSpawnPatch]) {
40
+ return;
41
+ }
42
+ const patchedSpawn = createHiddenWindowsSpawn(childProcess.spawn);
43
+ patchedSpawn[hiddenWindowsSpawnPatch] = true;
44
+ childProcess.spawn = patchedSpawn;
45
+ syncBuiltinESMExports();
46
+ }
47
+ let codexSdkModulePromise = null;
48
+ async function loadCodexSdk() {
49
+ // The SDK captures child_process.spawn during module import and currently
50
+ // does not expose windowsHide; install the Windows default before loading it.
51
+ installHiddenWindowsSpawnPatch();
52
+ codexSdkModulePromise ??= import("@openai/codex-sdk");
53
+ return codexSdkModulePromise;
54
+ }
8
55
  function normalizeEnv(input) {
56
+ const agentSettings = resolveAgentSettings(input.env);
57
+ if (agentSettings.authMode === "codex-login" && agentSettings.codexHome) {
58
+ mkdirSync(agentSettings.codexHome, { recursive: true });
59
+ }
9
60
  const normalized = {};
10
61
  for (const [key, value] of Object.entries({
11
62
  ...process.env,
12
63
  ...input.env,
13
- ...(input.env?.WIKI_AGENT_API_KEY && !input.env.OPENAI_API_KEY ? { OPENAI_API_KEY: input.env.WIKI_AGENT_API_KEY } : {}),
64
+ ...(agentSettings.authMode === "api-key" && agentSettings.apiKey && !input.env?.OPENAI_API_KEY
65
+ ? { OPENAI_API_KEY: agentSettings.apiKey }
66
+ : {}),
67
+ ...(agentSettings.authMode === "codex-login" && agentSettings.codexHome
68
+ ? { CODEX_HOME: agentSettings.codexHome }
69
+ : {}),
14
70
  })) {
15
71
  if (typeof value === "string") {
16
72
  normalized[key] = value;
17
73
  }
18
74
  }
75
+ if (agentSettings.authMode === "codex-login") {
76
+ delete normalized.OPENAI_API_KEY;
77
+ delete normalized.CODEX_API_KEY;
78
+ }
19
79
  normalized.PATH = [input.skillArtifactsPath, normalized.PATH].filter(Boolean).join(path.delimiter);
20
80
  return normalized;
21
81
  }
22
- function createCodexClient(input) {
82
+ async function createCodexClient(input) {
83
+ const agentSettings = resolveAgentSettings(input.env);
23
84
  const env = normalizeEnv(input);
24
- const baseUrl = input.env?.WIKI_AGENT_BASE_URL?.trim();
25
- const apiKey = input.env?.WIKI_AGENT_API_KEY?.trim();
85
+ const baseUrl = agentSettings.authMode === "api-key" ? agentSettings.baseUrl : null;
86
+ const apiKey = agentSettings.authMode === "api-key" ? agentSettings.apiKey : null;
26
87
  const options = {
27
88
  apiKey: apiKey || undefined,
28
89
  env,
@@ -43,6 +104,7 @@ function createCodexClient(input) {
43
104
  },
44
105
  };
45
106
  }
107
+ const { Codex } = await loadCodexSdk();
46
108
  return new Codex(options);
47
109
  }
48
110
  function persistWorkflowThreadId(queueItemPath, threadId) {
@@ -117,7 +179,7 @@ export class CodexSdkWorkflowRunner {
117
179
  // The SDK can only continue a thread by sending a new input, so queue retries
118
180
  // must not automatically resume real workflow threads inline.
119
181
  async startWorkflow(input) {
120
- const codex = createCodexClient(input);
182
+ const codex = await createCodexClient(input);
121
183
  const thread = codex.startThread({
122
184
  model: input.model ?? undefined,
123
185
  modelReasoningEffort: "low",
@@ -133,7 +195,7 @@ export class CodexSdkWorkflowRunner {
133
195
  return { threadId, mode: "start" };
134
196
  }
135
197
  async resumeWorkflow(threadId, input) {
136
- const codex = createCodexClient(input);
198
+ const codex = await createCodexClient(input);
137
199
  const thread = codex.resumeThread(threadId, {
138
200
  model: input.model ?? undefined,
139
201
  modelReasoningEffort: "low",
package/dist/core/db.js CHANGED
@@ -97,6 +97,8 @@ function ensureBaseTables(db, embeddingDimensions) {
97
97
  queued_at TEXT NOT NULL,
98
98
  claimed_at TEXT,
99
99
  started_at TEXT,
100
+ heartbeat_at TEXT,
101
+ processing_owner_id TEXT,
100
102
  processed_at TEXT,
101
103
  result_page_id TEXT,
102
104
  error_message TEXT,
@@ -133,6 +135,8 @@ function ensureBaseTables(db, embeddingDimensions) {
133
135
  ensureTableColumns(db, "vault_processing_queue", {
134
136
  claimed_at: "TEXT",
135
137
  started_at: "TEXT",
138
+ heartbeat_at: "TEXT",
139
+ processing_owner_id: "TEXT",
136
140
  thread_id: "TEXT",
137
141
  workflow_version: "TEXT",
138
142
  decision: "TEXT",
@@ -1,5 +1,6 @@
1
1
  import { sha256Text } from "../utils/fs.js";
2
2
  import { AppError } from "../utils/errors.js";
3
+ export const DEFAULT_EMBEDDING_DIMENSIONS = 1536;
3
4
  export class EmbeddingClient {
4
5
  settings;
5
6
  constructor(settings) {
@@ -9,7 +10,7 @@ export class EmbeddingClient {
9
10
  const baseUrl = env.EMBEDDING_BASE_URL ?? env.OPENROUTER_BASE_URL;
10
11
  const apiKey = env.EMBEDDING_API_KEY ?? env.OPENROUTER_API_KEY;
11
12
  const model = env.EMBEDDING_MODEL ?? env.OPENROUTER_EMBEDDING_MODEL;
12
- const rawDimensions = env.EMBEDDING_DIMENSIONS ?? "384";
13
+ const rawDimensions = env.EMBEDDING_DIMENSIONS ?? String(DEFAULT_EMBEDDING_DIMENSIONS);
13
14
  if (!baseUrl || !apiKey || !model) {
14
15
  return null;
15
16
  }
@@ -4,9 +4,9 @@ import { fileURLToPath } from "node:url";
4
4
  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
- import { EmbeddingClient } from "./embedding.js";
7
+ import { DEFAULT_EMBEDDING_DIMENSIONS, EmbeddingClient } from "./embedding.js";
8
8
  import { writeGlobalConfig } from "./global-config.js";
9
- import { parseVaultHashMode, parseWikiAgentSandboxMode, resolveAgentSettings } from "./paths.js";
9
+ import { DEFAULT_WIKI_AGENT_MODEL, defaultWikiAgentCodexHome, parseVaultHashMode, parseWikiAgentAuthMode, parseWikiAgentSandboxMode, resolveAgentSettings, } from "./paths.js";
10
10
  import { loadSynologyConfigFromEnv, normalizeSynologyRemotePath, withSynologyClient } from "./synology.js";
11
11
  import { ensureWikiSkillInstall, formatParserSkills, inspectSkillInstall, installParserSkill, OPTIONAL_PARSER_SKILLS, parseParserSkillSelection, parseParserSkills, resolveWorkspaceRootFromWikiPath, resolveWorkspaceSkillPath, resolveWorkspaceSkillPaths, } from "./workspace-skills.js";
12
12
  import { scaffoldWorkspaceAssets } from "./workspace-bootstrap.js";
@@ -32,8 +32,10 @@ const MANAGED_ENV_KEYS = new Set([
32
32
  "EMBEDDING_MODEL",
33
33
  "EMBEDDING_DIMENSIONS",
34
34
  "WIKI_AGENT_ENABLED",
35
+ "WIKI_AGENT_AUTH_MODE",
35
36
  "WIKI_AGENT_BASE_URL",
36
37
  "WIKI_AGENT_API_KEY",
38
+ "WIKI_AGENT_CODEX_HOME",
37
39
  "WIKI_AGENT_MODEL",
38
40
  "WIKI_AGENT_BATCH_SIZE",
39
41
  "WIKI_AGENT_SANDBOX_MODE",
@@ -86,6 +88,17 @@ function safeAgentSandboxMode(rawValue) {
86
88
  return "danger-full-access";
87
89
  }
88
90
  }
91
+ function safeAgentAuthMode(env) {
92
+ try {
93
+ if (env.WIKI_AGENT_AUTH_MODE && env.WIKI_AGENT_AUTH_MODE.trim()) {
94
+ return parseWikiAgentAuthMode(env.WIKI_AGENT_AUTH_MODE);
95
+ }
96
+ }
97
+ catch {
98
+ return env.WIKI_AGENT_API_KEY?.trim() ? "api-key" : "codex-login";
99
+ }
100
+ return env.WIKI_AGENT_API_KEY?.trim() ? "api-key" : "codex-login";
101
+ }
89
102
  function safeBooleanFlag(rawValue, defaultValue) {
90
103
  if (rawValue === undefined || rawValue.trim().length === 0) {
91
104
  return defaultValue;
@@ -120,6 +133,12 @@ function validateWikiPath(rawValue) {
120
133
  }
121
134
  return null;
122
135
  }
136
+ function validateAbsolutePath(rawValue, label) {
137
+ if (!path.isAbsolute(rawValue.trim())) {
138
+ return `${label} must be an absolute path.`;
139
+ }
140
+ return null;
141
+ }
123
142
  class InquirerPromptDriver {
124
143
  inputStream;
125
144
  outputStream;
@@ -368,6 +387,7 @@ function getPathDefaults(env, cwd) {
368
387
  const configPath = env.WIKI_CONFIG_PATH ? path.resolve(env.WIKI_CONFIG_PATH) : path.join(wikiRoot, "wiki.config.json");
369
388
  const templatesPath = env.WIKI_TEMPLATES_PATH ? path.resolve(env.WIKI_TEMPLATES_PATH) : path.join(wikiRoot, "templates");
370
389
  const defaultHashMode = vaultSource === "synology" ? "mtime" : "content";
390
+ const agentAuthMode = safeAgentAuthMode(env);
371
391
  return {
372
392
  envFilePath: env.WIKI_ENV_FILE ? path.resolve(cwd, env.WIKI_ENV_FILE) : path.join(cwd, DEFAULT_WIKI_ENV_FILE),
373
393
  vaultSource,
@@ -388,11 +408,13 @@ function getPathDefaults(env, cwd) {
388
408
  embeddingBaseUrl: env.EMBEDDING_BASE_URL ?? env.OPENROUTER_BASE_URL ?? "https://api.openai.com/v1",
389
409
  embeddingApiKey: env.EMBEDDING_API_KEY ?? env.OPENROUTER_API_KEY ?? null,
390
410
  embeddingModel: env.EMBEDDING_MODEL ?? env.OPENROUTER_EMBEDDING_MODEL ?? "text-embedding-3-small",
391
- embeddingDimensions: env.EMBEDDING_DIMENSIONS ?? "384",
411
+ embeddingDimensions: env.EMBEDDING_DIMENSIONS ?? String(DEFAULT_EMBEDDING_DIMENSIONS),
392
412
  agentEnabled: (env.WIKI_AGENT_ENABLED ?? "").trim().toLowerCase() === "true",
393
- agentBaseUrl: env.WIKI_AGENT_BASE_URL ?? "https://api.openai.com/v1",
413
+ agentAuthMode,
414
+ agentBaseUrl: env.WIKI_AGENT_BASE_URL ?? (agentAuthMode === "api-key" ? "https://api.openai.com/v1" : null),
394
415
  agentApiKey: env.WIKI_AGENT_API_KEY ?? null,
395
- agentModel: env.WIKI_AGENT_MODEL ?? null,
416
+ agentCodexHome: env.WIKI_AGENT_CODEX_HOME ?? defaultWikiAgentCodexHome(),
417
+ agentModel: env.WIKI_AGENT_MODEL ?? DEFAULT_WIKI_AGENT_MODEL,
396
418
  agentBatchSize: env.WIKI_AGENT_BATCH_SIZE ?? "5",
397
419
  agentSandboxMode: safeAgentSandboxMode(env.WIKI_AGENT_SANDBOX_MODE),
398
420
  parserSkills: parseParserSkills(env.WIKI_PARSER_SKILLS, { strict: false }),
@@ -417,7 +439,7 @@ async function collectEmbeddingSettings(driver, ctx, defaults, env) {
417
439
  required: true,
418
440
  });
419
441
  const embeddingModel = await promptText(driver, "EMBEDDING_MODEL", defaults.embeddingModel ?? "text-embedding-3-small", { required: true });
420
- const embeddingDimensions = await promptText(driver, "EMBEDDING_DIMENSIONS", defaults.embeddingDimensions ?? "384", { validator: (value) => validateNonNegativeInteger(value, "EMBEDDING_DIMENSIONS") });
442
+ const embeddingDimensions = await promptText(driver, "EMBEDDING_DIMENSIONS", defaults.embeddingDimensions ?? String(DEFAULT_EMBEDDING_DIMENSIONS), { validator: (value) => validateNonNegativeInteger(value, "EMBEDDING_DIMENSIONS") });
421
443
  const shouldProbe = await promptYesNo(driver, "Probe the embedding endpoint now?", false);
422
444
  if (shouldProbe) {
423
445
  try {
@@ -457,19 +479,50 @@ async function collectAgentSettings(driver, ctx, defaults) {
457
479
  if (!enabled) {
458
480
  return {
459
481
  agentEnabled: false,
482
+ agentAuthMode: null,
460
483
  agentBaseUrl: null,
461
484
  agentApiKey: null,
485
+ agentCodexHome: null,
462
486
  agentModel: null,
463
487
  agentBatchSize: null,
464
488
  agentSandboxMode: null,
465
489
  };
466
490
  }
467
491
  writeWarning(ctx.output, "Warning: danger-full-access grants full access to the runtime workspace.");
492
+ const agentAuthMode = await driver.select({
493
+ message: "WIKI_AGENT_AUTH_MODE",
494
+ defaultValue: defaults.agentAuthMode ?? "codex-login",
495
+ choices: [
496
+ {
497
+ value: "codex-login",
498
+ label: "codex-login",
499
+ description: "Use a local Codex ChatGPT login stored under WIKI_AGENT_CODEX_HOME.",
500
+ },
501
+ {
502
+ value: "api-key",
503
+ label: "api-key",
504
+ description: "Use WIKI_AGENT_API_KEY and optional WIKI_AGENT_BASE_URL.",
505
+ },
506
+ ],
507
+ });
508
+ const authSettings = agentAuthMode === "api-key"
509
+ ? {
510
+ agentBaseUrl: await promptText(driver, "WIKI_AGENT_BASE_URL", defaults.agentBaseUrl ?? "https://api.openai.com/v1", { validator: (value) => validateUrl(value, "WIKI_AGENT_BASE_URL") }),
511
+ agentApiKey: await promptPassword(driver, "WIKI_AGENT_API_KEY", defaults.agentApiKey ?? "", {
512
+ required: true,
513
+ }),
514
+ agentCodexHome: null,
515
+ }
516
+ : {
517
+ agentBaseUrl: null,
518
+ agentApiKey: null,
519
+ agentCodexHome: await promptText(driver, "WIKI_AGENT_CODEX_HOME", defaults.agentCodexHome ?? defaultWikiAgentCodexHome(), { validator: (value) => validateAbsolutePath(value, "WIKI_AGENT_CODEX_HOME") }),
520
+ };
468
521
  return {
469
522
  agentEnabled: true,
470
- agentBaseUrl: await promptText(driver, "WIKI_AGENT_BASE_URL", defaults.agentBaseUrl ?? "https://api.openai.com/v1", { validator: (value) => validateUrl(value, "WIKI_AGENT_BASE_URL") }),
471
- agentApiKey: await promptPassword(driver, "WIKI_AGENT_API_KEY", defaults.agentApiKey ?? "", { required: true }),
472
- agentModel: await promptText(driver, "WIKI_AGENT_MODEL", defaults.agentModel ?? "", { required: true }),
523
+ agentAuthMode,
524
+ ...authSettings,
525
+ agentModel: await promptText(driver, "WIKI_AGENT_MODEL", defaults.agentModel ?? DEFAULT_WIKI_AGENT_MODEL, { required: true }),
473
526
  agentBatchSize: await promptText(driver, "WIKI_AGENT_BATCH_SIZE", defaults.agentBatchSize ?? "5", { validator: (value) => validateNonNegativeInteger(value, "WIKI_AGENT_BATCH_SIZE") }),
474
527
  agentSandboxMode: await driver.select({
475
528
  message: "WIKI_AGENT_SANDBOX_MODE",
@@ -561,7 +614,13 @@ function buildSetupSummary(values) {
561
614
  lines.push(` EMBEDDING_DIMENSIONS: ${values.embeddingDimensions}`);
562
615
  }
563
616
  if (values.agentEnabled) {
564
- lines.push(` WIKI_AGENT_BASE_URL: ${values.agentBaseUrl}`);
617
+ lines.push(` WIKI_AGENT_AUTH_MODE: ${values.agentAuthMode}`);
618
+ if (values.agentAuthMode === "api-key") {
619
+ lines.push(` WIKI_AGENT_BASE_URL: ${values.agentBaseUrl}`);
620
+ }
621
+ if (values.agentAuthMode === "codex-login") {
622
+ lines.push(` WIKI_AGENT_CODEX_HOME: ${values.agentCodexHome}`);
623
+ }
565
624
  lines.push(` WIKI_AGENT_MODEL: ${values.agentModel}`);
566
625
  lines.push(` WIKI_AGENT_BATCH_SIZE: ${values.agentBatchSize}`);
567
626
  lines.push(` WIKI_AGENT_SANDBOX_MODE: ${values.agentSandboxMode}`);
@@ -598,8 +657,10 @@ function writeSetupEnvFile(values) {
598
657
  ["EMBEDDING_MODEL", values.embeddingEnabled ? values.embeddingModel : null],
599
658
  ["EMBEDDING_DIMENSIONS", values.embeddingEnabled ? values.embeddingDimensions : null],
600
659
  ["WIKI_AGENT_ENABLED", values.agentEnabled ? "true" : "false"],
601
- ["WIKI_AGENT_BASE_URL", values.agentEnabled ? values.agentBaseUrl : null],
602
- ["WIKI_AGENT_API_KEY", values.agentEnabled ? values.agentApiKey : null],
660
+ ["WIKI_AGENT_AUTH_MODE", values.agentEnabled ? values.agentAuthMode : null],
661
+ ["WIKI_AGENT_BASE_URL", values.agentEnabled && values.agentAuthMode === "api-key" ? values.agentBaseUrl : null],
662
+ ["WIKI_AGENT_API_KEY", values.agentEnabled && values.agentAuthMode === "api-key" ? values.agentApiKey : null],
663
+ ["WIKI_AGENT_CODEX_HOME", values.agentEnabled && values.agentAuthMode === "codex-login" ? values.agentCodexHome : null],
603
664
  ["WIKI_AGENT_MODEL", values.agentEnabled ? values.agentModel : null],
604
665
  ["WIKI_AGENT_BATCH_SIZE", values.agentEnabled ? values.agentBatchSize : null],
605
666
  ["WIKI_AGENT_SANDBOX_MODE", values.agentEnabled ? values.agentSandboxMode : null],
@@ -908,7 +969,9 @@ function inspectAgent(checks, env) {
908
969
  collectDoctorCheck(checks, "error", "agent", `Automatic vault processing is enabled but missing: ${settings.missing.join(", ")}`, "Set the missing WIKI_AGENT_* values in `.wiki.env` or rerun `tiangong-wiki setup`.");
909
970
  return;
910
971
  }
911
- collectDoctorCheck(checks, "ok", "agent", `Automatic vault processing is enabled with model ${settings.model}.`);
972
+ collectDoctorCheck(checks, "ok", "agent", settings.authMode === "codex-login"
973
+ ? `Automatic vault processing is enabled with model ${settings.model} via Codex login at ${settings.codexHome}.`
974
+ : `Automatic vault processing is enabled with model ${settings.model} via API key auth.`);
912
975
  }
913
976
  catch (error) {
914
977
  const message = error instanceof Error ? error.message : String(error);
@@ -1,8 +1,10 @@
1
1
  import { fileURLToPath } from "node:url";
2
+ import os from "node:os";
2
3
  import path from "node:path";
3
4
  import { AppError } from "../utils/errors.js";
4
5
  const TRUE_VALUES = new Set(["1", "true", "yes", "on"]);
5
6
  const FALSE_VALUES = new Set(["0", "false", "no", "off"]);
7
+ export const DEFAULT_WIKI_AGENT_MODEL = "gpt-5.5";
6
8
  function parseBooleanFlag(label, rawValue, defaultValue) {
7
9
  if (rawValue === undefined) {
8
10
  return defaultValue;
@@ -75,6 +77,19 @@ function requireAbsolutePath(label, rawValue) {
75
77
  }
76
78
  return path.resolve(rawValue);
77
79
  }
80
+ function normalizeOptionalAbsolutePath(label, rawValue) {
81
+ const value = rawValue?.trim();
82
+ if (!value) {
83
+ return null;
84
+ }
85
+ if (!path.isAbsolute(value)) {
86
+ throw new AppError(`${label} must be an absolute path: ${rawValue}`, "config");
87
+ }
88
+ return path.resolve(value);
89
+ }
90
+ export function defaultWikiAgentCodexHome() {
91
+ return path.join(os.homedir(), ".codex-tiangong-wiki");
92
+ }
78
93
  export function parseVaultHashMode(raw) {
79
94
  const value = (raw ?? "content").trim().toLowerCase();
80
95
  if (value === "content" || value === "mtime") {
@@ -89,6 +104,13 @@ export function parseWikiAgentBackend(raw) {
89
104
  }
90
105
  throw new AppError(`WIKI_AGENT_BACKEND must be "codex-workflow", got ${raw}`, "config");
91
106
  }
107
+ export function parseWikiAgentAuthMode(raw) {
108
+ const value = (raw ?? "api-key").trim().toLowerCase();
109
+ if (value === "api-key" || value === "codex-login") {
110
+ return value;
111
+ }
112
+ throw new AppError(`WIKI_AGENT_AUTH_MODE must be "api-key" or "codex-login", got ${raw}`, "config");
113
+ }
92
114
  export function parseWikiAgentSandboxMode(raw) {
93
115
  const value = (raw ?? "danger-full-access").trim().toLowerCase();
94
116
  if (value === "danger-full-access" || value === "workspace-write") {
@@ -98,28 +120,33 @@ export function parseWikiAgentSandboxMode(raw) {
98
120
  }
99
121
  export function resolveAgentSettings(env = process.env, options = {}) {
100
122
  const enabled = parseBooleanFlag("WIKI_AGENT_ENABLED", env.WIKI_AGENT_ENABLED, false);
101
- const baseUrl = normalizeOptionalUrl(env.WIKI_AGENT_BASE_URL);
102
- const apiKey = env.WIKI_AGENT_API_KEY?.trim() || null;
103
- const model = env.WIKI_AGENT_MODEL?.trim() || null;
123
+ const authMode = parseWikiAgentAuthMode(env.WIKI_AGENT_AUTH_MODE);
124
+ const rawBaseUrl = normalizeOptionalUrl(env.WIKI_AGENT_BASE_URL);
125
+ const baseUrl = authMode === "api-key" ? rawBaseUrl : null;
126
+ const rawApiKey = env.WIKI_AGENT_API_KEY?.trim() || null;
127
+ const apiKey = authMode === "api-key" ? rawApiKey : null;
128
+ const codexHome = authMode === "codex-login"
129
+ ? normalizeOptionalAbsolutePath("WIKI_AGENT_CODEX_HOME", env.WIKI_AGENT_CODEX_HOME) ?? defaultWikiAgentCodexHome()
130
+ : null;
131
+ const model = env.WIKI_AGENT_MODEL?.trim() || DEFAULT_WIKI_AGENT_MODEL;
104
132
  const batchSize = parseNonNegativeInteger(env.WIKI_AGENT_BATCH_SIZE, 5, "WIKI_AGENT_BATCH_SIZE");
105
133
  const sandboxMode = parseWikiAgentSandboxMode(env.WIKI_AGENT_SANDBOX_MODE);
106
134
  const workflowTimeoutSeconds = parsePositiveInteger(env.WIKI_WORKFLOW_TIMEOUT, 600, "WIKI_WORKFLOW_TIMEOUT");
107
135
  const missing = [];
108
136
  if (enabled) {
109
- if (!apiKey) {
137
+ if (authMode === "api-key" && !apiKey) {
110
138
  missing.push("WIKI_AGENT_API_KEY");
111
139
  }
112
- if (!model) {
113
- missing.push("WIKI_AGENT_MODEL");
114
- }
115
140
  }
116
141
  if (options.strict && enabled && missing.length > 0) {
117
142
  throw new AppError(`WIKI_AGENT_ENABLED=true but missing required settings: ${missing.join(", ")}`, "config");
118
143
  }
119
144
  return {
120
145
  enabled,
146
+ authMode,
121
147
  baseUrl,
122
148
  apiKey,
149
+ codexHome,
123
150
  model,
124
151
  batchSize,
125
152
  sandboxMode,
@@ -1,11 +1,11 @@
1
1
  import { loadConfig } from "./config.js";
2
2
  import { openDb } from "./db.js";
3
- import { EmbeddingClient } from "./embedding.js";
3
+ import { DEFAULT_EMBEDDING_DIMENSIONS, EmbeddingClient } from "./embedding.js";
4
4
  import { resolveRuntimePaths } from "./paths.js";
5
5
  export function getEmbeddingDimensionFromEnv(env = process.env) {
6
- const raw = env.EMBEDDING_DIMENSIONS ?? "384";
6
+ const raw = env.EMBEDDING_DIMENSIONS ?? String(DEFAULT_EMBEDDING_DIMENSIONS);
7
7
  const value = Number.parseInt(raw, 10);
8
- return Number.isFinite(value) && value > 0 ? value : 384;
8
+ return Number.isFinite(value) && value > 0 ? value : DEFAULT_EMBEDDING_DIMENSIONS;
9
9
  }
10
10
  export function loadRuntimeConfig(env = process.env) {
11
11
  const paths = resolveRuntimePaths(env);
@@ -23,21 +23,25 @@ function getExistingFtsSql(db) {
23
23
  .get();
24
24
  return row?.sql ?? null;
25
25
  }
26
- function resolveBundledSimpleExtensionPath(packageRoot) {
27
- const byArch = SIMPLE_ASSET_MAP[process.platform];
26
+ export function resolveBundledSimpleExtensionRelativePath(platform = process.platform, arch = process.arch) {
27
+ const byArch = SIMPLE_ASSET_MAP[platform];
28
28
  if (!byArch) {
29
- throw new AppError(`Bundled simple extension is not available for platform ${process.platform}-${process.arch}.`, "config", {
30
- platform: process.platform,
31
- arch: process.arch,
29
+ throw new AppError(`Bundled simple extension is not available for platform ${platform}-${arch}.`, "config", {
30
+ platform,
31
+ arch,
32
32
  });
33
33
  }
34
- const relativePath = byArch[process.arch];
34
+ const relativePath = byArch[arch];
35
35
  if (!relativePath) {
36
- throw new AppError(`Bundled simple extension is not available for platform ${process.platform}-${process.arch}.`, "config", {
37
- platform: process.platform,
38
- arch: process.arch,
36
+ throw new AppError(`Bundled simple extension is not available for platform ${platform}-${arch}.`, "config", {
37
+ platform,
38
+ arch,
39
39
  });
40
40
  }
41
+ return relativePath;
42
+ }
43
+ function resolveBundledSimpleExtensionPath(packageRoot) {
44
+ const relativePath = resolveBundledSimpleExtensionRelativePath();
41
45
  const extensionPath = path.join(packageRoot, relativePath);
42
46
  if (!pathExistsSync(extensionPath)) {
43
47
  throw new AppError(`Bundled simple extension not found: ${extensionPath}`, "runtime", {
package/dist/core/sync.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { loadConfig } from "./config.js";
2
2
  import { clearAllIndexedData, getMeta, openDb, resetVectorTable, setMetaValues, } from "./db.js";
3
- import { EmbeddingClient } from "./embedding.js";
3
+ import { DEFAULT_EMBEDDING_DIMENSIONS, EmbeddingClient } from "./embedding.js";
4
4
  import { applyChanges, scanPages, scanSpecificPages } from "./indexer.js";
5
5
  import { resolveRuntimePaths } from "./paths.js";
6
6
  import { collectVaultFiles, syncVaultIndex } from "./vault.js";
@@ -8,9 +8,9 @@ import { AppError } from "../utils/errors.js";
8
8
  import { pathExistsSync } from "../utils/fs.js";
9
9
  import { makeSyncId, toOffsetIso } from "../utils/time.js";
10
10
  function getEmbeddingDimension(env) {
11
- const raw = env.EMBEDDING_DIMENSIONS ?? "384";
11
+ const raw = env.EMBEDDING_DIMENSIONS ?? String(DEFAULT_EMBEDDING_DIMENSIONS);
12
12
  const value = Number.parseInt(raw, 10);
13
- return Number.isFinite(value) && value > 0 ? value : 384;
13
+ return Number.isFinite(value) && value > 0 ? value : DEFAULT_EMBEDDING_DIMENSIONS;
14
14
  }
15
15
  function getEmbeddingTargets(db, embedAll, insertedIds, summaryChangedIds) {
16
16
  const rows = db.prepare("SELECT rowid, id, summary_text AS summaryText, embedding_status AS embeddingStatus FROM pages").all();