@dotdrelle/wiki-manager 0.6.31 → 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/README.md +3 -2
- package/bin/wiki-manager.js +1 -1
- package/package.json +8 -7
- package/src/agent/graph.js +6 -28
- package/src/cli/wiki-manager.js +17 -2
- package/src/commands/slash.js +121 -28
- package/src/commands/slash.test.js +146 -0
- package/src/core/compose.js +3 -3
- package/src/core/mcp.js +1 -1
- package/src/core/modelFetch.js +97 -0
- package/src/core/modelFetch.test.js +38 -0
- package/src/core/startupCheck.js +130 -0
- package/src/core/startupCheck.test.js +66 -0
- package/src/core/wikiSetup.js +156 -0
- package/src/core/wikirc.js +82 -3
- package/src/core/wikirc.test.js +111 -0
- package/src/core/workspaces.js +1 -1
- package/src/shell/LeftPane.tsx +54 -28
- package/src/shell/RightPane.tsx +25 -2
- package/src/shell/SetupWizard.tsx +806 -0
- package/src/shell/SlashDialog.tsx +4 -3
- package/src/shell/repl.js +21 -8
- package/src/shell/tui.tsx +85 -13
- package/src/shell/useSession.ts +15 -7
- package/wiki-workspace +19 -15
package/README.md
CHANGED
|
@@ -551,8 +551,9 @@ The TUI uses a two-pane layout:
|
|
|
551
551
|
Useful primitives:
|
|
552
552
|
|
|
553
553
|
```text
|
|
554
|
-
/
|
|
555
|
-
/new <name> [path]
|
|
554
|
+
/workspace list
|
|
555
|
+
/new <name> [path] # interactive TUI wizard
|
|
556
|
+
/workspace init <name> [path] # low-level non-interactive creation
|
|
556
557
|
/use <workspace>
|
|
557
558
|
/config list
|
|
558
559
|
/config use <name>
|
package/bin/wiki-manager.js
CHANGED
|
@@ -81,7 +81,7 @@ async function main() {
|
|
|
81
81
|
// exports these before Bun starts.
|
|
82
82
|
if (parsed.cacert) Object.assign(process.env, cacertEnvVars(parsed.cacert));
|
|
83
83
|
await import('@opentui/solid/preload');
|
|
84
|
-
const interactive = process.stdout.isTTY && process.stdin.isTTY && !argv.includes('--headless') && !argv.includes('--once') && !argv.includes('--version') && !argv.includes('-v') && !argv.includes('--help') && !argv.includes('-h');
|
|
84
|
+
const interactive = process.stdout.isTTY && process.stdin.isTTY && !argv.includes('--setup-wizard') && !argv.includes('--headless') && !argv.includes('--once') && !argv.includes('--version') && !argv.includes('-v') && !argv.includes('--help') && !argv.includes('-h');
|
|
85
85
|
if (interactive) process.stdout.write('Starting wiki-manager…\r');
|
|
86
86
|
const { runCli } = await import('../src/cli/wiki-manager.js');
|
|
87
87
|
await runCli(argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotdrelle/wiki-manager",
|
|
3
|
-
"version": "0.6.
|
|
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",
|
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
"wiki-manager": "bin/wiki-manager",
|
|
10
10
|
"wiki-workspace": "wiki-workspace"
|
|
11
11
|
},
|
|
12
|
+
"scripts": {
|
|
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/core/startupCheck.test.js src/commands/slash.test.js",
|
|
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
|
+
},
|
|
12
17
|
"engines": {
|
|
13
18
|
"node": ">=22.0.0",
|
|
14
19
|
"bun": ">=1.2.0"
|
|
@@ -35,6 +40,7 @@
|
|
|
35
40
|
"orchestration",
|
|
36
41
|
"cli"
|
|
37
42
|
],
|
|
43
|
+
"packageManager": "pnpm@10.29.2",
|
|
38
44
|
"dependencies": {
|
|
39
45
|
"@langchain/langgraph": "^1.3.2",
|
|
40
46
|
"@opentui/core": "^0.3.2",
|
|
@@ -47,10 +53,5 @@
|
|
|
47
53
|
},
|
|
48
54
|
"devDependencies": {
|
|
49
55
|
"@types/node": "^26.0.0"
|
|
50
|
-
},
|
|
51
|
-
"scripts": {
|
|
52
|
-
"start": "bun ./bin/wiki-manager.js",
|
|
53
|
-
"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",
|
|
54
|
-
"check": "bun ./bin/wiki-manager.js --version && bun ./bin/wiki-manager.js --help && bun ./bin/wiki-manager.js --once \"verifie le mode agent\""
|
|
55
56
|
}
|
|
56
|
-
}
|
|
57
|
+
}
|
package/src/agent/graph.js
CHANGED
|
@@ -17,8 +17,6 @@ const MAX_SPINNER_ARG_LENGTH = 96;
|
|
|
17
17
|
const AGENT_SLASH_COMMANDS = new Set([
|
|
18
18
|
'help',
|
|
19
19
|
'version',
|
|
20
|
-
'workspaces',
|
|
21
|
-
'new',
|
|
22
20
|
'workspace',
|
|
23
21
|
'use',
|
|
24
22
|
'config',
|
|
@@ -36,8 +34,8 @@ const SHELL_RUN_COMMAND_TOOL = {
|
|
|
36
34
|
name: 'shell__run_command',
|
|
37
35
|
description: [
|
|
38
36
|
'Run a deterministic wiki-manager slash command inside the current shell session.',
|
|
39
|
-
'Allowed commands: /
|
|
40
|
-
'Do not use for arbitrary system shell commands, /mcp call, /wiki run, /start, /stop, /logs, or /exit.',
|
|
37
|
+
'Allowed commands: /workspace list, /workspace init <name> [path], /use <workspace>, /config, /status, /services, /skills, /skills show <name>, /skills run <name>, /upload <path>, /upload convert <id|pending>, /uploads.',
|
|
38
|
+
'Do not use for arbitrary system shell commands, /workspace delete, /mcp call, /wiki run, /start, /stop, /logs, or /exit.',
|
|
41
39
|
].join(' '),
|
|
42
40
|
parameters: {
|
|
43
41
|
type: 'object',
|
|
@@ -45,7 +43,7 @@ const SHELL_RUN_COMMAND_TOOL = {
|
|
|
45
43
|
properties: {
|
|
46
44
|
command: {
|
|
47
45
|
type: 'string',
|
|
48
|
-
description: 'Slash command to run, for example "/
|
|
46
|
+
description: 'Slash command to run, for example "/workspace list", "/workspace init <name>", or "/use <workspace>".',
|
|
49
47
|
},
|
|
50
48
|
},
|
|
51
49
|
required: ['command'],
|
|
@@ -217,9 +215,6 @@ function assertAgentSlashCommandAllowed(commandLine) {
|
|
|
217
215
|
if (!AGENT_SLASH_COMMANDS.has(command)) {
|
|
218
216
|
throw new Error(`Command is not available to the agent: /${command}`);
|
|
219
217
|
}
|
|
220
|
-
if (command === 'new' && parts.length < 2) {
|
|
221
|
-
throw new Error('Usage: /new <name> [path].');
|
|
222
|
-
}
|
|
223
218
|
if (command === 'workspace' && parts[1] !== 'init') {
|
|
224
219
|
throw new Error('Only /workspace init is available to the agent.');
|
|
225
220
|
}
|
|
@@ -324,7 +319,7 @@ export function buildAgentSystemPrompt(state) {
|
|
|
324
319
|
'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
320
|
'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
321
|
'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
|
-
'You can call shell__run_command for safe manager slash commands such as /
|
|
322
|
+
'You can call shell__run_command for safe manager slash commands such as /workspace list, /workspace init <name> [path], /use <workspace>, /config, /status, /services, /skills, /skills show <name>, and /skills run <name>.',
|
|
328
323
|
'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
324
|
[
|
|
330
325
|
state.session.headless ? 'HEADLESS MODE ACTIVE. Execute the requested skill or task autonomously using available safe primitives and MCP tools. Do not ask for interactive confirmation unless the request is genuinely ambiguous or outside the loaded workspace.' : null,
|
|
@@ -361,7 +356,7 @@ export function buildAgentSystemPrompt(state) {
|
|
|
361
356
|
'For ingest/build/export/polish/pipeline workflows, use production MCP tools. Do not route these through direct /wiki shortcuts. To chain multiple sequential steps (e.g. build then polish), always use a single production_start_job call with type="pipeline" and steps=["build","polish"] — never start them as separate jobs: the first job is asynchronous and the second would run before it completes. For existing deliverables where content stability matters, pass stabilize:true so the build step preserves unchanged sections; keep polish in the pipeline when publication output is requested. Do not ask the user to confirm between steps; start the pipeline call directly.',
|
|
362
357
|
'Long-running MCP jobs: do not call the same status tool more than once consecutively. When chaining jobs sequentially: (1) start the job, report job/activity id and status; (2) check status once — if done, proceed to the next step immediately; (3) if still running, report status, list the remaining steps, and return control; (4) when re-invoked, check status first, then proceed. Do not spin-poll (status → status → status with no new action between). The shell activity panel monitors non-terminal jobs automatically.',
|
|
363
358
|
'If production_start_job is returned as queued/waiting by the manager, report that it is waiting in the local queue and return control. Do not continue as if the production job has started.',
|
|
364
|
-
'For diagnostics, use /wiki run doctor when the user asks for doctor. Use /
|
|
359
|
+
'For diagnostics, use /wiki run doctor when the user asks for doctor. Use /workspace init <name> [path] for low-level non-interactive workspace creation. In the interactive TUI, /new <name> opens the setup wizard. Use /wiki for index, or /wiki run index through the explicit backup hatch. Use /wiki run init only for explicit current-workspace llm-wiki init.',
|
|
365
360
|
'If an action requires tools or skills not available yet, explain the limitation and name the expected primitive.',
|
|
366
361
|
].join('\n');
|
|
367
362
|
|
|
@@ -371,23 +366,6 @@ export function buildAgentSystemPrompt(state) {
|
|
|
371
366
|
export function buildLimitedAgentResponse(state, reason = 'no workspace loaded with .wikirc.yaml') {
|
|
372
367
|
const workspace = state.session.workspace ?? 'no workspace selected';
|
|
373
368
|
const wikirc = state.session.wikirc?.profile ?? 'no profile loaded';
|
|
374
|
-
const language = state.session.language ?? 'en-US';
|
|
375
|
-
if (language.toLowerCase().startsWith('fr')) {
|
|
376
|
-
return [
|
|
377
|
-
`Donna est active. Workspace courant: ${workspace}.`,
|
|
378
|
-
`Profil wikirc courant: ${wikirc}.`,
|
|
379
|
-
'',
|
|
380
|
-
"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.",
|
|
381
|
-
`Connexion LLM: mode limite (${reason}).`,
|
|
382
|
-
`Primitives disponibles maintenant: ${commandList(state.session)}.`,
|
|
383
|
-
'',
|
|
384
|
-
'Outils MCP connectes:',
|
|
385
|
-
formatMcpToolsForAgent(state.session.mcp),
|
|
386
|
-
'',
|
|
387
|
-
'Mode limite: workspace, Docker Compose, appels MCP, echappatoire /wiki, decouverte skills et mode headless sont branches.',
|
|
388
|
-
"Utilise `/help` pour voir les commandes deterministes disponibles.",
|
|
389
|
-
].join('\n');
|
|
390
|
-
}
|
|
391
369
|
return [
|
|
392
370
|
`Donna is active. Current workspace: ${workspace}.`,
|
|
393
371
|
`Current wikirc profile: ${wikirc}.`,
|
|
@@ -511,7 +489,7 @@ export function createAgentGraph(options = {}) {
|
|
|
511
489
|
} catch (err) {
|
|
512
490
|
if (err.name === 'AbortError') throw err;
|
|
513
491
|
const message = err instanceof Error ? err.message : String(err);
|
|
514
|
-
return { response: buildLimitedAgentResponse(state, `LLM
|
|
492
|
+
return { response: buildLimitedAgentResponse(state, `LLM unavailable: ${message}`), pendingToolCalls: null, readyToStream: false };
|
|
515
493
|
}
|
|
516
494
|
}
|
|
517
495
|
|
package/src/cli/wiki-manager.js
CHANGED
|
@@ -7,6 +7,7 @@ loadManagerEnv();
|
|
|
7
7
|
import { createAgentGraph, buildAgentSystemPrompt, buildLimitedAgentResponse } from '../agent/graph.js';
|
|
8
8
|
import { handleSlashCommand, printHelp, printVersion } from '../commands/slash.js';
|
|
9
9
|
import { runShell } from '../shell/repl.js';
|
|
10
|
+
import { runChecks } from '../core/startupCheck.js';
|
|
10
11
|
import { callMcpTool, formatMcpToolResult } from '../core/mcp.js';
|
|
11
12
|
import { extractActivity, parseJsonText, sessionActivities } from '../core/activity.js';
|
|
12
13
|
import { extractHeadlessPlan, syncActivitiesToPlan, formatPlanStatus, formatCompletedActivities } from '../core/plan.js';
|
|
@@ -15,7 +16,7 @@ import { createAgentEvent, dispatchAgentEvent } from '../core/agentEvents.js';
|
|
|
15
16
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
17
|
const packageJsonPath = resolve(__dirname, '../../package.json');
|
|
17
18
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
18
|
-
const SHELL_COMMANDS = ['help', 'version', 'exit', '
|
|
19
|
+
const SHELL_COMMANDS = ['help', 'version', 'exit', 'workspace', 'new', 'use', 'config', 'status', 'services', 'start', 'stop', 'logs', 'mcp', 'wiki', 'skills', 'clear', 'chat', 'agent'];
|
|
19
20
|
|
|
20
21
|
function valueAfter(argv, flag) {
|
|
21
22
|
const index = argv.indexOf(flag);
|
|
@@ -345,6 +346,18 @@ async function runHeadless(argv, agent) {
|
|
|
345
346
|
}
|
|
346
347
|
|
|
347
348
|
export async function runCli(argv) {
|
|
349
|
+
if (argv.includes('--setup-wizard')) {
|
|
350
|
+
if (!process.versions.bun) {
|
|
351
|
+
throw new Error('Setup wizard requires Bun. Run: bun ./bin/wiki-manager.js --setup-wizard');
|
|
352
|
+
}
|
|
353
|
+
const { runSetupWizard } = await import('../shell/tui.tsx');
|
|
354
|
+
await runSetupWizard({
|
|
355
|
+
workspaceName: valueAfter(argv, '--workspace-name'),
|
|
356
|
+
workspacePath: valueAfter(argv, '--workspace-path') ?? null,
|
|
357
|
+
});
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
348
361
|
if (argv.includes('--version') || argv.includes('-v')) {
|
|
349
362
|
printVersion(packageJson);
|
|
350
363
|
return;
|
|
@@ -380,7 +393,9 @@ export async function runCli(argv) {
|
|
|
380
393
|
if (!process.versions.bun) {
|
|
381
394
|
throw new Error('Interactive TUI requires Bun. Run: bun ./bin/wiki-manager.js');
|
|
382
395
|
}
|
|
383
|
-
const { runOpenTuiShell } = await import('../shell/tui.tsx');
|
|
396
|
+
const { runOpenTuiShell, runStartupWizard } = await import('../shell/tui.tsx');
|
|
397
|
+
const gaps = await runChecks();
|
|
398
|
+
if (gaps.length > 0) await runStartupWizard(gaps);
|
|
384
399
|
await runOpenTuiShell({ agent, packageJson });
|
|
385
400
|
return;
|
|
386
401
|
}
|
package/src/commands/slash.js
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
resolveWikircProfile,
|
|
31
31
|
summarizeWikircConfig,
|
|
32
32
|
} from '../core/wikirc.js';
|
|
33
|
+
import { deleteWorkspaceAndFiles, startAgents, stopAgents } from '../core/wikiSetup.js';
|
|
33
34
|
import {
|
|
34
35
|
cleanDocumentUploads,
|
|
35
36
|
convertPendingDocumentUploads,
|
|
@@ -281,6 +282,13 @@ function workspaceStatsText(stats) {
|
|
|
281
282
|
}
|
|
282
283
|
|
|
283
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.'];
|
|
284
292
|
return [
|
|
285
293
|
`Workspace: ${workspace.name}`,
|
|
286
294
|
'',
|
|
@@ -299,6 +307,12 @@ function workspaceLoadedText(workspace, summary, session) {
|
|
|
299
307
|
`vector: ${summary.vectorEnabled ? 'enabled' : 'disabled'}`,
|
|
300
308
|
`embedding: ${summary.embeddingModel ?? '-'}`,
|
|
301
309
|
'',
|
|
310
|
+
'Available configs',
|
|
311
|
+
'',
|
|
312
|
+
...profileLines,
|
|
313
|
+
'',
|
|
314
|
+
`Switch config: /config use <profile>`,
|
|
315
|
+
'',
|
|
302
316
|
'Session',
|
|
303
317
|
'',
|
|
304
318
|
`llm: ${session.llm ? 'configured' : 'missing config'}`,
|
|
@@ -433,8 +447,7 @@ async function createWorkspaceCommand(context, workspaceName, targetPath) {
|
|
|
433
447
|
output: [
|
|
434
448
|
'Usage: /new <name> [path]',
|
|
435
449
|
'',
|
|
436
|
-
'Creates
|
|
437
|
-
'Legacy form: /workspace init <name> [path].',
|
|
450
|
+
'Creates and registers a workspace via wiki-workspace config.',
|
|
438
451
|
'For llm-wiki init inside the current workspace, use /wiki run init.',
|
|
439
452
|
].join('\n'),
|
|
440
453
|
};
|
|
@@ -456,6 +469,7 @@ async function createWorkspaceCommand(context, workspaceName, targetPath) {
|
|
|
456
469
|
}
|
|
457
470
|
}
|
|
458
471
|
|
|
472
|
+
|
|
459
473
|
function formatMcpCallActivity(serverName, toolName, resultText) {
|
|
460
474
|
if (serverName === 'production') return null;
|
|
461
475
|
return formatActivitySummary(serverName, toolName, resultText);
|
|
@@ -551,6 +565,47 @@ function loadSessionWikirc(session, profileName = 'default') {
|
|
|
551
565
|
return summarizeWikircConfig(loaded.profile, loaded.config);
|
|
552
566
|
}
|
|
553
567
|
|
|
568
|
+
function clearWorkspaceSession(session) {
|
|
569
|
+
session.workspace = null;
|
|
570
|
+
session.workspacePath = null;
|
|
571
|
+
session.workspaceEnv = null;
|
|
572
|
+
session.workspaceEnvFile = null;
|
|
573
|
+
session.wikirc = null;
|
|
574
|
+
session.wikircConfig = null;
|
|
575
|
+
session.language = null;
|
|
576
|
+
session.llm = null;
|
|
577
|
+
session.mcp = null;
|
|
578
|
+
session.systemPrompt = null;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function formatWorkspaceList(workspaces, session = null) {
|
|
582
|
+
if (workspaces.length === 0) return 'No workspace configured.';
|
|
583
|
+
return [
|
|
584
|
+
'Workspaces',
|
|
585
|
+
'',
|
|
586
|
+
...workspaces.flatMap((workspace) => {
|
|
587
|
+
const active = workspace.name === session?.workspace ? 'active' : 'available';
|
|
588
|
+
return [
|
|
589
|
+
`${workspace.name}\t${active}`,
|
|
590
|
+
` path\t${workspace.workspacePath}`,
|
|
591
|
+
` use\t/use ${workspace.name}`,
|
|
592
|
+
` delete\t/workspace delete ${workspace.name}`,
|
|
593
|
+
'',
|
|
594
|
+
];
|
|
595
|
+
}),
|
|
596
|
+
].join('\n').trimEnd();
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function workspaceDeletePrompt(workspaces) {
|
|
600
|
+
if (workspaces.length === 0) return 'No workspace configured.';
|
|
601
|
+
return [
|
|
602
|
+
'Delete a workspace:',
|
|
603
|
+
...workspaces.map((workspace) => ` /workspace delete ${workspace.name}\t${workspace.workspacePath}`),
|
|
604
|
+
'',
|
|
605
|
+
'The next step asks for confirmation before deleting files.',
|
|
606
|
+
].join('\n');
|
|
607
|
+
}
|
|
608
|
+
|
|
554
609
|
export function helpText(packageJson) {
|
|
555
610
|
return `wiki-manager ${packageJson.version}
|
|
556
611
|
|
|
@@ -576,12 +631,12 @@ Options:
|
|
|
576
631
|
|
|
577
632
|
Interactive shell:
|
|
578
633
|
${helpPair('/help', 'Help', '/version', 'Version')}
|
|
579
|
-
${helpPair('/
|
|
634
|
+
${helpPair('/workspace list', 'Workspaces', '/new <n> [path]', 'New workspace')}
|
|
580
635
|
${helpPair('/use <workspace>', 'Use workspace', '/status', 'Session status')}
|
|
581
636
|
${helpPair('/config list', 'Config profiles', '/config use <n>', 'Use config')}
|
|
582
|
-
${helpPair('/config edit <n>', 'Edit config', '/
|
|
583
|
-
${helpPair('/services', 'Services', '/start [service]', 'Start service(s)')}
|
|
584
|
-
${helpPair('/stop [service]', 'Stop service(s)', '/logs <service>', 'Service logs')}
|
|
637
|
+
${helpPair('/config edit <n>', 'Edit config', '/workspace delete <n>', 'Delete workspace')}
|
|
638
|
+
${helpPair('/services', 'Services', '/start [service|agents]', 'Start service(s)')}
|
|
639
|
+
${helpPair('/stop [service|agents]', 'Stop service(s)', '/logs <service>', 'Service logs')}
|
|
585
640
|
${helpPair('/skills', 'List skills', '/skills show <n>', 'Show skill')}
|
|
586
641
|
${helpPair('/skills run <n>', 'Run skill guide', '/skills edit <n>', 'Edit skill')}
|
|
587
642
|
${helpPair('/mcp status', 'MCP status', '/mcp endpoints', 'MCP endpoints')}
|
|
@@ -605,7 +660,7 @@ Modes:
|
|
|
605
660
|
Status:
|
|
606
661
|
Agent-first shell is installed with workspace services, MCP calls, wiki CLI, skill discovery, and headless runs.
|
|
607
662
|
Shell UI is English. Agent exchange language is read from the active .wikirc.yaml.
|
|
608
|
-
LLM config is intentionally workspace-scoped and
|
|
663
|
+
LLM config is intentionally workspace-scoped and is read from .wikirc.yaml after /use <workspace>.
|
|
609
664
|
Headless mode supports one-shot workspace prompts and skill runs with log output.
|
|
610
665
|
`;
|
|
611
666
|
}
|
|
@@ -618,6 +673,16 @@ export async function handleSlashCommand(line, context) {
|
|
|
618
673
|
const args = line.slice(1).trim().split(/\s+/).filter(Boolean);
|
|
619
674
|
const [command] = args;
|
|
620
675
|
const step = context.onStep ?? (() => {});
|
|
676
|
+
const runAgentCommand = async (fn, verb) => {
|
|
677
|
+
try {
|
|
678
|
+
step(`Agents: ${verb}ing external agents…`);
|
|
679
|
+
const output = await fn();
|
|
680
|
+
return { output: output || `Agents ${verb}ed.` };
|
|
681
|
+
} catch (err) {
|
|
682
|
+
step(formatActivityError('agents', verb, err));
|
|
683
|
+
return { output: err instanceof Error ? err.message : String(err) };
|
|
684
|
+
}
|
|
685
|
+
};
|
|
621
686
|
|
|
622
687
|
switch (command) {
|
|
623
688
|
case '':
|
|
@@ -631,17 +696,6 @@ export async function handleSlashCommand(line, context) {
|
|
|
631
696
|
case 'agent':
|
|
632
697
|
context.session.chatMode = false;
|
|
633
698
|
return { setMode: 'agent', output: 'Mode: agent' };
|
|
634
|
-
case 'workspaces': {
|
|
635
|
-
const workspaces = listWorkspaces();
|
|
636
|
-
if (workspaces.length === 0) {
|
|
637
|
-
return { output: 'No workspace configured.' };
|
|
638
|
-
}
|
|
639
|
-
return {
|
|
640
|
-
output: workspaces
|
|
641
|
-
.map((workspace) => `${workspace.name}\t${workspace.workspacePath}`)
|
|
642
|
-
.join('\n'),
|
|
643
|
-
};
|
|
644
|
-
}
|
|
645
699
|
case 'status': {
|
|
646
700
|
step('Shell: refreshing workspace, services and MCP status…');
|
|
647
701
|
return { output: await statusText(context.session) };
|
|
@@ -649,21 +703,20 @@ export async function handleSlashCommand(line, context) {
|
|
|
649
703
|
case 'use': {
|
|
650
704
|
const workspaceName = args[1];
|
|
651
705
|
if (!workspaceName) {
|
|
706
|
+
return { output: formatWorkspaceList(listWorkspaces(), context.session) };
|
|
707
|
+
}
|
|
708
|
+
if (args[2]) {
|
|
652
709
|
return { output: 'Usage: /use <workspace>' };
|
|
653
710
|
}
|
|
654
711
|
const workspace = findWorkspace(workspaceName);
|
|
655
712
|
if (!workspace) {
|
|
656
713
|
return { output: `Workspace not found: ${workspaceName}` };
|
|
657
714
|
}
|
|
715
|
+
clearWorkspaceSession(context.session);
|
|
658
716
|
context.session.workspace = workspace.name;
|
|
659
717
|
context.session.workspacePath = workspace.workspacePath;
|
|
660
718
|
context.session.workspaceEnv = workspace.env;
|
|
661
719
|
context.session.workspaceEnvFile = workspace.envFile;
|
|
662
|
-
context.session.wikirc = null;
|
|
663
|
-
context.session.wikircConfig = null;
|
|
664
|
-
context.session.language = null;
|
|
665
|
-
context.session.llm = null;
|
|
666
|
-
context.session.mcp = null;
|
|
667
720
|
context.session.systemPrompt = loadWorkspaceSystemPrompt(workspace.workspacePath);
|
|
668
721
|
try {
|
|
669
722
|
step(`Workspace: loading ${workspace.name} config…`);
|
|
@@ -778,6 +831,7 @@ export async function handleSlashCommand(line, context) {
|
|
|
778
831
|
}
|
|
779
832
|
case 'start': {
|
|
780
833
|
const service = args[1];
|
|
834
|
+
if (service === 'agents') return runAgentCommand(startAgents, 'start');
|
|
781
835
|
try {
|
|
782
836
|
step(`Services: starting ${service ?? 'workspace services'}…`);
|
|
783
837
|
const output = await startService(context.session, service);
|
|
@@ -792,6 +846,7 @@ export async function handleSlashCommand(line, context) {
|
|
|
792
846
|
}
|
|
793
847
|
case 'stop': {
|
|
794
848
|
const service = args[1];
|
|
849
|
+
if (service === 'agents') return runAgentCommand(stopAgents, 'stop');
|
|
795
850
|
try {
|
|
796
851
|
step(`Services: stopping ${service ?? 'workspace services'}…`);
|
|
797
852
|
const output = await stopService(context.session, service);
|
|
@@ -955,12 +1010,50 @@ export async function handleSlashCommand(line, context) {
|
|
|
955
1010
|
case 'new': {
|
|
956
1011
|
return createWorkspaceCommand(context, args[1], args[2] ?? null);
|
|
957
1012
|
}
|
|
958
|
-
case 'workspace':
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
1013
|
+
case 'workspace':
|
|
1014
|
+
case 'workplace': {
|
|
1015
|
+
const subcommand = args[1] ?? 'list';
|
|
1016
|
+
if (subcommand === 'list') {
|
|
1017
|
+
return { output: formatWorkspaceList(listWorkspaces(), context.session) };
|
|
1018
|
+
}
|
|
1019
|
+
if (subcommand === 'delete') {
|
|
1020
|
+
const workspaceName = args[2];
|
|
1021
|
+
const confirmed = args.includes('--confirm');
|
|
1022
|
+
const workspaces = listWorkspaces();
|
|
1023
|
+
if (!workspaceName) return { output: workspaceDeletePrompt(workspaces) };
|
|
1024
|
+
const workspace = workspaces.find((item) => item.name === workspaceName);
|
|
1025
|
+
if (!workspace) return { output: `Workspace not found: ${workspaceName}` };
|
|
1026
|
+
if (!confirmed) {
|
|
1027
|
+
return {
|
|
1028
|
+
output: [
|
|
1029
|
+
`Confirm workspace deletion: ${workspace.name}`,
|
|
1030
|
+
`Path: ${workspace.workspacePath}`,
|
|
1031
|
+
'This removes the registry entry and deletes the workspace files.',
|
|
1032
|
+
'',
|
|
1033
|
+
`Run: /workspace delete ${workspace.name} --confirm`,
|
|
1034
|
+
].join('\n'),
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
try {
|
|
1038
|
+
step(`Workspace: deleting ${workspace.name}…`);
|
|
1039
|
+
const result = await deleteWorkspaceAndFiles(workspace, workspace.workspacePath);
|
|
1040
|
+
const wasCurrent = context.session.workspace === workspace.name
|
|
1041
|
+
|| context.session.workspacePath === workspace.workspacePath;
|
|
1042
|
+
if (wasCurrent) clearWorkspaceSession(context.session);
|
|
1043
|
+
return {
|
|
1044
|
+
output: [
|
|
1045
|
+
`Deleted workspace: ${workspace.name}`,
|
|
1046
|
+
`Removed registry entry and files at: ${result.deletedPath}`,
|
|
1047
|
+
wasCurrent ? 'Current session cleared. Use /use <workspace> or /workspace init <name> [path].' : null,
|
|
1048
|
+
].filter(Boolean).join('\n'),
|
|
1049
|
+
};
|
|
1050
|
+
} catch (err) {
|
|
1051
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1052
|
+
return { output: message };
|
|
1053
|
+
}
|
|
962
1054
|
}
|
|
963
|
-
return createWorkspaceCommand(context, args[2], args[3] ?? null);
|
|
1055
|
+
if (subcommand === 'init') return createWorkspaceCommand(context, args[2], args[3] ?? null);
|
|
1056
|
+
return { output: 'Usage: /workspace <list|delete <name> --confirm|init <name> [path]>' };
|
|
964
1057
|
}
|
|
965
1058
|
case 'wiki': {
|
|
966
1059
|
const subcommand = args[1];
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { existsSync, 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 { handleSlashCommand } from './slash.js';
|
|
8
|
+
import { completionContext } from '../shell/repl.js';
|
|
9
|
+
|
|
10
|
+
test('/workspace delete removes files and clears current session context after confirmation', async () => {
|
|
11
|
+
const root = await mkdtemp(join(tmpdir(), 'wiki-manager-delete-workspace-'));
|
|
12
|
+
const registryRoot = join(root, 'registry');
|
|
13
|
+
const registryPath = join(registryRoot, 'demo');
|
|
14
|
+
const workspacePath = join(root, 'workspace');
|
|
15
|
+
mkdirSync(registryPath, { recursive: true });
|
|
16
|
+
mkdirSync(workspacePath, { recursive: true });
|
|
17
|
+
writeFileSync(join(registryPath, '.env'), [
|
|
18
|
+
'WORKSPACE_NAME=demo',
|
|
19
|
+
`WIKI_WORKSPACE_PATH=${workspacePath}`,
|
|
20
|
+
'',
|
|
21
|
+
].join('\n'), 'utf8');
|
|
22
|
+
|
|
23
|
+
const previousDir = process.env.WIKI_WORKSPACES_DIR;
|
|
24
|
+
process.env.WIKI_WORKSPACES_DIR = registryRoot;
|
|
25
|
+
const session = {
|
|
26
|
+
workspace: 'demo',
|
|
27
|
+
workspacePath,
|
|
28
|
+
workspaceEnv: { WORKSPACE_NAME: 'demo' },
|
|
29
|
+
workspaceEnvFile: join(registryPath, '.env'),
|
|
30
|
+
wikirc: { profile: 'default' },
|
|
31
|
+
wikircConfig: {},
|
|
32
|
+
language: 'en-US',
|
|
33
|
+
llm: {},
|
|
34
|
+
mcp: {},
|
|
35
|
+
systemPrompt: 'prompt',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const prompt = await handleSlashCommand('/workspace delete demo', {
|
|
40
|
+
packageJson: { version: 'test' },
|
|
41
|
+
session,
|
|
42
|
+
});
|
|
43
|
+
assert.match(prompt.output, /Confirm workspace deletion: demo/);
|
|
44
|
+
assert.equal(existsSync(workspacePath), true);
|
|
45
|
+
assert.equal(session.workspace, 'demo');
|
|
46
|
+
|
|
47
|
+
const result = await handleSlashCommand('/workspace delete demo --confirm', {
|
|
48
|
+
packageJson: { version: 'test' },
|
|
49
|
+
session,
|
|
50
|
+
});
|
|
51
|
+
assert.match(result.output, /Deleted workspace: demo/);
|
|
52
|
+
assert.equal(session.workspace, null);
|
|
53
|
+
assert.equal(session.workspacePath, null);
|
|
54
|
+
assert.equal(session.llm, null);
|
|
55
|
+
assert.equal(session.mcp, null);
|
|
56
|
+
} finally {
|
|
57
|
+
if (previousDir === undefined) delete process.env.WIKI_WORKSPACES_DIR;
|
|
58
|
+
else process.env.WIKI_WORKSPACES_DIR = previousDir;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('/new without a name shows usage', async () => {
|
|
63
|
+
const result = await handleSlashCommand('/new', {
|
|
64
|
+
packageJson: { version: 'test' },
|
|
65
|
+
session: {},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
assert.match(result.output ?? '', /Usage/i);
|
|
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
|
+
});
|
package/src/core/compose.js
CHANGED
|
@@ -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
|
|
|
@@ -238,7 +239,7 @@ export async function composePs(session) {
|
|
|
238
239
|
return runCompose(session, ['ps'], { timeout: 30_000 });
|
|
239
240
|
}
|
|
240
241
|
|
|
241
|
-
function parseComposePsJson(output) {
|
|
242
|
+
export function parseComposePsJson(output) {
|
|
242
243
|
const text = output.trim();
|
|
243
244
|
if (!text) return [];
|
|
244
245
|
try {
|
|
@@ -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
|
-
|
|
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.
|
|
214
|
+
clientInfo: { name: 'wiki-manager', version: '0.6.47' },
|
|
215
215
|
},
|
|
216
216
|
}),
|
|
217
217
|
});
|