@dotdrelle/wiki-manager 0.6.30 → 0.6.34
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 +39 -1
- package/bin/wiki-manager.js +62 -3
- package/package.json +8 -7
- package/src/agent/graph.js +5 -10
- package/src/cli/wiki-manager.js +17 -2
- package/src/commands/slash.js +105 -27
- package/src/commands/slash.test.js +68 -0
- package/src/core/cacert.js +66 -0
- package/src/core/compose.js +10 -8
- package/src/core/env.js +13 -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/wikiSetup.js +156 -0
- package/src/core/wikirc.js +80 -1
- 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 +20 -8
- package/src/shell/tui.tsx +85 -13
- package/src/shell/useSession.ts +15 -7
- package/wiki-workspace +116 -23
|
@@ -24,14 +24,15 @@ export function SlashDialog(props: { context: any }) {
|
|
|
24
24
|
{(item: any, index) => (
|
|
25
25
|
<text
|
|
26
26
|
height={1}
|
|
27
|
-
fg={index() === context().
|
|
28
|
-
bg={index() === context().
|
|
27
|
+
fg={index() === context().visibleSelected ? '#111318' : '#D6DEE8'}
|
|
28
|
+
bg={index() === context().visibleSelected ? '#8BD5CA' : '#111318'}
|
|
29
29
|
>
|
|
30
30
|
{item.value.padEnd(16, ' ')} {item.description}
|
|
31
31
|
</text>
|
|
32
32
|
)}
|
|
33
33
|
</For>
|
|
34
|
-
<text
|
|
34
|
+
<text height={1} />
|
|
35
|
+
<text fg="#7F8C8D">[up/down navigate, Tab complete, Enter confirm, Esc clear]</text>
|
|
35
36
|
</box>
|
|
36
37
|
)}
|
|
37
38
|
</Show>
|
package/src/shell/repl.js
CHANGED
|
@@ -54,8 +54,8 @@ const COMMAND_COMPLETION_DESCRIPTIONS = {
|
|
|
54
54
|
'/help': 'Show shell commands.',
|
|
55
55
|
'/version': 'Print the wiki-manager version.',
|
|
56
56
|
'/exit': 'Exit the shell.',
|
|
57
|
-
'/
|
|
58
|
-
'/new': '
|
|
57
|
+
'/workspace': 'List, create, or delete workspaces.',
|
|
58
|
+
'/new': 'Open the setup wizard in the interactive TUI.',
|
|
59
59
|
'/use': 'Load a workspace and its default config.',
|
|
60
60
|
'/config': 'Inspect or switch .wikirc.yaml profiles.',
|
|
61
61
|
'/status': 'Show the current workspace and session state.',
|
|
@@ -89,7 +89,9 @@ const SUBCOMMAND_COMPLETION_DESCRIPTIONS = {
|
|
|
89
89
|
'/queue': 'Inspect or cancel queued MCP jobs.',
|
|
90
90
|
'/queue:cancel': 'Cancel a queued or running queue item.',
|
|
91
91
|
'/queue:clear': 'Clear finished queue items.',
|
|
92
|
-
'/workspace:init': '
|
|
92
|
+
'/workspace:init': 'Low-level workspace creation.',
|
|
93
|
+
'/workspace:list': 'List configured workspaces.',
|
|
94
|
+
'/workspace:delete': 'Delete one workspace after confirmation.',
|
|
93
95
|
'/wiki:run': 'Use the low-level llm-wiki CLI fallback.',
|
|
94
96
|
'/skills:edit': 'Edit one workspace skill file.',
|
|
95
97
|
'/skills:list': 'List workspace skills.',
|
|
@@ -106,7 +108,7 @@ export function createSession() {
|
|
|
106
108
|
wikircConfig: null,
|
|
107
109
|
language: null,
|
|
108
110
|
mcp: null,
|
|
109
|
-
commands: ['help', 'version', 'exit', '
|
|
111
|
+
commands: ['help', 'version', 'exit', 'workspace', 'new', 'use', 'config', 'status', 'services', 'start', 'stop', 'logs', 'mcp', 'wiki', 'skills', 'upload', 'uploads', 'clear', 'chat', 'agent', 'openui', 'queue'],
|
|
110
112
|
chatMode: true,
|
|
111
113
|
llm: null,
|
|
112
114
|
activities: {},
|
|
@@ -216,11 +218,16 @@ function completionValuesFor(parts, inputBuffer, session) {
|
|
|
216
218
|
.filter((item) => ['waiting', 'starting', 'running'].includes(item.status))
|
|
217
219
|
.map((item) => item.id);
|
|
218
220
|
}
|
|
219
|
-
if (command === '/workspace'
|
|
221
|
+
if (command === '/workspace') {
|
|
222
|
+
if (tokenIndex === 1) return ['delete', 'init', 'list'];
|
|
223
|
+
if (previousToken === 'delete') return workspaceNames();
|
|
224
|
+
if (parts[1] === 'delete' && tokenIndex === 3) return ['--confirm'];
|
|
225
|
+
}
|
|
220
226
|
if (command === '/wiki' && tokenIndex === 1) return ['run'];
|
|
221
227
|
if (command === '/skills' && tokenIndex === 1) return ['edit', 'list', 'run', 'show'];
|
|
222
228
|
if (command === '/skills' && ['edit', 'run', 'show'].includes(previousToken ?? '')) return skillNames(session);
|
|
223
|
-
if ((command === '/start' || command === '/stop'
|
|
229
|
+
if ((command === '/start' || command === '/stop') && tokenIndex === 1) return ['agents', ...serviceNames()];
|
|
230
|
+
if (command === '/logs' && tokenIndex === 1) return serviceNames();
|
|
224
231
|
return [];
|
|
225
232
|
}
|
|
226
233
|
|
|
@@ -289,6 +296,11 @@ export function completionDescription(value, parts) {
|
|
|
289
296
|
if (command === '/start') return serviceDescription(value) ?? 'Start this Docker Compose service.';
|
|
290
297
|
if (command === '/stop') return serviceDescription(value) ?? 'Stop this Docker Compose service.';
|
|
291
298
|
if (command === '/logs') return serviceDescription(value) ?? 'Show logs for this Docker Compose service.';
|
|
299
|
+
if (command === '/workspace') {
|
|
300
|
+
if (parts[1] === 'delete' && value === '--confirm') return 'Confirm workspace deletion.';
|
|
301
|
+
if (parts.at(-1) === 'delete') return 'Delete this workspace.';
|
|
302
|
+
return 'Choose a workspace action.';
|
|
303
|
+
}
|
|
292
304
|
if (command === '/mcp') return parts[1] === 'call' ? 'Use this MCP server.' : 'Filter tools to this MCP server.';
|
|
293
305
|
if (command === '/skills') {
|
|
294
306
|
if (parts.at(-1) === 'edit') return 'Edit this skill.';
|
|
@@ -1174,7 +1186,7 @@ async function runTuiShell({ agent, packageJson, session }) {
|
|
|
1174
1186
|
rerender();
|
|
1175
1187
|
|
|
1176
1188
|
try {
|
|
1177
|
-
const { output: wsOutput } = await handleSlashCommand('/
|
|
1189
|
+
const { output: wsOutput } = await handleSlashCommand('/workspace list', { packageJson, session });
|
|
1178
1190
|
if (wsOutput) messages.push({ role: 'command', content: wsOutput });
|
|
1179
1191
|
} finally {
|
|
1180
1192
|
clearInterval(spinnerInterval);
|
|
@@ -1316,7 +1328,7 @@ async function runTuiShell({ agent, packageJson, session }) {
|
|
|
1316
1328
|
}
|
|
1317
1329
|
return;
|
|
1318
1330
|
}
|
|
1319
|
-
if (key?.ctrl && key.name === 'c') {
|
|
1331
|
+
if ((key?.ctrl || key?.meta) && key.name === 'c') {
|
|
1320
1332
|
if (busy) {
|
|
1321
1333
|
currentAbortController?.abort();
|
|
1322
1334
|
spinnerLabel = 'Interrupting…';
|
package/src/shell/tui.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import { FileEditorDialog } from './FileEditorDialog';
|
|
|
6
6
|
import { LeftPane } from './LeftPane';
|
|
7
7
|
import { RightPane } from './RightPane';
|
|
8
8
|
import { SlashDialog } from './SlashDialog';
|
|
9
|
+
import { SetupWizard } from './SetupWizard';
|
|
9
10
|
import { useSession } from './useSession';
|
|
10
11
|
|
|
11
12
|
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
@@ -37,7 +38,10 @@ function copyToClipboard(text: string, renderer: unknown) {
|
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
function App(props: {
|
|
41
|
+
function App(props: {
|
|
42
|
+
agent: unknown;
|
|
43
|
+
packageJson: Record<string, unknown>;
|
|
44
|
+
}) {
|
|
41
45
|
const renderer = useRenderer();
|
|
42
46
|
const dimensions = useTerminalDimensions();
|
|
43
47
|
const [spinnerIndex, setSpinnerIndex] = createSignal(0);
|
|
@@ -100,11 +104,12 @@ function App(props: { agent: unknown; packageJson: Record<string, unknown> }) {
|
|
|
100
104
|
});
|
|
101
105
|
|
|
102
106
|
useKeyboard((key) => {
|
|
107
|
+
const keyName = String(key.name ?? '').toLowerCase();
|
|
103
108
|
if (state.activeEditor()) {
|
|
104
|
-
if (
|
|
109
|
+
if (keyName === 'escape') state.closeEditor();
|
|
105
110
|
return;
|
|
106
111
|
}
|
|
107
|
-
if (key.ctrl
|
|
112
|
+
if ((key.ctrl || key.meta) && keyName === 'c') {
|
|
108
113
|
if (state.busy()) {
|
|
109
114
|
state.abort();
|
|
110
115
|
return;
|
|
@@ -121,19 +126,19 @@ function App(props: { agent: unknown; packageJson: Record<string, unknown> }) {
|
|
|
121
126
|
}, 1600);
|
|
122
127
|
return;
|
|
123
128
|
}
|
|
124
|
-
if (key.ctrl &&
|
|
129
|
+
if (key.ctrl && keyName === 'q') {
|
|
125
130
|
state.toggleRightTab();
|
|
126
131
|
return;
|
|
127
132
|
}
|
|
128
133
|
if (state.busy()) return;
|
|
129
|
-
if (
|
|
130
|
-
if (
|
|
131
|
-
else if (
|
|
132
|
-
if (
|
|
133
|
-
else if (
|
|
134
|
-
else if (
|
|
135
|
-
else if (
|
|
136
|
-
else if (
|
|
134
|
+
if (keyName === 'tab') state.completeSelected();
|
|
135
|
+
if (keyName === 'pageup') state.scrollConversation(conversationRows());
|
|
136
|
+
else if (keyName === 'pagedown') state.scrollConversation(-conversationRows());
|
|
137
|
+
if (keyName === 'up' && state.slash()) state.moveCompletion(-1);
|
|
138
|
+
else if (keyName === 'down' && state.slash()) state.moveCompletion(1);
|
|
139
|
+
else if (keyName === 'up' && !state.input().includes('\n')) state.historyUp();
|
|
140
|
+
else if (keyName === 'down' && !state.input().includes('\n')) state.historyDown();
|
|
141
|
+
else if (keyName === 'escape') {
|
|
137
142
|
if (state.slash()) state.dismissSlash();
|
|
138
143
|
else state.setInput('');
|
|
139
144
|
}
|
|
@@ -196,10 +201,77 @@ function App(props: { agent: unknown; packageJson: Record<string, unknown> }) {
|
|
|
196
201
|
);
|
|
197
202
|
}
|
|
198
203
|
|
|
199
|
-
export async function runOpenTuiShell({
|
|
204
|
+
export async function runOpenTuiShell({
|
|
205
|
+
agent,
|
|
206
|
+
packageJson,
|
|
207
|
+
}: {
|
|
208
|
+
agent: unknown;
|
|
209
|
+
packageJson: Record<string, unknown>;
|
|
210
|
+
}) {
|
|
200
211
|
await render(() => <App agent={agent} packageJson={packageJson} />, {
|
|
201
212
|
exitOnCtrlC: false,
|
|
202
213
|
useMouse: true,
|
|
203
214
|
targetFps: 30,
|
|
204
215
|
});
|
|
216
|
+
return {};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function WizardApp(props: {
|
|
220
|
+
mode: 'startup' | 'setup';
|
|
221
|
+
gaps?: any[];
|
|
222
|
+
initialWorkspaceName?: string;
|
|
223
|
+
initialWorkspacePath?: string | null;
|
|
224
|
+
onDone: () => void;
|
|
225
|
+
}) {
|
|
226
|
+
const renderer = useRenderer();
|
|
227
|
+
const dimensions = useTerminalDimensions();
|
|
228
|
+
const close = () => {
|
|
229
|
+
props.onDone();
|
|
230
|
+
renderer.destroy();
|
|
231
|
+
};
|
|
232
|
+
return (
|
|
233
|
+
<box width="100%" height="100%" backgroundColor="#0B0D12">
|
|
234
|
+
<SetupWizard
|
|
235
|
+
mode={props.mode}
|
|
236
|
+
session={{}}
|
|
237
|
+
gaps={props.gaps}
|
|
238
|
+
width={dimensions().width}
|
|
239
|
+
height={dimensions().height}
|
|
240
|
+
initialRoute={props.mode === 'setup' ? 'workspace-name' : undefined}
|
|
241
|
+
initialWorkspaceName={props.initialWorkspaceName}
|
|
242
|
+
initialWorkspacePath={props.initialWorkspacePath ?? null}
|
|
243
|
+
closeOnDone={props.mode === 'setup'}
|
|
244
|
+
onComplete={close}
|
|
245
|
+
onClose={close}
|
|
246
|
+
/>
|
|
247
|
+
</box>
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export async function runStartupWizard(gaps: any[]) {
|
|
252
|
+
if (!gaps.length) return;
|
|
253
|
+
await new Promise<void>((resolve, reject) => {
|
|
254
|
+
render(() => <WizardApp mode="startup" gaps={gaps} onDone={resolve} />, {
|
|
255
|
+
exitOnCtrlC: false,
|
|
256
|
+
useMouse: true,
|
|
257
|
+
targetFps: 30,
|
|
258
|
+
}).catch(reject);
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export async function runSetupWizard(options: { workspaceName?: string; workspacePath?: string | null } = {}) {
|
|
263
|
+
await new Promise<void>((resolve, reject) => {
|
|
264
|
+
render(() => (
|
|
265
|
+
<WizardApp
|
|
266
|
+
mode="setup"
|
|
267
|
+
initialWorkspaceName={options.workspaceName}
|
|
268
|
+
initialWorkspacePath={options.workspacePath ?? null}
|
|
269
|
+
onDone={resolve}
|
|
270
|
+
/>
|
|
271
|
+
), {
|
|
272
|
+
exitOnCtrlC: false,
|
|
273
|
+
useMouse: true,
|
|
274
|
+
targetFps: 30,
|
|
275
|
+
}).catch(reject);
|
|
276
|
+
});
|
|
205
277
|
}
|
package/src/shell/useSession.ts
CHANGED
|
@@ -86,14 +86,22 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
86
86
|
session.llm ? 'llm ready' : 'llm limited',
|
|
87
87
|
].join(' ');
|
|
88
88
|
});
|
|
89
|
-
const
|
|
89
|
+
const matchContext = createMemo(() => {
|
|
90
90
|
if (input() === dismissedSlashInput()) return null;
|
|
91
|
-
|
|
91
|
+
return completionContext(input(), session);
|
|
92
|
+
});
|
|
93
|
+
const slash = createMemo(() => {
|
|
94
|
+
const context = matchContext();
|
|
92
95
|
if (!context) return null;
|
|
96
|
+
const selected = Math.min(selectedCompletion(), Math.max(0, context.matches.length - 1));
|
|
97
|
+
const visibleCount = 10;
|
|
98
|
+
const start = Math.max(0, Math.min(selected - Math.floor(visibleCount / 2), Math.max(0, context.matches.length - visibleCount)));
|
|
99
|
+
const visibleMatches = context.matches.slice(start, start + visibleCount);
|
|
93
100
|
return {
|
|
94
101
|
...context,
|
|
95
|
-
selected
|
|
96
|
-
|
|
102
|
+
selected,
|
|
103
|
+
visibleSelected: selected - start,
|
|
104
|
+
items: visibleMatches.map((value: string) => ({
|
|
97
105
|
value,
|
|
98
106
|
description: completionDescription(value, context.parts),
|
|
99
107
|
})),
|
|
@@ -249,8 +257,8 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
249
257
|
|
|
250
258
|
function completeSelected() {
|
|
251
259
|
const context = slash();
|
|
252
|
-
if (!context || context.
|
|
253
|
-
const selected = context.
|
|
260
|
+
if (!context || context.matches.length === 0) return;
|
|
261
|
+
const selected = context.matches[context.selected];
|
|
254
262
|
if (!selected) return;
|
|
255
263
|
const lastSpace = input().lastIndexOf(' ');
|
|
256
264
|
const base = input().endsWith(' ') ? input() : input().slice(0, lastSpace + 1);
|
|
@@ -259,7 +267,7 @@ export function useSession(props: { agent: unknown; packageJson: Record<string,
|
|
|
259
267
|
}
|
|
260
268
|
|
|
261
269
|
function moveCompletion(delta: number) {
|
|
262
|
-
const count = slash()?.
|
|
270
|
+
const count = slash()?.matches.length ?? 0;
|
|
263
271
|
if (!count) return;
|
|
264
272
|
setSelectedCompletion((value) => (value + delta + count) % count);
|
|
265
273
|
}
|
package/wiki-workspace
CHANGED
|
@@ -16,10 +16,66 @@ DEFAULT_MANAGER_DIR="$PWD"
|
|
|
16
16
|
WORKSPACES_DIR="${WIKI_WORKSPACES_DIR:-$DEFAULT_MANAGER_DIR/workspaces}"
|
|
17
17
|
MANAGER_ENV_FILE="${WIKI_MANAGER_ENV_FILE:-$DEFAULT_MANAGER_DIR/.env}"
|
|
18
18
|
MANAGER_ENDPOINTS_FILE="${WIKI_MANAGER_ENDPOINTS_FILE:-$DEFAULT_MANAGER_DIR/mcp.endpoints.json}"
|
|
19
|
+
MANAGER_STATE_DIR="$(cd "$(dirname "$MANAGER_ENV_FILE")" && pwd)"
|
|
20
|
+
MANAGER_RUNTIME_DIR="$MANAGER_STATE_DIR/.wiki-manager"
|
|
21
|
+
CACERT_PATH="${WIKI_MANAGER_CACERT_PATH:-}"
|
|
22
|
+
|
|
23
|
+
normalize_path() {
|
|
24
|
+
local value="$1"
|
|
25
|
+
if command -v wslpath >/dev/null 2>&1 && [[ "$value" =~ ^[A-Za-z]:[\\/] ]]; then
|
|
26
|
+
wslpath -u "$value"
|
|
27
|
+
return
|
|
28
|
+
fi
|
|
29
|
+
printf '%s\n' "$value"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
absolute_path() {
|
|
33
|
+
local value="$1"
|
|
34
|
+
local base="${2:-$ROOT_DIR}"
|
|
35
|
+
value="$(normalize_path "$value")"
|
|
36
|
+
if [[ "$value" = /* ]]; then
|
|
37
|
+
printf '%s\n' "$value"
|
|
38
|
+
else
|
|
39
|
+
printf '%s\n' "$base/$value"
|
|
40
|
+
fi
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
parse_global_options() {
|
|
44
|
+
local parsed=()
|
|
45
|
+
while [[ $# -gt 0 ]]; do
|
|
46
|
+
case "$1" in
|
|
47
|
+
--cacert)
|
|
48
|
+
[[ $# -ge 2 ]] || { printf 'Error: --cacert requires a file path\n' >&2; exit 2; }
|
|
49
|
+
CACERT_PATH="$2"
|
|
50
|
+
shift 2
|
|
51
|
+
;;
|
|
52
|
+
*)
|
|
53
|
+
parsed+=("$1")
|
|
54
|
+
shift
|
|
55
|
+
;;
|
|
56
|
+
esac
|
|
57
|
+
done
|
|
58
|
+
if [[ -n "$CACERT_PATH" ]]; then
|
|
59
|
+
CACERT_PATH="$(absolute_path "$CACERT_PATH" "$PWD")"
|
|
60
|
+
[[ -f "$CACERT_PATH" ]] || { printf 'Error: --cacert file not found: %s\n' "$CACERT_PATH" >&2; exit 2; }
|
|
61
|
+
export WIKI_MANAGER_CACERT_PATH="$CACERT_PATH"
|
|
62
|
+
export NODE_EXTRA_CA_CERTS="$CACERT_PATH"
|
|
63
|
+
export SSL_CERT_FILE="$CACERT_PATH"
|
|
64
|
+
export REQUESTS_CA_BUNDLE="$CACERT_PATH"
|
|
65
|
+
export CURL_CA_BUNDLE="$CACERT_PATH"
|
|
66
|
+
fi
|
|
67
|
+
set -- "${parsed[@]}"
|
|
68
|
+
PARSED_ARGS=("$@")
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
PARSED_ARGS=()
|
|
72
|
+
parse_global_options "$@"
|
|
73
|
+
set -- "${PARSED_ARGS[@]}"
|
|
19
74
|
|
|
20
75
|
usage() {
|
|
21
76
|
cat <<'EOF'
|
|
22
77
|
Usage:
|
|
78
|
+
wiki-workspace [--cacert <path>] <command> ...
|
|
23
79
|
wiki-workspace config <workspace> [path]
|
|
24
80
|
wiki-workspace up <workspace>
|
|
25
81
|
wiki-workspace wiki <workspace> <command> [args...]
|
|
@@ -50,6 +106,7 @@ Commands:
|
|
|
50
106
|
Configuration:
|
|
51
107
|
workspaces/<workspace>/.env
|
|
52
108
|
Override directory: WIKI_WORKSPACES_DIR=/path/to/dir
|
|
109
|
+
Local CA: --cacert /absolute/path/to/ca.pem (Docker must be able to read this host path)
|
|
53
110
|
EOF
|
|
54
111
|
}
|
|
55
112
|
|
|
@@ -58,26 +115,6 @@ die() {
|
|
|
58
115
|
exit 1
|
|
59
116
|
}
|
|
60
117
|
|
|
61
|
-
normalize_path() {
|
|
62
|
-
local value="$1"
|
|
63
|
-
if command -v wslpath >/dev/null 2>&1 && [[ "$value" =~ ^[A-Za-z]:[\\/] ]]; then
|
|
64
|
-
wslpath -u "$value"
|
|
65
|
-
return
|
|
66
|
-
fi
|
|
67
|
-
printf '%s\n' "$value"
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
absolute_path() {
|
|
71
|
-
local value="$1"
|
|
72
|
-
local base="${2:-$ROOT_DIR}"
|
|
73
|
-
value="$(normalize_path "$value")"
|
|
74
|
-
if [[ "$value" = /* ]]; then
|
|
75
|
-
printf '%s\n' "$value"
|
|
76
|
-
else
|
|
77
|
-
printf '%s\n' "$base/$value"
|
|
78
|
-
fi
|
|
79
|
-
}
|
|
80
|
-
|
|
81
118
|
workspace_env_file() {
|
|
82
119
|
local workspace="$1"
|
|
83
120
|
printf '%s/%s/.env\n' "$WORKSPACES_DIR" "$workspace"
|
|
@@ -169,9 +206,12 @@ agents_compose() {
|
|
|
169
206
|
[[ -f "$MANAGER_ENV_FILE" ]] && compose_env_args+=(--env-file "$MANAGER_ENV_FILE")
|
|
170
207
|
|
|
171
208
|
_agents_dc() {
|
|
209
|
+
local cacert_args=()
|
|
210
|
+
WORKSPACES_ROOT="$workspaces_root" AGENTS_DATA_DIR="$agents_data_dir" \
|
|
211
|
+
read_lines_into_array cacert_args cacert_compose_args "$agents_compose_file" "agents.cacert.compose.yml"
|
|
172
212
|
WORKSPACES_ROOT="$workspaces_root" AGENTS_DATA_DIR="$agents_data_dir" \
|
|
173
213
|
docker compose --project-directory "$DEFAULT_MANAGER_DIR" \
|
|
174
|
-
"${compose_env_args[@]}" -f "$agents_compose_file" -p wiki-agents "$@"
|
|
214
|
+
${compose_env_args[@]+"${compose_env_args[@]}"} -f "$agents_compose_file" ${cacert_args[@]+"${cacert_args[@]}"} -p wiki-agents "$@"
|
|
175
215
|
}
|
|
176
216
|
|
|
177
217
|
case "$subcommand" in
|
|
@@ -444,13 +484,19 @@ compose_for_workspace() {
|
|
|
444
484
|
local compose_env_args=()
|
|
445
485
|
[[ -f "$MANAGER_ENV_FILE" ]] && compose_env_args+=(--env-file "$MANAGER_ENV_FILE")
|
|
446
486
|
compose_env_args+=(--env-file "$workspace_env")
|
|
487
|
+
local cacert_args=()
|
|
488
|
+
WIKI_WORKSPACE_PATH="$ws_path" \
|
|
489
|
+
WIKI_SERVE_PORT="$serve_port" \
|
|
490
|
+
WIKI_MCP_PORT="$mcp_port" \
|
|
491
|
+
PRODUCTION_MCP_PORT="$prod_port" \
|
|
492
|
+
read_lines_into_array cacert_args cacert_compose_args "$ROOT_DIR/docker-compose.yml" "cacert.compose.yml"
|
|
447
493
|
|
|
448
494
|
WIKI_WORKSPACE_PATH="$ws_path" \
|
|
449
495
|
WIKI_SERVE_PORT="$serve_port" \
|
|
450
496
|
WIKI_MCP_PORT="$mcp_port" \
|
|
451
497
|
PRODUCTION_MCP_PORT="$prod_port" \
|
|
452
498
|
docker compose --project-directory "$DEFAULT_MANAGER_DIR" \
|
|
453
|
-
"${compose_env_args[@]}" -f "$ROOT_DIR/docker-compose.yml" -p "$project" "$@"
|
|
499
|
+
${compose_env_args[@]+"${compose_env_args[@]}"} -f "$ROOT_DIR/docker-compose.yml" ${cacert_args[@]+"${cacert_args[@]}"} -p "$project" "$@"
|
|
454
500
|
}
|
|
455
501
|
|
|
456
502
|
logs_args() {
|
|
@@ -474,6 +520,48 @@ read_lines_into_array() {
|
|
|
474
520
|
done < <("$@")
|
|
475
521
|
}
|
|
476
522
|
|
|
523
|
+
cacert_compose_args() {
|
|
524
|
+
local compose_file="$1"
|
|
525
|
+
local override_name="$2"
|
|
526
|
+
[[ -n "$CACERT_PATH" ]] || return 0
|
|
527
|
+
command -v node >/dev/null 2>&1 || die "--cacert requires node to generate Docker Compose overrides"
|
|
528
|
+
|
|
529
|
+
local services
|
|
530
|
+
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
|
|
541
|
+
)"
|
|
542
|
+
[[ -n "$services" ]] || return 0
|
|
543
|
+
|
|
544
|
+
mkdir -p "$MANAGER_RUNTIME_DIR"
|
|
545
|
+
local override_path="$MANAGER_RUNTIME_DIR/$override_name"
|
|
546
|
+
{
|
|
547
|
+
printf '# Generated by wiki-manager. Safe to delete.\n'
|
|
548
|
+
printf '# Rewritten when --cacert is used with a Docker Compose command.\n'
|
|
549
|
+
printf 'services:\n'
|
|
550
|
+
while IFS= read -r service || [[ -n "$service" ]]; do
|
|
551
|
+
[[ -n "$service" ]] || continue
|
|
552
|
+
printf ' %s:\n' "$service"
|
|
553
|
+
printf ' volumes:\n'
|
|
554
|
+
printf ' - %s:/wiki-manager-ca.pem:ro\n' "$CACERT_PATH"
|
|
555
|
+
printf ' environment:\n'
|
|
556
|
+
printf ' NODE_EXTRA_CA_CERTS: /wiki-manager-ca.pem\n'
|
|
557
|
+
printf ' SSL_CERT_FILE: /wiki-manager-ca.pem\n'
|
|
558
|
+
printf ' REQUESTS_CA_BUNDLE: /wiki-manager-ca.pem\n'
|
|
559
|
+
printf ' CURL_CA_BUNDLE: /wiki-manager-ca.pem\n'
|
|
560
|
+
done <<< "$services"
|
|
561
|
+
} > "$override_path"
|
|
562
|
+
printf '%s\n' -f "$override_path"
|
|
563
|
+
}
|
|
564
|
+
|
|
477
565
|
run_wiki() {
|
|
478
566
|
local workspace="$1"
|
|
479
567
|
shift
|
|
@@ -623,12 +711,17 @@ config_workspace() {
|
|
|
623
711
|
local compose_env_args=()
|
|
624
712
|
[[ -f "$MANAGER_ENV_FILE" ]] && compose_env_args+=(--env-file "$MANAGER_ENV_FILE")
|
|
625
713
|
compose_env_args+=(--env-file "$env_file")
|
|
714
|
+
local cacert_args=()
|
|
715
|
+
WIKI_WORKSPACE_PATH="$target_path" \
|
|
716
|
+
WIKI_SERVE_PORT="$serve_port" \
|
|
717
|
+
WIKI_MCP_PORT="$mcp_port" \
|
|
718
|
+
read_lines_into_array cacert_args cacert_compose_args "$ROOT_DIR/docker-compose.yml" "cacert.compose.yml"
|
|
626
719
|
|
|
627
720
|
WIKI_WORKSPACE_PATH="$target_path" \
|
|
628
721
|
WIKI_SERVE_PORT="$serve_port" \
|
|
629
722
|
WIKI_MCP_PORT="$mcp_port" \
|
|
630
723
|
docker compose --project-directory "$DEFAULT_MANAGER_DIR" \
|
|
631
|
-
"${compose_env_args[@]}" -f "$ROOT_DIR/docker-compose.yml" run --rm wiki init
|
|
724
|
+
${compose_env_args[@]+"${compose_env_args[@]}"} -f "$ROOT_DIR/docker-compose.yml" ${cacert_args[@]+"${cacert_args[@]}"} run --rm wiki init
|
|
632
725
|
|
|
633
726
|
printf 'Workspace ready: %s\n' "$target_path"
|
|
634
727
|
printf 'Start with: wiki-workspace up %s\n' "$workspace"
|