total-recall-brain 3.41.2 → 3.41.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/bin/total-recall.mjs +19 -4
  2. package/frontend/dist/assets/{index-BG9NkVUy.css → index-BIQmqDKP.css} +1 -1
  3. package/frontend/dist/assets/{index-BtuiZqwP.js → index-CRzjQLu7.js} +7 -7
  4. package/frontend/dist/index.html +2 -2
  5. package/frontend/dist/sw.js +8 -5
  6. package/frontend/src/api/auth.ts +0 -3
  7. package/frontend/src/components/plugins/PluginPanel.tsx +7 -3
  8. package/frontend/src/components/plugins/card/CardEngine.tsx +1 -1
  9. package/frontend/src/pages/LoginPage.tsx +1 -41
  10. package/frontend/src/pages/PluginsPage.tsx +14 -7
  11. package/package.json +7 -5
  12. package/scaffold/.agent/skills/plugin/scripts/record.mjs +6 -3
  13. package/scaffold/.agent/skills/total-recall/plugins/auth/cli.mjs +197 -31
  14. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +6 -6
  15. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -1
  16. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +8 -1
  17. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +48 -0
  18. package/scaffold/.agent/skills/total-recall/plugins/auth/src/login-projections.mjs +42 -0
  19. package/scaffold/.agent/skills/total-recall/plugins/auth/src/mesh-secrets.mjs +18 -0
  20. package/scaffold/.agent/skills/total-recall/plugins/auth/src/routes/secrets.mjs +5 -2
  21. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-carriers.mjs +76 -0
  22. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-prune.mjs +37 -0
  23. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-sync.mjs +53 -17
  24. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/DESIGN.md +56 -8
  25. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/card.js +1068 -204
  26. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/panel.js +4 -2
  27. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/DESIGN.md +32 -0
  28. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +436 -274
  29. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +12 -12
  30. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +1 -1
  31. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/DESIGN.md +16 -2
  32. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/card.js +461 -242
  33. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +12 -12
  34. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
  35. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -7
  36. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/DESIGN.md +17 -2
  37. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/card.js +426 -300
  38. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +23 -8
  39. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +6 -2
  40. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/DESIGN.md +32 -0
  41. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/card.js +350 -283
  42. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +23 -8
  43. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +5 -1
  44. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/card.js +31 -19
  45. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +23 -10
  46. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +5 -3
  47. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
  48. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/grants.mjs +7 -3
  49. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +61 -38
  50. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/leader-election.mjs +57 -2
  51. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +220 -36
  52. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/server/routes/mesh.mjs +7 -2
  53. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +65 -0
  54. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +59 -0
  55. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +534 -0
  56. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +139 -0
  57. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +2849 -0
  58. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +26 -0
  59. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +376 -0
  60. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +37 -0
  61. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adapters/dsh.mjs +446 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +23 -0
  63. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +398 -0
  64. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +152 -0
  65. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +82 -0
  66. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +90 -0
  67. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +415 -0
  68. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +36 -0
  69. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +35 -0
  70. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/tracker.mjs +133 -0
  71. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +233 -0
  72. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +32 -0
  73. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +845 -0
  74. package/scaffold/.agent/skills/total-recall/plugins/openwiki/capability/BRICK.md +64 -0
  75. package/scaffold/.agent/skills/total-recall/plugins/openwiki/capability/index.mjs +250 -0
  76. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +344 -14
  77. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +2917 -140
  78. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +9 -3
  79. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +223 -5
  80. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/config.mjs +129 -0
  81. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +355 -0
  82. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/upstream-watcher.mjs +375 -0
  83. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +37 -10
  84. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +986 -280
  85. package/scaffold/.agent/skills/total-recall/plugins/security/cli.mjs +7 -1
  86. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +25 -10
  87. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +6 -2
  88. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
  89. package/scaffold/.agent/skills/total-recall/plugins/security/src/scanner.mjs +17 -3
  90. package/scaffold/.agent/skills/total-recall/plugins/security/ui/DESIGN.md +38 -15
  91. package/scaffold/.agent/skills/total-recall/plugins/security/ui/card.js +60 -34
  92. package/src/cli/command.mjs +23 -18
  93. package/src/cli/config.mjs +3 -0
  94. package/src/cli/connect.mjs +12 -116
  95. package/src/cli/daemon.mjs +23 -1
  96. package/src/cli/harness.mjs +0 -1
  97. package/src/cli/plugin/copies.mjs +8 -5
  98. package/src/cli/plugin/create.mjs +56 -3
  99. package/src/cli/plugin/index.mjs +40 -5
  100. package/src/cli/plugin/monetization.mjs +232 -12
  101. package/src/cli/preflight.mjs +161 -0
  102. package/src/cli/rebuild.mjs +22 -0
  103. package/src/cli/reset-password.mjs +3 -2
  104. package/src/cli/skill.mjs +8 -2
  105. package/src/cli/status.mjs +25 -7
  106. package/src/core/automatic-brain-updates.mjs +9 -3
  107. package/src/core/brain-updates.mjs +63 -5
  108. package/src/core/cli-commands.mjs +2 -0
  109. package/src/core/context-compiler.mjs +8 -1
  110. package/src/core/default-plugins.mjs +1 -1
  111. package/src/core/global-brain-updates.mjs +21 -1
  112. package/src/core/mesh-brain-updates.mjs +5 -2
  113. package/src/core/meta-harness.mjs +32 -79
  114. package/src/core/plugin-dependencies.mjs +6 -1
  115. package/src/core/plugin-runner.mjs +6 -1
  116. package/src/core/plugin-store.mjs +67 -1
  117. package/src/core/plugin-updates.mjs +1 -1
  118. package/src/core/portable-skills-maintenance.mjs +19 -0
  119. package/src/core/preflight-gate.mjs +77 -0
  120. package/src/core/preflight-receipt.mjs +123 -0
  121. package/src/core/required-plugins.mjs +7 -0
  122. package/src/core/runtime.mjs +0 -3
  123. package/src/core/skills-registry.mjs +33 -3
  124. package/src/core/surface.mjs +38 -1
  125. package/src/server/auth.mjs +41 -1
  126. package/src/server/memory-app.mjs +2 -1
  127. package/src/server/routes/auth.mjs +25 -1
  128. package/src/server/routes/plugins.mjs +17 -4
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "tr-plugin-openwiki",
3
- "version": "0.1.2",
3
+ "version": "0.1.7",
4
4
  "type": "module",
