@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,232 @@
1
+ #!/usr/bin/env ts-node
2
+
3
+ import * as fs from 'fs/promises';
4
+ import * as path from 'path';
5
+ import { WORKSPACES_DIR } from '../../utils/config';
6
+ import { fetchOrgRepos, verifyGhAuth, displayAuthInstructions } from '../../utils/github';
7
+ import { promptWorkspaceName, confirmWorkspaceCreation } from '../../utils/prompts';
8
+ import { cloneRepositories, reportCloneResults } from '../../utils/git-operations';
9
+ import { createMetadata, saveMetadata } from '../../utils/workspace-meta';
10
+ import { generateClaudeContext } from '../../utils/claude-integration';
11
+ import { logInfo, logSuccess, logError, logStep } from '../../utils/logger';
12
+ import { colorize } from '../../utils/colors';
13
+ import { listSuites } from '../../utils/suite';
14
+ import { runPostCloneHooks } from '../../utils/hooks';
15
+ import { GitHubRepo } from '../../types';
16
+ import inquirer from 'inquirer';
17
+ import { validateWorkspaceName, checkWorkspaceExists, sanitizeWorkspaceName, resolveWorkspaceNameConflict } from '../../utils/validation';
18
+
19
+ export interface SuiteUseOpts {
20
+ suite?: string;
21
+ workspace?: string;
22
+ yes?: boolean;
23
+ forceRefresh?: boolean;
24
+ }
25
+
26
+ export async function main(opts?: SuiteUseOpts) {
27
+ console.log('\n' + '='.repeat(60));
28
+ console.log(colorize('Create Workspace from Suite', 'bright'));
29
+ console.log('='.repeat(60) + '\n');
30
+
31
+ // Read from opts (Commander) or fall back to process.argv (legacy/direct)
32
+ const suiteFlag = opts?.suite;
33
+ const workspaceFlag = opts?.workspace;
34
+ const yes = opts?.yes ?? false;
35
+ const nonInteractive = !process.stdout.isTTY;
36
+
37
+ try {
38
+ // Step 1: Load suites
39
+ logStep(1, 7, 'Loading suites...');
40
+ const suites = await listSuites();
41
+
42
+ if (suites.length === 0) {
43
+ logError('No suites found. Create one first with: workspace suite-create');
44
+ process.exit(1);
45
+ }
46
+
47
+ logSuccess(`Found ${suites.length} suite(s)`);
48
+
49
+ // Step 2: Select suite
50
+ logStep(2, 7, 'Select suite...');
51
+ if (!suiteFlag && nonInteractive) {
52
+ logError("Missing required option: --suite"); logError("Usage: w suite use --suite <name> --workspace <name>");
53
+ process.exit(1);
54
+ }
55
+
56
+ let selectedSuite: typeof suites[0];
57
+
58
+ if (suiteFlag) {
59
+ const found = suites.find(s => s.name === suiteFlag);
60
+ if (!found) {
61
+ logError(`Suite not found: ${suiteFlag}`);
62
+ logError(`Available suites: ${suites.map(s => s.name).join(', ')}`);
63
+ process.exit(1);
64
+ }
65
+ selectedSuite = found;
66
+ } else {
67
+ const { suite } = await inquirer.prompt([
68
+ {
69
+ type: 'list',
70
+ name: 'suite',
71
+ message: 'Select a suite:',
72
+ choices: suites.map(s => ({
73
+ name: `${s.name} (${s.entries.length} repos)${s.description ? ` - ${s.description}` : ''}`,
74
+ value: s,
75
+ short: s.name,
76
+ })),
77
+ pageSize: 15,
78
+ },
79
+ ]);
80
+ selectedSuite = suite;
81
+ }
82
+
83
+ console.log(`\n${colorize('Suite repositories:', 'cyan')} ${selectedSuite.entries.length}`);
84
+ for (const entry of selectedSuite.entries) {
85
+ const alias = entry.directoryName !== entry.repoName ? ` (${entry.repoName})` : '';
86
+ console.log(` - ${entry.directoryName}${alias}`);
87
+ }
88
+ console.log('');
89
+
90
+ // Step 3: Verify gh CLI authentication
91
+ logStep(3, 7, 'Verifying GitHub CLI authentication...');
92
+ const isAuthenticated = await verifyGhAuth();
93
+
94
+ if (!isAuthenticated) {
95
+ logError('GitHub CLI is not authenticated');
96
+ displayAuthInstructions();
97
+ process.exit(1);
98
+ }
99
+
100
+ logSuccess('GitHub CLI authenticated');
101
+
102
+ // Step 4: Fetch repos and resolve suite entries
103
+ logStep(4, 7, 'Fetching repositories...');
104
+ const forceRefresh = opts?.forceRefresh ?? false;
105
+ const allRepos = await fetchOrgRepos({ forceRefresh });
106
+ const repoMap = new Map(allRepos.map(repo => [repo.name, repo]));
107
+
108
+ const resolvedEntries: Array<{ repo: GitHubRepo; directoryName: string }> = [];
109
+ const missing: string[] = [];
110
+
111
+ for (const entry of selectedSuite.entries) {
112
+ const repo = repoMap.get(entry.repoName);
113
+ if (repo) {
114
+ resolvedEntries.push({ repo, directoryName: entry.directoryName });
115
+ } else {
116
+ missing.push(entry.repoName);
117
+ }
118
+ }
119
+
120
+ if (missing.length > 0) {
121
+ logInfo(`${missing.length} repositories from suite not found or not accessible:`);
122
+ missing.forEach(name => console.log(` - ${colorize(name, 'yellow')}`));
123
+ console.log('');
124
+ }
125
+
126
+ if (resolvedEntries.length === 0) {
127
+ logError('No repositories from the suite are available');
128
+ process.exit(1);
129
+ }
130
+
131
+ logInfo(`${resolvedEntries.length} repositories will be cloned`);
132
+
133
+ // Step 5: Workspace name
134
+ logStep(5, 7, 'Enter workspace name...');
135
+ if (!workspaceFlag && nonInteractive) {
136
+ logError("Missing required option: --workspace"); logError("Usage: w suite use --suite <name> --workspace <name>");
137
+ process.exit(1);
138
+ }
139
+
140
+ let workspaceName: string;
141
+
142
+ if (workspaceFlag) {
143
+ workspaceName = sanitizeWorkspaceName(workspaceFlag);
144
+ const validation = validateWorkspaceName(workspaceName);
145
+ if (validation !== true) {
146
+ logError(typeof validation === 'string' ? validation : 'Invalid workspace name');
147
+ process.exit(1);
148
+ }
149
+ const exists = await checkWorkspaceExists(workspaceName);
150
+ if (exists) {
151
+ const repoNames = selectedSuite.entries.map(e => e.repoName);
152
+ const resolved = await resolveWorkspaceNameConflict(workspaceName, repoNames);
153
+ logInfo(`Workspace "${workspaceName}" already exists — using "${colorize(resolved, 'cyan')}" instead.`);
154
+ workspaceName = resolved;
155
+ }
156
+ } else {
157
+ workspaceName = await promptWorkspaceName();
158
+ // Auto-resolve conflict in case the interactively chosen name already exists
159
+ const interactiveExists = await checkWorkspaceExists(workspaceName);
160
+ if (interactiveExists) {
161
+ const repoNames = selectedSuite.entries.map(e => e.repoName);
162
+ const resolved = await resolveWorkspaceNameConflict(workspaceName, repoNames);
163
+ logInfo(`Workspace "${workspaceName}" already exists — using "${colorize(resolved, 'cyan')}" instead.`);
164
+ workspaceName = resolved;
165
+ }
166
+ }
167
+
168
+ const workspacePath = path.join(WORKSPACES_DIR, workspaceName!);
169
+
170
+ // Step 6: Confirm creation
171
+ logStep(6, 7, 'Confirm workspace creation...');
172
+ if (!yes) {
173
+ const confirmed = await confirmWorkspaceCreation(
174
+ workspaceName!,
175
+ resolvedEntries.length,
176
+ workspacePath
177
+ );
178
+
179
+ if (!confirmed) {
180
+ logInfo('Workspace creation cancelled');
181
+ process.exit(0);
182
+ }
183
+ }
184
+
185
+ // Step 7: Create workspace and clone repositories
186
+ logStep(7, 7, 'Creating workspace from suite...');
187
+
188
+ await fs.mkdir(workspacePath, { recursive: true });
189
+ logSuccess(`Created workspace directory: ${workspacePath}`);
190
+
191
+ const cloneResults = await cloneRepositories(resolvedEntries, workspacePath);
192
+
193
+ const metadata = createMetadata(workspaceName!, cloneResults);
194
+ await saveMetadata(workspacePath, metadata);
195
+
196
+ reportCloneResults(cloneResults);
197
+
198
+ if (selectedSuite.postCloneHooks?.length) {
199
+ await runPostCloneHooks(
200
+ workspacePath,
201
+ metadata.repositories.filter(r => r.status === 'success'),
202
+ selectedSuite.postCloneHooks
203
+ );
204
+ }
205
+
206
+ const successfulRepos = cloneResults
207
+ .filter(r => r.status === 'success')
208
+ .map(r => r.repo);
209
+
210
+ if (successfulRepos.length > 0) {
211
+ logInfo('Setting up Claude Code integration...');
212
+ await generateClaudeContext(workspacePath, workspaceName!, successfulRepos, metadata);
213
+ }
214
+
215
+ const tempFile = path.join(process.env.HOME || '~', '.workspace-last-created');
216
+ try {
217
+ await fs.writeFile(tempFile, workspacePath, 'utf-8');
218
+ } catch {
219
+ // Ignore errors writing temp file
220
+ }
221
+
222
+ logSuccess('Workspace created from suite successfully!');
223
+ } catch (error) {
224
+ logError('Failed to create workspace from suite');
225
+ if (error instanceof Error) {
226
+ logError(error.message);
227
+ }
228
+ process.exit(1);
229
+ }
230
+ }
231
+
232
+ if (require.main === module) main();
@@ -0,0 +1,97 @@
1
+ import { Command } from 'commander';
2
+ import * as path from 'path';
3
+ import { WORKSPACES_DIR } from '../utils/config';
4
+ import { loadMetadata } from '../utils/workspace-meta';
5
+ import { logInfo, logSuccess, logError } from '../utils/logger';
6
+ import { colorize } from '../utils/colors';
7
+ import { createSimpleProgressBar } from '../utils/progress';
8
+ import { syncRepository, SyncResult } from '../utils/sync-operations';
9
+ import { resolveWorkspace } from '../utils/command-helpers';
10
+
11
+ export function registerSyncCommand(parent: Command) {
12
+ parent
13
+ .command('sync')
14
+ .alias('s')
15
+ .description('Git pull all repos in a workspace')
16
+ .argument('[workspace]', 'Workspace name')
17
+ .option('-w, --workspace <name>', 'Workspace name (alternative to positional)')
18
+ .action(async (workspaceArg, opts) => {
19
+ await handleSync(opts.workspace || workspaceArg);
20
+ });
21
+ }
22
+
23
+ async function handleSync(workspaceArg?: string) {
24
+ console.log('\n' + '='.repeat(60));
25
+ console.log(colorize('Sync Workspace', 'bright'));
26
+ console.log('='.repeat(60) + '\n');
27
+
28
+ try {
29
+ const workspaceName = await resolveWorkspace(workspaceArg);
30
+ const workspacePath = path.join(WORKSPACES_DIR, workspaceName);
31
+
32
+ logInfo(`Syncing workspace: ${colorize(workspaceName, 'cyan')}`);
33
+
34
+ const metadata = await loadMetadata(workspacePath);
35
+ if (!metadata) {
36
+ logError('No metadata found for this workspace');
37
+ process.exit(1);
38
+ }
39
+
40
+ const repos = metadata.repositories.filter(r => r.status === 'success');
41
+ console.log(`\nFound ${repos.length} repositories to sync\n`);
42
+
43
+ const results: SyncResult[] = [];
44
+ const bar = createSimpleProgressBar(repos.length, 'Syncing');
45
+ bar.start(repos.length, 0);
46
+
47
+ for (const repo of repos) {
48
+ const repoPath = path.join(workspacePath, repo.directoryName);
49
+ const displayName = repo.directoryName !== repo.name
50
+ ? `${repo.directoryName} (${repo.name})`
51
+ : repo.name;
52
+ const result = await syncRepository(repoPath, displayName, true);
53
+ results.push(result);
54
+ bar.update(results.length);
55
+ }
56
+
57
+ bar.stop();
58
+ console.log('');
59
+
60
+ console.log('\n' + '='.repeat(60));
61
+ console.log('Sync Results Summary');
62
+ console.log('='.repeat(60));
63
+
64
+ const successful = results.filter(r => r.status === 'success');
65
+ const failed = results.filter(r => r.status === 'failed');
66
+ const skipped = results.filter(r => r.status === 'skipped');
67
+
68
+ console.log(`${colorize('✓', 'green')} Updated: ${successful.length}`);
69
+ console.log(`${colorize('✗', 'red')} Failed: ${failed.length}`);
70
+ console.log(`${colorize('○', 'yellow')} Skipped: ${skipped.length}`);
71
+ console.log('='.repeat(60));
72
+
73
+ if (failed.length > 0) {
74
+ console.log('\nFailed repositories:');
75
+ failed.forEach(result => {
76
+ console.log(` ${colorize('✗', 'red')} ${result.repo}: ${result.message}`);
77
+ });
78
+ }
79
+
80
+ if (skipped.length > 0) {
81
+ console.log('\nSkipped repositories:');
82
+ skipped.forEach(result => {
83
+ console.log(` ${colorize('○', 'yellow')} ${result.repo}: ${result.message}`);
84
+ });
85
+ }
86
+
87
+ console.log('');
88
+ logSuccess('Workspace sync complete!');
89
+
90
+ } catch (error) {
91
+ logError('Failed to sync workspace');
92
+ if (error instanceof Error) {
93
+ logError(error.message);
94
+ }
95
+ process.exit(1);
96
+ }
97
+ }
@@ -0,0 +1,197 @@
1
+ import { Command } from 'commander';
2
+ import * as path from 'path';
3
+ import { WORKSPACES_DIR, getCloneUrl } from '../utils/config';
4
+ import { verifyGhAuth, fetchOrgRepos, displayAuthInstructions } from '../utils/github';
5
+ import { promptRepositorySelection, promptWorkspaceSelection } from '../utils/prompts';
6
+ import { cloneRepositories, reportCloneResults } from '../utils/git-operations';
7
+ import { warnIfGhqMissing } from '../utils/ghq-integration';
8
+ import { loadMetadata, saveMetadata, listWorkspaces } from '../utils/workspace-meta';
9
+ import { generateClaudeContext } from '../utils/claude-integration';
10
+ import { logInfo, logSuccess, logError, logStep, logWarning } from '../utils/logger';
11
+ import { colorize } from '../utils/colors';
12
+ import { getGlobalOpts, resolveWorkspace, parseList } from '../utils/command-helpers';
13
+ import { resolveRepoSpecs } from '../utils/repo-resolver';
14
+
15
+ export function registerUpdateCommand(parent: Command) {
16
+ parent
17
+ .command('update')
18
+ .alias('u')
19
+ .description('Add repositories to an existing workspace')
20
+ .option('-w, --workspace <name>', 'Workspace name')
21
+ .option('-r, --repos <repos>', 'Comma-separated repository names. Add a suffix to clone the same repo again under a separate folder, e.g. casper:cas-101 -> casper-cas-101')
22
+ .action(async (opts, cmd) => {
23
+ const globalOpts = getGlobalOpts(cmd);
24
+ await handleUpdate({ ...opts, ...globalOpts });
25
+ });
26
+ }
27
+
28
+ async function handleUpdate(opts: {
29
+ workspace?: string;
30
+ repos?: string;
31
+ forceRefresh: boolean;
32
+ yes: boolean;
33
+ }) {
34
+ console.log('\n' + '='.repeat(60));
35
+ console.log(colorize('Update Workspace', 'bright'));
36
+ console.log('='.repeat(60) + '\n');
37
+
38
+ try {
39
+ // Step 1: Verify gh CLI authentication
40
+ logStep(1, 5, 'Verifying GitHub CLI authentication...');
41
+ const isAuthenticated = await verifyGhAuth();
42
+
43
+ if (!isAuthenticated) {
44
+ logError('GitHub CLI is not authenticated');
45
+ displayAuthInstructions();
46
+ process.exit(1);
47
+ }
48
+
49
+ logSuccess('GitHub CLI authenticated');
50
+
51
+ // Step 2: List and select workspace
52
+ logStep(2, 5, 'Select workspace to update...');
53
+ const workspaceName = await resolveWorkspace(opts.workspace);
54
+ const workspacePath = path.join(WORKSPACES_DIR, workspaceName);
55
+
56
+ logInfo(`Selected workspace: ${colorize(workspaceName, 'cyan')}`);
57
+
58
+ // Load existing metadata
59
+ const metadata = await loadMetadata(workspacePath);
60
+ const existingDirectoryNames = metadata?.repositories.map(r => r.directoryName) || [];
61
+ // Duplicate detection is by directory name (the actual folder). The same
62
+ // repo CAN be added again under a different suffix (e.g. casper:cas-101 ->
63
+ // casper-cas-101), so we must NOT block on the canonical repo name.
64
+ const existingDirSet = new Set(existingDirectoryNames);
65
+
66
+ console.log(`\nCurrent repositories: ${existingDirectoryNames.length}`);
67
+ metadata?.repositories.forEach(r => {
68
+ const displayName = r.directoryName !== r.name
69
+ ? `${r.directoryName} (${r.name})`
70
+ : r.name;
71
+ console.log(` - ${displayName}`);
72
+ });
73
+ console.log('');
74
+
75
+ // Step 3: Fetch all available repos
76
+ logStep(3, 5, 'Fetching repositories...');
77
+ const allRepos = await fetchOrgRepos({ forceRefresh: opts.forceRefresh });
78
+
79
+ if (allRepos.length === 0) {
80
+ logError('No repositories found');
81
+ process.exit(1);
82
+ }
83
+
84
+ // Step 4: Select new repositories to add
85
+ logStep(4, 5, 'Select additional repositories to clone...');
86
+
87
+ let selectedEntries;
88
+
89
+ if (opts.repos) {
90
+ const repoSpecs = parseList(opts.repos);
91
+ const { resolved, notFound, invalidSuffix } = resolveRepoSpecs(repoSpecs, allRepos);
92
+
93
+ if (invalidSuffix.length > 0) {
94
+ logError(`Invalid instance suffix in: ${invalidSuffix.join(', ')}`);
95
+ logInfo('Use repo:suffix (e.g. casper:cas-101). Suffix may contain letters, numbers, hyphens, underscores.');
96
+ process.exit(1);
97
+ }
98
+
99
+ // Warn about fuzzy-resolved names
100
+ for (const r of resolved) {
101
+ if (!r.exact) logWarning(`Repo "${r.input}" not found exactly — using "${r.repo.name}" (best match)`);
102
+ }
103
+
104
+ const duplicates = resolved.filter(r => existingDirSet.has(r.directoryName));
105
+ selectedEntries = resolved
106
+ .filter(r => !existingDirSet.has(r.directoryName))
107
+ .map(r => ({ repo: r.repo, directoryName: r.directoryName }));
108
+
109
+ if (duplicates.length > 0) {
110
+ logWarning(`Skipping entries whose directory already exists: ${duplicates.map(r => r.directoryName).join(', ')}`);
111
+ }
112
+ if (notFound.length > 0) {
113
+ logError(`Repositories not found: ${notFound.join(', ')}`);
114
+ process.exit(1);
115
+ }
116
+ } else {
117
+ console.log(`\n${allRepos.length} repositories available to add\n`);
118
+ selectedEntries = await promptRepositorySelection(allRepos, existingDirectoryNames);
119
+ }
120
+
121
+ if (selectedEntries.length === 0) {
122
+ logInfo('No new repositories selected');
123
+ process.exit(0);
124
+ }
125
+
126
+ logInfo(`Selected ${selectedEntries.length} new repositories to add`);
127
+
128
+ // Step 5: Clone new repositories
129
+ logStep(5, 5, 'Cloning new repositories...');
130
+
131
+ await warnIfGhqMissing();
132
+ const cloneResults = await cloneRepositories(selectedEntries!, workspacePath);
133
+
134
+ // Update metadata — create from scratch if the workspace had none
135
+ const newRepoMetadata = cloneResults.map(result => ({
136
+ name: result.repo.name,
137
+ directoryName: result.directoryName,
138
+ owner: result.repo.owner.login,
139
+ clonedAt: result.clonedAt || new Date().toISOString(),
140
+ cloneUrl: getCloneUrl(result.repo),
141
+ status: result.status as 'success' | 'failed',
142
+ error: result.error,
143
+ }));
144
+
145
+ let finalMetadata;
146
+ if (metadata) {
147
+ metadata.repositories.push(...newRepoMetadata);
148
+ metadata.lastModified = new Date().toISOString();
149
+ await saveMetadata(workspacePath, metadata);
150
+ finalMetadata = metadata;
151
+ } else {
152
+ // Workspace had no metadata file — create one now
153
+ const fresh = {
154
+ workspaceName,
155
+ createdAt: new Date().toISOString(),
156
+ lastModified: new Date().toISOString(),
157
+ repositories: newRepoMetadata,
158
+ };
159
+ await saveMetadata(workspacePath, fresh);
160
+ finalMetadata = fresh;
161
+ }
162
+
163
+ // Regenerate the agent context (CLAUDE.md/AGENTS.md) + .mcp.json so the
164
+ // newly added repos actually surface — otherwise an investigate-first
165
+ // workspace keeps saying "0 repositories" and its context stays stale.
166
+ // Resolve the FULL remaining set (existing + new) to catalog objects so
167
+ // pre-existing repos keep their descriptions/URLs on regenerate (matches
168
+ // the remove-repo path). Best-effort: never fail the update over this.
169
+ const repoObjects = finalMetadata.repositories
170
+ .filter(r => r.status === 'success')
171
+ .map(r => allRepos.find(ar => ar.name === r.name))
172
+ .filter((r): r is NonNullable<typeof r> => r != null);
173
+ // Only regenerate when this update actually added a repo — a failure-only
174
+ // update must not rewrite (and clobber) the existing context.
175
+ const addedNewRepo = cloneResults.some(r => r.status === 'success');
176
+ try {
177
+ if (addedNewRepo && repoObjects.length > 0) {
178
+ await generateClaudeContext(workspacePath, workspaceName, repoObjects, finalMetadata);
179
+ }
180
+ } catch { /* context regen is best-effort */ }
181
+
182
+ // Report results
183
+ reportCloneResults(cloneResults);
184
+
185
+ const totalRepos = existingDirectoryNames.length + cloneResults.filter(r => r.status === 'success').length;
186
+ console.log(`Workspace now has ${colorize(String(totalRepos), 'cyan')} repositories\n`);
187
+
188
+ logSuccess('Workspace updated successfully!');
189
+
190
+ } catch (error) {
191
+ logError('Failed to update workspace');
192
+ if (error instanceof Error) {
193
+ logError(error.message);
194
+ }
195
+ process.exit(1);
196
+ }
197
+ }