@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.
Files changed (320) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/LICENSE +21 -0
  3. package/README.md +275 -0
  4. package/assets/README.md +53 -0
  5. package/assets/banner.png +0 -0
  6. package/assets/banner.svg +66 -0
  7. package/assets/favicon-32.png +0 -0
  8. package/assets/favicon-64.png +0 -0
  9. package/assets/icon-256.png +0 -0
  10. package/assets/icon.svg +23 -0
  11. package/assets/logo-256.png +0 -0
  12. package/assets/logo-512.png +0 -0
  13. package/assets/logo-wordmark.svg +35 -0
  14. package/assets/logo.svg +32 -0
  15. package/bin/workspace.js +130 -0
  16. package/dist/cli/ai-prompt.js +421 -0
  17. package/dist/cli/dashboard/AgentList.js +61 -0
  18. package/dist/cli/dashboard/DashboardSidebar.js +280 -0
  19. package/dist/cli/dashboard/HelpFooter.js +73 -0
  20. package/dist/cli/dashboard/StatusBadge.js +50 -0
  21. package/dist/cli/dashboard/index.js +11 -0
  22. package/dist/cli/dashboard/types.js +2 -0
  23. package/dist/cli/tui.js +48 -0
  24. package/dist/commands/ai-prompt.js +22 -0
  25. package/dist/commands/analyze-deps.js +138 -0
  26. package/dist/commands/archive.js +113 -0
  27. package/dist/commands/branch/create.js +119 -0
  28. package/dist/commands/branch/index.js +104 -0
  29. package/dist/commands/branch/merge.js +90 -0
  30. package/dist/commands/branch/rebase.js +84 -0
  31. package/dist/commands/branch/switch.js +165 -0
  32. package/dist/commands/cache/index.js +80 -0
  33. package/dist/commands/cache/manager.js +198 -0
  34. package/dist/commands/cleanup.js +153 -0
  35. package/dist/commands/configure-claude.js +55 -0
  36. package/dist/commands/configure.js +231 -0
  37. package/dist/commands/create.js +204 -0
  38. package/dist/commands/dashboard/hook-handler.js +162 -0
  39. package/dist/commands/dashboard/index.js +258 -0
  40. package/dist/commands/dashboard/launcher.js +40 -0
  41. package/dist/commands/dashboard/session-picker.js +106 -0
  42. package/dist/commands/dashboard/sidebar.js +39 -0
  43. package/dist/commands/dashboard/workspace-picker.js +95 -0
  44. package/dist/commands/delete.js +194 -0
  45. package/dist/commands/deprecated-aliases.js +44 -0
  46. package/dist/commands/diff.js +129 -0
  47. package/dist/commands/doctor.js +141 -0
  48. package/dist/commands/generate-docs.js +77 -0
  49. package/dist/commands/ghq-status.js +72 -0
  50. package/dist/commands/go.js +154 -0
  51. package/dist/commands/history.js +124 -0
  52. package/dist/commands/list.js +191 -0
  53. package/dist/commands/mcp/index.js +69 -0
  54. package/dist/commands/migrate.js +296 -0
  55. package/dist/commands/remove-repo.js +217 -0
  56. package/dist/commands/report-bug.js +112 -0
  57. package/dist/commands/run.js +147 -0
  58. package/dist/commands/save-context.js +171 -0
  59. package/dist/commands/sessions.js +129 -0
  60. package/dist/commands/status.js +138 -0
  61. package/dist/commands/suite/create.js +212 -0
  62. package/dist/commands/suite/delete.js +64 -0
  63. package/dist/commands/suite/export.js +126 -0
  64. package/dist/commands/suite/import.js +128 -0
  65. package/dist/commands/suite/index.js +94 -0
  66. package/dist/commands/suite/list.js +49 -0
  67. package/dist/commands/suite/use.js +227 -0
  68. package/dist/commands/sync.js +117 -0
  69. package/dist/commands/update.js +203 -0
  70. package/dist/mcp/install.js +550 -0
  71. package/dist/mcp/server.js +398 -0
  72. package/dist/mcp/tools.js +922 -0
  73. package/dist/pi-extensions/permission-sync.ts.template +41 -0
  74. package/dist/pi-extensions/permissions-gate.ts.template +69 -0
  75. package/dist/pi-extensions/pr-fix-trigger.ts.template +124 -0
  76. package/dist/pi-extensions/review-reminder.ts.template +58 -0
  77. package/dist/pi-extensions/workspace-input-status.ts.template +919 -0
  78. package/dist/program.js +136 -0
  79. package/dist/scripts/workspace-table.py +394 -0
  80. package/dist/types/dashboard.js +11 -0
  81. package/dist/types/index.js +2 -0
  82. package/dist/utils/agent-config.js +263 -0
  83. package/dist/utils/agent-state.js +178 -0
  84. package/dist/utils/banner.js +24 -0
  85. package/dist/utils/branch-operations.js +232 -0
  86. package/dist/utils/bug-report.js +355 -0
  87. package/dist/utils/cache.js +168 -0
  88. package/dist/utils/claude-integration.js +617 -0
  89. package/dist/utils/claude-sessions.js +260 -0
  90. package/dist/utils/cleanup-operations.js +120 -0
  91. package/dist/utils/colors.js +26 -0
  92. package/dist/utils/command-helpers.js +44 -0
  93. package/dist/utils/config.js +150 -0
  94. package/dist/utils/context-file.js +33 -0
  95. package/dist/utils/dashboard-hooks.js +172 -0
  96. package/dist/utils/dependency-analyzer.js +234 -0
  97. package/dist/utils/diff-operations.js +80 -0
  98. package/dist/utils/doc-generator.js +184 -0
  99. package/dist/utils/ghq-integration.js +332 -0
  100. package/dist/utils/git-operations.js +237 -0
  101. package/dist/utils/git-status.js +187 -0
  102. package/dist/utils/github.js +68 -0
  103. package/dist/utils/health-checks.js +345 -0
  104. package/dist/utils/history.js +150 -0
  105. package/dist/utils/hooks.js +100 -0
  106. package/dist/utils/logger.js +40 -0
  107. package/dist/utils/permission-sync.js +681 -0
  108. package/dist/utils/pi-extensions.js +224 -0
  109. package/dist/utils/progress.js +92 -0
  110. package/dist/utils/prompts.js +279 -0
  111. package/dist/utils/repo-resolver.js +260 -0
  112. package/dist/utils/retry.js +65 -0
  113. package/dist/utils/run-operations.js +30 -0
  114. package/dist/utils/suite.js +159 -0
  115. package/dist/utils/sync-operations.js +107 -0
  116. package/dist/utils/tmux-dashboard.js +184 -0
  117. package/dist/utils/validation.js +153 -0
  118. package/dist/utils/version-check.js +121 -0
  119. package/dist/utils/workspace-meta.js +185 -0
  120. package/install-shell-integration.sh +532 -0
  121. package/install.sh +401 -0
  122. package/package.json +90 -0
  123. package/remove-shell-block.awk +44 -0
  124. package/scripts/postinstall.js +85 -0
  125. package/skills/analyze-deps.md +23 -0
  126. package/skills/archive-workspace.md +26 -0
  127. package/skills/branch-create.md +18 -0
  128. package/skills/create-workspace.md +39 -0
  129. package/skills/delete-workspace.md +37 -0
  130. package/skills/list-org-repos.md +26 -0
  131. package/skills/list-suites.md +24 -0
  132. package/skills/list-workspaces.md +27 -0
  133. package/skills/refresh-workspace-docs.md +89 -0
  134. package/skills/remove-repo.md +25 -0
  135. package/skills/run-command.md +23 -0
  136. package/skills/save-context.md +68 -0
  137. package/skills/search-repos.md +31 -0
  138. package/skills/snapshot-list.md +18 -0
  139. package/skills/snapshot-save.md +12 -0
  140. package/skills/switch-branch.md +18 -0
  141. package/skills/update-cache.md +21 -0
  142. package/skills/update-workspace.md +66 -0
  143. package/skills/workspace-cleanup.md +18 -0
  144. package/skills/workspace-diff.md +23 -0
  145. package/skills/workspace-doctor.md +23 -0
  146. package/skills/workspace-info.md +45 -0
  147. package/skills/workspace-manager/SKILL.md +201 -0
  148. package/skills/workspace-manager/references/ai-prompt.md +44 -0
  149. package/skills/workspace-manager/references/analyze-deps.md +45 -0
  150. package/skills/workspace-manager/references/archive-workspace.md +38 -0
  151. package/skills/workspace-manager/references/branch-create.md +31 -0
  152. package/skills/workspace-manager/references/branch-merge.md +22 -0
  153. package/skills/workspace-manager/references/branch-rebase.md +22 -0
  154. package/skills/workspace-manager/references/cache-clear.md +18 -0
  155. package/skills/workspace-manager/references/cache-info.md +22 -0
  156. package/skills/workspace-manager/references/configure-claude.md +31 -0
  157. package/skills/workspace-manager/references/configure.md +35 -0
  158. package/skills/workspace-manager/references/create-workspace.md +45 -0
  159. package/skills/workspace-manager/references/dashboard.md +61 -0
  160. package/skills/workspace-manager/references/delete-workspace.md +33 -0
  161. package/skills/workspace-manager/references/generate-docs.md +36 -0
  162. package/skills/workspace-manager/references/ghq-status.md +28 -0
  163. package/skills/workspace-manager/references/go.md +33 -0
  164. package/skills/workspace-manager/references/history.md +41 -0
  165. package/skills/workspace-manager/references/list-org-repos.md +26 -0
  166. package/skills/workspace-manager/references/list-suites.md +21 -0
  167. package/skills/workspace-manager/references/list-workspaces.md +27 -0
  168. package/skills/workspace-manager/references/mcp.md +49 -0
  169. package/skills/workspace-manager/references/remove-repo.md +26 -0
  170. package/skills/workspace-manager/references/run-command.md +44 -0
  171. package/skills/workspace-manager/references/search-repos.md +24 -0
  172. package/skills/workspace-manager/references/sessions.md +27 -0
  173. package/skills/workspace-manager/references/suite-create.md +22 -0
  174. package/skills/workspace-manager/references/suite-delete.md +21 -0
  175. package/skills/workspace-manager/references/suite-export.md +19 -0
  176. package/skills/workspace-manager/references/suite-import.md +19 -0
  177. package/skills/workspace-manager/references/suite-use.md +27 -0
  178. package/skills/workspace-manager/references/switch-branch.md +25 -0
  179. package/skills/workspace-manager/references/update-cache.md +22 -0
  180. package/skills/workspace-manager/references/update-workspace.md +50 -0
  181. package/skills/workspace-manager/references/workspace-cleanup.md +33 -0
  182. package/skills/workspace-manager/references/workspace-diff.md +27 -0
  183. package/skills/workspace-manager/references/workspace-doctor.md +27 -0
  184. package/skills/workspace-manager/references/workspace-info.md +33 -0
  185. package/skills/workspace-manager/references/workspace-status.md +43 -0
  186. package/skills/workspace-manager/references/workspace-sync.md +26 -0
  187. package/skills/workspace-status.md +26 -0
  188. package/skills/workspace-sync.md +23 -0
  189. package/src/cli/ai-prompt.test.ts +667 -0
  190. package/src/cli/ai-prompt.ts +421 -0
  191. package/src/cli/dashboard/AgentList.tsx +44 -0
  192. package/src/cli/dashboard/DashboardSidebar.tsx +291 -0
  193. package/src/cli/dashboard/HelpFooter.tsx +22 -0
  194. package/src/cli/dashboard/StatusBadge.tsx +23 -0
  195. package/src/cli/dashboard/index.ts +5 -0
  196. package/src/cli/dashboard/types.ts +16 -0
  197. package/src/cli/tui.tsx +51 -0
  198. package/src/cli/workspace-cli.test.ts +30 -0
  199. package/src/commands/ai-prompt.ts +21 -0
  200. package/src/commands/analyze-deps.ts +119 -0
  201. package/src/commands/archive.ts +114 -0
  202. package/src/commands/branch/create.ts +94 -0
  203. package/src/commands/branch/index.ts +74 -0
  204. package/src/commands/branch/merge.ts +61 -0
  205. package/src/commands/branch/rebase.ts +54 -0
  206. package/src/commands/branch/switch.ts +150 -0
  207. package/src/commands/cache/index.ts +51 -0
  208. package/src/commands/cache/manager.ts +168 -0
  209. package/src/commands/cleanup.ts +132 -0
  210. package/src/commands/configure-claude.ts +55 -0
  211. package/src/commands/configure.ts +204 -0
  212. package/src/commands/create.ts +196 -0
  213. package/src/commands/dashboard/hook-handler.ts +138 -0
  214. package/src/commands/dashboard/index.ts +248 -0
  215. package/src/commands/dashboard/launcher.ts +42 -0
  216. package/src/commands/dashboard/session-picker.ts +73 -0
  217. package/src/commands/dashboard/sidebar.js +39 -0
  218. package/src/commands/dashboard/workspace-picker.ts +62 -0
  219. package/src/commands/delete.test.ts +250 -0
  220. package/src/commands/delete.ts +171 -0
  221. package/src/commands/deprecated-aliases.ts +55 -0
  222. package/src/commands/diff.ts +106 -0
  223. package/src/commands/doctor.ts +110 -0
  224. package/src/commands/generate-docs.ts +45 -0
  225. package/src/commands/ghq-status.ts +70 -0
  226. package/src/commands/go.ts +125 -0
  227. package/src/commands/history.ts +136 -0
  228. package/src/commands/list.test.ts +219 -0
  229. package/src/commands/list.ts +176 -0
  230. package/src/commands/mcp/index.ts +39 -0
  231. package/src/commands/migrate.ts +277 -0
  232. package/src/commands/remove-repo.ts +194 -0
  233. package/src/commands/report-bug.ts +88 -0
  234. package/src/commands/run.ts +125 -0
  235. package/src/commands/save-context.test.ts +113 -0
  236. package/src/commands/save-context.ts +141 -0
  237. package/src/commands/sessions.ts +99 -0
  238. package/src/commands/status.ts +111 -0
  239. package/src/commands/suite/create.ts +240 -0
  240. package/src/commands/suite/delete.ts +65 -0
  241. package/src/commands/suite/export.ts +97 -0
  242. package/src/commands/suite/import.ts +100 -0
  243. package/src/commands/suite/index.ts +66 -0
  244. package/src/commands/suite/list.ts +54 -0
  245. package/src/commands/suite/use.ts +232 -0
  246. package/src/commands/sync.ts +97 -0
  247. package/src/commands/update.ts +197 -0
  248. package/src/mcp/install.ts +529 -0
  249. package/src/mcp/server.ts +579 -0
  250. package/src/mcp/tools.test.ts +545 -0
  251. package/src/mcp/tools.ts +1019 -0
  252. package/src/mcp/update-workspace.test.ts +220 -0
  253. package/src/pi-extensions/permission-sync.ts.template +41 -0
  254. package/src/pi-extensions/permissions-gate.ts.template +69 -0
  255. package/src/pi-extensions/pr-fix-trigger.ts.template +124 -0
  256. package/src/pi-extensions/review-reminder.ts.template +58 -0
  257. package/src/pi-extensions/workspace-input-status.ts.template +919 -0
  258. package/src/program.ts +112 -0
  259. package/src/scripts/workspace-table.py +394 -0
  260. package/src/types/dashboard.ts +29 -0
  261. package/src/types/index.ts +109 -0
  262. package/src/types/ink.d.ts +171 -0
  263. package/src/utils/agent-config.test.ts +303 -0
  264. package/src/utils/agent-config.ts +264 -0
  265. package/src/utils/agent-state.test.ts +166 -0
  266. package/src/utils/agent-state.ts +135 -0
  267. package/src/utils/banner.ts +24 -0
  268. package/src/utils/branch-operations.ts +237 -0
  269. package/src/utils/bug-report.test.ts +174 -0
  270. package/src/utils/bug-report.ts +355 -0
  271. package/src/utils/cache.ts +150 -0
  272. package/src/utils/claude-integration.test.ts +381 -0
  273. package/src/utils/claude-integration.ts +649 -0
  274. package/src/utils/claude-sessions.test.ts +262 -0
  275. package/src/utils/claude-sessions.ts +251 -0
  276. package/src/utils/cleanup-operations.ts +94 -0
  277. package/src/utils/colors.ts +25 -0
  278. package/src/utils/command-helpers.ts +46 -0
  279. package/src/utils/config.test.ts +203 -0
  280. package/src/utils/config.ts +133 -0
  281. package/src/utils/context-file.ts +36 -0
  282. package/src/utils/dashboard-hooks.test.ts +168 -0
  283. package/src/utils/dashboard-hooks.ts +163 -0
  284. package/src/utils/dependency-analyzer.ts +243 -0
  285. package/src/utils/diff-operations.ts +59 -0
  286. package/src/utils/doc-generator.ts +187 -0
  287. package/src/utils/ghq-integration.test.ts +195 -0
  288. package/src/utils/ghq-integration.ts +317 -0
  289. package/src/utils/git-operations.test.ts +265 -0
  290. package/src/utils/git-operations.ts +233 -0
  291. package/src/utils/git-status.ts +171 -0
  292. package/src/utils/github.ts +74 -0
  293. package/src/utils/health-checks.ts +359 -0
  294. package/src/utils/history.ts +140 -0
  295. package/src/utils/hooks.ts +92 -0
  296. package/src/utils/logger.ts +37 -0
  297. package/src/utils/permission-sync.test.ts +593 -0
  298. package/src/utils/permission-sync.ts +720 -0
  299. package/src/utils/pi-extensions.test.ts +182 -0
  300. package/src/utils/pi-extensions.ts +226 -0
  301. package/src/utils/progress.ts +73 -0
  302. package/src/utils/prompts.test.ts +169 -0
  303. package/src/utils/prompts.ts +296 -0
  304. package/src/utils/repo-resolver.test.ts +313 -0
  305. package/src/utils/repo-resolver.ts +294 -0
  306. package/src/utils/retry.test.ts +124 -0
  307. package/src/utils/retry.ts +85 -0
  308. package/src/utils/run-operations.ts +38 -0
  309. package/src/utils/suite.test.ts +256 -0
  310. package/src/utils/suite.ts +144 -0
  311. package/src/utils/sync-operations.ts +87 -0
  312. package/src/utils/tmux-dashboard.ts +193 -0
  313. package/src/utils/validation.test.ts +139 -0
  314. package/src/utils/validation.ts +128 -0
  315. package/src/utils/version-check.test.ts +108 -0
  316. package/src/utils/version-check.ts +96 -0
  317. package/src/utils/workspace-meta.ts +173 -0
  318. package/sync-permissions.sh +137 -0
  319. package/tsconfig.json +20 -0
  320. 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();