@dotdrelle/wiki-manager 0.6.31 → 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.
@@ -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().selected ? '#111318' : '#D6DEE8'}
28
- bg={index() === context().selected ? '#8BD5CA' : '#111318'}
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 fg="#7F8C8D">[up/down navigate Tab complete Enter confirm Esc clear]</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
- '/workspaces': 'List configured workspaces.',
58
- '/new': 'Create or configure a new workspace.',
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': 'Legacy form of /new.',
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', 'workspaces', 'new', 'use', 'config', 'status', 'services', 'start', 'stop', 'logs', 'mcp', 'wiki', 'skills', 'upload', 'uploads', 'clear', 'chat', 'agent', 'openui', 'queue'],
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' && tokenIndex === 1) return ['init'];
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' || command === '/logs') && tokenIndex === 1) return serviceNames();
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('/workspaces', { packageJson, session });
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: { agent: unknown; packageJson: Record<string, unknown> }) {
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 (key.name === 'escape') state.closeEditor();
109
+ if (keyName === 'escape') state.closeEditor();
105
110
  return;
106
111
  }
107
- if (key.ctrl && key.name === 'c') {
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 && key.name === 'q') {
129
+ if (key.ctrl && keyName === 'q') {
125
130
  state.toggleRightTab();
126
131
  return;
127
132
  }
128
133
  if (state.busy()) return;
129
- if (key.name === 'tab') state.completeSelected();
130
- if (key.name === 'pageup') state.scrollConversation(conversationRows());
131
- else if (key.name === 'pagedown') state.scrollConversation(-conversationRows());
132
- if (key.name === 'up' && state.slash()) state.moveCompletion(-1);
133
- else if (key.name === 'down' && state.slash()) state.moveCompletion(1);
134
- else if (key.name === 'up' && !state.input().includes('\n')) state.historyUp();
135
- else if (key.name === 'down' && !state.input().includes('\n')) state.historyDown();
136
- else if (key.name === 'escape') {
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({ agent, packageJson }: { agent: unknown; packageJson: Record<string, unknown> }) {
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
  }
@@ -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 slash = createMemo(() => {
89
+ const matchContext = createMemo(() => {
90
90
  if (input() === dismissedSlashInput()) return null;
91
- const context = completionContext(input(), session);
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: Math.min(selectedCompletion(), Math.max(0, context.matches.length - 1)),
96
- items: context.matches.slice(0, 10).map((value: string) => ({
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.items.length === 0) return;
253
- const selected = context.items[context.selected]?.value;
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()?.items.length ?? 0;
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
@@ -211,7 +211,7 @@ agents_compose() {
211
211
  read_lines_into_array cacert_args cacert_compose_args "$agents_compose_file" "agents.cacert.compose.yml"
212
212
  WORKSPACES_ROOT="$workspaces_root" AGENTS_DATA_DIR="$agents_data_dir" \
213
213
  docker compose --project-directory "$DEFAULT_MANAGER_DIR" \
214
- "${compose_env_args[@]}" -f "$agents_compose_file" "${cacert_args[@]}" -p wiki-agents "$@"
214
+ ${compose_env_args[@]+"${compose_env_args[@]}"} -f "$agents_compose_file" ${cacert_args[@]+"${cacert_args[@]}"} -p wiki-agents "$@"
215
215
  }
216
216
 
217
217
  case "$subcommand" in
@@ -496,7 +496,7 @@ compose_for_workspace() {
496
496
  WIKI_MCP_PORT="$mcp_port" \
497
497
  PRODUCTION_MCP_PORT="$prod_port" \
498
498
  docker compose --project-directory "$DEFAULT_MANAGER_DIR" \
499
- "${compose_env_args[@]}" -f "$ROOT_DIR/docker-compose.yml" "${cacert_args[@]}" -p "$project" "$@"
499
+ ${compose_env_args[@]+"${compose_env_args[@]}"} -f "$ROOT_DIR/docker-compose.yml" ${cacert_args[@]+"${cacert_args[@]}"} -p "$project" "$@"
500
500
  }
501
501
 
502
502
  logs_args() {
@@ -721,7 +721,7 @@ config_workspace() {
721
721
  WIKI_SERVE_PORT="$serve_port" \
722
722
  WIKI_MCP_PORT="$mcp_port" \
723
723
  docker compose --project-directory "$DEFAULT_MANAGER_DIR" \
724
- "${compose_env_args[@]}" -f "$ROOT_DIR/docker-compose.yml" "${cacert_args[@]}" 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
725
725
 
726
726
  printf 'Workspace ready: %s\n' "$target_path"
727
727
  printf 'Start with: wiki-workspace up %s\n' "$workspace"