5
5
  "description": "Multi-repo knowledge graph and documentation wiki engine for Total Recall",
6
6
  "scripts": {
7
- "test": "node --test test/*.test.mjs"
7
+ "test": "node --test test/*.test.mjs",
8
+ "ssss": "ssss"
8
9
  },
9
10
  "license": "MIT",
10
11
  "files": [
@@ -14,6 +15,11 @@
14
15
  "LICENSE",
15
16
  "ui/",
16
17
  "src/",
18
+ "capability/",
17
19
  "templates/"
18
- ]
20
+ ],
21
+ "dependencies": {
22
+ "@gregiteen/ssss-cli": "0.14.3",
23
+ "openwiki": "^0.7.3"
24
+ }
19
25
  }
@@ -2,15 +2,34 @@
2
2
  "$schema": "https://github.com/gregiteen/total-recall/blob/main/metadata.plugin.schema.json",
3
3
  "id": "openwiki",
4
4
  "name": "OpenWiki",
5
- "version": "0.1.2",
5
+ "version": "0.1.7",
6
6
  "description": "Multi-repo knowledge graph and documentation wiki engine for Total Recall.",
7
7
  "license": "MIT",
8
+ "tasks": [
9
+ {
10
+ "intent": "Watch upstream OpenWiki repository and announce/propagate updates",
11
+ "schedule": "0 */4 * * *",
12
+ "command": "watch"
13
+ }
14
+ ],
8
15
  "branding": {
9
16
  "name": "OpenWiki",
10
17
  "color": "#b45309",
11
18
  "badge": "Knowledge Graph",
12
19
  "font": "newsreader"
13
20
  },
