@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,184 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HIDDEN_WINDOW = void 0;
4
+ exports.getAgentPanes = getAgentPanes;
5
+ exports.getHiddenPanes = getHiddenPanes;
6
+ exports.findPaneForAgent = findPaneForAgent;
7
+ exports.sortAgentsByPaneOrder = sortAgentsByPaneOrder;
8
+ exports.sortAgentsByPidOrder = sortAgentsByPidOrder;
9
+ exports.restoreHiddenPanes = restoreHiddenPanes;
10
+ exports.resetLayout = resetLayout;
11
+ exports.zoomPane = zoomPane;
12
+ const child_process_1 = require("child_process");
13
+ const dashboard_1 = require("../types/dashboard");
14
+ const { sessionName } = dashboard_1.DASHBOARD_DEFAULTS;
15
+ exports.HIDDEN_WINDOW = `${sessionName}:1`;
16
+ /**
17
+ * Get agent panes in the main dashboard window (excludes sidebar pane 0).
18
+ */
19
+ function getAgentPanes() {
20
+ const result = (0, child_process_1.spawnSync)('tmux', [
21
+ 'list-panes', '-t', sessionName, '-F',
22
+ '#{pane_index} #{pane_pid}',
23
+ ], { stdio: 'pipe' });
24
+ if (!result.stdout)
25
+ return [];
26
+ return result.stdout.toString().trim().split('\n')
27
+ .filter(l => l.trim())
28
+ .map(line => {
29
+ const [index, pid] = line.split(' ');
30
+ return { index, pid: parseInt(pid, 10) || 0 };
31
+ })
32
+ .filter(p => p.index !== '0');
33
+ }
34
+ /**
35
+ * Get panes from the hidden window (window 1) used during zoom.
36
+ */
37
+ function getHiddenPanes() {
38
+ const result = (0, child_process_1.spawnSync)('tmux', [
39
+ 'list-panes', '-t', exports.HIDDEN_WINDOW, '-F',
40
+ '#{pane_index} #{pane_pid}',
41
+ ], { stdio: 'pipe' });
42
+ if (!result.stdout)
43
+ return [];
44
+ return result.stdout.toString().trim().split('\n')
45
+ .filter(l => l.trim())
46
+ .map(line => {
47
+ const [index, pid] = line.split(' ');
48
+ return { index, pid: parseInt(pid, 10) || 0 };
49
+ });
50
+ }
51
+ /**
52
+ * Find the tmux pane matching an agent by PID (direct match only).
53
+ */
54
+ function findPaneForAgent(agent, panes) {
55
+ return panes.find(p => p.pid === agent.pid) ?? null;
56
+ }
57
+ /**
58
+ * Sort agents to match tmux pane order (top-to-bottom) by PID.
59
+ */
60
+ function sortAgentsByPaneOrder(agents, panes) {
61
+ const pidToIndex = new Map();
62
+ panes.forEach((p, i) => pidToIndex.set(p.pid, i));
63
+ return [...agents].sort((a, b) => {
64
+ const aIdx = pidToIndex.get(a.pid) ?? 999;
65
+ const bIdx = pidToIndex.get(b.pid) ?? 999;
66
+ return aIdx - bIdx;
67
+ });
68
+ }
69
+ /**
70
+ * Sort agents by an explicit PID order array (used during zoom to
71
+ * preserve the original order regardless of current pane positions).
72
+ */
73
+ function sortAgentsByPidOrder(agents, pidOrder) {
74
+ const pidToIndex = new Map();
75
+ pidOrder.forEach((pid, i) => pidToIndex.set(pid, i));
76
+ return [...agents].sort((a, b) => {
77
+ const aIdx = pidToIndex.get(a.pid) ?? 999;
78
+ const bIdx = pidToIndex.get(b.pid) ?? 999;
79
+ return aIdx - bIdx;
80
+ });
81
+ }
82
+ /**
83
+ * Restore all panes from the hidden window back to the main window,
84
+ * then reorder them to match the given original PID order.
85
+ */
86
+ function restoreHiddenPanes(originalPidOrder) {
87
+ // First, bring all hidden panes back (order may be wrong)
88
+ for (let i = 0; i < 20; i++) {
89
+ const winCheck = (0, child_process_1.spawnSync)('tmux', [
90
+ 'list-windows', '-t', sessionName, '-F', '#{window_index}',
91
+ ], { stdio: 'pipe' });
92
+ if (!winCheck.stdout || !winCheck.stdout.toString().split('\n').some(l => l.trim() === '1'))
93
+ break;
94
+ const hidden = getHiddenPanes();
95
+ if (hidden.length === 0)
96
+ break;
97
+ const result = (0, child_process_1.spawnSync)('tmux', [
98
+ 'join-pane', '-d',
99
+ '-s', `${exports.HIDDEN_WINDOW}.${hidden[0].index}`,
100
+ '-t', `${sessionName}:0`,
101
+ ], { stdio: 'pipe' });
102
+ if (result.status !== 0)
103
+ break;
104
+ }
105
+ // If we have a desired order, reorder panes by swapping them into place
106
+ if (originalPidOrder && originalPidOrder.length > 0) {
107
+ reorderPanesByPid(originalPidOrder);
108
+ }
109
+ }
110
+ /**
111
+ * Reorder agent panes to match the given PID order using swap-pane.
112
+ * Uses a simple selection sort: for each position, find the pane with
113
+ * the correct PID and swap it into place.
114
+ */
115
+ function reorderPanesByPid(desiredPidOrder) {
116
+ for (let i = 0; i < desiredPidOrder.length; i++) {
117
+ const targetPid = desiredPidOrder[i];
118
+ const currentPanes = getAgentPanes();
119
+ // Find the current index of the pane with the desired PID
120
+ const currentIdx = currentPanes.findIndex(p => p.pid === targetPid);
121
+ if (currentIdx === -1)
122
+ continue; // pane not found (maybe died)
123
+ // The pane that should be at position i
124
+ const shouldBeAt = currentPanes[i];
125
+ if (!shouldBeAt)
126
+ continue;
127
+ // If already in the right position, skip
128
+ if (currentPanes[currentIdx].index === shouldBeAt.index)
129
+ continue;
130
+ // Swap the pane at position i with the pane that has the correct PID
131
+ (0, child_process_1.spawnSync)('tmux', [
132
+ 'swap-pane',
133
+ '-s', `${sessionName}:0.${currentPanes[currentIdx].index}`,
134
+ '-t', `${sessionName}:0.${shouldBeAt.index}`,
135
+ ], { stdio: 'pipe' });
136
+ }
137
+ }
138
+ /**
139
+ * Reset to normal layout: restore hidden panes + rebalance.
140
+ */
141
+ function resetLayout() {
142
+ restoreHiddenPanes();
143
+ (0, child_process_1.spawnSync)('tmux', ['select-layout', '-t', sessionName, 'main-vertical'], { stdio: 'pipe' });
144
+ (0, child_process_1.spawnSync)('tmux', ['resize-pane', '-t', `${sessionName}:0.0`, '-x', '32'], { stdio: 'pipe' });
145
+ (0, child_process_1.spawnSync)('tmux', ['select-pane', '-t', `${sessionName}:0.0`], { stdio: 'pipe' });
146
+ }
147
+ /**
148
+ * Zoom: hide all other agent panes to a hidden window.
149
+ * First pane: break-pane (creates window 1).
150
+ * Subsequent panes: join-pane into existing window 1.
151
+ */
152
+ function zoomPane(targetPane) {
153
+ const targetPid = targetPane.pid;
154
+ let hiddenWindowExists = false;
155
+ const winCheck = (0, child_process_1.spawnSync)('tmux', [
156
+ 'list-windows', '-t', sessionName, '-F', '#{window_index}',
157
+ ], { stdio: 'pipe' });
158
+ if (winCheck.stdout && winCheck.stdout.toString().split('\n').some(l => l.trim() === '1')) {
159
+ hiddenWindowExists = true;
160
+ }
161
+ for (let i = 0; i < 20; i++) {
162
+ const currentPanes = getAgentPanes();
163
+ const toHide = currentPanes.find(p => p.pid !== targetPid);
164
+ if (!toHide)
165
+ break;
166
+ if (!hiddenWindowExists) {
167
+ (0, child_process_1.spawnSync)('tmux', [
168
+ 'break-pane', '-d',
169
+ '-s', `${sessionName}:0.${toHide.index}`,
170
+ ], { stdio: 'pipe' });
171
+ hiddenWindowExists = true;
172
+ }
173
+ else {
174
+ (0, child_process_1.spawnSync)('tmux', [
175
+ 'join-pane', '-d',
176
+ '-s', `${sessionName}:0.${toHide.index}`,
177
+ '-t', exports.HIDDEN_WINDOW,
178
+ ], { stdio: 'pipe' });
179
+ }
180
+ }
181
+ (0, child_process_1.spawnSync)('tmux', ['select-layout', '-t', `${sessionName}:0`, 'main-vertical'], { stdio: 'pipe' });
182
+ (0, child_process_1.spawnSync)('tmux', ['resize-pane', '-t', `${sessionName}:0.0`, '-x', '32'], { stdio: 'pipe' });
183
+ (0, child_process_1.spawnSync)('tmux', ['select-pane', '-t', `${sessionName}:0.0`], { stdio: 'pipe' });
184
+ }
@@ -0,0 +1,153 @@
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.resolveWorkspaceNameConflict = exports.sanitizeWorkspaceName = exports.checkWorkspaceExists = exports.validateWorkspaceName = exports.safeWorkspacePath = exports.assertSafeWorkspaceName = exports.isSafePathSegment = void 0;
37
+ const fs = __importStar(require("fs/promises"));
38
+ const config_1 = require("./config");
39
+ const path = __importStar(require("path"));
40
+ const WORKSPACE_NAME_REGEX = /^[a-zA-Z0-9_-]+$/;
41
+ /**
42
+ * Allowlist for a single, safe path segment used as a workspace (or suite) name.
43
+ * Alphanumerics plus hyphen/underscore only — it cannot express a path
44
+ * separator, a Windows drive, a leading `~`, or a `.`/`..` traversal component,
45
+ * so a value that matches this can never escape its parent directory.
46
+ */
47
+ const isSafePathSegment = (name) => typeof name === 'string' && name.length > 0 && name.length <= 64 && WORKSPACE_NAME_REGEX.test(name);
48
+ exports.isSafePathSegment = isSafePathSegment;
49
+ /**
50
+ * Throw unless `name` is a safe workspace-name path segment. Use this in any
51
+ * handler that receives a workspace/suite name from an untrusted caller (e.g.
52
+ * the MCP server, whose Zod schemas otherwise accept any string) BEFORE the
53
+ * name is used to build a filesystem path.
54
+ */
55
+ const assertSafeWorkspaceName = (name, label = 'workspace name') => {
56
+ if (!(0, exports.isSafePathSegment)(name)) {
57
+ throw new Error(`Invalid ${label} "${String(name)}": only letters, numbers, hyphens, and underscores are allowed ` +
58
+ `(no path separators, "..", or other special characters).`);
59
+ }
60
+ };
61
+ exports.assertSafeWorkspaceName = assertSafeWorkspaceName;
62
+ /**
63
+ * Validate a workspace name and resolve it to an absolute path guaranteed to sit
64
+ * directly inside WORKSPACES_DIR. This is the single choke point every code path
65
+ * (especially the MCP handlers) must use instead of a raw
66
+ * `path.join(WORKSPACES_DIR, name)` — it closes the path-traversal vector where
67
+ * a caller could pass e.g. "../../etc" as a workspace name to read, write,
68
+ * delete, or run commands outside the workspaces sandbox.
69
+ *
70
+ * Two independent guards: the allowlist above (which cannot express traversal)
71
+ * and a resolved-path containment check (belt-and-suspenders, also catches any
72
+ * future change that loosens the allowlist).
73
+ */
74
+ const safeWorkspacePath = (name) => {
75
+ (0, exports.assertSafeWorkspaceName)(name);
76
+ const base = path.resolve(config_1.WORKSPACES_DIR);
77
+ const resolved = path.resolve(base, name);
78
+ if (resolved !== base && !resolved.startsWith(base + path.sep)) {
79
+ throw new Error(`Refusing to use a workspace path outside the workspaces directory: "${name}"`);
80
+ }
81
+ return resolved;
82
+ };
83
+ exports.safeWorkspacePath = safeWorkspacePath;
84
+ const validateWorkspaceName = (name) => {
85
+ if (!name || name.trim().length === 0) {
86
+ return 'Workspace name cannot be empty';
87
+ }
88
+ if (!WORKSPACE_NAME_REGEX.test(name)) {
89
+ return 'Workspace name must contain only alphanumeric characters, hyphens, and underscores (spaces are automatically converted to hyphens)';
90
+ }
91
+ if (name.length < 3) {
92
+ return 'Workspace name must be at least 3 characters long';
93
+ }
94
+ if (name.length > 50) {
95
+ return 'Workspace name must be less than 50 characters';
96
+ }
97
+ return true;
98
+ };
99
+ exports.validateWorkspaceName = validateWorkspaceName;
100
+ const checkWorkspaceExists = async (name) => {
101
+ const workspacePath = path.join(config_1.WORKSPACES_DIR, name);
102
+ try {
103
+ await fs.access(workspacePath);
104
+ return true;
105
+ }
106
+ catch {
107
+ return false;
108
+ }
109
+ };
110
+ exports.checkWorkspaceExists = checkWorkspaceExists;
111
+ const sanitizeWorkspaceName = (name) => {
112
+ return name.trim().toLowerCase().replace(/ +/g, '-');
113
+ };
114
+ exports.sanitizeWorkspaceName = sanitizeWorkspaceName;
115
+ /**
116
+ * Resolves a workspace name conflict by generating a unique alternative name.
117
+ *
118
+ * Strategy:
119
+ * 1. Try appending each provided candidate suffix (e.g. repo names): `<name>-<candidate>`
120
+ * 2. Fall back to numeric suffixes: `<name>-2`, `<name>-3`, …
121
+ *
122
+ * @param baseName - The sanitized workspace name that already exists.
123
+ * @param candidates - Optional list of preferred suffixes to try first (e.g. selected repo names).
124
+ * @returns A workspace name that does not yet exist on disk and passes validation.
125
+ */
126
+ const resolveWorkspaceNameConflict = async (baseName, candidates = []) => {
127
+ // 1. Try candidate-based suffixes (e.g. repo names)
128
+ for (const candidate of candidates) {
129
+ const suffix = (0, exports.sanitizeWorkspaceName)(candidate)
130
+ .replace(/[^a-z0-9_-]/g, '-') // extra safety — strip any bad chars after sanitise
131
+ .replace(/-+/g, '-') // collapse consecutive hyphens
132
+ .replace(/^-|-$/g, ''); // trim leading/trailing hyphens
133
+ if (!suffix)
134
+ continue;
135
+ const attempt = `${baseName}-${suffix}`;
136
+ if ((0, exports.validateWorkspaceName)(attempt) !== true)
137
+ continue;
138
+ const exists = await (0, exports.checkWorkspaceExists)(attempt);
139
+ if (!exists)
140
+ return attempt;
141
+ }
142
+ // 2. Fall back to numeric suffixes
143
+ for (let i = 2; i <= 99; i++) {
144
+ const attempt = `${baseName}-${i}`;
145
+ if ((0, exports.validateWorkspaceName)(attempt) !== true)
146
+ continue;
147
+ const exists = await (0, exports.checkWorkspaceExists)(attempt);
148
+ if (!exists)
149
+ return attempt;
150
+ }
151
+ throw new Error(`Could not resolve workspace name conflict for "${baseName}" — all attempted names already exist.`);
152
+ };
153
+ exports.resolveWorkspaceNameConflict = resolveWorkspaceNameConflict;
@@ -0,0 +1,121 @@
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.checkForUpdate = checkForUpdate;
37
+ const fs = __importStar(require("fs/promises"));
38
+ const path = __importStar(require("path"));
39
+ const child_process_1 = require("child_process");
40
+ const util_1 = require("util");
41
+ const config_1 = require("./config");
42
+ const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
43
+ const VERSION_CHECK_FILE = path.join(config_1.CACHE_DIR, 'last-version-check.json');
44
+ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
45
+ function compareVersions(current, latest) {
46
+ const a = current.split('.').map(Number);
47
+ const b = latest.split('.').map(Number);
48
+ for (let i = 0; i < 3; i++) {
49
+ if ((a[i] || 0) < (b[i] || 0))
50
+ return -1;
51
+ if ((a[i] || 0) > (b[i] || 0))
52
+ return 1;
53
+ }
54
+ return 0;
55
+ }
56
+ async function loadCache() {
57
+ try {
58
+ const content = await fs.readFile(VERSION_CHECK_FILE, 'utf-8');
59
+ return JSON.parse(content);
60
+ }
61
+ catch {
62
+ return null;
63
+ }
64
+ }
65
+ async function saveCache(cache) {
66
+ try {
67
+ await fs.mkdir(config_1.CACHE_DIR, { recursive: true });
68
+ await fs.writeFile(VERSION_CHECK_FILE, JSON.stringify(cache), 'utf-8');
69
+ }
70
+ catch {
71
+ // Silently ignore cache write failures
72
+ }
73
+ }
74
+ async function fetchLatestVersion() {
75
+ try {
76
+ const { stdout } = await execFileAsync('npm', ['view', '@nemus-cli/nemus', 'version'], {
77
+ timeout: 5000,
78
+ });
79
+ return stdout.trim();
80
+ }
81
+ catch {
82
+ return null;
83
+ }
84
+ }
85
+ /**
86
+ * Check if a newer version is available.
87
+ * Returns upgrade message string if an update is available, null otherwise.
88
+ * Caches the result for 24 hours to avoid spamming npm on every invocation.
89
+ * This is designed to be non-blocking and best-effort — failures are silent.
90
+ */
91
+ async function checkForUpdate() {
92
+ try {
93
+ const currentVersion = (0, config_1.getPackageVersion)();
94
+ const cache = await loadCache();
95
+ // If we checked recently, use cached result
96
+ if (cache) {
97
+ const elapsed = Date.now() - new Date(cache.checkedAt).getTime();
98
+ if (elapsed < CHECK_INTERVAL_MS) {
99
+ if (compareVersions(currentVersion, cache.latestVersion) < 0) {
100
+ return formatUpdateMessage(currentVersion, cache.latestVersion);
101
+ }
102
+ return null;
103
+ }
104
+ }
105
+ // Fetch latest version from npm (async with timeout)
106
+ const latestVersion = await fetchLatestVersion();
107
+ if (!latestVersion)
108
+ return null;
109
+ await saveCache({ checkedAt: new Date().toISOString(), latestVersion });
110
+ if (compareVersions(currentVersion, latestVersion) < 0) {
111
+ return formatUpdateMessage(currentVersion, latestVersion);
112
+ }
113
+ return null;
114
+ }
115
+ catch {
116
+ return null;
117
+ }
118
+ }
119
+ function formatUpdateMessage(current, latest) {
120
+ return `\x1b[33m[nemus] Update available: ${current} -> ${latest}. Run: npm install -g @nemus-cli/nemus@latest\x1b[0m`;
121
+ }
@@ -0,0 +1,185 @@
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.purgeExpiredArchives = exports.unarchiveWorkspace = exports.archiveWorkspace = exports.listWorkspaces = exports.loadMetadata = exports.saveMetadata = exports.createMetadata = void 0;
37
+ const fs = __importStar(require("fs/promises"));
38
+ const path = __importStar(require("path"));
39
+ const logger_1 = require("./logger");
40
+ const config_1 = require("./config");
41
+ const validation_1 = require("./validation");
42
+ const ARCHIVE_EXPIRY_DAYS = 30;
43
+ const createMetadata = (workspaceName, cloneResults, options) => {
44
+ const repositories = cloneResults.map(result => ({
45
+ name: result.repo.name,
46
+ directoryName: result.directoryName,
47
+ owner: result.repo.owner.login,
48
+ clonedAt: result.clonedAt || new Date().toISOString(),
49
+ cloneUrl: (0, config_1.getCloneUrl)(result.repo),
50
+ status: result.status,
51
+ error: result.error,
52
+ }));
53
+ return {
54
+ workspaceName,
55
+ createdAt: new Date().toISOString(),
56
+ repositories,
57
+ ...(options?.prompt ? { prompt: options.prompt } : {}),
58
+ };
59
+ };
60
+ exports.createMetadata = createMetadata;
61
+ const saveMetadata = async (workspacePath, metadata) => {
62
+ try {
63
+ const metaPath = path.join(workspacePath, config_1.META_FILENAME);
64
+ await fs.writeFile(metaPath, JSON.stringify(metadata, null, 2), 'utf-8');
65
+ (0, logger_1.logSuccess)('Saved workspace metadata');
66
+ }
67
+ catch (error) {
68
+ (0, logger_1.logWarning)('Failed to save workspace metadata');
69
+ if (error instanceof Error) {
70
+ (0, logger_1.logError)(error.message);
71
+ }
72
+ }
73
+ };
74
+ exports.saveMetadata = saveMetadata;
75
+ const loadMetadata = async (workspacePath) => {
76
+ try {
77
+ const metaPath = path.join(workspacePath, config_1.META_FILENAME);
78
+ const content = await fs.readFile(metaPath, 'utf-8');
79
+ const metadata = JSON.parse(content);
80
+ // Backward compatibility: default directoryName to name for old entries
81
+ for (const repo of metadata.repositories) {
82
+ if (!repo.directoryName) {
83
+ repo.directoryName = repo.name;
84
+ }
85
+ }
86
+ return metadata;
87
+ }
88
+ catch {
89
+ return null;
90
+ }
91
+ };
92
+ exports.loadMetadata = loadMetadata;
93
+ const listWorkspaces = async (includeArchived = false) => {
94
+ try {
95
+ await fs.access(config_1.WORKSPACES_DIR);
96
+ }
97
+ catch {
98
+ return [];
99
+ }
100
+ try {
101
+ // Lazily purge expired archives on every list call
102
+ await (0, exports.purgeExpiredArchives)();
103
+ const entries = await fs.readdir(config_1.WORKSPACES_DIR, { withFileTypes: true });
104
+ const workspaces = [];
105
+ for (const entry of entries) {
106
+ if (entry.isDirectory()) {
107
+ const workspacePath = path.join(config_1.WORKSPACES_DIR, entry.name);
108
+ const metadata = await (0, exports.loadMetadata)(workspacePath);
109
+ // Filter based on archive status
110
+ if (!includeArchived && metadata?.archivedAt) {
111
+ continue;
112
+ }
113
+ workspaces.push({
114
+ name: entry.name,
115
+ path: workspacePath,
116
+ metadata,
117
+ });
118
+ }
119
+ }
120
+ return workspaces;
121
+ }
122
+ catch (error) {
123
+ (0, logger_1.logError)('Failed to list workspaces');
124
+ return [];
125
+ }
126
+ };
127
+ exports.listWorkspaces = listWorkspaces;
128
+ const archiveWorkspace = async (workspaceName) => {
129
+ const workspacePath = (0, validation_1.safeWorkspacePath)(workspaceName);
130
+ const metadata = await (0, exports.loadMetadata)(workspacePath);
131
+ if (!metadata) {
132
+ throw new Error(`Workspace not found: ${workspaceName}`);
133
+ }
134
+ if (metadata.archivedAt) {
135
+ throw new Error(`Workspace is already archived: ${workspaceName}`);
136
+ }
137
+ metadata.archivedAt = new Date().toISOString();
138
+ await (0, exports.saveMetadata)(workspacePath, metadata);
139
+ };
140
+ exports.archiveWorkspace = archiveWorkspace;
141
+ const unarchiveWorkspace = async (workspaceName) => {
142
+ const workspacePath = (0, validation_1.safeWorkspacePath)(workspaceName);
143
+ const metadata = await (0, exports.loadMetadata)(workspacePath);
144
+ if (!metadata) {
145
+ throw new Error(`Workspace not found: ${workspaceName}`);
146
+ }
147
+ if (!metadata.archivedAt) {
148
+ throw new Error(`Workspace is not archived: ${workspaceName}`);
149
+ }
150
+ delete metadata.archivedAt;
151
+ await (0, exports.saveMetadata)(workspacePath, metadata);
152
+ };
153
+ exports.unarchiveWorkspace = unarchiveWorkspace;
154
+ const purgeExpiredArchives = async () => {
155
+ try {
156
+ await fs.access(config_1.WORKSPACES_DIR);
157
+ }
158
+ catch {
159
+ return;
160
+ }
161
+ try {
162
+ const entries = await fs.readdir(config_1.WORKSPACES_DIR, { withFileTypes: true });
163
+ const now = Date.now();
164
+ for (const entry of entries) {
165
+ if (!entry.isDirectory())
166
+ continue;
167
+ const workspacePath = path.join(config_1.WORKSPACES_DIR, entry.name);
168
+ const metadata = await (0, exports.loadMetadata)(workspacePath);
169
+ if (!metadata?.archivedAt)
170
+ continue;
171
+ const archivedTime = new Date(metadata.archivedAt).getTime();
172
+ const daysSinceArchive = (now - archivedTime) / (1000 * 60 * 60 * 24);
173
+ if (daysSinceArchive > ARCHIVE_EXPIRY_DAYS) {
174
+ await fs.rm(workspacePath, { recursive: true, force: true });
175
+ process.stderr.write(`Purged expired archive: ${entry.name}\n`);
176
+ }
177
+ }
178
+ }
179
+ catch (error) {
180
+ if (error instanceof Error) {
181
+ process.stderr.write(`Error purging expired archives: ${error.message}\n`);
182
+ }
183
+ }
184
+ };
185
+ exports.purgeExpiredArchives = purgeExpiredArchives;