@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,260 @@
1
+ "use strict";
2
+ /**
3
+ * Fuzzy repository name resolver.
4
+ *
5
+ * Resolves user-supplied repo name inputs (which may be partial, mis-spelled,
6
+ * or differently-cased) to actual repository objects from the full list.
7
+ *
8
+ * Scoring ladder (higher = better match):
9
+ * 100 exact case-sensitive match
10
+ * 95 case-insensitive exact match
11
+ * 90 normalised exact match (spaces/underscores → hyphens)
12
+ * 80 input is a hyphen-boundary prefix of repo name e.g. "platform" → "platform-app"
13
+ * 75 repo name is a hyphen-boundary prefix of input
14
+ * 65 input is a substring of repo name
15
+ * 60 repo name is a substring of input
16
+ * 30–50 Levenshtein edit distance ≤ threshold (typo tolerance)
17
+ *
18
+ * A match is only returned when score ≥ MIN_SCORE (30).
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.VALID_SUFFIX = void 0;
22
+ exports.levenshtein = levenshtein;
23
+ exports.damerauLevenshtein = damerauLevenshtein;
24
+ exports.fuzzyFindRepo = fuzzyFindRepo;
25
+ exports.resolveRepoNames = resolveRepoNames;
26
+ exports.parseRepoSpec = parseRepoSpec;
27
+ exports.resolveRepoSpecs = resolveRepoSpecs;
28
+ /** Minimum score required to accept a fuzzy match. */
29
+ const MIN_SCORE = 30;
30
+ /**
31
+ * Normalise a repo name for comparison:
32
+ * - lowercase
33
+ * - spaces and underscores → hyphens
34
+ * - collapse repeated hyphens
35
+ */
36
+ function normalise(s) {
37
+ return s
38
+ .trim() // strip leading/trailing whitespace first
39
+ .toLowerCase()
40
+ .replace(/[\s_]+/g, '-') // spaces and underscores → hyphens
41
+ .replace(/-{2,}/g, '-') // collapse repeated hyphens
42
+ .replace(/^-+|-+$/g, ''); // strip any leading/trailing hyphens
43
+ }
44
+ /**
45
+ * Compute the Levenshtein edit distance between two strings.
46
+ */
47
+ function levenshtein(a, b) {
48
+ const m = a.length;
49
+ const n = b.length;
50
+ // Use two alternating rows to keep memory O(n)
51
+ let prev = Array.from({ length: n + 1 }, (_, j) => j);
52
+ let curr = new Array(n + 1);
53
+ for (let i = 1; i <= m; i++) {
54
+ curr[0] = i;
55
+ for (let j = 1; j <= n; j++) {
56
+ curr[j] =
57
+ a[i - 1] === b[j - 1]
58
+ ? prev[j - 1]
59
+ : 1 + Math.min(prev[j - 1], prev[j], curr[j - 1]);
60
+ }
61
+ [prev, curr] = [curr, prev];
62
+ }
63
+ return prev[n];
64
+ }
65
+ /**
66
+ * Damerau-Levenshtein distance (Optimal String Alignment variant).
67
+ *
68
+ * Like {@link levenshtein} but an ADJACENT TRANSPOSITION counts as a single
69
+ * edit — so "capser" ↔ "casper" is distance 1, not 2. Transposing two
70
+ * neighbouring characters is the most common human typo, so this is what we
71
+ * use for typo-tolerant repo matching. Names are short, so the full O(mn)
72
+ * matrix is negligible.
73
+ */
74
+ function damerauLevenshtein(a, b) {
75
+ const m = a.length;
76
+ const n = b.length;
77
+ if (m === 0)
78
+ return n;
79
+ if (n === 0)
80
+ return m;
81
+ const d = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
82
+ for (let i = 0; i <= m; i++)
83
+ d[i][0] = i;
84
+ for (let j = 0; j <= n; j++)
85
+ d[0][j] = j;
86
+ for (let i = 1; i <= m; i++) {
87
+ for (let j = 1; j <= n; j++) {
88
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
89
+ d[i][j] = Math.min(d[i - 1][j] + 1, // deletion
90
+ d[i][j - 1] + 1, // insertion
91
+ d[i - 1][j - 1] + cost);
92
+ // Adjacent transposition: a[i-1] a[i-2] == b[j-2] b[j-1]
93
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
94
+ d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1);
95
+ }
96
+ }
97
+ }
98
+ return d[m][n];
99
+ }
100
+ /**
101
+ * Score how well `originalInput` matches `originalRepo`.
102
+ * All normalisation is done here so the scoring ladder is fully reachable.
103
+ */
104
+ function scoreMatch(originalInput, originalRepo) {
105
+ // Exact (case-sensitive)
106
+ if (originalRepo === originalInput)
107
+ return 100;
108
+ // Case-insensitive exact
109
+ if (originalRepo.toLowerCase() === originalInput.toLowerCase())
110
+ return 95;
111
+ // Normalised exact (spaces/underscores → hyphens; reachable for e.g. "my_app" or "my app")
112
+ const normInput = normalise(originalInput);
113
+ const normRepo = normalise(originalRepo);
114
+ if (normRepo === normInput)
115
+ return 90;
116
+ // Prefix: normInput is a hyphen-boundary prefix of normRepo
117
+ // e.g. "platform" → "platform-app"
118
+ if (normRepo.startsWith(normInput + '-'))
119
+ return 80;
120
+ // Prefix: normRepo is a hyphen-boundary prefix of normInput
121
+ if (normInput.startsWith(normRepo + '-'))
122
+ return 75;
123
+ // Substring: normInput appears inside normRepo
124
+ if (normRepo.includes(normInput))
125
+ return 65;
126
+ // Substring: normRepo appears inside normInput
127
+ if (normInput.includes(normRepo))
128
+ return 60;
129
+ // Token-level match: split by hyphens and see how many tokens overlap
130
+ const inputTokens = normInput.split('-').filter(Boolean);
131
+ const repoTokens = normRepo.split('-').filter(Boolean);
132
+ if (inputTokens.length > 0 && repoTokens.length > 0) {
133
+ let matched = 0;
134
+ for (const it of inputTokens) {
135
+ if (repoTokens.some(rt => rt === it || rt.startsWith(it) || it.startsWith(rt))) {
136
+ matched++;
137
+ }
138
+ }
139
+ if (matched > 0) {
140
+ const tokenScore = Math.round((matched / Math.max(inputTokens.length, repoTokens.length)) * 55);
141
+ if (tokenScore >= MIN_SCORE)
142
+ return tokenScore;
143
+ }
144
+ }
145
+ // Typo tolerance — Damerau-Levenshtein so an adjacent transposition
146
+ // (e.g. "capser" → "casper") counts as a single edit.
147
+ const dist = damerauLevenshtein(normInput, normRepo);
148
+ // Allow more edits for longer names (up to 3, never more)
149
+ const maxDist = Math.min(3, Math.floor(Math.max(normInput.length, normRepo.length) * 0.25));
150
+ if (dist <= maxDist && maxDist > 0) {
151
+ return Math.max(MIN_SCORE, 50 - dist * 10);
152
+ }
153
+ return 0;
154
+ }
155
+ /**
156
+ * Find the best-matching repo from `allRepos` for a given user-supplied `input`.
157
+ *
158
+ * @param input The repo name as typed/extracted (may be partial or mis-spelled).
159
+ * @param allRepos Full list of available repositories (must have a `.name` string field).
160
+ * @returns The best match and its score, or `null` if no match is good enough.
161
+ */
162
+ function fuzzyFindRepo(input, allRepos) {
163
+ if (!input || allRepos.length === 0)
164
+ return null;
165
+ // Inputs like "_", "-", or " " normalise to ""; String#includes('') is
166
+ // always true, so they would match every repo. Reject them early.
167
+ if (!normalise(input))
168
+ return null;
169
+ let best = null;
170
+ for (const repo of allRepos) {
171
+ const score = scoreMatch(input, repo.name);
172
+ if (score > 0 && (!best || score > best.score)) {
173
+ best = { repo, score, exact: score >= 90 };
174
+ }
175
+ }
176
+ if (best && best.score >= MIN_SCORE)
177
+ return best;
178
+ return null;
179
+ }
180
+ /**
181
+ * Resolve an array of user-supplied repo name inputs against the full repo list.
182
+ *
183
+ * Returns:
184
+ * - `resolved`: array of `{ input, repo, exact }` for each successfully matched name
185
+ * - `notFound`: array of input strings that had no good match
186
+ */
187
+ function resolveRepoNames(inputs, allRepos) {
188
+ const resolved = [];
189
+ const notFound = [];
190
+ // Deduplicate: if two inputs fuzzy-resolve to the same repo, keep only the first.
191
+ const seen = new Set();
192
+ for (const input of inputs) {
193
+ const match = fuzzyFindRepo(input, allRepos);
194
+ if (match) {
195
+ if (!seen.has(match.repo.name)) {
196
+ seen.add(match.repo.name);
197
+ resolved.push({ input, repo: match.repo, exact: match.exact });
198
+ }
199
+ // else: duplicate resolution — silently skip (first wins)
200
+ }
201
+ else {
202
+ notFound.push(input);
203
+ }
204
+ }
205
+ return { resolved, notFound };
206
+ }
207
+ /** Suffix charset shared with the MCP update_workspace tool (name-suffix dir). */
208
+ exports.VALID_SUFFIX = /^[a-zA-Z0-9_-]+$/;
209
+ /**
210
+ * Parse a single `--repos` entry that may carry an instance suffix.
211
+ *
212
+ * The same repository can be added to a workspace more than once by giving
213
+ * each copy a distinct suffix, e.g. a git-worktree-style second checkout:
214
+ *
215
+ * "casper" -> { name: "casper" } (dir: casper)
216
+ * "casper:cas-101" -> { name: "casper", suffix: "cas-101"} (dir: casper-cas-101)
217
+ *
218
+ * This mirrors the MCP `update_workspace` tool's `{ name, suffix }` shape so
219
+ * the CLI and agent paths behave identically.
220
+ */
221
+ function parseRepoSpec(spec) {
222
+ const trimmed = spec.trim();
223
+ const idx = trimmed.indexOf(':');
224
+ if (idx > 0) {
225
+ const name = trimmed.slice(0, idx).trim();
226
+ const suffix = trimmed.slice(idx + 1).trim();
227
+ return suffix ? { name, suffix } : { name };
228
+ }
229
+ return { name: trimmed };
230
+ }
231
+ /**
232
+ * Resolve an array of `--repos` specs (each possibly `name:suffix`) into repo
233
+ * entries with their target directory names. Unlike {@link resolveRepoNames},
234
+ * this dedupes by the final **directory name**, so the same repo can appear
235
+ * multiple times under different suffixes.
236
+ */
237
+ function resolveRepoSpecs(inputs, allRepos) {
238
+ const resolved = [];
239
+ const notFound = [];
240
+ const invalidSuffix = [];
241
+ const seenDirs = new Set();
242
+ for (const input of inputs) {
243
+ const spec = parseRepoSpec(input);
244
+ if (spec.suffix !== undefined && !exports.VALID_SUFFIX.test(spec.suffix)) {
245
+ invalidSuffix.push(input);
246
+ continue;
247
+ }
248
+ const match = fuzzyFindRepo(spec.name, allRepos);
249
+ if (!match) {
250
+ notFound.push(spec.name);
251
+ continue;
252
+ }
253
+ const directoryName = spec.suffix ? `${match.repo.name}-${spec.suffix}` : match.repo.name;
254
+ if (seenDirs.has(directoryName))
255
+ continue; // duplicate target dir within this batch
256
+ seenDirs.add(directoryName);
257
+ resolved.push({ input, repo: match.repo, exact: match.exact, suffix: spec.suffix, directoryName });
258
+ }
259
+ return { resolved, notFound, invalidSuffix };
260
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isRetryableError = isRetryableError;
4
+ exports.withRetry = withRetry;
5
+ const logger_1 = require("./logger");
6
+ const DEFAULT_OPTIONS = {
7
+ maxRetries: 1,
8
+ initialDelayMs: 2000,
9
+ backoffMultiplier: 2,
10
+ maxDelayMs: 10000,
11
+ };
12
+ const NON_RETRYABLE_PATTERNS = [
13
+ 'authentication failed',
14
+ 'permission denied',
15
+ 'repository not found',
16
+ 'could not read from remote repository',
17
+ '401',
18
+ '403',
19
+ '404',
20
+ ];
21
+ const RETRYABLE_PATTERNS = [
22
+ 'timeout',
23
+ 'etimedout',
24
+ 'econnrefused',
25
+ 'econnreset',
26
+ 'enotfound',
27
+ ];
28
+ function isRetryableError(error) {
29
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
30
+ for (const pattern of NON_RETRYABLE_PATTERNS) {
31
+ if (message.includes(pattern)) {
32
+ return false;
33
+ }
34
+ }
35
+ for (const pattern of RETRYABLE_PATTERNS) {
36
+ if (message.includes(pattern)) {
37
+ return true;
38
+ }
39
+ }
40
+ return false;
41
+ }
42
+ function sleep(ms) {
43
+ return new Promise(resolve => setTimeout(resolve, ms));
44
+ }
45
+ async function withRetry(operation, options, operationName) {
46
+ const opts = { ...DEFAULT_OPTIONS, ...options };
47
+ let lastError;
48
+ let delay = opts.initialDelayMs;
49
+ for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
50
+ try {
51
+ return await operation();
52
+ }
53
+ catch (error) {
54
+ lastError = error;
55
+ if (attempt >= opts.maxRetries || !isRetryableError(error)) {
56
+ throw error;
57
+ }
58
+ const name = operationName || 'operation';
59
+ (0, logger_1.logWarning)(`${name} failed (attempt ${attempt + 1}/${opts.maxRetries + 1}), retrying in ${(delay / 1000).toFixed(1)}s...`);
60
+ await sleep(delay);
61
+ delay = Math.min(delay * opts.backoffMultiplier, opts.maxDelayMs);
62
+ }
63
+ }
64
+ throw lastError;
65
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runInRepo = runInRepo;
4
+ const child_process_1 = require("child_process");
5
+ const util_1 = require("util");
6
+ const execAsync = (0, util_1.promisify)(child_process_1.exec);
7
+ const COMMAND_TIMEOUT = 5 * 60 * 1000; // 5 minutes
8
+ async function runInRepo(repoPath, repoName, command) {
9
+ try {
10
+ const { stdout, stderr } = await execAsync(command, {
11
+ cwd: repoPath,
12
+ timeout: COMMAND_TIMEOUT,
13
+ });
14
+ return {
15
+ repo: repoName,
16
+ status: 'success',
17
+ stdout: stdout.trim(),
18
+ stderr: stderr.trim(),
19
+ };
20
+ }
21
+ catch (error) {
22
+ return {
23
+ repo: repoName,
24
+ status: 'failed',
25
+ stdout: error.stdout?.trim() || '',
26
+ stderr: error.stderr?.trim() || '',
27
+ error: error.message,
28
+ };
29
+ }
30
+ }
@@ -0,0 +1,159 @@
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.loadSuitesStore = loadSuitesStore;
37
+ exports.saveSuitesStore = saveSuitesStore;
38
+ exports.listSuites = listSuites;
39
+ exports.getSuite = getSuite;
40
+ exports.saveSuite = saveSuite;
41
+ exports.deleteSuite = deleteSuite;
42
+ exports.exportSuite = exportSuite;
43
+ exports.exportAllSuites = exportAllSuites;
44
+ exports.importSuites = importSuites;
45
+ exports.validateSuiteName = validateSuiteName;
46
+ const fs = __importStar(require("fs/promises"));
47
+ const config_1 = require("./config");
48
+ const logger_1 = require("./logger");
49
+ const SUITE_NAME_REGEX = /^[a-zA-Z0-9_-]+$/;
50
+ async function ensureCacheDir() {
51
+ try {
52
+ await fs.mkdir(config_1.CACHE_DIR, { recursive: true });
53
+ }
54
+ catch {
55
+ (0, logger_1.logWarning)('Failed to create cache directory');
56
+ }
57
+ }
58
+ async function loadSuitesStore() {
59
+ try {
60
+ const content = await fs.readFile(config_1.SUITES_FILE, 'utf-8');
61
+ return JSON.parse(content);
62
+ }
63
+ catch {
64
+ return { version: 1, suites: [] };
65
+ }
66
+ }
67
+ async function saveSuitesStore(store) {
68
+ await ensureCacheDir();
69
+ await fs.writeFile(config_1.SUITES_FILE, JSON.stringify(store, null, 2), 'utf-8');
70
+ }
71
+ async function listSuites() {
72
+ const store = await loadSuitesStore();
73
+ return store.suites.sort((a, b) => a.name.localeCompare(b.name));
74
+ }
75
+ async function getSuite(name) {
76
+ const store = await loadSuitesStore();
77
+ return store.suites.find(s => s.name === name);
78
+ }
79
+ async function saveSuite(suite) {
80
+ const store = await loadSuitesStore();
81
+ const existingIndex = store.suites.findIndex(s => s.name === suite.name);
82
+ if (existingIndex >= 0) {
83
+ store.suites[existingIndex] = suite;
84
+ }
85
+ else {
86
+ store.suites.push(suite);
87
+ }
88
+ await saveSuitesStore(store);
89
+ }
90
+ async function deleteSuite(name) {
91
+ const store = await loadSuitesStore();
92
+ const index = store.suites.findIndex(s => s.name === name);
93
+ if (index < 0) {
94
+ return false;
95
+ }
96
+ store.suites.splice(index, 1);
97
+ await saveSuitesStore(store);
98
+ return true;
99
+ }
100
+ async function exportSuite(name) {
101
+ const suite = await getSuite(name);
102
+ if (!suite) {
103
+ return null;
104
+ }
105
+ return { version: 1, suites: [suite] };
106
+ }
107
+ async function exportAllSuites() {
108
+ const store = await loadSuitesStore();
109
+ return { version: 1, suites: store.suites };
110
+ }
111
+ async function importSuites(data, overwrite) {
112
+ const result = { imported: [], skipped: [], errors: [] };
113
+ const store = await loadSuitesStore();
114
+ for (const suite of data.suites) {
115
+ const nameValidation = validateSuiteName(suite.name);
116
+ if (nameValidation !== true) {
117
+ result.errors.push(`${suite.name}: ${nameValidation}`);
118
+ continue;
119
+ }
120
+ if (!Array.isArray(suite.entries)) {
121
+ result.errors.push(`${suite.name}: missing or invalid entries`);
122
+ continue;
123
+ }
124
+ const hasInvalidEntries = suite.entries.some(e => !e.repoName || typeof e.repoName !== 'string' || !e.directoryName || typeof e.directoryName !== 'string');
125
+ if (hasInvalidEntries) {
126
+ result.errors.push(`${suite.name}: entries contain invalid repoName or directoryName values`);
127
+ continue;
128
+ }
129
+ const existingIndex = store.suites.findIndex(s => s.name === suite.name);
130
+ if (existingIndex >= 0 && !overwrite) {
131
+ result.skipped.push(suite.name);
132
+ continue;
133
+ }
134
+ if (existingIndex >= 0) {
135
+ store.suites[existingIndex] = suite;
136
+ }
137
+ else {
138
+ store.suites.push(suite);
139
+ }
140
+ result.imported.push(suite.name);
141
+ }
142
+ await saveSuitesStore(store);
143
+ return result;
144
+ }
145
+ function validateSuiteName(name) {
146
+ if (!name || name.trim().length === 0) {
147
+ return 'Suite name cannot be empty';
148
+ }
149
+ if (!SUITE_NAME_REGEX.test(name)) {
150
+ return 'Suite name must contain only alphanumeric characters, hyphens, and underscores';
151
+ }
152
+ if (name.length < 2) {
153
+ return 'Suite name must be at least 2 characters long';
154
+ }
155
+ if (name.length > 50) {
156
+ return 'Suite name must be 50 characters or less';
157
+ }
158
+ return true;
159
+ }
@@ -0,0 +1,107 @@
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.syncRepository = syncRepository;
37
+ const child_process_1 = require("child_process");
38
+ const util_1 = require("util");
39
+ const fs = __importStar(require("fs/promises"));
40
+ const logger_1 = require("./logger");
41
+ const colors_1 = require("./colors");
42
+ const retry_1 = require("./retry");
43
+ const execAsync = (0, util_1.promisify)(child_process_1.exec);
44
+ async function syncRepository(repoPath, repoName, silent = false) {
45
+ try {
46
+ // Check if directory exists
47
+ try {
48
+ await fs.access(repoPath);
49
+ }
50
+ catch {
51
+ return {
52
+ repo: repoName,
53
+ status: 'skipped',
54
+ message: 'Directory not found',
55
+ };
56
+ }
57
+ // Check if it's a git repository
58
+ try {
59
+ await execAsync('git rev-parse --git-dir', { cwd: repoPath });
60
+ }
61
+ catch {
62
+ return {
63
+ repo: repoName,
64
+ status: 'skipped',
65
+ message: 'Not a git repository',
66
+ };
67
+ }
68
+ // Check for uncommitted changes
69
+ const { stdout: statusOutput } = await execAsync('git status --porcelain', { cwd: repoPath });
70
+ if (statusOutput.trim().length > 0) {
71
+ return {
72
+ repo: repoName,
73
+ status: 'skipped',
74
+ message: 'Uncommitted changes',
75
+ };
76
+ }
77
+ // Get current branch
78
+ const { stdout: branchOutput } = await execAsync('git branch --show-current', { cwd: repoPath });
79
+ const currentBranch = branchOutput.trim();
80
+ if (!currentBranch) {
81
+ return {
82
+ repo: repoName,
83
+ status: 'skipped',
84
+ message: 'Detached HEAD state',
85
+ };
86
+ }
87
+ if (!silent)
88
+ (0, logger_1.logInfo)(`Pulling ${(0, colors_1.colorize)(repoName, 'cyan')} (${currentBranch})...`);
89
+ // Git pull with retry
90
+ await (0, retry_1.withRetry)(() => execAsync('git pull', { cwd: repoPath, timeout: 60000 }), undefined, `Pull ${repoName}`);
91
+ if (!silent)
92
+ (0, logger_1.logSuccess)(`Updated ${(0, colors_1.colorize)(repoName, 'cyan')}`);
93
+ return {
94
+ repo: repoName,
95
+ status: 'success',
96
+ };
97
+ }
98
+ catch (error) {
99
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
100
+ (0, logger_1.logError)(`Failed to sync ${(0, colors_1.colorize)(repoName, 'cyan')}: ${errorMessage}`);
101
+ return {
102
+ repo: repoName,
103
+ status: 'failed',
104
+ message: errorMessage,
105
+ };
106
+ }
107
+ }