21
+ "secrets": [
22
+ {
23
+ "key": "OPENROUTER_API_KEY",
24
+ "description": "Platform OpenRouter API key for cheap model documentation generation",
25
+ "required": false
26
+ },
27
+ {
28
+ "key": "GOOGLE_API_KEY",
29
+ "description": "Platform Google Gemini API key for direct Gemini model documentation generation",
30
+ "required": false
31
+ }
32
+ ],
14
33
  "card_face": {
15
34
  "headline": "OpenWiki Control Deck",
16
35
  "quick_actions": [
@@ -70,18 +89,64 @@
70
89
  {
71
90
  "name": "status",
72
91
  "description": "Show which OpenWiki subcommands are implemented",
73
- "effect": "read"
92
+ "effect": "read",
93
+ "output": "json",
94
+ "args": [
95
+ {
96
+ "name": "json",
97
+ "flag": "--json",
98
+ "label": "JSON output",
99
+ "type": "boolean"
100
+ }
101
+ ]
74
102
  },
75
103
  {
76
104
  "name": "hubs",
77
105
  "description": "List indexed OpenWiki hubs and page counts",
78
- "effect": "read"
106
+ "effect": "read",
107
+ "output": "json",
108
+ "args": [
109
+ {
110
+ "name": "json",
111
+ "flag": "--json",
112
+ "label": "JSON output",
113
+ "type": "boolean"
114
+ }
115
+ ]
79
116
  },
80
117
  {
81
118
  "name": "ingest",
82
119
  "description": "Ingest markdown documentation into the semantic knowledge graph",
83
120
  "timeout_ms": 600000,
84
- "effect": "write"
121
+ "effect": "write",
122
+ "output": "json",
123
+ "args": [
124
+ {
125
+ "name": "path",
126
+ "label": "OpenWiki path",
127
+ "type": "string",
128
+ "required": true,
129
+ "positional": true
130
+ },
131
+ {
132
+ "name": "dryRun",
133
+ "flag": "--dry-run",
134
+ "label": "Preview without writing",
135
+ "type": "boolean"
136
+ },
137
+ {
138
+ "name": "global",
139
+ "flag": "--global",
140
+ "label": "Target global brain",
141
+ "type": "boolean"
142
+ },
143
+ {
144
+ "name": "project",
145
+ "flag": "--project",
146
+ "label": "Target project brain",
147
+ "type": "boolean"
148
+ }
149
+ ]
85
150
  },
86
151
  {
87
152
  "name": "bootstrap",
@@ -119,7 +184,146 @@
119
184
  {
120
185
  "name": "probe",
121
186
  "description": "Run HTTP 200 health verification probe",
122
- "effect": "read"
187
+ "effect": "read",
188
+ "output": "json",
189
+ "args": [
190
+ {
191
+ "name": "json",
192
+ "flag": "--json",
193
+ "label": "JSON output",
194
+ "type": "boolean"
195
+ }
196
+ ]
197
+ },
198
+ {
199
+ "name": "refresh",
200
+ "description": "Report which registered projects' generated OpenWiki pages are stale; --apply regenerates them (openwiki/ only)",
201
+ "timeout_ms": 600000,
202
+ "effect": "write",
203
+ "output": "json",
204
+ "args": [
205
+ {
206
+ "name": "repo",
207
+ "flag": "--repo",
208
+ "label": "Target repository",
209
+ "type": "string"
210
+ },
211
+ {
212
+ "name": "apply",
213
+ "flag": "--apply",
214
+ "label": "Regenerate stale pages",
215
+ "type": "boolean"
216
+ },
217
+ {
218
+ "name": "force",
219
+ "flag": "--force",
220
+ "label": "Force regeneration",
221
+ "type": "boolean"
222
+ },
223
+ {
224
+ "name": "json",
225
+ "flag": "--json",
226
+ "label": "JSON output",
227
+ "type": "boolean"
228
+ }
229
+ ]
230
+ },
231
+ {
232
+ "name": "watch",
233
+ "description": "Watch upstream OpenWiki repository, announce new releases, and trigger propagation",
234
+ "effect": "write",
235
+ "output": "json",
236
+ "args": [
237
+ {
238
+ "name": "checkOnly",
239
+ "flag": "--check-only",
240
+ "label": "Check without announcing or propagating",
241
+ "type": "boolean"
242
+ },
243
+ {
244
+ "name": "announce",
245
+ "flag": "--announce",
246
+ "label": "Dispatch operator announcement",
247
+ "type": "boolean"
248
+ },
249
+ {
250
+ "name": "propagate",
251
+ "flag": "--propagate",
252
+ "label": "Execute fleet propagation",
253
+ "type": "boolean"
254
+ },
255
+ {
256
+ "name": "json",
257
+ "flag": "--json",
258
+ "label": "JSON output",
259
+ "type": "boolean"
260
+ }
261
+ ]
262
+ },
263
+ {
264
+ "name": "propagate",
265
+ "description": "Propagate updated OpenWiki generator across registered user repositories",
266
+ "effect": "write",
267
+ "output": "json",
268
+ "timeout_ms": 600000,
269
+ "args": [
270
+ {
271
+ "name": "repo",
272
+ "flag": "--repo",
273
+ "label": "Target repository",
274
+ "type": "string"
275
+ },
276
+ {
277
+ "name": "force",
278
+ "flag": "--force",
279
+ "label": "Force regeneration",
280
+ "type": "boolean"
281
+ },
282
+ {
283
+ "name": "dryRun",
284
+ "flag": "--dry-run",
285
+ "label": "Preview without writing",
286
+ "type": "boolean"
287
+ },
288
+ {
289
+ "name": "json",
290
+ "flag": "--json",
291
+ "label": "JSON output",
292
+ "type": "boolean"
293
+ }
294
+ ]
295
+ },
296
+ {
297
+ "name": "config",
298
+ "description": "Configure OpenWiki generator model routing, token bounds, and timeout",
299
+ "effect": "write",
300
+ "output": "json",
301
+ "args": [
302
+ {
303
+ "name": "action",
304
+ "label": "Action verb (show, get, set, reset)",
305
+ "type": "string",
306
+ "positional": true
307
+ },
308
+ {
309
+ "name": "key",
310
+ "label": "Configuration key",
311
+ "type": "string",
312
+ "positional": true
313
+ },
314
+ {
315
+ "name": "value",
316
+ "label": "Configuration value",
317
+ "type": "string",
318
+ "positional": true
319
+ },
320
+ {
321
+ "name": "json",
322
+ "flag": "--json",
323
+ "label": "JSON output",
324
+ "type": "boolean"
325
+ }
326
+ ]
123
327
  }
124
328
  ]
125
329
  },
@@ -161,6 +365,20 @@
161
365
  ],
