@dotdrelle/wiki-manager 0.6.26 → 0.6.30

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
@@ -33,7 +33,7 @@ and a *replaceable* toolbox of *external* MCP servers — to produce the **core
33
33
  wiki** outputs, all driven by an agentic, multi-model orchestrator and grounded
34
34
  in isolated workspaces.
35
35
 
36
- ![wikiLLM functional diagram — inputs, MCP calls and outputs around the agentic orchestrator and workspaces](docs/architecture.svg)
36
+ ![wikiLLM functional diagram — inputs, MCP calls and outputs around the agentic orchestrator and workspaces](https://raw.githubusercontent.com/dotdrelle/llm-wiki-manager/main/docs/architecture.svg)
37
37
 
38
38
  ## Quick start — your first wiki in ~5 minutes
39
39
 
@@ -56,7 +56,7 @@ mkdir -p ~/llm-wiki && cd ~/llm-wiki # all manager state lives here
56
56
  **2 — Set the environment.**
57
57
  Copy the template and keep the defaults — nothing is mandatory for the local
58
58
  demo (tokens/credentials are only needed when you connect real sources, see
59
- [docs/usage.md](docs/usage.md)). The `mcp.endpoints.json` file is created
59
+ [docs/usage.md](https://raw.githubusercontent.com/dotdrelle/llm-wiki-manager/main/docs/usage.md)). The `mcp.endpoints.json` file is created
60
60
  automatically on the first command.
61
61
 
62
62
  ```bash
@@ -137,20 +137,10 @@ wiki-workspace wiki demo build # or, in the shell: /skills run pipeli
137
137
  ```
138
138
 
139
139
  That's the whole loop. Next: the four ways to use it and how to configure the
140
- external agents (CME & co.) live in [docs/usage.md](docs/usage.md); the detailed
140
+ external agents (CME & co.) live in [docs/usage.md](https://raw.githubusercontent.com/dotdrelle/llm-wiki-manager/main/docs/usage.md); the detailed
141
141
  story is in [The journey](#the-journey-from-first-launch-to-first-result); and
142
142
  installing from source is in [Initial Setup](#initial-setup).
143
143
 
144
- ## The 4 ways to use it & agent configuration
145
-
146
- The same system has four faces — the **web interface** (explore with the mouse),
147
- **scripting** (let it run on its own), the **`donna` shell** (talk in plain
148
- language), and the **shared external agents** (the common toolbox). Each external
149
- agent (Confluence export with **CME**, document conversion, mail) also needs a
150
- little setup the first time.
151
-
152
- Both are covered in **[docs/usage.md](docs/usage.md)**.
153
-
154
144
  ## The journey: from first launch to first result
155
145
 
156
146
  Follow this little story in order. By the end you'll have seen it all: the
@@ -353,6 +343,30 @@ ingest -> build -> export -> polish
353
343
  The legacy copy step is only for deployments that explicitly configure external
354
344
  import mappings.
355
345
 
346
+ ## Configuration overview
347
+
348
+ wikiLLM is configured by **four files** held together by two families of keys:
349
+ **MCP keys** (Bearer tokens that authenticate *who connects to whom*) and **LLM
350
+ keys** (`apiKey` + `baseUrl` that *reach a model*).
351
+
352
+ ![wikiLLM configuration keys — MCP vs LLM, where each key is configured](https://raw.githubusercontent.com/dotdrelle/llm-wiki-manager/main/docs/config-keys.svg)
353
+
354
+ | File | Owner | Scope | Holds |
355
+ | --- | --- | --- | --- |
356
+ | `.env` | manager | global | shared secrets: agent MCP tokens, MailerSend, OCR LLM, port overrides |
357
+ | `mcp.endpoints.json` | manager | global | where each external agent lives + which `Bearer`/header to send |
358
+ | `workspaces/<name>/.env` | manager | per workspace | ports, workspace path, the wiki's own MCP tokens |
359
+ | `workspaces/<name>/.wikirc.yaml` (+ `.wikirc.yaml.<profile>`) | workspace | per workspace | LLM & vector keys (provider/model/apiKey/baseUrl/retrieval) |
360
+
361
+ Donna reaches the external agents and the internal wiki MCP through Bearer
362
+ tokens; the wiki then uses its `.wikirc.yaml` LLM keys to call models and
363
+ embeddings. Because every MCP server is an HTTP endpoint, remote MCP clients can
364
+ connect to the same surfaces with the same tokens. **MCP keys** are set in the
365
+ root `.env`; the wiki's **LLM keys** live in each workspace `.wikirc.yaml`.
366
+
367
+ See the full, field-by-field reference in
368
+ **[docs/configuration.md](https://raw.githubusercontent.com/dotdrelle/llm-wiki-manager/main/docs/configuration.md)**.
369
+
356
370
  ## Initial Setup
357
371
 
358
372
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotdrelle/wiki-manager",
3
- "version": "0.6.26",
3
+ "version": "0.6.30",
4
4
  "description": "Agentic shell and orchestration cockpit for llm-wiki workspaces.",
5
5
  "license": "PolyForm-Noncommercial-1.0.0",
6
6
  "author": "dotrelle",
@@ -322,8 +322,8 @@ export function buildAgentSystemPrompt(state) {
322
322
  skills,
323
323
  'You can call MCP tools directly using the provided tool functions.',
324
324
  'When the user asks for an action that can be performed with connected MCP tools or safe primitives, do not answer with future intent such as "I will call...", "I am going to run...", or "launching..." unless you also call the tool in the same turn. Either call the tool now, ask for the exact missing required arguments, or explain the concrete blocker.',
325
- 'For CME configuration/setup/update requests, if a matching CME tool such as cme_setup is connected and the required arguments are known, call it immediately. If the CME server or tool is not connected, say which CME capability is missing and recommend the exact service/status primitive to inspect it. Do not invent a pending CME action in plain text.',
326
- 'For workspace-scoped external MCP tools, the orchestrator enforces workspace injection. When calling documents_* conversion tools or cme_* configuration/source/export tools, use the active workspace only. cme_export_status(job_id=...) and cme_export_cancel(job_id=...) can be used from any active workspace.',
325
+ 'For connector configuration/setup/update requests, if a matching setup/configuration tool is connected and the required arguments are known, call it immediately. If the connector or tool is not connected, say which concrete capability is missing and recommend the exact service/status primitive to inspect it. Do not invent a pending connector action in plain text.',
326
+ 'For workspace-scoped external MCP tools, the orchestrator enforces workspace injection. Use the active workspace for configuration, source, import, export, conversion, and generation tools unless a tool is explicitly job-scoped and only requires a job id.',
327
327
  'You can call shell__run_command for safe manager slash commands such as /workspaces, /new <name> [path], /use <workspace>, /config, /status, /services, /skills, /skills show <name>, and /skills run <name>.',
328
328
  'Skills are workflow instructions, not executable code. When a user asks to run a skill, inspect it, propose the concrete primitive/tool plan, and ask for confirmation before costly or mutating actions.',
329
329
  [
@@ -658,7 +658,11 @@ export async function handleSlashCommand(line, context) {
658
658
  context.session.workspacePath = workspace.workspacePath;
659
659
  context.session.workspaceEnv = workspace.env;
660
660
  context.session.workspaceEnvFile = workspace.envFile;
661
- context.session.mcp = buildMcpStatus(context.session);
661
+ context.session.wikirc = null;
662
+ context.session.wikircConfig = null;
663
+ context.session.language = null;
664
+ context.session.llm = null;
665
+ context.session.mcp = null;
662
666
  context.session.systemPrompt = loadWorkspaceSystemPrompt(workspace.workspacePath);
663
667
  try {
664
668
  step(`Workspace: loading ${workspace.name} config…`);
@@ -702,6 +706,7 @@ export async function handleSlashCommand(line, context) {
702
706
  }
703
707
  try {
704
708
  const summary = loadSessionWikirc(context.session, profileName);
709
+ await refreshMcpRuntimeStatus(context.session);
705
710
  return {
706
711
  output: [
707
712
  'Active wikirc:',
package/src/core/mcp.js CHANGED
@@ -2,10 +2,12 @@ import { existsSync, readFileSync } from 'node:fs';
2
2
  import { managerEnvFile, managerMcpEndpointsFile, readEnvFile } from './env.js';
3
3
 
4
4
  function envValue(key) {
5
- if (key in process.env) return process.env[key];
6
5
  const filePath = managerEnvFile();
7
- if (!existsSync(filePath)) return undefined;
8
- return readEnvFile(filePath)[key];
6
+ if (existsSync(filePath)) {
7
+ const fileValue = readEnvFile(filePath)[key];
8
+ if (fileValue !== undefined) return fileValue;
9
+ }
10
+ return process.env[key];
9
11
  }
10
12
 
11
13
  function interpolateEnv(value) {
@@ -75,16 +77,20 @@ const MCP_SERVICE_MAP = {
75
77
 
76
78
  export function buildMcpStatus(session) {
77
79
  const workspaceEnv = session.workspaceEnv ?? {};
80
+ const wikiMcpToken = session.wikircConfig?.mcp?.accessKey;
81
+ const wikiMcpDetail = workspaceEnv.WIKI_MCP_PORT
82
+ ? (wikiMcpToken ? `:${workspaceEnv.WIKI_MCP_PORT}` : `:${workspaceEnv.WIKI_MCP_PORT} (mcp.accessKey missing in active wikirc)`)
83
+ : '';
78
84
  const external = readExternalMcpEndpoints();
79
85
 
80
86
  return {
81
87
  wiki: {
82
88
  ...endpointStatus(
83
- workspaceEnv.WIKI_MCP_PORT && workspaceEnv.WIKI_MCP_AUTH_TOKEN,
84
- workspaceEnv.WIKI_MCP_PORT ? `:${workspaceEnv.WIKI_MCP_PORT}` : '',
89
+ workspaceEnv.WIKI_MCP_PORT && wikiMcpToken,
90
+ wikiMcpDetail,
85
91
  ),
86
92
  url: workspaceEnv.WIKI_MCP_PORT ? `http://127.0.0.1:${workspaceEnv.WIKI_MCP_PORT}/mcp` : null,
87
- token: workspaceEnv.WIKI_MCP_AUTH_TOKEN || null,
93
+ token: wikiMcpToken || null,
88
94
  },
89
95
  production: {
90
96
  ...endpointStatus(
@@ -205,7 +211,7 @@ async function mcpRequest(endpoint, method, params, signal, options = {}) {
205
211
  params: {
206
212
  protocolVersion: '2025-06-18',
207
213
  capabilities: {},
208
- clientInfo: { name: 'wiki-manager', version: '0.6.26' },
214
+ clientInfo: { name: 'wiki-manager', version: '0.6.30' },
209
215
  },
210
216
  }),
211
217
  });
@@ -295,6 +301,7 @@ export async function discoverMcpTools(mcpStatus) {
295
301
  const message = err instanceof Error ? err.message : String(err);
296
302
  next[name] = {
297
303
  ...value,
304
+ status: value.status === 'connected' ? 'configured' : value.status,
298
305
  tools: [],
299
306
  toolError: message,
300
307
  };
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict';
3
3
  import { mkdtemp, writeFile } from 'node:fs/promises';
4
4
  import os from 'node:os';
5
5
  import path from 'node:path';
6
- import { buildMcpStatus, callMcpTool } from './mcp.js';
6
+ import { buildMcpStatus, callMcpTool, discoverMcpTools } from './mcp.js';
7
7
 
8
8
  test('buildMcpStatus reads external MCP endpoints from mcp.endpoints.json', async () => {
9
9
  const originalCwd = process.cwd();
@@ -87,6 +87,82 @@ test('buildMcpStatus interpolates external endpoints from manager .env', async (
87
87
  }
88
88
  });
89
89
 
90
+ test('buildMcpStatus reloads external endpoint keys changed in manager .env', async () => {
91
+ const originalCwd = process.cwd();
92
+ const originalToken = process.env.TEST_EXTERNAL_TOKEN;
93
+ const root = await mkdtemp(path.join(os.tmpdir(), 'wiki-manager-mcp-env-reload-'));
94
+ await writeFile(
95
+ path.join(root, '.env'),
96
+ 'TEST_EXTERNAL_TOKEN=first-token\n',
97
+ 'utf8',
98
+ );
99
+ await writeFile(
100
+ path.join(root, 'mcp.endpoints.json'),
101
+ JSON.stringify({
102
+ mcpServers: {
103
+ external: {
104
+ url: 'http://127.0.0.1:9999/mcp/',
105
+ headers: {
106
+ Authorization: 'Bearer ${TEST_EXTERNAL_TOKEN}',
107
+ },
108
+ },
109
+ },
110
+ }),
111
+ 'utf8',
112
+ );
113
+ process.env.TEST_EXTERNAL_TOKEN = 'first-token';
114
+
115
+ try {
116
+ process.chdir(root);
117
+ await writeFile(
118
+ path.join(root, '.env'),
119
+ 'TEST_EXTERNAL_TOKEN=second-token\n',
120
+ 'utf8',
121
+ );
122
+ const status = buildMcpStatus({ workspaceEnv: {} });
123
+ assert.deepEqual(status.external.headers, {
124
+ authorization: 'Bearer second-token',
125
+ });
126
+ } finally {
127
+ process.chdir(originalCwd);
128
+ if (originalToken === undefined) delete process.env.TEST_EXTERNAL_TOKEN;
129
+ else process.env.TEST_EXTERNAL_TOKEN = originalToken;
130
+ }
131
+ });
132
+
133
+ test('buildMcpStatus does not use workspace env token for wiki MCP without active wikirc accessKey', () => {
134
+ const status = buildMcpStatus({
135
+ workspaceEnv: {
136
+ WIKI_MCP_PORT: '3101',
137
+ WIKI_MCP_AUTH_TOKEN: 'wiki-token-2',
138
+ PRODUCTION_MCP_PORT: '3102',
139
+ PRODUCTION_MCP_AUTH_TOKEN: 'production-token-2',
140
+ },
141
+ });
142
+
143
+ assert.equal(status.wiki.status, 'missing');
144
+ assert.equal(status.wiki.token, null);
145
+ assert.match(status.wiki.detail, /mcp\.accessKey missing/);
146
+ assert.equal(status.production.token, 'production-token-2');
147
+ });
148
+
149
+ test('buildMcpStatus uses active wikirc mcp.accessKey for wiki MCP', () => {
150
+ const status = buildMcpStatus({
151
+ workspaceEnv: {
152
+ WIKI_MCP_PORT: '3101',
153
+ WIKI_MCP_AUTH_TOKEN: 'env-wiki-token',
154
+ },
155
+ wikircConfig: {
156
+ mcp: {
157
+ accessKey: 'wikirc-wiki-token',
158
+ },
159
+ },
160
+ });
161
+
162
+ assert.equal(status.wiki.status, 'configured');
163
+ assert.equal(status.wiki.token, 'wikirc-wiki-token');
164
+ });
165
+
90
166
  test('callMcpTool injects active configPath for production_start_job', async () => {
91
167
  const originalFetch = globalThis.fetch;
92
168
  let requestBody = null;
@@ -193,6 +269,32 @@ test('callMcpTool sends configured endpoint headers', async () => {
193
269
  }
194
270
  });
195
271
 
272
+ test('discoverMcpTools downgrades connected endpoint when tool discovery fails', async () => {
273
+ const originalFetch = globalThis.fetch;
274
+ globalThis.fetch = async () => ({
275
+ ok: false,
276
+ status: 401,
277
+ headers: { get: () => null },
278
+ text: async () => '{"error":"invalid or missing bearer token"}',
279
+ });
280
+
281
+ try {
282
+ const status = await discoverMcpTools({
283
+ wiki: {
284
+ status: 'connected',
285
+ url: 'http://127.0.0.1:3201/mcp',
286
+ token: 'token',
287
+ },
288
+ });
289
+
290
+ assert.equal(status.wiki.status, 'configured');
291
+ assert.equal(status.wiki.tools.length, 0);
292
+ assert.match(status.wiki.toolError, /401/);
293
+ } finally {
294
+ globalThis.fetch = originalFetch;
295
+ }
296
+ });
297
+
196
298
  test('callMcpTool parses SSE responses after keepalive comments', async () => {
197
299
  const originalFetch = globalThis.fetch;
198
300
  globalThis.fetch = async () => ({