@dotdrelle/wiki-manager 0.6.34 → 0.6.47

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotdrelle/wiki-manager",
3
- "version": "0.6.34",
3
+ "version": "0.6.47",
4
4
  "description": "Agentic shell and orchestration cockpit for llm-wiki workspaces.",
5
5
  "license": "PolyForm-Noncommercial-1.0.0",
6
6
  "author": "dotrelle",
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "scripts": {
13
13
  "start": "bun ./bin/wiki-manager.js",
14
- "test": "node --test src/core/activity.test.js src/core/agentEvents.test.js src/core/plan.test.js src/core/mcp.test.js src/core/documentIntake.test.js src/core/wikirc.test.js src/core/modelFetch.test.js src/commands/slash.test.js",
14
+ "test": "node --test src/core/activity.test.js src/core/agentEvents.test.js src/core/plan.test.js src/core/mcp.test.js src/core/documentIntake.test.js src/core/wikirc.test.js src/core/modelFetch.test.js src/core/startupCheck.test.js src/commands/slash.test.js",
15
15
  "check": "bun ./bin/wiki-manager.js --version && bun ./bin/wiki-manager.js --help && bun ./bin/wiki-manager.js --once \"verifie le mode agent\""
16
16
  },
17
17
  "engines": {
@@ -43,7 +43,7 @@ const SHELL_RUN_COMMAND_TOOL = {
43
43
  properties: {
44
44
  command: {
45
45
  type: 'string',
46
- description: 'Slash command to run, for example "/workspace list", "/workspace init demo", or "/use juno".',
46
+ description: 'Slash command to run, for example "/workspace list", "/workspace init <name>", or "/use <workspace>".',
47
47
  },
48
48
  },
49
49
  required: ['command'],
@@ -366,23 +366,6 @@ export function buildAgentSystemPrompt(state) {
366
366
  export function buildLimitedAgentResponse(state, reason = 'no workspace loaded with .wikirc.yaml') {
367
367
  const workspace = state.session.workspace ?? 'no workspace selected';
368
368
  const wikirc = state.session.wikirc?.profile ?? 'no profile loaded';
369
- const language = state.session.language ?? 'en-US';
370
- if (language.toLowerCase().startsWith('fr')) {
371
- return [
372
- `Donna est active. Workspace courant: ${workspace}.`,
373
- `Profil wikirc courant: ${wikirc}.`,
374
- '',
375
- "Je suis le mode agent du shell: utilise `/agent` pour router les entrees libres vers ce graphe LangGraph, ou `/chat` pour revenir au chat direct.",
376
- `Connexion LLM: mode limite (${reason}).`,
377
- `Primitives disponibles maintenant: ${commandList(state.session)}.`,
378
- '',
379
- 'Outils MCP connectes:',
380
- formatMcpToolsForAgent(state.session.mcp),
381
- '',
382
- 'Mode limite: workspace, Docker Compose, appels MCP, echappatoire /wiki, decouverte skills et mode headless sont branches.',
383
- "Utilise `/help` pour voir les commandes deterministes disponibles.",
384
- ].join('\n');
385
- }
386
369
  return [
387
370
  `Donna is active. Current workspace: ${workspace}.`,
388
371
  `Current wikirc profile: ${wikirc}.`,
@@ -506,7 +489,7 @@ export function createAgentGraph(options = {}) {
506
489
  } catch (err) {
507
490
  if (err.name === 'AbortError') throw err;
508
491
  const message = err instanceof Error ? err.message : String(err);
509
- return { response: buildLimitedAgentResponse(state, `LLM indisponible: ${message}`), pendingToolCalls: null, readyToStream: false };
492
+ return { response: buildLimitedAgentResponse(state, `LLM unavailable: ${message}`), pendingToolCalls: null, readyToStream: false };
510
493
  }
511
494
  }
512
495
 
@@ -282,6 +282,13 @@ function workspaceStatsText(stats) {
282
282
  }
283
283
 
284
284
  function workspaceLoadedText(workspace, summary, session) {
285
+ const profiles = listWikircProfiles(workspace.workspacePath);
286
+ const profileLines = profiles.length > 0
287
+ ? profiles.map((profile) => {
288
+ const marker = profile.name === summary.profile ? '*' : ' ';
289
+ return `${marker} ${profile.name}\t${profile.fileName}`;
290
+ })
291
+ : ['No .wikirc.yaml profile found.'];
285
292
  return [
286
293
  `Workspace: ${workspace.name}`,
287
294
  '',
@@ -300,6 +307,12 @@ function workspaceLoadedText(workspace, summary, session) {
300
307
  `vector: ${summary.vectorEnabled ? 'enabled' : 'disabled'}`,
301
308
  `embedding: ${summary.embeddingModel ?? '-'}`,
302
309
  '',
310
+ 'Available configs',
311
+ '',
312
+ ...profileLines,
313
+ '',
314
+ `Switch config: /config use <profile>`,
315
+ '',
303
316
  'Session',
304
317
  '',
305
318
  `llm: ${session.llm ? 'configured' : 'missing config'}`,
@@ -647,7 +660,7 @@ Modes:
647
660
  Status:
648
661
  Agent-first shell is installed with workspace services, MCP calls, wiki CLI, skill discovery, and headless runs.
649
662
  Shell UI is English. Agent exchange language is read from the active .wikirc.yaml.
650
- LLM config is intentionally workspace-scoped and will be read from .wikirc.yaml after /use <workspace>.
663
+ LLM config is intentionally workspace-scoped and is read from .wikirc.yaml after /use <workspace>.
651
664
  Headless mode supports one-shot workspace prompts and skill runs with log output.
652
665
  `;
653
666
  }
@@ -690,6 +703,9 @@ export async function handleSlashCommand(line, context) {
690
703
  case 'use': {
691
704
  const workspaceName = args[1];
692
705
  if (!workspaceName) {
706
+ return { output: formatWorkspaceList(listWorkspaces(), context.session) };
707
+ }
708
+ if (args[2]) {
693
709
  return { output: 'Usage: /use <workspace>' };
694
710
  }
695
711
  const workspace = findWorkspace(workspaceName);
@@ -5,6 +5,7 @@ import { tmpdir } from 'node:os';
5
5
  import { join } from 'node:path';
6
6
  import test from 'node:test';
7
7
  import { handleSlashCommand } from './slash.js';
8
+ import { completionContext } from '../shell/repl.js';
8
9
 
9
10
  test('/workspace delete removes files and clears current session context after confirmation', async () => {
10
11
  const root = await mkdtemp(join(tmpdir(), 'wiki-manager-delete-workspace-'));
@@ -66,3 +67,80 @@ test('/new without a name shows usage', async () => {
66
67
 
67
68
  assert.match(result.output ?? '', /Usage/i);
68
69
  });
70
+
71
+ test('/use loads only workspaces and /config use switches wikirc profiles', async () => {
72
+ const root = await mkdtemp(join(tmpdir(), 'wiki-manager-use-profile-'));
73
+ const registryRoot = join(root, 'registry');
74
+ const registryPath = join(registryRoot, 'demo');
75
+ const workspacePath = join(root, 'workspace');
76
+ mkdirSync(registryPath, { recursive: true });
77
+ mkdirSync(workspacePath, { recursive: true });
78
+ writeFileSync(join(registryPath, '.env'), [
79
+ 'WORKSPACE_NAME=demo',
80
+ `WIKI_WORKSPACE_PATH=${workspacePath}`,
81
+ '',
82
+ ].join('\n'), 'utf8');
83
+ writeFileSync(join(workspacePath, '.wikirc.yaml'), [
84
+ 'language: fr',
85
+ 'llm:',
86
+ ' provider: default-provider',
87
+ ' model: default-model',
88
+ '',
89
+ ].join('\n'), 'utf8');
90
+ writeFileSync(join(workspacePath, '.wikirc.yaml.vpn'), [
91
+ 'language: fr',
92
+ 'llm:',
93
+ ' provider: vpn-provider',
94
+ ' model: vpn-model',
95
+ '',
96
+ ].join('\n'), 'utf8');
97
+
98
+ const previousDir = process.env.WIKI_WORKSPACES_DIR;
99
+ process.env.WIKI_WORKSPACES_DIR = registryRoot;
100
+
101
+ try {
102
+ const session = {};
103
+ const listResult = await handleSlashCommand('/use', {
104
+ packageJson: { version: 'test' },
105
+ session,
106
+ });
107
+ assert.match(listResult.output ?? '', /Workspaces/);
108
+ assert.match(listResult.output ?? '', /demo\tavailable/);
109
+ assert.doesNotMatch(listResult.output ?? '', /vpn\t\.wikirc\.yaml\.vpn/);
110
+
111
+ const useResult = await handleSlashCommand('/use demo', {
112
+ packageJson: { version: 'test' },
113
+ session,
114
+ });
115
+
116
+ assert.equal(session.workspace, 'demo');
117
+ assert.equal(session.wikirc.profile, 'default');
118
+ assert.match(useResult.output ?? '', /profile: default/);
119
+ assert.match(useResult.output ?? '', /\* default\t\.wikirc\.yaml/);
120
+ assert.match(useResult.output ?? '', /vpn\t\.wikirc\.yaml\.vpn/);
121
+ assert.match(useResult.output ?? '', /Switch config: \/config use <profile>/);
122
+
123
+ const invalidUse = await handleSlashCommand('/use demo vpn', {
124
+ packageJson: { version: 'test' },
125
+ session,
126
+ });
127
+ assert.match(invalidUse.output ?? '', /Usage: \/use <workspace>/);
128
+ assert.equal(session.wikirc.profile, 'default');
129
+
130
+ const result = await handleSlashCommand('/config use vpn', {
131
+ packageJson: { version: 'test' },
132
+ session,
133
+ });
134
+ assert.equal(session.workspace, 'demo');
135
+ assert.equal(session.wikirc.profile, 'vpn');
136
+ assert.match(result.output ?? '', /profile=vpn/);
137
+
138
+ const completion = completionContext('/use ', { commands: ['use'] });
139
+ assert.deepEqual(completion?.matches, ['demo']);
140
+ const configCompletion = completionContext('/config use ', session);
141
+ assert.deepEqual(configCompletion?.matches, ['default', 'vpn']);
142
+ } finally {
143
+ if (previousDir === undefined) delete process.env.WIKI_WORKSPACES_DIR;
144
+ else process.env.WIKI_WORKSPACES_DIR = previousDir;
145
+ }
146
+ });
@@ -113,6 +113,7 @@ function composeEnv(session) {
113
113
  ...cacertEnv(),
114
114
  WORKSPACE_NAME: session.workspace,
115
115
  WIKI_WORKSPACE_PATH: session.workspacePath,
116
+ ...(session.wikirc?.fileName && { WIKI_CONFIG_PATH: session.wikirc.fileName }),
116
117
  };
117
118
  }
118
119
 
@@ -303,8 +304,7 @@ export async function runWikiCli(session, args, options = {}) {
303
304
  if (!Array.isArray(args) || args.length === 0) {
304
305
  throw new Error('Usage: /wiki run <args...>');
305
306
  }
306
- const configEnv = session.wikirc?.fileName ? ['-e', `WIKI_CONFIG_PATH=${session.wikirc.fileName}`] : [];
307
- return runCompose(session, ['run', '--rm', ...configEnv, 'wiki', ...args], {
307
+ return runCompose(session, ['run', '--rm', 'wiki', ...args], {
308
308
  timeout: options.timeout ?? 180_000,
309
309
  maxBuffer: options.maxBuffer ?? 1024 * 1024 * 8,
310
310
  onOutput: options.onOutput,
package/src/core/mcp.js CHANGED
@@ -211,7 +211,7 @@ async function mcpRequest(endpoint, method, params, signal, options = {}) {
211
211
  params: {
212
212
  protocolVersion: '2025-06-18',
213
213
  capabilities: {},
214
- clientInfo: { name: 'wiki-manager', version: '0.6.34' },
214
+ clientInfo: { name: 'wiki-manager', version: '0.6.47' },
215
215
  },
216
216
  }),
217
217
  });
@@ -92,7 +92,7 @@ function checkWikirc(workspace) {
92
92
  profileName: loaded.profile.name,
93
93
  };
94
94
  const gaps = [];
95
- if (!summary.hasApiKey || !summary.model) gaps.push({ kind: 'llm', context });
95
+ if (!summary.provider || !summary.baseUrl || !summary.hasApiKey || !summary.model) gaps.push({ kind: 'llm', context });
96
96
  if (!summary.vectorEnabled) gaps.push({ kind: 'vector', context });
97
97
  return gaps;
98
98
  } catch {
@@ -0,0 +1,66 @@
1
+ import assert from 'node:assert/strict';
2
+ import { mkdirSync, writeFileSync } from 'node:fs';
3
+ import { mkdtemp } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import test from 'node:test';
7
+ import { runChecks } from './startupCheck.js';
8
+
9
+ async function withWorkspace(wikircLines, fn) {
10
+ const root = await mkdtemp(join(tmpdir(), 'wiki-manager-startup-check-'));
11
+ const registryRoot = join(root, 'registry');
12
+ const registryPath = join(registryRoot, 'demo');
13
+ const workspacePath = join(root, 'workspace');
14
+ mkdirSync(registryPath, { recursive: true });
15
+ mkdirSync(workspacePath, { recursive: true });
16
+ writeFileSync(join(registryPath, '.env'), [
17
+ 'WORKSPACE_NAME=demo',
18
+ `WIKI_WORKSPACE_PATH=${workspacePath}`,
19
+ '',
20
+ ].join('\n'), 'utf8');
21
+ writeFileSync(join(workspacePath, '.wikirc.yaml'), [...wikircLines, ''].join('\n'), 'utf8');
22
+
23
+ const previousDir = process.env.WIKI_WORKSPACES_DIR;
24
+ process.env.WIKI_WORKSPACES_DIR = registryRoot;
25
+ try {
26
+ await fn({ workspacePath });
27
+ } finally {
28
+ if (previousDir === undefined) delete process.env.WIKI_WORKSPACES_DIR;
29
+ else process.env.WIKI_WORKSPACES_DIR = previousDir;
30
+ }
31
+ }
32
+
33
+ function hasGap(gaps, kind) {
34
+ return gaps.some((gap) => gap.kind === kind);
35
+ }
36
+
37
+ test('runChecks treats default LLM config without baseUrl as incomplete', async () => {
38
+ await withWorkspace([
39
+ 'llm:',
40
+ ' provider: openai-compatible',
41
+ ' apiKey: key',
42
+ ' model: chat-model',
43
+ 'retrieval:',
44
+ ' vector:',
45
+ ' enabled: true',
46
+ ], async () => {
47
+ const gaps = await runChecks();
48
+ assert.equal(hasGap(gaps, 'llm'), true);
49
+ });
50
+ });
51
+
52
+ test('runChecks treats default LLM config with provider, baseUrl, apiKey and model as complete', async () => {
53
+ await withWorkspace([
54
+ 'llm:',
55
+ ' provider: openai-compatible',
56
+ ' baseUrl: http://localhost:8000/v1',
57
+ ' apiKey: key',
58
+ ' model: chat-model',
59
+ 'retrieval:',
60
+ ' vector:',
61
+ ' enabled: true',
62
+ ], async () => {
63
+ const gaps = await runChecks();
64
+ assert.equal(hasGap(gaps, 'llm'), false);
65
+ });
66
+ });
@@ -29,8 +29,8 @@ export function resolveWikircProfile(workspacePath, profileName = 'default') {
29
29
  const normalized = profileName || 'default';
30
30
  const found = profiles.find((profile) => profile.name === normalized || profile.fileName === normalized);
31
31
  if (!found) {
32
- const available = profiles.map((profile) => profile.name).join(', ') || 'aucun';
33
- throw new Error(`profil wikirc introuvable: ${normalized} (disponibles: ${available})`);
32
+ const available = profiles.map((profile) => profile.name).join(', ') || 'none';
33
+ throw new Error(`wikirc profile not found: ${normalized} (available: ${available})`);
34
34
  }
35
35
  return found;
36
36
  }
package/src/shell/repl.js CHANGED
@@ -202,6 +202,7 @@ function completionValuesFor(parts, inputBuffer, session) {
202
202
  if (tokenIndex === 0) return slashCompletions(session);
203
203
  if (command === '/new' && tokenIndex === 1) return [];
204
204
  if (command === '/use' && tokenIndex === 1) return workspaceNames();
205
+ if (command === '/use' && tokenIndex === 2) return [];
205
206
  if (command === '/config' && tokenIndex === 1) return ['edit', 'list', 'status', 'use'];
206
207
  if (command === '/config' && (previousToken === 'use' || previousToken === 'edit')) return wikircProfileNames(session);
207
208
  if (command === '/mcp' && tokenIndex === 1) return ['call', 'endpoints', 'status', 'tools'];
package/wiki-workspace CHANGED
@@ -225,9 +225,14 @@ agents_compose() {
225
225
  mkdir -p "$agents_data_dir/cme" "$agents_data_dir/documents/input" "$agents_data_dir/documents/output" "$agents_data_dir/documents/uploads"
226
226
  mkdir -p "$workspaces_root"
227
227
  $do_pull && _agents_dc pull
228
- _agents_dc up -d
228
+ local up_args=(up -d)
229
+ if [[ -n "$CACERT_PATH" ]]; then
230
+ up_args+=(--force-recreate)
231
+ fi
232
+ _agents_dc "${up_args[@]}"
229
233
  printf 'Workspaces root: %s\n' "$workspaces_root"
230
234
  printf 'Agents data: %s\n' "$agents_data_dir"
235
+ [[ -z "$CACERT_PATH" ]] || printf 'CA cert: %s -> /wiki-manager-ca.pem\n' "$CACERT_PATH"
231
236
  printf 'Agents: cme=:%s documents=:%s mailer=:%s\n' \
232
237
  "${CME_MCP_PORT:-3336}" "${DOCUMENTS_MCP_PORT:-3337}" "${MAILER_MCP_PORT:-3335}"
233
238
  ;;
@@ -524,20 +529,19 @@ cacert_compose_args() {
524
529
  local compose_file="$1"
525
530
  local override_name="$2"
526
531
  [[ -n "$CACERT_PATH" ]] || return 0
527
- command -v node >/dev/null 2>&1 || die "--cacert requires node to generate Docker Compose overrides"
528
532
 
529
533
  local services
530
534
  services="$(
531
- ROOT_DIR="$ROOT_DIR" _WIKI_COMPOSE_FILE_PATH="$compose_file" node <<'NODE'
532
- const { createRequire } = require('node:module');
533
- const fs = require('node:fs');
534
- const requireFromPackage = createRequire(`${process.env.ROOT_DIR}/package.json`);
535
- const YAML = requireFromPackage('yaml');
536
- const parsed = YAML.parse(fs.readFileSync(process.env._WIKI_COMPOSE_FILE_PATH, 'utf8')) ?? {};
537
- for (const service of Object.keys(parsed.services ?? {})) {
538
- console.log(service);
539
- }
540
- NODE
535
+ awk '
536
+ /^[[:space:]]*services:[[:space:]]*$/ { in_services=1; next }
537
+ in_services && /^[^[:space:]#]/ { exit }
538
+ in_services && /^ [A-Za-z0-9_.-]+:[[:space:]]*$/ {
539
+ line=$0
540
+ sub(/^ /, "", line)
541
+ sub(/:[[:space:]]*$/, "", line)
542
+ print line
543
+ }
544
+ ' "$compose_file"
541
545
  )"
542
546
  [[ -n "$services" ]] || return 0
543
547