162
366
  "session_verification": true
163
367
  },
368
+ "capability": {
369
+ "entry": "capability/index.mjs",
370
+ "description": "Standalone documentation hub discovery, wiki page auditing, and knowledge structure bootstrapping",
371
+ "dependencies": [],
372
+ "exports": [
373
+ "discoverWikiHubs",
374
+ "parseWikiHub",
375
+ "inspectWikiStaleness",
376
+ "bootstrapWikiPages",
377
+ "compareSemanticVersions",
378
+ "createPropagationPlan",
379
+ "formatPropagationReceipt"
380
+ ]
381
+ },
164
382
  "status": {
165
383
  "command": "status",
166
384
  "refresh_seconds": 300
@@ -0,0 +1,129 @@
1
+ /**
2
+ * OpenWiki Configuration Management
3
+ * Handles persistent settings for documentation generation, provider selection, and cheap model routing.
4
+ */
5
+ import fs from 'node:fs';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+
9
+ export const DEFAULT_OPENWIKI_CONFIG = Object.freeze({
10
+ provider: 'openrouter',
11
+ model: 'deepseek/deepseek-v4.1-flash',
12
+ fallback_model: 'qwen/qwen-2.5-coder-32b-instruct',
13
+ max_tokens: 4096,
14
+ timeout_ms: 300000
15
+ });
16
+
17
+ export const ALLOWED_CONFIG_KEYS = Object.freeze([
18
+ 'provider',
19
+ 'model',
20
+ 'fallback_model',
21
+ 'max_tokens',
22
+ 'timeout_ms'
23
+ ]);
24
+
25
+ export const ALLOWED_PROVIDERS = Object.freeze([
26
+ 'openrouter',
27
+ 'google',
28
+ 'custom'
29
+ ]);
30
+
31
+ /** Resolve the active configuration file path. */
32
+ export function resolveConfigPath(options = {}) {
33
+ if (options.configPath) return options.configPath;
34
+ if (process.env.OPENWIKI_CONFIG_PATH) return process.env.OPENWIKI_CONFIG_PATH;
35
+ const home = options.home || os.homedir();
36
+ return path.join(home, '.agent', 'skills', 'total-recall', 'config', 'openwiki.json');
37
+ }
38
+
39
+ /** Load current OpenWiki configuration merged with defaults. */
40
+ export function loadOpenWikiConfig(options = {}) {
41
+ const filePath = resolveConfigPath(options);
42
+ try {
43
+ if (!fs.existsSync(filePath)) {
44
+ return { ...DEFAULT_OPENWIKI_CONFIG };
45
+ }
46
+ const raw = fs.readFileSync(filePath, 'utf8');
47
+ const parsed = JSON.parse(raw);
48
+ if (!parsed || typeof parsed !== 'object') {
49
+ return { ...DEFAULT_OPENWIKI_CONFIG };
50
+ }
51
+ return {
52
+ provider: typeof parsed.provider === 'string' && parsed.provider ? parsed.provider : DEFAULT_OPENWIKI_CONFIG.provider,
53
+ model: typeof parsed.model === 'string' && parsed.model ? parsed.model : DEFAULT_OPENWIKI_CONFIG.model,
54
+ fallback_model: typeof parsed.fallback_model === 'string' && parsed.fallback_model ? parsed.fallback_model : DEFAULT_OPENWIKI_CONFIG.fallback_model,
55
+ max_tokens: Number.isInteger(parsed.max_tokens) && parsed.max_tokens >= 512 && parsed.max_tokens <= 131072
56
+ ? parsed.max_tokens
57
+ : DEFAULT_OPENWIKI_CONFIG.max_tokens,
58
+ timeout_ms: Number.isInteger(parsed.timeout_ms) && parsed.timeout_ms >= 30000 && parsed.timeout_ms <= 1800000
59
+ ? parsed.timeout_ms
60
+ : DEFAULT_OPENWIKI_CONFIG.timeout_ms
61
+ };
62
+ } catch {
63
+ return { ...DEFAULT_OPENWIKI_CONFIG };
64
+ }
65
+ }
66
+
67
+ /** Save configuration atomically to disk. */
68
+ export function saveOpenWikiConfig(config, options = {}) {
69
+ const filePath = resolveConfigPath(options);
70
+ const validated = {
71
+ provider: String(config.provider || DEFAULT_OPENWIKI_CONFIG.provider).toLowerCase(),
72
+ model: String(config.model || DEFAULT_OPENWIKI_CONFIG.model).trim(),
73
+ fallback_model: String(config.fallback_model || DEFAULT_OPENWIKI_CONFIG.fallback_model).trim(),
74
+ max_tokens: Number.isInteger(Number(config.max_tokens)) ? Math.max(512, Math.min(131072, Number(config.max_tokens))) : DEFAULT_OPENWIKI_CONFIG.max_tokens,
75
+ timeout_ms: Number.isInteger(Number(config.timeout_ms)) ? Math.max(30000, Math.min(1800000, Number(config.timeout_ms))) : DEFAULT_OPENWIKI_CONFIG.timeout_ms
76
+ };
77
+
78
+ if (!ALLOWED_PROVIDERS.includes(validated.provider)) {
79
+ throw new Error(`Invalid provider: ${validated.provider}. Allowed: ${ALLOWED_PROVIDERS.join(', ')}`);
80
+ }
81
+ if (!validated.model) {
82
+ throw new Error('Model identifier cannot be empty');
83
+ }
84
+
85
+ const dir = path.dirname(filePath);
86
+ if (!fs.existsSync(dir)) {
87
+ fs.mkdirSync(dir, { recursive: true });
88
+ }
89
+
90
+ const tmpPath = `${filePath}.tmp.${Date.now()}`;
91
+ fs.writeFileSync(tmpPath, `${JSON.stringify(validated, null, 2)}\n`, 'utf8');
92
+ fs.renameSync(tmpPath, filePath);
93
+ return validated;
94
+ }
95
+
96
+ /** Update a single configuration key. */
97
+ export function setOpenWikiConfigValue(key, value, options = {}) {
98
+ if (!ALLOWED_CONFIG_KEYS.includes(key)) {
99
+ throw new Error(`Unknown configuration key: ${key}. Allowed: ${ALLOWED_CONFIG_KEYS.join(', ')}`);
100
+ }
101
+
102
+ const current = loadOpenWikiConfig(options);
103
+ let coercedValue = value;
104
+
105
+ if (key === 'max_tokens' || key === 'timeout_ms') {
106
+ const num = Number(value);
107
+ if (!Number.isInteger(num)) {
108
+ throw new Error(`${key} must be an integer`);
109
+ }
110
+ coercedValue = num;
111
+ } else if (key === 'provider') {
112
+ const p = String(value).toLowerCase().trim();
113
+ if (!ALLOWED_PROVIDERS.includes(p)) {
114
+ throw new Error(`Invalid provider: ${p}. Allowed: ${ALLOWED_PROVIDERS.join(', ')}`);
115
+ }
116
+ coercedValue = p;
117
+ } else {
118
+ coercedValue = String(value).trim();
119
+ if (!coercedValue) throw new Error(`${key} cannot be empty`);
120
+ }
121
+
122
+ current[key] = coercedValue;
123
+ return saveOpenWikiConfig(current, options);
124
+ }
125
+
126
+ /** Reset configuration to defaults. */
127
+ export function resetOpenWikiConfig(options = {}) {
128
+ return saveOpenWikiConfig(DEFAULT_OPENWIKI_CONFIG, options);
129
+ }