@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,240 @@
|
|
|
1
|
+
#!/usr/bin/env ts-node
|
|
2
|
+
|
|
3
|
+
import { verifyGhAuth, fetchOrgRepos, displayAuthInstructions } from '../../utils/github';
|
|
4
|
+
import { promptRepositorySelection, promptWorkspaceSelection } from '../../utils/prompts';
|
|
5
|
+
import { listWorkspaces } from '../../utils/workspace-meta';
|
|
6
|
+
import { logInfo, logSuccess, logError, logStep } from '../../utils/logger';
|
|
7
|
+
import { colorize } from '../../utils/colors';
|
|
8
|
+
import { saveSuite, getSuite, validateSuiteName } from '../../utils/suite';
|
|
9
|
+
import { SuiteEntry, WorkspaceSuite, PostCloneHook } from '../../types';
|
|
10
|
+
import inquirer from 'inquirer';
|
|
11
|
+
|
|
12
|
+
async function promptSuiteDetails(defaultName?: string): Promise<{ name: string; description: string }> {
|
|
13
|
+
const { name, description } = await inquirer.prompt([
|
|
14
|
+
{
|
|
15
|
+
type: 'input',
|
|
16
|
+
name: 'name',
|
|
17
|
+
message: 'Suite name:',
|
|
18
|
+
default: defaultName,
|
|
19
|
+
validate: (input: string) => validateSuiteName(input.trim()),
|
|
20
|
+
filter: (input: string) => input.trim(),
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
type: 'input',
|
|
24
|
+
name: 'description',
|
|
25
|
+
message: 'Suite description (optional):',
|
|
26
|
+
},
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
return { name, description: description || '' };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function promptPostCloneHooks(): Promise<PostCloneHook[] | undefined> {
|
|
33
|
+
const { addHooks } = await inquirer.prompt([
|
|
34
|
+
{
|
|
35
|
+
type: 'confirm',
|
|
36
|
+
name: 'addHooks',
|
|
37
|
+
message: 'Add post-clone hooks?',
|
|
38
|
+
default: false,
|
|
39
|
+
},
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
if (!addHooks) return undefined;
|
|
43
|
+
|
|
44
|
+
const commands: string[] = [];
|
|
45
|
+
console.log('Enter commands one at a time. Type "done" when finished.\n');
|
|
46
|
+
|
|
47
|
+
while (true) {
|
|
48
|
+
const { command } = await inquirer.prompt([
|
|
49
|
+
{
|
|
50
|
+
type: 'input',
|
|
51
|
+
name: 'command',
|
|
52
|
+
message: `Command ${commands.length + 1} (or "done"):`,
|
|
53
|
+
},
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
if (command.trim().toLowerCase() === 'done') break;
|
|
57
|
+
if (command.trim()) {
|
|
58
|
+
commands.push(command.trim());
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (commands.length === 0) return undefined;
|
|
63
|
+
|
|
64
|
+
return [{
|
|
65
|
+
commands,
|
|
66
|
+
continueOnError: true,
|
|
67
|
+
}];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function checkOverwrite(name: string): Promise<boolean> {
|
|
71
|
+
const existing = await getSuite(name);
|
|
72
|
+
if (!existing) return true;
|
|
73
|
+
|
|
74
|
+
const { overwrite } = await inquirer.prompt([
|
|
75
|
+
{
|
|
76
|
+
type: 'confirm',
|
|
77
|
+
name: 'overwrite',
|
|
78
|
+
message: `Suite "${name}" already exists (${existing.entries.length} repos). Overwrite?`,
|
|
79
|
+
default: false,
|
|
80
|
+
},
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
return overwrite;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function createInteractive() {
|
|
87
|
+
// Step 1: Verify gh CLI authentication
|
|
88
|
+
logStep(1, 4, 'Verifying GitHub CLI authentication...');
|
|
89
|
+
const isAuthenticated = await verifyGhAuth();
|
|
90
|
+
|
|
91
|
+
if (!isAuthenticated) {
|
|
92
|
+
logError('GitHub CLI is not authenticated');
|
|
93
|
+
displayAuthInstructions();
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
logSuccess('GitHub CLI authenticated');
|
|
98
|
+
|
|
99
|
+
// Step 2: Fetch repos
|
|
100
|
+
logStep(2, 4, 'Fetching repositories...');
|
|
101
|
+
const forceRefresh = process.argv.includes('--force-refresh') || process.argv.includes('-f');
|
|
102
|
+
const repos = await fetchOrgRepos({ forceRefresh });
|
|
103
|
+
|
|
104
|
+
if (repos.length === 0) {
|
|
105
|
+
logError('No repositories found');
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Step 3: Select repositories
|
|
110
|
+
logStep(3, 4, 'Select repositories for the suite...');
|
|
111
|
+
const selections = await promptRepositorySelection(repos);
|
|
112
|
+
|
|
113
|
+
const entries: SuiteEntry[] = selections.map(s => ({
|
|
114
|
+
repoName: s.repo.name,
|
|
115
|
+
directoryName: s.directoryName,
|
|
116
|
+
}));
|
|
117
|
+
|
|
118
|
+
// Step 4: Name and save
|
|
119
|
+
logStep(4, 5, 'Enter suite details...');
|
|
120
|
+
const { name, description } = await promptSuiteDetails();
|
|
121
|
+
|
|
122
|
+
if (!(await checkOverwrite(name))) {
|
|
123
|
+
logInfo('Suite creation cancelled');
|
|
124
|
+
process.exit(0);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Step 5: Optional hooks
|
|
128
|
+
logStep(5, 5, 'Post-clone hooks (optional)...');
|
|
129
|
+
const postCloneHooks = await promptPostCloneHooks();
|
|
130
|
+
|
|
131
|
+
const existing = await getSuite(name);
|
|
132
|
+
const now = new Date().toISOString();
|
|
133
|
+
const suite: WorkspaceSuite = {
|
|
134
|
+
name,
|
|
135
|
+
description,
|
|
136
|
+
entries,
|
|
137
|
+
createdAt: existing?.createdAt || now,
|
|
138
|
+
updatedAt: now,
|
|
139
|
+
...(postCloneHooks && { postCloneHooks }),
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
await saveSuite(suite);
|
|
143
|
+
logSuccess(`Suite "${colorize(name, 'cyan')}" created with ${entries.length} repositories`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function createFromWorkspace() {
|
|
147
|
+
// Step 1: Select workspace
|
|
148
|
+
logStep(1, 3, 'Select a workspace...');
|
|
149
|
+
const workspaces = await listWorkspaces();
|
|
150
|
+
|
|
151
|
+
if (workspaces.length === 0) {
|
|
152
|
+
logError('No workspaces found. Create one first with: workspace create');
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const workspaceName = await promptWorkspaceSelection(workspaces);
|
|
157
|
+
const workspace = workspaces.find(ws => ws.name === workspaceName);
|
|
158
|
+
|
|
159
|
+
if (!workspace?.metadata) {
|
|
160
|
+
logError('No metadata found for this workspace');
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const entries: SuiteEntry[] = workspace.metadata.repositories
|
|
165
|
+
.filter(r => r.status === 'success')
|
|
166
|
+
.map(r => ({
|
|
167
|
+
repoName: r.name,
|
|
168
|
+
directoryName: r.directoryName,
|
|
169
|
+
}));
|
|
170
|
+
|
|
171
|
+
console.log(`\n${colorize('Repositories from workspace:', 'cyan')} ${entries.length}`);
|
|
172
|
+
for (const e of entries) {
|
|
173
|
+
const alias = e.directoryName !== e.repoName ? ` (${e.repoName})` : '';
|
|
174
|
+
console.log(` - ${e.directoryName}${alias}`);
|
|
175
|
+
}
|
|
176
|
+
console.log('');
|
|
177
|
+
|
|
178
|
+
// Step 2: Name and save
|
|
179
|
+
logStep(2, 4, 'Enter suite details...');
|
|
180
|
+
const { name, description } = await promptSuiteDetails(workspaceName);
|
|
181
|
+
|
|
182
|
+
if (!(await checkOverwrite(name))) {
|
|
183
|
+
logInfo('Suite creation cancelled');
|
|
184
|
+
process.exit(0);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Step 3: Optional hooks
|
|
188
|
+
logStep(3, 4, 'Post-clone hooks (optional)...');
|
|
189
|
+
const postCloneHooks = await promptPostCloneHooks();
|
|
190
|
+
|
|
191
|
+
const existing = await getSuite(name);
|
|
192
|
+
const now = new Date().toISOString();
|
|
193
|
+
const suite: WorkspaceSuite = {
|
|
194
|
+
name,
|
|
195
|
+
description,
|
|
196
|
+
entries,
|
|
197
|
+
createdAt: existing?.createdAt || now,
|
|
198
|
+
updatedAt: now,
|
|
199
|
+
...(postCloneHooks && { postCloneHooks }),
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// Step 4: Save
|
|
203
|
+
logStep(4, 4, 'Saving suite...');
|
|
204
|
+
await saveSuite(suite);
|
|
205
|
+
logSuccess(`Suite "${colorize(name, 'cyan')}" created with ${entries.length} repositories`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export async function main() {
|
|
209
|
+
console.log('\n' + '='.repeat(60));
|
|
210
|
+
console.log(colorize('Create Suite', 'bright'));
|
|
211
|
+
console.log('='.repeat(60) + '\n');
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
const { mode } = await inquirer.prompt([
|
|
215
|
+
{
|
|
216
|
+
type: 'list',
|
|
217
|
+
name: 'mode',
|
|
218
|
+
message: 'How would you like to create the suite?',
|
|
219
|
+
choices: [
|
|
220
|
+
{ name: 'Pick repositories interactively', value: 'interactive' },
|
|
221
|
+
{ name: 'Save from an existing workspace', value: 'workspace' },
|
|
222
|
+
],
|
|
223
|
+
},
|
|
224
|
+
]);
|
|
225
|
+
|
|
226
|
+
if (mode === 'interactive') {
|
|
227
|
+
await createInteractive();
|
|
228
|
+
} else {
|
|
229
|
+
await createFromWorkspace();
|
|
230
|
+
}
|
|
231
|
+
} catch (error) {
|
|
232
|
+
logError('Failed to create suite');
|
|
233
|
+
if (error instanceof Error) {
|
|
234
|
+
logError(error.message);
|
|
235
|
+
}
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (require.main === module) main();
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env ts-node
|
|
2
|
+
|
|
3
|
+
import { listSuites, deleteSuite } from '../../utils/suite';
|
|
4
|
+
import { logInfo, logSuccess, logError } from '../../utils/logger';
|
|
5
|
+
import { colorize } from '../../utils/colors';
|
|
6
|
+
import inquirer from 'inquirer';
|
|
7
|
+
|
|
8
|
+
export async function main() {
|
|
9
|
+
console.log('\n' + '='.repeat(60));
|
|
10
|
+
console.log(colorize('Delete Suite', 'bright'));
|
|
11
|
+
console.log('='.repeat(60) + '\n');
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const suites = await listSuites();
|
|
15
|
+
|
|
16
|
+
if (suites.length === 0) {
|
|
17
|
+
logInfo('No suites found');
|
|
18
|
+
process.exit(0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const { suiteName } = await inquirer.prompt([
|
|
22
|
+
{
|
|
23
|
+
type: 'list',
|
|
24
|
+
name: 'suiteName',
|
|
25
|
+
message: 'Select a suite to delete:',
|
|
26
|
+
choices: suites.map(s => ({
|
|
27
|
+
name: `${s.name} (${s.entries.length} repos)${s.description ? ` - ${s.description}` : ''}`,
|
|
28
|
+
value: s.name,
|
|
29
|
+
short: s.name,
|
|
30
|
+
})),
|
|
31
|
+
pageSize: 15,
|
|
32
|
+
},
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
const { confirmed } = await inquirer.prompt([
|
|
36
|
+
{
|
|
37
|
+
type: 'confirm',
|
|
38
|
+
name: 'confirmed',
|
|
39
|
+
message: `Are you sure you want to delete suite "${suiteName}"?`,
|
|
40
|
+
default: false,
|
|
41
|
+
},
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
if (!confirmed) {
|
|
45
|
+
logInfo('Deletion cancelled');
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const deleted = await deleteSuite(suiteName);
|
|
50
|
+
|
|
51
|
+
if (deleted) {
|
|
52
|
+
logSuccess(`Suite "${colorize(suiteName, 'cyan')}" deleted`);
|
|
53
|
+
} else {
|
|
54
|
+
logError(`Suite "${suiteName}" not found`);
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {
|
|
57
|
+
logError('Failed to delete suite');
|
|
58
|
+
if (error instanceof Error) {
|
|
59
|
+
logError(error.message);
|
|
60
|
+
}
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (require.main === module) main();
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env ts-node
|
|
2
|
+
|
|
3
|
+
import * as fs from 'fs/promises';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { listSuites, exportSuite, exportAllSuites } from '../../utils/suite';
|
|
6
|
+
import { logInfo, logSuccess, logError } from '../../utils/logger';
|
|
7
|
+
import { colorize } from '../../utils/colors';
|
|
8
|
+
import inquirer from 'inquirer';
|
|
9
|
+
|
|
10
|
+
export async function main(opts?: { file?: string }) {
|
|
11
|
+
console.log('\n' + '='.repeat(60));
|
|
12
|
+
console.log(colorize('Export Suite', 'bright'));
|
|
13
|
+
console.log('='.repeat(60) + '\n');
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const suites = await listSuites();
|
|
17
|
+
|
|
18
|
+
if (suites.length === 0) {
|
|
19
|
+
logInfo('No suites found');
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { mode } = await inquirer.prompt([
|
|
24
|
+
{
|
|
25
|
+
type: 'list',
|
|
26
|
+
name: 'mode',
|
|
27
|
+
message: 'What would you like to export?',
|
|
28
|
+
choices: [
|
|
29
|
+
{ name: 'A single suite', value: 'single' },
|
|
30
|
+
{ name: 'All suites', value: 'all' },
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
let data;
|
|
36
|
+
let defaultFilename: string;
|
|
37
|
+
|
|
38
|
+
if (mode === 'single') {
|
|
39
|
+
const { suiteName } = await inquirer.prompt([
|
|
40
|
+
{
|
|
41
|
+
type: 'list',
|
|
42
|
+
name: 'suiteName',
|
|
43
|
+
message: 'Select a suite to export:',
|
|
44
|
+
choices: suites.map(s => ({
|
|
45
|
+
name: `${s.name} (${s.entries.length} repos)${s.description ? ` - ${s.description}` : ''}`,
|
|
46
|
+
value: s.name,
|
|
47
|
+
short: s.name,
|
|
48
|
+
})),
|
|
49
|
+
pageSize: 15,
|
|
50
|
+
},
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
data = await exportSuite(suiteName);
|
|
54
|
+
if (!data) {
|
|
55
|
+
logError(`Suite "${suiteName}" not found`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
defaultFilename = `${suiteName}-suite.json`;
|
|
60
|
+
} else {
|
|
61
|
+
data = await exportAllSuites();
|
|
62
|
+
defaultFilename = 'all-suites.json';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const outputArg = opts?.file;
|
|
66
|
+
let outputPath: string;
|
|
67
|
+
|
|
68
|
+
if (outputArg && !outputArg.startsWith('-')) {
|
|
69
|
+
outputPath = path.resolve(outputArg);
|
|
70
|
+
} else {
|
|
71
|
+
const { filePath } = await inquirer.prompt([
|
|
72
|
+
{
|
|
73
|
+
type: 'input',
|
|
74
|
+
name: 'filePath',
|
|
75
|
+
message: 'Output file path:',
|
|
76
|
+
default: `./${defaultFilename}`,
|
|
77
|
+
},
|
|
78
|
+
]);
|
|
79
|
+
outputPath = path.resolve(filePath);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
await fs.writeFile(outputPath, JSON.stringify(data, null, 2), 'utf-8');
|
|
83
|
+
logSuccess(`Exported to ${colorize(outputPath, 'cyan')}`);
|
|
84
|
+
|
|
85
|
+
const suiteCount = data.suites.length;
|
|
86
|
+
const totalRepos = data.suites.reduce((sum, s) => sum + s.entries.length, 0);
|
|
87
|
+
console.log(` ${suiteCount} suite(s), ${totalRepos} total repositories\n`);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
logError('Failed to export suite');
|
|
90
|
+
if (error instanceof Error) {
|
|
91
|
+
logError(error.message);
|
|
92
|
+
}
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (require.main === module) main();
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env ts-node
|
|
2
|
+
|
|
3
|
+
import * as fs from 'fs/promises';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { importSuites } from '../../utils/suite';
|
|
6
|
+
import { logInfo, logSuccess, logError, logWarning } from '../../utils/logger';
|
|
7
|
+
import { colorize } from '../../utils/colors';
|
|
8
|
+
import { SuitesStore } from '../../types';
|
|
9
|
+
import inquirer from 'inquirer';
|
|
10
|
+
|
|
11
|
+
export async function main(opts?: { file?: string }) {
|
|
12
|
+
console.log('\n' + '='.repeat(60));
|
|
13
|
+
console.log(colorize('Import Suite', 'bright'));
|
|
14
|
+
console.log('='.repeat(60) + '\n');
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const fileArg = opts?.file;
|
|
18
|
+
let filePath: string;
|
|
19
|
+
|
|
20
|
+
if (fileArg && !fileArg.startsWith('-')) {
|
|
21
|
+
filePath = path.resolve(fileArg);
|
|
22
|
+
} else {
|
|
23
|
+
const { inputPath } = await inquirer.prompt([
|
|
24
|
+
{
|
|
25
|
+
type: 'input',
|
|
26
|
+
name: 'inputPath',
|
|
27
|
+
message: 'Path to suite JSON file:',
|
|
28
|
+
validate: (input: string) => input.trim().length > 0 || 'File path is required',
|
|
29
|
+
},
|
|
30
|
+
]);
|
|
31
|
+
filePath = path.resolve(inputPath);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let content: string;
|
|
35
|
+
try {
|
|
36
|
+
content = await fs.readFile(filePath, 'utf-8');
|
|
37
|
+
} catch {
|
|
38
|
+
logError(`Could not read file: ${filePath}`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let data: SuitesStore;
|
|
43
|
+
try {
|
|
44
|
+
data = JSON.parse(content);
|
|
45
|
+
} catch {
|
|
46
|
+
logError('Invalid JSON file');
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!data.suites || !Array.isArray(data.suites)) {
|
|
51
|
+
logError('Invalid suite file format: missing "suites" array');
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log(`Found ${colorize(String(data.suites.length), 'yellow')} suite(s):\n`);
|
|
56
|
+
for (const suite of data.suites) {
|
|
57
|
+
console.log(` ${colorize(suite.name, 'cyan')} (${suite.entries?.length ?? 0} repos)`);
|
|
58
|
+
if (suite.description) {
|
|
59
|
+
console.log(` ${suite.description}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
console.log('');
|
|
63
|
+
|
|
64
|
+
const { overwrite } = await inquirer.prompt([
|
|
65
|
+
{
|
|
66
|
+
type: 'confirm',
|
|
67
|
+
name: 'overwrite',
|
|
68
|
+
message: 'Overwrite existing suites with the same name?',
|
|
69
|
+
default: false,
|
|
70
|
+
},
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
const result = await importSuites(data, overwrite);
|
|
74
|
+
|
|
75
|
+
if (result.imported.length > 0) {
|
|
76
|
+
logSuccess(`Imported: ${result.imported.join(', ')}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (result.skipped.length > 0) {
|
|
80
|
+
logInfo(`Skipped (already exist): ${result.skipped.join(', ')}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (result.errors.length > 0) {
|
|
84
|
+
logWarning('Errors:');
|
|
85
|
+
for (const err of result.errors) {
|
|
86
|
+
logError(` ${err}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log('');
|
|
91
|
+
} catch (error) {
|
|
92
|
+
logError('Failed to import suite');
|
|
93
|
+
if (error instanceof Error) {
|
|
94
|
+
logError(error.message);
|
|
95
|
+
}
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (require.main === module) main();
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { getGlobalOpts } from '../../utils/command-helpers';
|
|
3
|
+
|
|
4
|
+
export function registerSuiteCommands(parent: Command) {
|
|
5
|
+
const suite = parent
|
|
6
|
+
.command('suite')
|
|
7
|
+
.description('Suite management');
|
|
8
|
+
|
|
9
|
+
suite
|
|
10
|
+
.command('create')
|
|
11
|
+
.description('Create a new suite (pick repos or save from workspace)')
|
|
12
|
+
.action(async () => {
|
|
13
|
+
const { main } = await import('./create');
|
|
14
|
+
await main();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
suite
|
|
18
|
+
.command('list')
|
|
19
|
+
.description('List all saved suites')
|
|
20
|
+
.action(async () => {
|
|
21
|
+
const { main } = await import('./list');
|
|
22
|
+
await main();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
suite
|
|
26
|
+
.command('delete')
|
|
27
|
+
.description('Delete a suite')
|
|
28
|
+
.action(async () => {
|
|
29
|
+
const { main } = await import('./delete');
|
|
30
|
+
await main();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
suite
|
|
34
|
+
.command('export')
|
|
35
|
+
.description('Export suite(s) to JSON file for sharing')
|
|
36
|
+
.argument('[file]', 'Output file path')
|
|
37
|
+
.action(async (file) => {
|
|
38
|
+
const { main } = await import('./export');
|
|
39
|
+
await main({ file });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
suite
|
|
43
|
+
.command('import')
|
|
44
|
+
.description('Import suite(s) from JSON file')
|
|
45
|
+
.argument('[file]', 'Input file path')
|
|
46
|
+
.action(async (file) => {
|
|
47
|
+
const { main } = await import('./import');
|
|
48
|
+
await main({ file });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
suite
|
|
52
|
+
.command('use')
|
|
53
|
+
.description('Create workspace from a suite')
|
|
54
|
+
.option('-s, --suite <name>', 'Suite name')
|
|
55
|
+
.option('-w, --workspace <name>', 'Workspace name')
|
|
56
|
+
.action(async (opts, cmd) => {
|
|
57
|
+
const globalOpts = getGlobalOpts(cmd);
|
|
58
|
+
const { main } = await import('./use');
|
|
59
|
+
await main({
|
|
60
|
+
suite: opts.suite,
|
|
61
|
+
workspace: opts.workspace,
|
|
62
|
+
yes: globalOpts.yes,
|
|
63
|
+
forceRefresh: globalOpts.forceRefresh,
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env ts-node
|
|
2
|
+
|
|
3
|
+
import { listSuites } from '../../utils/suite';
|
|
4
|
+
import { logInfo, logError } from '../../utils/logger';
|
|
5
|
+
import { colorize } from '../../utils/colors';
|
|
6
|
+
|
|
7
|
+
export async function main() {
|
|
8
|
+
console.log('\n' + '='.repeat(60));
|
|
9
|
+
console.log(colorize('Saved Suites', 'bright'));
|
|
10
|
+
console.log('='.repeat(60) + '\n');
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const suites = await listSuites();
|
|
14
|
+
|
|
15
|
+
if (suites.length === 0) {
|
|
16
|
+
logInfo('No suites found');
|
|
17
|
+
console.log('\nCreate a new suite with:');
|
|
18
|
+
console.log(' workspace suite-create\n');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
for (const suite of suites) {
|
|
23
|
+
console.log(colorize(suite.name, 'cyan'));
|
|
24
|
+
if (suite.description) {
|
|
25
|
+
console.log(` ${suite.description}`);
|
|
26
|
+
}
|
|
27
|
+
console.log(` Repositories: ${colorize(String(suite.entries.length), 'yellow')}`);
|
|
28
|
+
|
|
29
|
+
for (const entry of suite.entries) {
|
|
30
|
+
const alias = entry.directoryName !== entry.repoName ? ` (${entry.repoName})` : '';
|
|
31
|
+
console.log(` - ${entry.directoryName}${alias}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const createdDate = new Date(suite.createdAt).toLocaleString();
|
|
35
|
+
const updatedDate = new Date(suite.updatedAt).toLocaleString();
|
|
36
|
+
console.log(` Created: ${createdDate}`);
|
|
37
|
+
if (suite.updatedAt !== suite.createdAt) {
|
|
38
|
+
console.log(` Updated: ${updatedDate}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log('');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log('='.repeat(60) + '\n');
|
|
45
|
+
} catch (error) {
|
|
46
|
+
logError('Failed to list suites');
|
|
47
|
+
if (error instanceof Error) {
|
|
48
|
+
logError(error.message);
|
|
49
|
+
}
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (require.main === module) main();
|