skillrepo 2.0.0 → 3.1.0

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 (72) hide show
  1. package/README.md +276 -145
  2. package/bin/skillrepo.mjs +224 -36
  3. package/package.json +6 -3
  4. package/src/commands/add.mjs +176 -0
  5. package/src/commands/get.mjs +116 -0
  6. package/src/commands/init.mjs +589 -143
  7. package/src/commands/list.mjs +176 -0
  8. package/src/commands/remove.mjs +162 -0
  9. package/src/commands/search.mjs +188 -0
  10. package/src/commands/session-sync.mjs +152 -0
  11. package/src/commands/uninstall.mjs +484 -0
  12. package/src/commands/update.mjs +184 -0
  13. package/src/lib/artifact-registry.mjs +265 -0
  14. package/src/lib/cli-config.mjs +230 -0
  15. package/src/lib/config.mjs +238 -0
  16. package/src/lib/detect-ides.mjs +0 -19
  17. package/src/lib/errors.mjs +264 -0
  18. package/src/lib/file-write.mjs +705 -0
  19. package/src/lib/fs-utils.mjs +83 -1
  20. package/src/lib/http.mjs +817 -37
  21. package/src/lib/identifier.mjs +153 -0
  22. package/src/lib/mcp-merge.mjs +275 -0
  23. package/src/lib/mergers/gitignore.mjs +73 -18
  24. package/src/lib/mergers/session-hook.mjs +298 -0
  25. package/src/lib/paths.mjs +67 -17
  26. package/src/lib/prompt.mjs +11 -44
  27. package/src/lib/removers/claude-mcp.mjs +67 -0
  28. package/src/lib/removers/cursor-mcp.mjs +60 -0
  29. package/src/lib/removers/env-local.mjs +55 -0
  30. package/src/lib/removers/gitignore.mjs +108 -0
  31. package/src/lib/removers/settings.mjs +183 -0
  32. package/src/lib/removers/vscode-mcp.mjs +87 -0
  33. package/src/lib/removers/windsurf-mcp.mjs +65 -0
  34. package/src/lib/sync.mjs +305 -0
  35. package/src/test/commands/add.test.mjs +285 -0
  36. package/src/test/commands/get.test.mjs +176 -0
  37. package/src/test/commands/init.test.mjs +697 -0
  38. package/src/test/commands/list.test.mjs +172 -0
  39. package/src/test/commands/remove.test.mjs +234 -0
  40. package/src/test/commands/search.test.mjs +204 -0
  41. package/src/test/commands/session-sync.test.mjs +350 -0
  42. package/src/test/commands/uninstall.test.mjs +768 -0
  43. package/src/test/commands/update.test.mjs +322 -0
  44. package/src/test/detect-ides.test.mjs +9 -14
  45. package/src/test/dispatcher.test.mjs +224 -0
  46. package/src/test/e2e/cli-commands.test.mjs +576 -0
  47. package/src/test/e2e/mock-server.mjs +364 -22
  48. package/src/test/helpers/capture-stream.mjs +48 -0
  49. package/src/test/integration/file-write.integration.test.mjs +279 -0
  50. package/src/test/lib/artifact-registry.test.mjs +268 -0
  51. package/src/test/lib/cli-config.test.mjs +407 -0
  52. package/src/test/lib/config.test.mjs +257 -0
  53. package/src/test/lib/errors.test.mjs +359 -0
  54. package/src/test/lib/file-write.test.mjs +784 -0
  55. package/src/test/lib/http.test.mjs +1198 -0
  56. package/src/test/lib/identifier.test.mjs +157 -0
  57. package/src/test/lib/mcp-merge.test.mjs +345 -0
  58. package/src/test/lib/paths.test.mjs +83 -0
  59. package/src/test/lib/sync.test.mjs +514 -0
  60. package/src/test/mergers/gitignore.test.mjs +145 -20
  61. package/src/test/mergers/session-hook.test.mjs +745 -0
  62. package/src/test/mergers/uninstall-claude-mcp.test.mjs +145 -0
  63. package/src/test/mergers/uninstall-cursor-mcp.test.mjs +108 -0
  64. package/src/test/mergers/uninstall-env-local.test.mjs +144 -0
  65. package/src/test/mergers/uninstall-gitignore.test.mjs +209 -0
  66. package/src/test/mergers/uninstall-settings.test.mjs +285 -0
  67. package/src/test/mergers/uninstall-vscode-mcp.test.mjs +215 -0
  68. package/src/test/mergers/uninstall-windsurf-mcp.test.mjs +122 -0
  69. package/src/lib/write-configs.mjs +0 -202
  70. package/src/test/e2e/HANDOFF.md +0 -223
  71. package/src/test/e2e/cli-init.test.mjs +0 -213
  72. package/src/test/e2e/payload-factory.mjs +0 -22
