@nemus-cli/nemus 0.2.1
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/CHANGELOG.md +86 -0
- package/LICENSE +21 -0
- package/README.md +275 -0
- package/assets/README.md +53 -0
- package/assets/banner.png +0 -0
- package/assets/banner.svg +66 -0
- package/assets/favicon-32.png +0 -0
- package/assets/favicon-64.png +0 -0
- package/assets/icon-256.png +0 -0
- package/assets/icon.svg +23 -0
- package/assets/logo-256.png +0 -0
- package/assets/logo-512.png +0 -0
- package/assets/logo-wordmark.svg +35 -0
- package/assets/logo.svg +32 -0
- package/bin/workspace.js +130 -0
- package/dist/cli/ai-prompt.js +421 -0
- package/dist/cli/dashboard/AgentList.js +61 -0
- package/dist/cli/dashboard/DashboardSidebar.js +280 -0
- package/dist/cli/dashboard/HelpFooter.js +73 -0
- package/dist/cli/dashboard/StatusBadge.js +50 -0
- package/dist/cli/dashboard/index.js +11 -0
- package/dist/cli/dashboard/types.js +2 -0
- package/dist/cli/tui.js +48 -0
- package/dist/commands/ai-prompt.js +22 -0
- package/dist/commands/analyze-deps.js +138 -0
- package/dist/commands/archive.js +113 -0
- package/dist/commands/branch/create.js +119 -0
- package/dist/commands/branch/index.js +104 -0
- package/dist/commands/branch/merge.js +90 -0
- package/dist/commands/branch/rebase.js +84 -0
- package/dist/commands/branch/switch.js +165 -0
- package/dist/commands/cache/index.js +80 -0
- package/dist/commands/cache/manager.js +198 -0
- package/dist/commands/cleanup.js +153 -0
- package/dist/commands/configure-claude.js +55 -0
- package/dist/commands/configure.js +231 -0
- package/dist/commands/create.js +204 -0
- package/dist/commands/dashboard/hook-handler.js +162 -0
- package/dist/commands/dashboard/index.js +258 -0
- package/dist/commands/dashboard/launcher.js +40 -0
- package/dist/commands/dashboard/session-picker.js +106 -0
- package/dist/commands/dashboard/sidebar.js +39 -0
- package/dist/commands/dashboard/workspace-picker.js +95 -0
- package/dist/commands/delete.js +194 -0
- package/dist/commands/deprecated-aliases.js +44 -0
- package/dist/commands/diff.js +129 -0
- package/dist/commands/doctor.js +141 -0
- package/dist/commands/generate-docs.js +77 -0
- package/dist/commands/ghq-status.js +72 -0
- package/dist/commands/go.js +154 -0
- package/dist/commands/history.js +124 -0
- package/dist/commands/list.js +191 -0
- package/dist/commands/mcp/index.js +69 -0
- package/dist/commands/migrate.js +296 -0
- package/dist/commands/remove-repo.js +217 -0
- package/dist/commands/report-bug.js +112 -0
- package/dist/commands/run.js +147 -0
- package/dist/commands/save-context.js +171 -0
- package/dist/commands/sessions.js +129 -0
- package/dist/commands/status.js +138 -0
- package/dist/commands/suite/create.js +212 -0
- package/dist/commands/suite/delete.js +64 -0
- package/dist/commands/suite/export.js +126 -0
- package/dist/commands/suite/import.js +128 -0
- package/dist/commands/suite/index.js +94 -0
- package/dist/commands/suite/list.js +49 -0
- package/dist/commands/suite/use.js +227 -0
- package/dist/commands/sync.js +117 -0
- package/dist/commands/update.js +203 -0
- package/dist/mcp/install.js +550 -0
- package/dist/mcp/server.js +398 -0
- package/dist/mcp/tools.js +922 -0
- package/dist/pi-extensions/permission-sync.ts.template +41 -0
- package/dist/pi-extensions/permissions-gate.ts.template +69 -0
- package/dist/pi-extensions/pr-fix-trigger.ts.template +124 -0
- package/dist/pi-extensions/review-reminder.ts.template +58 -0
- package/dist/pi-extensions/workspace-input-status.ts.template +919 -0
- package/dist/program.js +136 -0
- package/dist/scripts/workspace-table.py +394 -0
- package/dist/types/dashboard.js +11 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/agent-config.js +263 -0
- package/dist/utils/agent-state.js +178 -0
- package/dist/utils/banner.js +24 -0
- package/dist/utils/branch-operations.js +232 -0
- package/dist/utils/bug-report.js +355 -0
- package/dist/utils/cache.js +168 -0
- package/dist/utils/claude-integration.js +617 -0
- package/dist/utils/claude-sessions.js +260 -0
- package/dist/utils/cleanup-operations.js +120 -0
- package/dist/utils/colors.js +26 -0
- package/dist/utils/command-helpers.js +44 -0
- package/dist/utils/config.js +150 -0
- package/dist/utils/context-file.js +33 -0
- package/dist/utils/dashboard-hooks.js +172 -0
- package/dist/utils/dependency-analyzer.js +234 -0
- package/dist/utils/diff-operations.js +80 -0
- package/dist/utils/doc-generator.js +184 -0
- package/dist/utils/ghq-integration.js +332 -0
- package/dist/utils/git-operations.js +237 -0
- package/dist/utils/git-status.js +187 -0
- package/dist/utils/github.js +68 -0
- package/dist/utils/health-checks.js +345 -0
- package/dist/utils/history.js +150 -0
- package/dist/utils/hooks.js +100 -0
- package/dist/utils/logger.js +40 -0
- package/dist/utils/permission-sync.js +681 -0
- package/dist/utils/pi-extensions.js +224 -0
- package/dist/utils/progress.js +92 -0
- package/dist/utils/prompts.js +279 -0
- package/dist/utils/repo-resolver.js +260 -0
- package/dist/utils/retry.js +65 -0
- package/dist/utils/run-operations.js +30 -0
- package/dist/utils/suite.js +159 -0
- package/dist/utils/sync-operations.js +107 -0
- package/dist/utils/tmux-dashboard.js +184 -0
- package/dist/utils/validation.js +153 -0
- package/dist/utils/version-check.js +121 -0
- package/dist/utils/workspace-meta.js +185 -0
- package/install-shell-integration.sh +532 -0
- package/install.sh +401 -0
- package/package.json +90 -0
- package/remove-shell-block.awk +44 -0
- package/scripts/postinstall.js +85 -0
- package/skills/analyze-deps.md +23 -0
- package/skills/archive-workspace.md +26 -0
- package/skills/branch-create.md +18 -0
- package/skills/create-workspace.md +39 -0
- package/skills/delete-workspace.md +37 -0
- package/skills/list-org-repos.md +26 -0
- package/skills/list-suites.md +24 -0
- package/skills/list-workspaces.md +27 -0
- package/skills/refresh-workspace-docs.md +89 -0
- package/skills/remove-repo.md +25 -0
- package/skills/run-command.md +23 -0
- package/skills/save-context.md +68 -0
- package/skills/search-repos.md +31 -0
- package/skills/snapshot-list.md +18 -0
- package/skills/snapshot-save.md +12 -0
- package/skills/switch-branch.md +18 -0
- package/skills/update-cache.md +21 -0
- package/skills/update-workspace.md +66 -0
- package/skills/workspace-cleanup.md +18 -0
- package/skills/workspace-diff.md +23 -0
- package/skills/workspace-doctor.md +23 -0
- package/skills/workspace-info.md +45 -0
- package/skills/workspace-manager/SKILL.md +201 -0
- package/skills/workspace-manager/references/ai-prompt.md +44 -0
- package/skills/workspace-manager/references/analyze-deps.md +45 -0
- package/skills/workspace-manager/references/archive-workspace.md +38 -0
- package/skills/workspace-manager/references/branch-create.md +31 -0
- package/skills/workspace-manager/references/branch-merge.md +22 -0
- package/skills/workspace-manager/references/branch-rebase.md +22 -0
- package/skills/workspace-manager/references/cache-clear.md +18 -0
- package/skills/workspace-manager/references/cache-info.md +22 -0
- package/skills/workspace-manager/references/configure-claude.md +31 -0
- package/skills/workspace-manager/references/configure.md +35 -0
- package/skills/workspace-manager/references/create-workspace.md +45 -0
- package/skills/workspace-manager/references/dashboard.md +61 -0
- package/skills/workspace-manager/references/delete-workspace.md +33 -0
- package/skills/workspace-manager/references/generate-docs.md +36 -0
- package/skills/workspace-manager/references/ghq-status.md +28 -0
- package/skills/workspace-manager/references/go.md +33 -0
- package/skills/workspace-manager/references/history.md +41 -0
- package/skills/workspace-manager/references/list-org-repos.md +26 -0
- package/skills/workspace-manager/references/list-suites.md +21 -0
- package/skills/workspace-manager/references/list-workspaces.md +27 -0
- package/skills/workspace-manager/references/mcp.md +49 -0
- package/skills/workspace-manager/references/remove-repo.md +26 -0
- package/skills/workspace-manager/references/run-command.md +44 -0
- package/skills/workspace-manager/references/search-repos.md +24 -0
- package/skills/workspace-manager/references/sessions.md +27 -0
- package/skills/workspace-manager/references/suite-create.md +22 -0
- package/skills/workspace-manager/references/suite-delete.md +21 -0
- package/skills/workspace-manager/references/suite-export.md +19 -0
- package/skills/workspace-manager/references/suite-import.md +19 -0
- package/skills/workspace-manager/references/suite-use.md +27 -0
- package/skills/workspace-manager/references/switch-branch.md +25 -0
- package/skills/workspace-manager/references/update-cache.md +22 -0
- package/skills/workspace-manager/references/update-workspace.md +50 -0
- package/skills/workspace-manager/references/workspace-cleanup.md +33 -0
- package/skills/workspace-manager/references/workspace-diff.md +27 -0
- package/skills/workspace-manager/references/workspace-doctor.md +27 -0
- package/skills/workspace-manager/references/workspace-info.md +33 -0
- package/skills/workspace-manager/references/workspace-status.md +43 -0
- package/skills/workspace-manager/references/workspace-sync.md +26 -0
- package/skills/workspace-status.md +26 -0
- package/skills/workspace-sync.md +23 -0
- package/src/cli/ai-prompt.test.ts +667 -0
- package/src/cli/ai-prompt.ts +421 -0
- package/src/cli/dashboard/AgentList.tsx +44 -0
- package/src/cli/dashboard/DashboardSidebar.tsx +291 -0
- package/src/cli/dashboard/HelpFooter.tsx +22 -0
- package/src/cli/dashboard/StatusBadge.tsx +23 -0
- package/src/cli/dashboard/index.ts +5 -0
- package/src/cli/dashboard/types.ts +16 -0
- package/src/cli/tui.tsx +51 -0
- package/src/cli/workspace-cli.test.ts +30 -0
- package/src/commands/ai-prompt.ts +21 -0
- package/src/commands/analyze-deps.ts +119 -0
- package/src/commands/archive.ts +114 -0
- package/src/commands/branch/create.ts +94 -0
- package/src/commands/branch/index.ts +74 -0
- package/src/commands/branch/merge.ts +61 -0
- package/src/commands/branch/rebase.ts +54 -0
- package/src/commands/branch/switch.ts +150 -0
- package/src/commands/cache/index.ts +51 -0
- package/src/commands/cache/manager.ts +168 -0
- package/src/commands/cleanup.ts +132 -0
- package/src/commands/configure-claude.ts +55 -0
- package/src/commands/configure.ts +204 -0
- package/src/commands/create.ts +196 -0
- package/src/commands/dashboard/hook-handler.ts +138 -0
- package/src/commands/dashboard/index.ts +248 -0
- package/src/commands/dashboard/launcher.ts +42 -0
- package/src/commands/dashboard/session-picker.ts +73 -0
- package/src/commands/dashboard/sidebar.js +39 -0
- package/src/commands/dashboard/workspace-picker.ts +62 -0
- package/src/commands/delete.test.ts +250 -0
- package/src/commands/delete.ts +171 -0
- package/src/commands/deprecated-aliases.ts +55 -0
- package/src/commands/diff.ts +106 -0
- package/src/commands/doctor.ts +110 -0
- package/src/commands/generate-docs.ts +45 -0
- package/src/commands/ghq-status.ts +70 -0
- package/src/commands/go.ts +125 -0
- package/src/commands/history.ts +136 -0
- package/src/commands/list.test.ts +219 -0
- package/src/commands/list.ts +176 -0
- package/src/commands/mcp/index.ts +39 -0
- package/src/commands/migrate.ts +277 -0
- package/src/commands/remove-repo.ts +194 -0
- package/src/commands/report-bug.ts +88 -0
- package/src/commands/run.ts +125 -0
- package/src/commands/save-context.test.ts +113 -0
- package/src/commands/save-context.ts +141 -0
- package/src/commands/sessions.ts +99 -0
- package/src/commands/status.ts +111 -0
- package/src/commands/suite/create.ts +240 -0
- package/src/commands/suite/delete.ts +65 -0
- package/src/commands/suite/export.ts +97 -0
- package/src/commands/suite/import.ts +100 -0
- package/src/commands/suite/index.ts +66 -0
- package/src/commands/suite/list.ts +54 -0
- package/src/commands/suite/use.ts +232 -0
- package/src/commands/sync.ts +97 -0
- package/src/commands/update.ts +197 -0
- package/src/mcp/install.ts +529 -0
- package/src/mcp/server.ts +579 -0
- package/src/mcp/tools.test.ts +545 -0
- package/src/mcp/tools.ts +1019 -0
- package/src/mcp/update-workspace.test.ts +220 -0
- package/src/pi-extensions/permission-sync.ts.template +41 -0
- package/src/pi-extensions/permissions-gate.ts.template +69 -0
- package/src/pi-extensions/pr-fix-trigger.ts.template +124 -0
- package/src/pi-extensions/review-reminder.ts.template +58 -0
- package/src/pi-extensions/workspace-input-status.ts.template +919 -0
- package/src/program.ts +112 -0
- package/src/scripts/workspace-table.py +394 -0
- package/src/types/dashboard.ts +29 -0
- package/src/types/index.ts +109 -0
- package/src/types/ink.d.ts +171 -0
- package/src/utils/agent-config.test.ts +303 -0
- package/src/utils/agent-config.ts +264 -0
- package/src/utils/agent-state.test.ts +166 -0
- package/src/utils/agent-state.ts +135 -0
- package/src/utils/banner.ts +24 -0
- package/src/utils/branch-operations.ts +237 -0
- package/src/utils/bug-report.test.ts +174 -0
- package/src/utils/bug-report.ts +355 -0
- package/src/utils/cache.ts +150 -0
- package/src/utils/claude-integration.test.ts +381 -0
- package/src/utils/claude-integration.ts +649 -0
- package/src/utils/claude-sessions.test.ts +262 -0
- package/src/utils/claude-sessions.ts +251 -0
- package/src/utils/cleanup-operations.ts +94 -0
- package/src/utils/colors.ts +25 -0
- package/src/utils/command-helpers.ts +46 -0
- package/src/utils/config.test.ts +203 -0
- package/src/utils/config.ts +133 -0
- package/src/utils/context-file.ts +36 -0
- package/src/utils/dashboard-hooks.test.ts +168 -0
- package/src/utils/dashboard-hooks.ts +163 -0
- package/src/utils/dependency-analyzer.ts +243 -0
- package/src/utils/diff-operations.ts +59 -0
- package/src/utils/doc-generator.ts +187 -0
- package/src/utils/ghq-integration.test.ts +195 -0
- package/src/utils/ghq-integration.ts +317 -0
- package/src/utils/git-operations.test.ts +265 -0
- package/src/utils/git-operations.ts +233 -0
- package/src/utils/git-status.ts +171 -0
- package/src/utils/github.ts +74 -0
- package/src/utils/health-checks.ts +359 -0
- package/src/utils/history.ts +140 -0
- package/src/utils/hooks.ts +92 -0
- package/src/utils/logger.ts +37 -0
- package/src/utils/permission-sync.test.ts +593 -0
- package/src/utils/permission-sync.ts +720 -0
- package/src/utils/pi-extensions.test.ts +182 -0
- package/src/utils/pi-extensions.ts +226 -0
- package/src/utils/progress.ts +73 -0
- package/src/utils/prompts.test.ts +169 -0
- package/src/utils/prompts.ts +296 -0
- package/src/utils/repo-resolver.test.ts +313 -0
- package/src/utils/repo-resolver.ts +294 -0
- package/src/utils/retry.test.ts +124 -0
- package/src/utils/retry.ts +85 -0
- package/src/utils/run-operations.ts +38 -0
- package/src/utils/suite.test.ts +256 -0
- package/src/utils/suite.ts +144 -0
- package/src/utils/sync-operations.ts +87 -0
- package/src/utils/tmux-dashboard.ts +193 -0
- package/src/utils/validation.test.ts +139 -0
- package/src/utils/validation.ts +128 -0
- package/src/utils/version-check.test.ts +108 -0
- package/src/utils/version-check.ts +96 -0
- package/src/utils/workspace-meta.ts +173 -0
- package/sync-permissions.sh +137 -0
- package/tsconfig.json +20 -0
- package/uninstall.sh +162 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Session resume picker for the agent dashboard.
|
|
4
|
+
* Shows recent sessions sorted by last active, launches
|
|
5
|
+
* the appropriate agent CLI with --resume <id> --fork in a new dashboard pane.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getWorkspaceSessions, WorkspaceSession } from '../../utils/claude-sessions';
|
|
9
|
+
import { launchAgentPane } from './launcher';
|
|
10
|
+
import { getAgentPaths, getPrimaryAgent, ConcreteAgentType } from '../../utils/agent-config';
|
|
11
|
+
import { colorize } from '../../utils/colors';
|
|
12
|
+
import inquirer from 'inquirer';
|
|
13
|
+
import autocompletePrompt from 'inquirer-autocomplete-prompt';
|
|
14
|
+
import * as fuzzy from 'fuzzy';
|
|
15
|
+
|
|
16
|
+
inquirer.registerPrompt('autocomplete', autocompletePrompt);
|
|
17
|
+
|
|
18
|
+
async function main() {
|
|
19
|
+
try {
|
|
20
|
+
const sessions = await getWorkspaceSessions();
|
|
21
|
+
|
|
22
|
+
if (sessions.length === 0) {
|
|
23
|
+
console.log('No sessions found. Launch a new agent with n.');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const items = sessions.map(session => ({
|
|
28
|
+
session,
|
|
29
|
+
displayName: session.workspaceName,
|
|
30
|
+
timeLabel: session.lastActiveLabel,
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
const { selected } = await inquirer.prompt([{
|
|
34
|
+
type: 'autocomplete',
|
|
35
|
+
name: 'selected',
|
|
36
|
+
message: 'Select session to resume:',
|
|
37
|
+
source: async (_answersSoFar: any, input: string | undefined) => {
|
|
38
|
+
const searchInput = input || '';
|
|
39
|
+
const source = items.map(item => ({
|
|
40
|
+
name: `${item.displayName} ${colorize(item.timeLabel, 'dim')}`,
|
|
41
|
+
value: item,
|
|
42
|
+
}));
|
|
43
|
+
if (!searchInput) return source;
|
|
44
|
+
const results = fuzzy.filter(searchInput, items, { extract: (item) => item.displayName });
|
|
45
|
+
return results.map(result => ({
|
|
46
|
+
name: `${result.original.displayName} ${colorize(result.original.timeLabel, 'dim')}`,
|
|
47
|
+
value: result.original,
|
|
48
|
+
}));
|
|
49
|
+
},
|
|
50
|
+
pageSize: 15,
|
|
51
|
+
} as any]);
|
|
52
|
+
|
|
53
|
+
const item = selected as { session: WorkspaceSession };
|
|
54
|
+
const session = item.session;
|
|
55
|
+
const rawSessionId = session.sessionId;
|
|
56
|
+
// Sanitize session ID before interpolating into shell command
|
|
57
|
+
if (!/^[a-zA-Z0-9\-]{1,100}$/.test(rawSessionId)) {
|
|
58
|
+
console.error('Invalid session ID format — skipping');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
// Use the session's agent type for resume, fall back to primary if missing
|
|
62
|
+
// (sessions from before the multi-agent upgrade won't have agentType)
|
|
63
|
+
const agentType = session.agentType || getPrimaryAgent().type;
|
|
64
|
+
const agent = getAgentPaths(agentType as ConcreteAgentType);
|
|
65
|
+
launchAgentPane(session.workspacePath, agent.resumeCommand(rawSessionId));
|
|
66
|
+
console.log(`\nResuming ${agentType} session in ${session.workspaceName}`);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if ((error as any)?.name === 'ExitPromptError') process.exit(0);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
main();
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Dashboard sidebar entry point.
|
|
4
|
+
*
|
|
5
|
+
* This is a plain .js file (not .ts) to preserve the real dynamic import().
|
|
6
|
+
* TypeScript compiles `await import('ink')` into `require('ink')` in CJS mode,
|
|
7
|
+
* which fails because ink v5 is ESM-only with top-level await.
|
|
8
|
+
*
|
|
9
|
+
* By keeping this as .js, tsc copies it as-is to dist/ and the real
|
|
10
|
+
* import() call is preserved at runtime.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
async function main() {
|
|
14
|
+
try {
|
|
15
|
+
const React = require('react');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
// Real dynamic ESM import — NOT compiled by tsc
|
|
19
|
+
const { render, Box, Text, useInput, useApp } = await import('ink');
|
|
20
|
+
|
|
21
|
+
// CJS require for our compiled TSX components
|
|
22
|
+
const { DashboardSidebar } = require(path.join(__dirname, '..', '..', 'cli', 'dashboard'));
|
|
23
|
+
|
|
24
|
+
const App = () => React.createElement(DashboardSidebar, {
|
|
25
|
+
Box,
|
|
26
|
+
Text,
|
|
27
|
+
useInput,
|
|
28
|
+
useApp,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
render(React.createElement(App), { exitOnCtrlC: false });
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error('Dashboard sidebar failed to start:', error.message);
|
|
34
|
+
// Keep the pane alive so user can see the error
|
|
35
|
+
await new Promise(() => {});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
main();
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Workspace picker for the agent dashboard.
|
|
4
|
+
* Runs inside a tmux popup — lets the user select a workspace,
|
|
5
|
+
* then spawns a fresh claude agent pane in the dashboard session.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { listWorkspaces } from '../../utils/workspace-meta';
|
|
9
|
+
import { launchAgentPane } from './launcher';
|
|
10
|
+
import { getPrimaryAgent } from '../../utils/agent-config';
|
|
11
|
+
import inquirer from 'inquirer';
|
|
12
|
+
import autocompletePrompt from 'inquirer-autocomplete-prompt';
|
|
13
|
+
import * as fuzzy from 'fuzzy';
|
|
14
|
+
|
|
15
|
+
inquirer.registerPrompt('autocomplete', autocompletePrompt);
|
|
16
|
+
|
|
17
|
+
async function main() {
|
|
18
|
+
try {
|
|
19
|
+
const workspaces = await listWorkspaces();
|
|
20
|
+
|
|
21
|
+
if (workspaces.length === 0) {
|
|
22
|
+
console.log('No workspaces found. Create one with: workspace create');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const items = workspaces.map(ws => ({
|
|
27
|
+
name: ws.name,
|
|
28
|
+
path: ws.path,
|
|
29
|
+
repoCount: ws.metadata?.repositories?.length ?? 0,
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
const { selected } = await inquirer.prompt([{
|
|
33
|
+
type: 'autocomplete',
|
|
34
|
+
name: 'selected',
|
|
35
|
+
message: 'Select workspace to launch agent in:',
|
|
36
|
+
source: async (_answersSoFar: any, input: string | undefined) => {
|
|
37
|
+
const searchInput = input || '';
|
|
38
|
+
const source = items.map(item => ({
|
|
39
|
+
name: `${item.name} (${item.repoCount} repos)`,
|
|
40
|
+
value: item,
|
|
41
|
+
}));
|
|
42
|
+
if (!searchInput) return source;
|
|
43
|
+
const results = fuzzy.filter(searchInput, items, { extract: (item) => item.name });
|
|
44
|
+
return results.map(result => ({
|
|
45
|
+
name: `${result.original.name} (${result.original.repoCount} repos)`,
|
|
46
|
+
value: result.original,
|
|
47
|
+
}));
|
|
48
|
+
},
|
|
49
|
+
pageSize: 15,
|
|
50
|
+
} as any]);
|
|
51
|
+
|
|
52
|
+
const workspace = selected as { name: string; path: string };
|
|
53
|
+
const agent = getPrimaryAgent();
|
|
54
|
+
launchAgentPane(workspace.path, agent.launchCommand);
|
|
55
|
+
console.log(`\nLaunched agent in ${workspace.name}`);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if ((error as any)?.name === 'ExitPromptError') process.exit(0);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
main();
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
mockRm,
|
|
5
|
+
mockListWorkspaces,
|
|
6
|
+
mockPromptMultiWorkspaceSelection,
|
|
7
|
+
mockPrompt,
|
|
8
|
+
} = vi.hoisted(() => ({
|
|
9
|
+
mockRm: vi.fn(),
|
|
10
|
+
mockListWorkspaces: vi.fn(),
|
|
11
|
+
mockPromptMultiWorkspaceSelection: vi.fn(),
|
|
12
|
+
mockPrompt: vi.fn(),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
vi.mock('fs/promises', () => ({
|
|
16
|
+
rm: mockRm,
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
vi.mock('../utils/config', () => ({
|
|
20
|
+
WORKSPACES_DIR: '/test-workspaces',
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
vi.mock('../utils/workspace-meta', () => ({
|
|
24
|
+
listWorkspaces: mockListWorkspaces,
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
vi.mock('../utils/prompts', () => ({
|
|
28
|
+
promptMultiWorkspaceSelection: mockPromptMultiWorkspaceSelection,
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
vi.mock('../utils/logger', () => ({
|
|
32
|
+
logInfo: vi.fn(),
|
|
33
|
+
logSuccess: vi.fn(),
|
|
34
|
+
logError: vi.fn(),
|
|
35
|
+
logWarning: vi.fn(),
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
vi.mock('../utils/colors', () => ({
|
|
39
|
+
colorize: (text: string) => text,
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
vi.mock('inquirer', () => ({
|
|
43
|
+
default: {
|
|
44
|
+
prompt: mockPrompt,
|
|
45
|
+
},
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
import { main } from './delete';
|
|
49
|
+
import { logInfo, logSuccess, logError } from '../utils/logger';
|
|
50
|
+
|
|
51
|
+
function makeWorkspaceList(...names: string[]) {
|
|
52
|
+
return names.map(name => ({
|
|
53
|
+
name,
|
|
54
|
+
path: `/test-workspaces/${name}`,
|
|
55
|
+
metadata: {
|
|
56
|
+
repositories: [{ name: 'repo-a' }],
|
|
57
|
+
createdAt: '2025-01-01T00:00:00.000Z',
|
|
58
|
+
},
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
describe('delete-workspace main', () => {
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
vi.clearAllMocks();
|
|
65
|
+
mockRm.mockResolvedValue(undefined);
|
|
66
|
+
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('exits immediately when no workspaces exist', async () => {
|
|
70
|
+
mockListWorkspaces.mockResolvedValueOnce([]);
|
|
71
|
+
|
|
72
|
+
await main();
|
|
73
|
+
|
|
74
|
+
expect(logInfo).toHaveBeenCalledWith('No more workspaces remaining');
|
|
75
|
+
expect(mockPromptMultiWorkspaceSelection).not.toHaveBeenCalled();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('deletes selected workspaces and exits when user declines "delete more"', async () => {
|
|
79
|
+
mockListWorkspaces
|
|
80
|
+
.mockResolvedValueOnce(makeWorkspaceList('ws-a', 'ws-b')) // initial fetch
|
|
81
|
+
.mockResolvedValueOnce(makeWorkspaceList('ws-b')); // after deletion check
|
|
82
|
+
|
|
83
|
+
mockPromptMultiWorkspaceSelection.mockResolvedValueOnce(['ws-a']);
|
|
84
|
+
|
|
85
|
+
mockPrompt
|
|
86
|
+
.mockResolvedValueOnce({ confirmed: true }) // confirm deletion
|
|
87
|
+
.mockResolvedValueOnce({ deleteMore: false }); // don't delete more
|
|
88
|
+
|
|
89
|
+
await main();
|
|
90
|
+
|
|
91
|
+
expect(mockRm).toHaveBeenCalledWith('/test-workspaces/ws-a', { recursive: true, force: true });
|
|
92
|
+
expect(logSuccess).toHaveBeenCalledWith(expect.stringContaining('ws-a'));
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('deletes multiple workspaces in one batch', async () => {
|
|
96
|
+
mockListWorkspaces
|
|
97
|
+
.mockResolvedValueOnce(makeWorkspaceList('ws-a', 'ws-b', 'ws-c'))
|
|
98
|
+
.mockResolvedValueOnce(makeWorkspaceList('ws-c'));
|
|
99
|
+
|
|
100
|
+
mockPromptMultiWorkspaceSelection.mockResolvedValueOnce(['ws-a', 'ws-b']);
|
|
101
|
+
|
|
102
|
+
mockPrompt
|
|
103
|
+
.mockResolvedValueOnce({ confirmed: true })
|
|
104
|
+
.mockResolvedValueOnce({ deleteMore: false });
|
|
105
|
+
|
|
106
|
+
await main();
|
|
107
|
+
|
|
108
|
+
expect(mockRm).toHaveBeenCalledTimes(2);
|
|
109
|
+
expect(mockRm).toHaveBeenCalledWith('/test-workspaces/ws-a', { recursive: true, force: true });
|
|
110
|
+
expect(mockRm).toHaveBeenCalledWith('/test-workspaces/ws-b', { recursive: true, force: true });
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('skips deletion when user cancels confirmation', async () => {
|
|
114
|
+
mockListWorkspaces
|
|
115
|
+
.mockResolvedValueOnce(makeWorkspaceList('ws-a'))
|
|
116
|
+
.mockResolvedValueOnce(makeWorkspaceList('ws-a'));
|
|
117
|
+
|
|
118
|
+
mockPromptMultiWorkspaceSelection.mockResolvedValueOnce(['ws-a']);
|
|
119
|
+
|
|
120
|
+
mockPrompt
|
|
121
|
+
.mockResolvedValueOnce({ confirmed: false }) // cancel deletion
|
|
122
|
+
.mockResolvedValueOnce({ deleteMore: false }); // don't delete more
|
|
123
|
+
|
|
124
|
+
await main();
|
|
125
|
+
|
|
126
|
+
expect(mockRm).not.toHaveBeenCalled();
|
|
127
|
+
// Should still ask "delete more?" — not exit immediately
|
|
128
|
+
expect(mockPrompt).toHaveBeenCalledTimes(2);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('loops when user wants to delete more', async () => {
|
|
132
|
+
mockListWorkspaces
|
|
133
|
+
.mockResolvedValueOnce(makeWorkspaceList('ws-a', 'ws-b')) // 1st iteration
|
|
134
|
+
.mockResolvedValueOnce(makeWorkspaceList('ws-b')) // after 1st deletion
|
|
135
|
+
.mockResolvedValueOnce(makeWorkspaceList('ws-b')) // 2nd iteration
|
|
136
|
+
.mockResolvedValueOnce([]); // after 2nd deletion
|
|
137
|
+
|
|
138
|
+
mockPromptMultiWorkspaceSelection
|
|
139
|
+
.mockResolvedValueOnce(['ws-a'])
|
|
140
|
+
.mockResolvedValueOnce(['ws-b']);
|
|
141
|
+
|
|
142
|
+
mockPrompt
|
|
143
|
+
.mockResolvedValueOnce({ confirmed: true }) // confirm 1st deletion
|
|
144
|
+
.mockResolvedValueOnce({ deleteMore: true }) // delete more
|
|
145
|
+
.mockResolvedValueOnce({ confirmed: true }); // confirm 2nd deletion
|
|
146
|
+
|
|
147
|
+
await main();
|
|
148
|
+
|
|
149
|
+
expect(mockRm).toHaveBeenCalledTimes(2);
|
|
150
|
+
expect(mockRm).toHaveBeenCalledWith('/test-workspaces/ws-a', { recursive: true, force: true });
|
|
151
|
+
expect(mockRm).toHaveBeenCalledWith('/test-workspaces/ws-b', { recursive: true, force: true });
|
|
152
|
+
// Should see "no more workspaces remaining" after all deleted
|
|
153
|
+
expect(logInfo).toHaveBeenCalledWith('No more workspaces remaining');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('breaks out of loop when no workspaces remain after deletion', async () => {
|
|
157
|
+
mockListWorkspaces
|
|
158
|
+
.mockResolvedValueOnce(makeWorkspaceList('ws-only'))
|
|
159
|
+
.mockResolvedValueOnce([]); // none left after deletion
|
|
160
|
+
|
|
161
|
+
mockPromptMultiWorkspaceSelection.mockResolvedValueOnce(['ws-only']);
|
|
162
|
+
mockPrompt.mockResolvedValueOnce({ confirmed: true });
|
|
163
|
+
|
|
164
|
+
await main();
|
|
165
|
+
|
|
166
|
+
expect(mockRm).toHaveBeenCalledTimes(1);
|
|
167
|
+
expect(logInfo).toHaveBeenCalledWith('No more workspaces remaining');
|
|
168
|
+
// Should NOT have asked "delete more?" since there are none left
|
|
169
|
+
expect(mockPrompt).toHaveBeenCalledTimes(1); // only the confirm prompt
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('handles individual deletion failure without stopping others', async () => {
|
|
173
|
+
mockListWorkspaces
|
|
174
|
+
.mockResolvedValueOnce(makeWorkspaceList('ws-a', 'ws-b', 'ws-c'))
|
|
175
|
+
.mockResolvedValueOnce(makeWorkspaceList('ws-c'));
|
|
176
|
+
|
|
177
|
+
mockPromptMultiWorkspaceSelection.mockResolvedValueOnce(['ws-a', 'ws-b']);
|
|
178
|
+
|
|
179
|
+
mockRm
|
|
180
|
+
.mockRejectedValueOnce(new Error('permission denied')) // ws-a fails
|
|
181
|
+
.mockResolvedValueOnce(undefined); // ws-b succeeds
|
|
182
|
+
|
|
183
|
+
mockPrompt
|
|
184
|
+
.mockResolvedValueOnce({ confirmed: true })
|
|
185
|
+
.mockResolvedValueOnce({ deleteMore: false });
|
|
186
|
+
|
|
187
|
+
await main();
|
|
188
|
+
|
|
189
|
+
expect(mockRm).toHaveBeenCalledTimes(2);
|
|
190
|
+
expect(logError).toHaveBeenCalledWith(expect.stringContaining('ws-a'));
|
|
191
|
+
expect(logSuccess).toHaveBeenCalledWith(expect.stringContaining('ws-b'));
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('uses singular message for single workspace confirmation', async () => {
|
|
195
|
+
mockListWorkspaces
|
|
196
|
+
.mockResolvedValueOnce(makeWorkspaceList('ws-alpha'))
|
|
197
|
+
.mockResolvedValueOnce([]);
|
|
198
|
+
|
|
199
|
+
mockPromptMultiWorkspaceSelection.mockResolvedValueOnce(['ws-alpha']);
|
|
200
|
+
mockPrompt.mockResolvedValueOnce({ confirmed: true });
|
|
201
|
+
|
|
202
|
+
await main();
|
|
203
|
+
|
|
204
|
+
expect(mockPrompt).toHaveBeenCalledWith(
|
|
205
|
+
expect.arrayContaining([
|
|
206
|
+
expect.objectContaining({
|
|
207
|
+
message: 'Delete workspace ws-alpha?',
|
|
208
|
+
default: true,
|
|
209
|
+
}),
|
|
210
|
+
])
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('uses plural message for multiple workspace confirmation', async () => {
|
|
215
|
+
mockListWorkspaces
|
|
216
|
+
.mockResolvedValueOnce(makeWorkspaceList('ws-a', 'ws-b', 'ws-c'))
|
|
217
|
+
.mockResolvedValueOnce([]);
|
|
218
|
+
|
|
219
|
+
mockPromptMultiWorkspaceSelection.mockResolvedValueOnce(['ws-a', 'ws-b', 'ws-c']);
|
|
220
|
+
mockPrompt.mockResolvedValueOnce({ confirmed: true });
|
|
221
|
+
|
|
222
|
+
await main();
|
|
223
|
+
|
|
224
|
+
expect(mockPrompt).toHaveBeenCalledWith(
|
|
225
|
+
expect.arrayContaining([
|
|
226
|
+
expect.objectContaining({
|
|
227
|
+
message: 'Delete these 3 workspaces?',
|
|
228
|
+
default: true,
|
|
229
|
+
}),
|
|
230
|
+
])
|
|
231
|
+
);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('shows workspace details including repo count and creation date', async () => {
|
|
235
|
+
const logSpy = vi.spyOn(console, 'log');
|
|
236
|
+
|
|
237
|
+
mockListWorkspaces
|
|
238
|
+
.mockResolvedValueOnce(makeWorkspaceList('ws-a'))
|
|
239
|
+
.mockResolvedValueOnce([]);
|
|
240
|
+
|
|
241
|
+
mockPromptMultiWorkspaceSelection.mockResolvedValueOnce(['ws-a']);
|
|
242
|
+
mockPrompt.mockResolvedValueOnce({ confirmed: true });
|
|
243
|
+
|
|
244
|
+
await main();
|
|
245
|
+
|
|
246
|
+
const messages = logSpy.mock.calls.map(c => c[0]);
|
|
247
|
+
expect(messages).toContainEqual(expect.stringContaining('Repositories: 1'));
|
|
248
|
+
expect(messages).toContainEqual(expect.stringContaining('/test-workspaces/ws-a'));
|
|
249
|
+
});
|
|
250
|
+
});
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import * as fs from 'fs/promises';
|
|
3
|
+
import { WORKSPACES_DIR } from '../utils/config';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { listWorkspaces } from '../utils/workspace-meta';
|
|
6
|
+
import { promptMultiWorkspaceSelection } from '../utils/prompts';
|
|
7
|
+
import { logInfo, logSuccess, logError, logWarning } from '../utils/logger';
|
|
8
|
+
import { colorize } from '../utils/colors';
|
|
9
|
+
import inquirer from 'inquirer';
|
|
10
|
+
import { getGlobalOpts, parseList } from '../utils/command-helpers';
|
|
11
|
+
|
|
12
|
+
export function registerDeleteCommand(parent: Command) {
|
|
13
|
+
parent
|
|
14
|
+
.command('delete')
|
|
15
|
+
.aliases(['d', 'del'])
|
|
16
|
+
.description('Delete a workspace')
|
|
17
|
+
.option('-w, --workspace <names>', 'Comma-separated workspace names')
|
|
18
|
+
.action(async (opts, cmd) => {
|
|
19
|
+
const globalOpts = getGlobalOpts(cmd);
|
|
20
|
+
await handleDelete({ ...opts, ...globalOpts });
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function handleDelete(opts: {
|
|
25
|
+
workspace?: string;
|
|
26
|
+
yes: boolean;
|
|
27
|
+
forceRefresh: boolean;
|
|
28
|
+
}) {
|
|
29
|
+
console.log('\n' + '='.repeat(60));
|
|
30
|
+
console.log(colorize('Delete Workspace', 'bright'));
|
|
31
|
+
console.log('='.repeat(60) + '\n');
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
// Non-interactive path: workspace names provided via flag
|
|
35
|
+
if (opts.workspace) {
|
|
36
|
+
const selectedNames = parseList(opts.workspace);
|
|
37
|
+
const workspaces = await listWorkspaces();
|
|
38
|
+
|
|
39
|
+
for (const name of selectedNames) {
|
|
40
|
+
const workspace = workspaces.find(ws => ws.name === name);
|
|
41
|
+
const workspacePath = path.join(WORKSPACES_DIR, name);
|
|
42
|
+
if (workspace?.metadata) {
|
|
43
|
+
console.log(`${colorize(name, 'cyan')}`);
|
|
44
|
+
console.log(` Repositories: ${workspace.metadata.repositories.length}`);
|
|
45
|
+
console.log(` Path: ${workspacePath}`);
|
|
46
|
+
} else {
|
|
47
|
+
console.log(`${colorize(name, 'cyan')}: ${workspacePath}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
console.log('');
|
|
51
|
+
|
|
52
|
+
logWarning('This will permanently delete all cloned repositories in the selected workspaces!');
|
|
53
|
+
|
|
54
|
+
if (!opts.yes) {
|
|
55
|
+
const { confirmed } = await inquirer.prompt([
|
|
56
|
+
{
|
|
57
|
+
type: 'confirm',
|
|
58
|
+
name: 'confirmed',
|
|
59
|
+
message: selectedNames.length === 1
|
|
60
|
+
? `Delete workspace ${selectedNames[0]}?`
|
|
61
|
+
: `Delete these ${selectedNames.length} workspaces?`,
|
|
62
|
+
default: true,
|
|
63
|
+
},
|
|
64
|
+
]);
|
|
65
|
+
if (!confirmed) {
|
|
66
|
+
logInfo('Deletion cancelled');
|
|
67
|
+
process.exit(0);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (const name of selectedNames) {
|
|
72
|
+
const workspacePath = path.join(WORKSPACES_DIR, name);
|
|
73
|
+
try {
|
|
74
|
+
await fs.rm(workspacePath, { recursive: true, force: true });
|
|
75
|
+
logSuccess(`Deleted "${colorize(name, 'cyan')}"`);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
logError(`Failed to delete "${name}"`);
|
|
78
|
+
if (error instanceof Error) logError(error.message);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Interactive path
|
|
85
|
+
while (true) {
|
|
86
|
+
const workspaces = await listWorkspaces();
|
|
87
|
+
|
|
88
|
+
if (workspaces.length === 0) {
|
|
89
|
+
logInfo('No more workspaces remaining');
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const selectedNames = await promptMultiWorkspaceSelection(workspaces);
|
|
94
|
+
|
|
95
|
+
for (const name of selectedNames) {
|
|
96
|
+
const workspace = workspaces.find(ws => ws.name === name);
|
|
97
|
+
const workspacePath = path.join(WORKSPACES_DIR, name);
|
|
98
|
+
if (workspace?.metadata) {
|
|
99
|
+
console.log(`${colorize(name, 'cyan')}`);
|
|
100
|
+
console.log(` Repositories: ${workspace.metadata.repositories.length}`);
|
|
101
|
+
console.log(` Created: ${new Date(workspace.metadata.createdAt).toLocaleString()}`);
|
|
102
|
+
console.log(` Path: ${workspacePath}`);
|
|
103
|
+
} else {
|
|
104
|
+
console.log(`${colorize(name, 'cyan')}`);
|
|
105
|
+
console.log(` Path: ${workspacePath}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
console.log('');
|
|
109
|
+
|
|
110
|
+
logWarning('This will permanently delete all cloned repositories in the selected workspaces!');
|
|
111
|
+
|
|
112
|
+
const confirmMessage = selectedNames.length === 1
|
|
113
|
+
? `Delete workspace ${selectedNames[0]}?`
|
|
114
|
+
: `Delete these ${selectedNames.length} workspaces?`;
|
|
115
|
+
|
|
116
|
+
const { confirmed } = await inquirer.prompt([
|
|
117
|
+
{
|
|
118
|
+
type: 'confirm',
|
|
119
|
+
name: 'confirmed',
|
|
120
|
+
message: confirmMessage,
|
|
121
|
+
default: true,
|
|
122
|
+
},
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
if (confirmed) {
|
|
126
|
+
for (const name of selectedNames) {
|
|
127
|
+
const workspacePath = path.join(WORKSPACES_DIR, name);
|
|
128
|
+
try {
|
|
129
|
+
await fs.rm(workspacePath, { recursive: true, force: true });
|
|
130
|
+
logSuccess(`Deleted "${colorize(name, 'cyan')}"`);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
logError(`Failed to delete "${name}"`);
|
|
133
|
+
if (error instanceof Error) logError(error.message);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const remaining = await listWorkspaces();
|
|
139
|
+
if (remaining.length === 0) {
|
|
140
|
+
logInfo('No more workspaces remaining');
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const { deleteMore } = await inquirer.prompt([
|
|
145
|
+
{
|
|
146
|
+
type: 'confirm',
|
|
147
|
+
name: 'deleteMore',
|
|
148
|
+
message: 'Delete more workspaces?',
|
|
149
|
+
default: false,
|
|
150
|
+
},
|
|
151
|
+
]);
|
|
152
|
+
|
|
153
|
+
if (!deleteMore) {
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} catch (error) {
|
|
158
|
+
logError('Failed to delete workspace');
|
|
159
|
+
if (error instanceof Error) {
|
|
160
|
+
logError(error.message);
|
|
161
|
+
}
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Legacy main() for backward compatibility with tests.
|
|
168
|
+
*/
|
|
169
|
+
export async function main() {
|
|
170
|
+
await handleDelete({ yes: false, forceRefresh: false });
|
|
171
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
|
|
3
|
+
interface DeprecatedAlias {
|
|
4
|
+
name: string;
|
|
5
|
+
target: [string, string]; // [group, subcommand]
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const DEPRECATED_SHORT_ALIASES: DeprecatedAlias[] = [
|
|
9
|
+
{ name: 'sc', target: ['suite', 'create'] },
|
|
10
|
+
{ name: 'sls', target: ['suite', 'list'] },
|
|
11
|
+
{ name: 'sd', target: ['suite', 'delete'] },
|
|
12
|
+
{ name: 'se', target: ['suite', 'export'] },
|
|
13
|
+
{ name: 'si', target: ['suite', 'import'] },
|
|
14
|
+
{ name: 'fs', target: ['suite', 'use'] },
|
|
15
|
+
{ name: 'sb', target: ['branch', 'switch'] },
|
|
16
|
+
{ name: 'bc', target: ['branch', 'create'] },
|
|
17
|
+
{ name: 'bm', target: ['branch', 'merge'] },
|
|
18
|
+
{ name: 'br', target: ['branch', 'rebase'] },
|
|
19
|
+
{ name: 'uc', target: ['cache', 'refresh'] },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const DEPRECATED_FLAT_COMMANDS: DeprecatedAlias[] = [
|
|
23
|
+
{ name: 'suite-create', target: ['suite', 'create'] },
|
|
24
|
+
{ name: 'suite-list', target: ['suite', 'list'] },
|
|
25
|
+
{ name: 'suite-delete', target: ['suite', 'delete'] },
|
|
26
|
+
{ name: 'suite-export', target: ['suite', 'export'] },
|
|
27
|
+
{ name: 'suite-import', target: ['suite', 'import'] },
|
|
28
|
+
{ name: 'from-suite', target: ['suite', 'use'] },
|
|
29
|
+
{ name: 'switch-branch', target: ['branch', 'switch'] },
|
|
30
|
+
{ name: 'branch-create', target: ['branch', 'create'] },
|
|
31
|
+
{ name: 'branch-merge', target: ['branch', 'merge'] },
|
|
32
|
+
{ name: 'branch-rebase', target: ['branch', 'rebase'] },
|
|
33
|
+
{ name: 'update-cache', target: ['cache', 'refresh'] },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
export function registerDeprecatedAliases(program: Command) {
|
|
37
|
+
const all = [...DEPRECATED_SHORT_ALIASES, ...DEPRECATED_FLAT_COMMANDS];
|
|
38
|
+
|
|
39
|
+
for (const alias of all) {
|
|
40
|
+
program
|
|
41
|
+
.command(alias.name, { hidden: true })
|
|
42
|
+
.allowUnknownOption()
|
|
43
|
+
.allowExcessArguments()
|
|
44
|
+
.action(async () => {
|
|
45
|
+
const [group, sub] = alias.target;
|
|
46
|
+
process.stderr.write(
|
|
47
|
+
`[nemus] "w ${alias.name}" is deprecated, use "w ${group} ${sub}" instead\n`
|
|
48
|
+
);
|
|
49
|
+
// Re-parse with the correct group + subcommand
|
|
50
|
+
await program.parseAsync(
|
|
51
|
+
[process.argv[0], process.argv[1], group, sub, ...process.argv.slice(3)]
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|