@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,332 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.describeCloneError = describeCloneError;
37
+ exports.isGhqInstalled = isGhqInstalled;
38
+ exports.warnIfGhqMissing = warnIfGhqMissing;
39
+ exports.getGhqRoot = getGhqRoot;
40
+ exports.ghqRepoExists = ghqRepoExists;
41
+ exports.getGhqRepoPath = getGhqRepoPath;
42
+ exports.ghqGet = ghqGet;
43
+ exports.ghqList = ghqList;
44
+ exports.displayGhqInfo = displayGhqInfo;
45
+ exports.cloneWithGhq = cloneWithGhq;
46
+ exports.getGhqStatus = getGhqStatus;
47
+ const child_process_1 = require("child_process");
48
+ const util_1 = require("util");
49
+ const path = __importStar(require("path"));
50
+ const logger_1 = require("./logger");
51
+ const colors_1 = require("./colors");
52
+ const config_1 = require("./config");
53
+ const execAsync = (0, util_1.promisify)(child_process_1.exec);
54
+ /**
55
+ * Turn a raw child-process clone failure (from exec or execFile) into an
56
+ * actionable message. The child API discards the reason on timeout/buffer kill (you just get "Command
57
+ * failed: git clone …"), so we inspect killed/signal/code and the captured
58
+ * stderr to explain what actually happened and how to fix it.
59
+ */
60
+ function describeCloneError(error, timeoutMs = config_1.CLONE_TIMEOUT_MS) {
61
+ const e = (error ?? {});
62
+ const stderr = (e.stderr || '').trim();
63
+ const tail = stderr ? `\n git: ${stderr.split('\n').filter(Boolean).slice(-3).join(' ')}` : '';
64
+ // Check maxBuffer FIRST: Node sets killed=true on a maxBuffer kill too, so
65
+ // the timeout heuristic below would otherwise shadow this case.
66
+ if (e.code === 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER') {
67
+ return `Clone output exceeded the buffer limit (very large repo). Install ghq (brew install ghq) for cached clones.${tail}`;
68
+ }
69
+ const timedOut = e.killed === true || e.signal === 'SIGTERM' || e.code === 'ETIMEDOUT';
70
+ if (timedOut) {
71
+ const mins = Math.round(timeoutMs / 60000);
72
+ return `Clone timed out after ${mins} min — the repo may be large or the network slow. `
73
+ + `Fixes: install ghq for fast cached clones (brew install ghq), switch to SSH (w configure), `
74
+ + `or raise the limit with WORKSPACE_CLONE_TIMEOUT_MS.${tail}`;
75
+ }
76
+ return `${e.message || 'Unknown error'}${tail}`;
77
+ }
78
+ /**
79
+ * Check if ghq is installed
80
+ */
81
+ async function isGhqInstalled() {
82
+ try {
83
+ await execAsync('which ghq');
84
+ return true;
85
+ }
86
+ catch {
87
+ return false;
88
+ }
89
+ }
90
+ /**
91
+ * Verify ghq is installed and, if not, warn with install guidance.
92
+ * ghq gives fast cached clones (shared across workspaces) and avoids the
93
+ * clone-timeout failures large repos hit over plain git-over-HTTPS.
94
+ * Returns true if ghq is available. Non-blocking — clones still work without it.
95
+ */
96
+ async function warnIfGhqMissing() {
97
+ if (await isGhqInstalled())
98
+ return true;
99
+ (0, logger_1.logWarning)('ghq is not installed — using direct git clone (slower, no shared cache).');
100
+ (0, logger_1.logInfo)(' ghq caches repos so re-cloning across workspaces is near-instant and large repos avoid clone timeouts.');
101
+ (0, logger_1.logInfo)(` Install: ${(0, colors_1.colorize)('brew install ghq', 'cyan')} (macOS) — Linux/Windows: https://github.com/x-motemen/ghq#installation`);
102
+ return false;
103
+ }
104
+ /**
105
+ * Get ghq root directory
106
+ */
107
+ async function getGhqRoot() {
108
+ try {
109
+ const { stdout } = await execAsync('ghq root');
110
+ return stdout.trim();
111
+ }
112
+ catch {
113
+ return null;
114
+ }
115
+ }
116
+ /**
117
+ * Check if a repository exists in ghq
118
+ */
119
+ async function ghqRepoExists(repoUrl) {
120
+ try {
121
+ const repoPath = await getGhqRepoPath(repoUrl);
122
+ if (!repoPath)
123
+ return false;
124
+ await execAsync(`test -d "${repoPath}"`);
125
+ return true;
126
+ }
127
+ catch {
128
+ return false;
129
+ }
130
+ }
131
+ /**
132
+ * Get the local path for a ghq-managed repository
133
+ */
134
+ async function getGhqRepoPath(repoUrl) {
135
+ try {
136
+ // Extract org/repo from URL
137
+ // e.g., git@github.com:my-org/my-app.git -> github.com/my-org/my-app
138
+ const match = repoUrl.match(/github\.com[:/]([^/]+)\/([^.]+)/);
139
+ if (!match)
140
+ return null;
141
+ const [, org, repo] = match;
142
+ const ghqRoot = await getGhqRoot();
143
+ if (!ghqRoot)
144
+ return null;
145
+ return path.join(ghqRoot, 'github.com', org, repo);
146
+ }
147
+ catch {
148
+ return null;
149
+ }
150
+ }
151
+ /**
152
+ * Clone repository using ghq
153
+ */
154
+ async function ghqGet(repoUrl) {
155
+ try {
156
+ (0, logger_1.logInfo)(`Using ghq to clone ${(0, colors_1.colorize)(repoUrl, 'cyan')}...`);
157
+ await execAsync(`ghq get "${repoUrl}"`, { timeout: config_1.CLONE_TIMEOUT_MS, maxBuffer: config_1.CLONE_MAX_BUFFER });
158
+ const repoPath = await getGhqRepoPath(repoUrl);
159
+ if (!repoPath) {
160
+ return { success: false, error: 'Could not determine ghq path' };
161
+ }
162
+ (0, logger_1.logSuccess)(`Repository available in ghq: ${(0, colors_1.colorize)(repoPath, 'gray')}`);
163
+ return { success: true, path: repoPath };
164
+ }
165
+ catch (error) {
166
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
167
+ return { success: false, error: errorMessage };
168
+ }
169
+ }
170
+ /**
171
+ * List all repositories managed by ghq
172
+ */
173
+ async function ghqList() {
174
+ try {
175
+ const { stdout } = await execAsync('ghq list');
176
+ return stdout.trim().split('\n').filter(line => line.length > 0);
177
+ }
178
+ catch {
179
+ return [];
180
+ }
181
+ }
182
+ /**
183
+ * Display ghq installation instructions
184
+ */
185
+ function displayGhqInfo() {
186
+ console.log('\n' + '='.repeat(60));
187
+ console.log((0, colors_1.colorize)('About ghq', 'bright'));
188
+ console.log('='.repeat(60));
189
+ console.log('\nghq is a tool for managing Git repositories in a structured way.');
190
+ console.log('The workspace manager can use ghq for better repository organization.\n');
191
+ console.log('Installation:');
192
+ console.log(' brew install ghq\n');
193
+ console.log('Benefits:');
194
+ console.log(' • Centralized repository cache');
195
+ console.log(' • Consistent directory structure');
196
+ console.log(' • Faster subsequent clones');
197
+ console.log(' • Works alongside workspace isolation\n');
198
+ console.log('After installing ghq, the workspace manager will automatically use it.');
199
+ console.log('='.repeat(60) + '\n');
200
+ }
201
+ /**
202
+ * Clone repository with ghq integration
203
+ * Falls back to direct git clone if ghq is not available
204
+ */
205
+ async function cloneWithGhq(repoUrl, targetPath) {
206
+ const hasGhq = await isGhqInstalled();
207
+ if (hasGhq) {
208
+ // Check if repo already exists in ghq cache
209
+ const ghqPath = await getGhqRepoPath(repoUrl);
210
+ let ghqReady = !!(ghqPath && await ghqRepoExists(repoUrl));
211
+ if (!ghqReady) {
212
+ // Not in cache — clone into ghq first (without -u to avoid extra fetch)
213
+ const ghqResult = await ghqGet(repoUrl);
214
+ if (ghqResult.success) {
215
+ ghqReady = true;
216
+ }
217
+ else {
218
+ (0, logger_1.logWarning)(`ghq clone failed: ${ghqResult.error}`);
219
+ }
220
+ }
221
+ // Use git clone --local from ghq cache (hardlinks .git/objects — nearly instant)
222
+ const sourcePath = ghqPath || (await getGhqRepoPath(repoUrl));
223
+ if (ghqReady && sourcePath) {
224
+ try {
225
+ // Refresh the ghq cache BEFORE clone-local so the workspace gets the
226
+ // latest content. Without this, an old cache (cloned weeks/months ago)
227
+ // would serve stale state via hardlinks — user would open a workspace
228
+ // missing dozens of recent commits.
229
+ try {
230
+ await execAsync(`git -C "${sourcePath}" fetch origin --quiet --prune`, { timeout: 90 * 1000 });
231
+ }
232
+ catch {
233
+ // Network failure or no upstream — fall through with stale cache,
234
+ // we'll log a warning after the local clone if we can't align to HEAD.
235
+ }
236
+ await execAsync(`git clone --local "${sourcePath}" "${targetPath}"`, { timeout: 60 * 1000 });
237
+ // Reset remote to point to the original repo URL (not the local ghq path)
238
+ await execAsync(`git remote set-url origin "${repoUrl}"`, { cwd: targetPath });
239
+ // Make sure the working tree is at origin's default-branch tip.
240
+ // Robust against:
241
+ // - cache having a non-default branch checked out
242
+ // - fetch above failing (network blip)
243
+ // - refs/remotes/origin/HEAD symref not being set (some git/ghq combos)
244
+ let alignedToHead = false;
245
+ let alignError = null;
246
+ try {
247
+ await execAsync(`git fetch origin --quiet`, { cwd: targetPath, timeout: 60 * 1000 });
248
+ // Explicitly set refs/remotes/origin/HEAD by querying the remote.
249
+ // Without this, symbolic-ref may fail if the local symref wasn’t set.
250
+ await execAsync(`git remote set-head origin --auto`, { cwd: targetPath, timeout: 30 * 1000 })
251
+ .catch(() => { });
252
+ let defaultBranch = null;
253
+ try {
254
+ const headRes = await execAsync(`git symbolic-ref --short refs/remotes/origin/HEAD`, { cwd: targetPath, timeout: 5000 });
255
+ defaultBranch = headRes.stdout.trim().replace(/^origin\//, '');
256
+ }
257
+ catch {
258
+ // Fall back to ls-remote query against the actual remote
259
+ try {
260
+ const lsRes = await execAsync(`git ls-remote --symref origin HEAD`, { cwd: targetPath, timeout: 30 * 1000 });
261
+ const match = lsRes.stdout.match(/^ref:\s+refs\/heads\/(\S+)\s+HEAD$/m);
262
+ if (match)
263
+ defaultBranch = match[1];
264
+ }
265
+ catch { /* fall through to common-name fallback */ }
266
+ }
267
+ // Final fallback: try common default branches
268
+ if (!defaultBranch) {
269
+ for (const candidate of ['main', 'master']) {
270
+ const exists = await execAsync(`git rev-parse --verify --quiet origin/${candidate}`, { cwd: targetPath, timeout: 5000 }).then(() => true).catch(() => false);
271
+ if (exists) {
272
+ defaultBranch = candidate;
273
+ break;
274
+ }
275
+ }
276
+ }
277
+ if (!defaultBranch) {
278
+ throw new Error('could not determine default branch (no origin/HEAD, no main, no master)');
279
+ }
280
+ await execAsync(`git checkout --quiet "${defaultBranch}"`, { cwd: targetPath, timeout: 10 * 1000 });
281
+ await execAsync(`git reset --hard --quiet "origin/${defaultBranch}"`, { cwd: targetPath, timeout: 10 * 1000 });
282
+ alignedToHead = true;
283
+ }
284
+ catch (error) {
285
+ alignError = error instanceof Error ? error.message : String(error);
286
+ }
287
+ if (!alignedToHead) {
288
+ // LOUD warning — stale content silently is the worst UX. Surface it.
289
+ (0, logger_1.logWarning)(`⚠️ Could not align ${repoUrl} to origin/HEAD — workspace may have stale content!`);
290
+ if (alignError)
291
+ (0, logger_1.logWarning)(` reason: ${alignError}`);
292
+ (0, logger_1.logWarning)(` manual fix: cd into the repo and run 'git pull --ff-only'`);
293
+ }
294
+ return { success: true, usedGhq: true };
295
+ }
296
+ catch (error) {
297
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
298
+ (0, logger_1.logWarning)(`Local clone from ghq failed: ${errorMessage}`);
299
+ // Clean up partial clone before fallback
300
+ try {
301
+ await execAsync(`rm -rf "${targetPath}"`);
302
+ }
303
+ catch { /* ignore */ }
304
+ // Fall through to direct clone
305
+ }
306
+ }
307
+ }
308
+ // Fallback to direct git clone
309
+ try {
310
+ await execAsync(`git clone "${repoUrl}" "${targetPath}"`, { timeout: config_1.CLONE_TIMEOUT_MS, maxBuffer: config_1.CLONE_MAX_BUFFER });
311
+ return { success: true, usedGhq: false };
312
+ }
313
+ catch (error) {
314
+ return { success: false, error: describeCloneError(error), usedGhq: false };
315
+ }
316
+ }
317
+ /**
318
+ * Show ghq status
319
+ */
320
+ async function getGhqStatus() {
321
+ const installed = await isGhqInstalled();
322
+ if (!installed) {
323
+ return { installed: false };
324
+ }
325
+ const root = await getGhqRoot();
326
+ const repos = await ghqList();
327
+ return {
328
+ installed: true,
329
+ root: root || undefined,
330
+ repoCount: repos.length,
331
+ };
332
+ }
@@ -0,0 +1,237 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.reportCloneResults = exports.cloneRepositories = exports.cloneSingleRepo = void 0;
37
+ const child_process_1 = require("child_process");
38
+ const util_1 = require("util");
39
+ const path = __importStar(require("path"));
40
+ const fs = __importStar(require("fs/promises"));
41
+ const logger_1 = require("./logger");
42
+ const colors_1 = require("./colors");
43
+ const ghq_integration_1 = require("./ghq-integration");
44
+ const retry_1 = require("./retry");
45
+ const progress_1 = require("./progress");
46
+ const config_1 = require("./config");
47
+ const execAsync = (0, util_1.promisify)(child_process_1.exec);
48
+ const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
49
+ const CONCURRENCY_LIMIT = 3;
50
+ const CLONE_TIMEOUT = config_1.CLONE_TIMEOUT_MS;
51
+ const cloneSingleRepo = async (repo, workspacePath, directoryName, useGhq = true, silent = false) => {
52
+ const repoPath = path.join(workspacePath, directoryName);
53
+ const cloneUrl = (0, config_1.getCloneUrl)(repo);
54
+ const displayName = directoryName !== repo.name
55
+ ? `${(0, colors_1.colorize)(directoryName, 'cyan')} (${repo.name})`
56
+ : (0, colors_1.colorize)(repo.name, 'cyan');
57
+ try {
58
+ if (!silent)
59
+ (0, logger_1.logInfo)(`Cloning ${displayName}...`);
60
+ await (0, retry_1.withRetry)(async () => {
61
+ // Clean up partial clone before retry
62
+ try {
63
+ await fs.rm(repoPath, { recursive: true, force: true });
64
+ }
65
+ catch {
66
+ // Ignore cleanup errors
67
+ }
68
+ if (useGhq) {
69
+ const result = await (0, ghq_integration_1.cloneWithGhq)(cloneUrl, repoPath);
70
+ if (!result.success) {
71
+ throw new Error(result.error || 'Clone failed');
72
+ }
73
+ if (!silent) {
74
+ if (result.usedGhq) {
75
+ (0, logger_1.logSuccess)(`Cloned ${displayName} ${(0, colors_1.colorize)('(via ghq)', 'gray')}`);
76
+ }
77
+ else {
78
+ (0, logger_1.logSuccess)(`Cloned ${displayName}`);
79
+ }
80
+ }
81
+ }
82
+ else {
83
+ try {
84
+ // execFile (no shell) so repo URLs / paths with spaces or shell
85
+ // metacharacters can't break or inject into the command.
86
+ await execFileAsync('git', ['clone', cloneUrl, repoPath], {
87
+ timeout: CLONE_TIMEOUT,
88
+ maxBuffer: config_1.CLONE_MAX_BUFFER,
89
+ });
90
+ }
91
+ catch (err) {
92
+ // execFile loses the reason on timeout/buffer kill — explain it.
93
+ throw new Error((0, ghq_integration_1.describeCloneError)(err, CLONE_TIMEOUT));
94
+ }
95
+ if (!silent)
96
+ (0, logger_1.logSuccess)(`Cloned ${displayName}`);
97
+ }
98
+ }, undefined, `Clone ${repo.name}`);
99
+ return {
100
+ repo,
101
+ directoryName,
102
+ status: 'success',
103
+ clonedAt: new Date().toISOString(),
104
+ };
105
+ }
106
+ catch (error) {
107
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
108
+ (0, logger_1.logError)(`Failed to clone ${displayName}: ${errorMessage}`);
109
+ return {
110
+ repo,
111
+ directoryName,
112
+ status: 'failed',
113
+ error: errorMessage,
114
+ };
115
+ }
116
+ };
117
+ exports.cloneSingleRepo = cloneSingleRepo;
118
+ async function cloneLocal(repo, sourcePath, workspacePath, directoryName) {
119
+ const targetPath = path.join(workspacePath, directoryName);
120
+ try {
121
+ // git clone --local creates hardlinks for .git/objects — nearly instant
122
+ await execAsync(`git clone --local "${sourcePath}" "${targetPath}"`, {
123
+ timeout: CLONE_TIMEOUT,
124
+ maxBuffer: config_1.CLONE_MAX_BUFFER,
125
+ });
126
+ // Reset the remote to point to the original repo (not the local source)
127
+ const remoteUrl = (0, config_1.getCloneUrl)(repo);
128
+ await execAsync(`git remote set-url origin "${remoteUrl}"`, {
129
+ cwd: targetPath,
130
+ });
131
+ return {
132
+ repo,
133
+ directoryName,
134
+ status: 'success',
135
+ clonedAt: new Date().toISOString(),
136
+ };
137
+ }
138
+ catch (error) {
139
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
140
+ return {
141
+ repo,
142
+ directoryName,
143
+ status: 'failed',
144
+ error: errorMessage,
145
+ };
146
+ }
147
+ }
148
+ const cloneRepositories = async (repoEntries, workspacePath) => {
149
+ // Check if ghq is available
150
+ const hasGhq = await (0, ghq_integration_1.isGhqInstalled)();
151
+ console.log('\n' + '='.repeat(60));
152
+ console.log(`Cloning ${repoEntries.length} repositories (${CONCURRENCY_LIMIT} concurrent clones)`);
153
+ if (hasGhq) {
154
+ console.log((0, colors_1.colorize)('Using ghq for efficient repository management', 'cyan'));
155
+ }
156
+ console.log('='.repeat(60) + '\n');
157
+ const bar = (0, progress_1.createSimpleProgressBar)(repoEntries.length, 'Cloning');
158
+ bar.start(repoEntries.length, 0);
159
+ // Group entries by repo SSH URL to avoid duplicate clones
160
+ const repoGroups = new Map();
161
+ for (const entry of repoEntries) {
162
+ const key = entry.repo.sshUrl;
163
+ if (!repoGroups.has(key)) {
164
+ repoGroups.set(key, []);
165
+ }
166
+ repoGroups.get(key).push(entry);
167
+ }
168
+ const allResults = [];
169
+ let completed = 0;
170
+ // Process unique repo groups with concurrency (different repos clone in parallel)
171
+ const groups = Array.from(repoGroups.values());
172
+ for (let i = 0; i < groups.length; i += CONCURRENCY_LIMIT) {
173
+ const chunk = groups.slice(i, i + CONCURRENCY_LIMIT);
174
+ const chunkPromises = chunk.map(async (entries) => {
175
+ const groupResults = [];
176
+ // Clone first instance normally
177
+ const first = entries[0];
178
+ const firstResult = await (0, exports.cloneSingleRepo)(first.repo, workspacePath, first.directoryName, hasGhq, true);
179
+ groupResults.push(firstResult);
180
+ completed++;
181
+ bar.update(completed);
182
+ // Clone remaining instances via --local from the first
183
+ if (firstResult.status === 'success' && entries.length > 1) {
184
+ const sourcePath = path.join(workspacePath, first.directoryName);
185
+ for (const entry of entries.slice(1)) {
186
+ const result = await cloneLocal(entry.repo, sourcePath, workspacePath, entry.directoryName);
187
+ groupResults.push(result);
188
+ completed++;
189
+ bar.update(completed);
190
+ }
191
+ }
192
+ else if (firstResult.status === 'failed' && entries.length > 1) {
193
+ // Mark remaining as failed too
194
+ for (const entry of entries.slice(1)) {
195
+ groupResults.push({
196
+ repo: entry.repo,
197
+ directoryName: entry.directoryName,
198
+ status: 'failed',
199
+ error: 'Primary clone failed',
200
+ });
201
+ completed++;
202
+ bar.update(completed);
203
+ }
204
+ }
205
+ return groupResults;
206
+ });
207
+ const chunkResults = await Promise.all(chunkPromises);
208
+ for (const groupResults of chunkResults) {
209
+ allResults.push(...groupResults);
210
+ }
211
+ }
212
+ bar.stop();
213
+ console.log('');
214
+ return allResults;
215
+ };
216
+ exports.cloneRepositories = cloneRepositories;
217
+ const reportCloneResults = (results) => {
218
+ const successful = results.filter(r => r.status === 'success');
219
+ const failed = results.filter(r => r.status === 'failed');
220
+ console.log('\n' + '='.repeat(60));
221
+ console.log('Clone Results Summary');
222
+ console.log('='.repeat(60));
223
+ console.log(`${(0, colors_1.colorize)('✓', 'green')} Successful: ${successful.length}`);
224
+ console.log(`${(0, colors_1.colorize)('✗', 'red')} Failed: ${failed.length}`);
225
+ console.log('='.repeat(60));
226
+ if (failed.length > 0) {
227
+ console.log('\nFailed repositories:');
228
+ failed.forEach(result => {
229
+ const displayName = result.directoryName !== result.repo.name
230
+ ? `${result.directoryName} (${result.repo.name})`
231
+ : result.repo.name;
232
+ console.log(` ${(0, colors_1.colorize)('✗', 'red')} ${displayName}: ${result.error}`);
233
+ });
234
+ }
235
+ console.log('');
236
+ };
237
+ exports.reportCloneResults = reportCloneResults;