@@ -3,7 +3,16 @@
3
3
  * Creates directories as needed, handles errors cleanly.
4
4
  */
5
5
 
6
- import { readFileSync, writeFileSync, existsSync, mkdirSync, statSync, chmodSync } from "node:fs";
6
+ import {
7
+ readFileSync,
8
+ writeFileSync,
9
+ existsSync,
10
+ mkdirSync,
11
+ statSync,
12
+ chmodSync,
13
+ renameSync,
14
+ unlinkSync,
15
+ } from "node:fs";
7
16
  import { dirname } from "node:path";
8
17
 
9
18
  /**
@@ -50,3 +59,76 @@ export function writeExecutable(filePath, content) {
50
59
  export function pathExists(p) {
51
60
  return existsSync(p);
52
61
  }
62
+
63
+ /**
64
+ * Atomic write via temp-file + rename. Matches the pattern in
65
+ * `config.mjs`: write to `<path>.tmp`, then `renameSync` into place
66
+ * so the destination is never observed in a half-written state, and
67
+ * unlink the temp file on rename failure so a partial credential
68
+ * value is never left behind on disk.
69
+ *
70
+ * Use this (not `writeFileSafe`) for any module that modifies a user
71
+ * config file containing credentials or shared state. The uninstall
72
+ * removers (#885) touch `.env.local`, `.mcp.json`, and
73
+ * `settings.local.json` — all three benefit from atomic writes, the
74
+ * first two because of the credential-leak risk, the third because
75
+ * Claude Code parses it on startup and a half-written JSON would
76
+ * break the user's session.
77
+ *
78
+ * Parent-directory semantics match `writeFileSafe`: created
79
+ * recursively if missing. Directory-as-file collision is rejected
80
+ * before the temp-file is written.
81
+ *
82
+ * @param {string} filePath - Absolute path to the final destination.
83
+ * @param {string} content - UTF-8 content to persist.
84
+ * @param {object} [options]
85
+ * @param {number} [options.mode] - chmod applied to the temp file
86
+ * BEFORE rename so the destination never exists with looser
87
+ * permissions than intended. Skipped on Windows.
88
+ */
89
+ export function writeFileAtomic(filePath, content, { mode } = {}) {
90
+ const dir = dirname(filePath);
91
+ if (!existsSync(dir)) {
92
+ mkdirSync(dir, { recursive: true });
93
+ }
94
+ if (existsSync(filePath) && statSync(filePath).isDirectory()) {
95
+ throw new Error(`${filePath} is a directory, expected a file`);
96
+ }
97
+
98
+ const tmpPath = `${filePath}.tmp`;
99
+ try {
100
+ writeFileSync(tmpPath, content, "utf-8");
101
+ } catch (err) {
102
+ // Re-throw with a clearer message but preserve the cause for
103
+ // --verbose. A temp-file write failure is almost always a
104
+ // permissions or disk-full issue; the original error surfaces it.
105
+ throw new Error(`Cannot write ${tmpPath}: ${err.message}`, { cause: err });
106
+ }
107
+
108
+ if (mode !== undefined && process.platform !== "win32") {
109
+ try {
110
+ chmodSync(tmpPath, mode);
111
+ } catch {
112
+ // Non-fatal — same rationale as config.mjs: chmod failure
113
+ // doesn't corrupt the file, the destination just has looser
114
+ // permissions than intended. Callers that care can stat the
115
+ // file after the write.
116
+ }
117
+ }
118
+
119
+ try {
120
+ renameSync(tmpPath, filePath);
121
+ } catch (err) {
122
+ // Clean up the stale temp file so a partial credential value
123
+ // isn't left behind. Best-effort — if the unlink also fails,
124
+ // the original rename error is still what we surface.
125
+ try {
126
+ unlinkSync(tmpPath);
127
+ } catch {
128
+ /* best-effort */
129
+ }
130
+ throw new Error(`Cannot install ${filePath}: ${err.message}`, {
131
+ cause: err,
132
+ });
133
+ }
134
+ }