4runr-cursor-setup 0.1.6 → 0.1.8

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.
package/dist/cli.js CHANGED
@@ -1,51 +1,59 @@
1
1
  #!/usr/bin/env node
2
- import { init } from "./commands/init.js";
3
- import { addGroup, listGroups, removeGroup, GROUPS } from "./commands/commands.js";
4
- const args = process.argv.slice(2);
5
- const cmd = args[0];
2
+ import { listGroups, addGroup, removeGroup, GROUPS } from "./commands/commands.js";
3
+ import { doctor } from "./commands/doctor.js";
4
+ import { createRequire } from "module";
5
+ const require = createRequire(import.meta.url);
6
+ const TOOL_VERSION = require("../package.json").version;
6
7
  function usage() {
7
- console.log("4runr-cursor-setup");
8
8
  console.log("Usage:");
9
- console.log(" 4runr-cursor-setup init [--force]");
10
- console.log(" 4runr-cursor-setup list");
11
- console.log(" 4runr-cursor-setup add <group> [--force]");
12
- console.log(" 4runr-cursor-setup remove <group>");
9
+ console.log(" list");
10
+ console.log(" add <group> [--force] [--dry-run]");
11
+ console.log(" remove <group> [--dry-run]");
12
+ console.log(" doctor [--fix] [--strict] [--json] [--dry-run]");
13
13
  console.log("");
14
14
  console.log("Groups: " + Object.keys(GROUPS).join(", "));
15
15
  }
16
- async function main() {
17
- const force = args.includes("--force");
18
- if (cmd === "init") {
19
- await init({ cwd: process.cwd() });
20
- addGroup({ cwd: process.cwd(), group: "core", force });
21
- return;
22
- }
23
- if (cmd === "list") {
24
- listGroups();
25
- return;
26
- }
27
- if (cmd === "add") {
28
- const group = args[1];
29
- if (!group || !(group in GROUPS)) {
30
- usage();
31
- process.exit(1);
32
- }
33
- addGroup({ cwd: process.cwd(), group, force });
34
- return;
35
- }
36
- if (cmd === "remove") {
37
- const group = args[1];
38
- if (!group || !(group in GROUPS)) {
39
- usage();
40
- process.exit(1);
41
- }
42
- removeGroup({ cwd: process.cwd(), group });
43
- return;
44
- }
45
- usage();
46
- process.exit(1);
16
+ const args = process.argv.slice(2);
17
+ const cmd = args[0];
18
+ const cwd = process.cwd();
19
+ const flags = new Set(args.slice(1));
20
+ const has = (f) => flags.has(f);
21
+ function parseGroup(raw) {
22
+ if (!raw)
23
+ throw new Error("Missing group name");
24
+ const g = raw;
25
+ if (!(g in GROUPS))
26
+ throw new Error(`Unknown group: ${raw}`);
27
+ return g;
28
+ }
29
+ if (cmd === "list") {
30
+ listGroups();
31
+ process.exit(0);
32
+ }
33
+ if (cmd === "add") {
34
+ const group = parseGroup(args[1]);
35
+ addGroup({
36
+ cwd,
37
+ group,
38
+ force: has("--force"),
39
+ dryRun: has("--dry-run"),
40
+ });
41
+ process.exit(0);
42
+ }
43
+ if (cmd === "remove") {
44
+ const group = parseGroup(args[1]);
45
+ removeGroup({ cwd, group, dryRun: has("--dry-run") });
46
+ process.exit(0);
47
+ }
48
+ if (cmd === "doctor") {
49
+ doctor(cwd, TOOL_VERSION, {
50
+ fix: has("--fix"),
51
+ json: has("--json"),
52
+ strict: has("--strict"),
53
+ dryRun: has("--dry-run"),
54
+ });
55
+ // Exit with the exit code set by doctor (or 0 if not set)
56
+ process.exit(process.exitCode || 0);
47
57
  }
48
- main().catch((err) => {
49
- console.error(err);
50
- process.exit(1);
51
- });
58
+ usage();
59
+ process.exit(1);
@@ -1,13 +1,23 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
3
  import * as url from "url";
4
+ import { createRequire } from "module";
4
5
  import { ensureMemory } from "./memory.js";
6
+ import { loadManifest, saveManifest, createEmptyManifest, upsertFile, removeFile, sha256, } from "./manifest.js";
5
7
  export const GROUPS = {
6
8
  core: ["4runr-start.md", "4runr-close.md"],
7
9
  planning: ["4runr-task.md", "4runr-phase.md"],
8
10
  governance: ["4runr-decision.md", "4runr-scope-change.md"],
9
11
  debugging: ["4runr-repro.md", "4runr-verify.md"],
10
12
  };
13
+ function assertSafeRelPath(rel) {
14
+ const norm = rel.replace(/\\/g, "/");
15
+ if (norm.includes(".."))
16
+ throw new Error("Refusing unsafe path traversal: " + rel);
17
+ if (path.isAbsolute(rel))
18
+ throw new Error("Refusing absolute path: " + rel);
19
+ return norm;
20
+ }
11
21
  const MANAGED_MARKER = "<!-- managed-by: 4runr-cursor-setup -->";
12
22
  function ensureDir(p) {
13
23
  fs.mkdirSync(p, { recursive: true });
@@ -46,6 +56,30 @@ function getTemplatesRoot() {
46
56
  const here = path.dirname(url.fileURLToPath(import.meta.url));
47
57
  return path.resolve(here, "..", "templates");
48
58
  }
59
+ function getToolVersion() {
60
+ const require = createRequire(import.meta.url);
61
+ // From dist/commands/*.js, go up two levels to reach package.json at root
62
+ return require("../../package.json").version;
63
+ }
64
+ function ensureManifest(cwd) {
65
+ let m = loadManifest(cwd);
66
+ if (!m) {
67
+ m = createEmptyManifest(getToolVersion());
68
+ saveManifest(cwd, m);
69
+ }
70
+ return m;
71
+ }
72
+ function assertPathUnderCommands(cwd, filePath) {
73
+ const resolved = path.resolve(cwd, filePath);
74
+ const commandsDir = path.resolve(cwd, ".cursor", "commands");
75
+ if (!resolved.startsWith(commandsDir + path.sep) && resolved !== commandsDir) {
76
+ throw new Error(`Path escapes .cursor/commands: ${filePath}`);
77
+ }
78
+ }
79
+ function getRelativePath(cwd, absolutePath) {
80
+ const rel = path.relative(cwd, absolutePath);
81
+ return assertSafeRelPath(rel);
82
+ }
49
83
  export function listGroups() {
50
84
  console.log("Available groups:");
51
85
  Object.keys(GROUPS).forEach((g) => {
@@ -53,42 +87,218 @@ export function listGroups() {
53
87
  });
54
88
  }
55
89
  export function addGroup(opts) {
56
- const { cwd, group, force = false } = opts;
90
+ const { cwd, group, force = false, dryRun = false } = opts;
57
91
  const templatesRoot = getTemplatesRoot();
58
92
  const srcBase = path.join(templatesRoot, "commands", group);
59
93
  const outBase = path.join(cwd, ".cursor", "commands");
60
- ensureDir(outBase);
94
+ if (dryRun) {
95
+ console.log(`PLAN add ${group} (dry-run)`);
96
+ }
97
+ else {
98
+ ensureDir(outBase);
99
+ }
100
+ // Load or ensure manifest exists (for dry-run, we only read it)
101
+ let manifest = loadManifest(cwd) || createEmptyManifest(getToolVersion());
102
+ if (!dryRun && !loadManifest(cwd)) {
103
+ // For real runs, ensure manifest exists on disk
104
+ manifest = ensureManifest(cwd);
105
+ }
61
106
  const files = GROUPS[group];
62
107
  let copied = 0;
108
+ const writtenPaths = new Set();
109
+ const operations = [];
63
110
  for (const f of files) {
64
111
  const src = path.join(srcBase, f);
65
112
  const dst = path.join(outBase, f);
66
- if (!fs.existsSync(src))
113
+ // Safety: ensure path is under .cursor/commands
114
+ assertPathUnderCommands(cwd, dst);
115
+ const relPath = getRelativePath(cwd, dst);
116
+ // Check for duplicates
117
+ if (writtenPaths.has(relPath)) {
118
+ const errorMsg = `ERROR duplicate destination path: ${relPath}`;
119
+ if (dryRun) {
120
+ operations.push(errorMsg);
121
+ continue;
122
+ }
123
+ throw new Error(`Duplicate destination path: ${relPath}`);
124
+ }
125
+ writtenPaths.add(relPath);
126
+ if (!fs.existsSync(src)) {
127
+ const errorMsg = `ERROR template missing: ${src}`;
128
+ if (dryRun) {
129
+ operations.push(errorMsg);
130
+ continue;
131
+ }
67
132
  throw new Error("Template missing: " + src);
68
- if (copyIfMissing(src, dst, force))
69
- copied++;
133
+ }
134
+ // Read template content to compute hash (needed for planning)
135
+ const templateBody = fs.readFileSync(src, "utf8");
136
+ const templateContent = templateBody.includes(MANAGED_MARKER) ? templateBody : `${MANAGED_MARKER}\n\n${templateBody}`;
137
+ const templateHash = sha256(templateContent);
138
+ const exists = fs.existsSync(dst);
139
+ const hasMarker = exists ? fileContainsMarker(dst) : false;
140
+ const inManifest = manifest.files.some((mf) => mf.path === relPath);
141
+ // Tool-managed = has marker AND in manifest
142
+ const isToolManaged = exists && hasMarker && inManifest;
143
+ if (!exists) {
144
+ // File doesn't exist - will create
145
+ if (dryRun) {
146
+ operations.push(`CREATE ${relPath}`);
147
+ }
148
+ else {
149
+ writeWithMarker(src, dst);
150
+ copied++;
151
+ const templateId = `commands/${group}/${f}`;
152
+ upsertFile(manifest, {
153
+ path: relPath,
154
+ templateId,
155
+ templateVersion: 1,
156
+ contentSha256: templateHash,
157
+ });
158
+ }
159
+ }
160
+ else if (isToolManaged && force) {
161
+ // File exists, is tool-managed (marker + manifest), and --force is set - will overwrite
162
+ if (dryRun) {
163
+ operations.push(`OVERWRITE ${relPath} (tool-managed, --force)`);
164
+ }
165
+ else {
166
+ writeWithMarker(src, dst);
167
+ copied++;
168
+ const templateId = `commands/${group}/${f}`;
169
+ upsertFile(manifest, {
170
+ path: relPath,
171
+ templateId,
172
+ templateVersion: 1,
173
+ contentSha256: templateHash,
174
+ });
175
+ }
176
+ }
177
+ else if (isToolManaged && !force) {
178
+ // File exists, is tool-managed, but no --force - will skip
179
+ if (dryRun) {
180
+ operations.push(`SKIP ${relPath} (exists, tool-managed)`);
181
+ }
182
+ else {
183
+ ensureMarkerOnFile(dst);
184
+ }
185
+ }
186
+ else {
187
+ // File exists but NOT tool-managed (user-modified, orphan marker, or collision)
188
+ // Always skip, even with --force
189
+ if (dryRun) {
190
+ operations.push(`SKIP ${relPath} (exists, un-managed)`);
191
+ }
192
+ else {
193
+ ensureMarkerOnFile(dst);
194
+ }
195
+ }
196
+ }
197
+ if (dryRun) {
198
+ for (const op of operations) {
199
+ console.log(op);
200
+ }
201
+ return;
202
+ }
203
+ // Update manifest groups (add if not present)
204
+ if (!manifest.groups.includes(group)) {
205
+ manifest.groups.push(group);
70
206
  }
207
+ manifest.updatedAt = new Date().toISOString();
208
+ saveManifest(cwd, manifest);
71
209
  console.log('Installed group "' + group + '" into .cursor/commands (' + copied + " file(s) written).");
72
210
  if (group === "core")
73
211
  ensureMemory(cwd);
74
212
  }
75
213
  export function removeGroup(opts) {
76
- const { cwd, group } = opts;
214
+ const { cwd, group, dryRun = false } = opts;
215
+ // Load manifest (doctor guarantees it exists, but handle gracefully)
216
+ const manifest = loadManifest(cwd);
217
+ if (!manifest) {
218
+ if (dryRun) {
219
+ console.log(`PLAN remove ${group} (dry-run)`);
220
+ console.log("SKIP manifest missing");
221
+ return;
222
+ }
223
+ // If manifest doesn't exist, skip removal (nothing to remove)
224
+ console.log("SKIP manifest missing");
225
+ return;
226
+ }
227
+ if (dryRun) {
228
+ console.log(`PLAN remove ${group} (dry-run)`);
229
+ }
77
230
  const outBase = path.join(cwd, ".cursor", "commands");
78
- const files = GROUPS[group];
79
231
  let removed = 0;
80
232
  let skipped = 0;
81
- for (const f of files) {
82
- const dst = path.join(outBase, f);
83
- if (!fs.existsSync(dst))
233
+ const operations = [];
234
+ // Find files in manifest that belong to this group
235
+ // templateId pattern: "commands/{group}/{filename}"
236
+ const groupPrefix = `commands/${group}/`;
237
+ const manifestFiles = manifest.files.filter((f) => f.templateId.startsWith(groupPrefix));
238
+ for (const manifestFile of manifestFiles) {
239
+ const dst = path.join(cwd, manifestFile.path);
240
+ // Safety: ensure path is under .cursor/commands
241
+ assertPathUnderCommands(cwd, dst);
242
+ if (!fs.existsSync(dst)) {
243
+ // File missing on disk
244
+ if (dryRun) {
245
+ operations.push(`IGNORE ${manifestFile.path} (missing on disk)`);
246
+ }
247
+ else {
248
+ // File missing on disk, remove from manifest
249
+ removeFile(manifest, manifestFile.path);
250
+ }
84
251
  continue;
85
- // Only delete files we manage.
252
+ }
253
+ // Only delete files that have marker AND are in manifest
254
+ // Never delete just because it has a marker
86
255
  if (!fileContainsMarker(dst)) {
87
256
  skipped++;
257
+ if (dryRun) {
258
+ operations.push(`SKIP ${manifestFile.path} (not managed)`);
259
+ }
88
260
  continue;
89
261
  }
90
- fs.unlinkSync(dst);
91
- removed++;
262
+ // File has marker and is in manifest - safe to delete
263
+ if (dryRun) {
264
+ operations.push(`DELETE ${manifestFile.path}`);
265
+ }
266
+ else {
267
+ fs.unlinkSync(dst);
268
+ removeFile(manifest, manifestFile.path);
269
+ removed++;
270
+ }
271
+ }
272
+ // Check for orphan marker files (files with marker but not in manifest for this group)
273
+ if (fs.existsSync(outBase)) {
274
+ const manifestPaths = new Set(manifest.files.map((f) => f.path));
275
+ const entries = fs.readdirSync(outBase, { withFileTypes: true });
276
+ for (const entry of entries) {
277
+ if (entry.isFile()) {
278
+ const filePath = path.join(outBase, entry.name);
279
+ const relPath = path.relative(cwd, filePath).replace(/\\/g, "/");
280
+ // If file has marker but is not in manifest, it's an orphan
281
+ if (fileContainsMarker(filePath) && !manifestPaths.has(relPath)) {
282
+ if (dryRun) {
283
+ operations.push(`IGNORE ${relPath} (orphan marker, not in manifest)`);
284
+ }
285
+ }
286
+ }
287
+ }
288
+ }
289
+ if (dryRun) {
290
+ for (const op of operations) {
291
+ console.log(op);
292
+ }
293
+ return;
294
+ }
295
+ // Remove group from manifest.groups if no files remain for this group
296
+ // Re-check after removals
297
+ const remainingGroupFiles = manifest.files.filter((f) => f.templateId.startsWith(groupPrefix));
298
+ if (remainingGroupFiles.length === 0 && manifest.groups.includes(group)) {
299
+ manifest.groups = manifest.groups.filter((g) => g !== group);
92
300
  }
301
+ manifest.updatedAt = new Date().toISOString();
302
+ saveManifest(cwd, manifest);
93
303
  console.log('Removed group "' + group + '" from .cursor/commands (' + removed + " file(s) deleted, " + skipped + " skipped).");
94
304
  }
@@ -0,0 +1,113 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { ensureMemory } from "./memory.js";
4
+ import { loadManifest, saveManifest, createEmptyManifest, MANIFEST_REL, sha256 } from "./manifest.js";
5
+ const REQUIRED = [
6
+ "docs/status.md",
7
+ "docs/todo.md",
8
+ "docs/decisions.md",
9
+ ".cursor/rules/project.md",
10
+ ];
11
+ const MANAGED_MARKER = "<!-- managed-by: 4runr-cursor-setup -->";
12
+ const COMMANDS_DIR = ".cursor/commands";
13
+ function fileContainsMarker(p) {
14
+ if (!fs.existsSync(p))
15
+ return false;
16
+ const s = fs.readFileSync(p, "utf8");
17
+ return s.includes(MANAGED_MARKER);
18
+ }
19
+ export function doctor(cwd, toolVersion, opts) {
20
+ // Run fixes first if requested
21
+ if (opts.fix) {
22
+ ensureMemory(cwd, { dryRun: !!opts.dryRun });
23
+ // Always ensure manifest exists when --fix is used
24
+ if (!loadManifest(cwd)) {
25
+ const nm = createEmptyManifest(toolVersion);
26
+ saveManifest(cwd, nm, !!opts.dryRun);
27
+ }
28
+ }
29
+ // Collect issues after fixes have run
30
+ const issues = [];
31
+ for (const rel of REQUIRED) {
32
+ if (!fs.existsSync(path.join(cwd, rel))) {
33
+ issues.push({ level: "error", code: "MISSING_FILE", detail: rel });
34
+ }
35
+ }
36
+ const m = loadManifest(cwd);
37
+ if (!m) {
38
+ issues.push({ level: "warn", code: "MISSING_MANIFEST", detail: MANIFEST_REL });
39
+ }
40
+ else {
41
+ // Check manifest entries for missing files
42
+ for (const f of m.files) {
43
+ const filePath = path.join(cwd, f.path);
44
+ if (!fs.existsSync(filePath)) {
45
+ issues.push({
46
+ level: "warn",
47
+ code: "MANIFEST_ENTRY_MISSING_ON_DISK",
48
+ detail: f.path,
49
+ });
50
+ }
51
+ else {
52
+ // Check for collisions: file exists but doesn't have marker or has wrong content
53
+ if (!fileContainsMarker(filePath)) {
54
+ issues.push({
55
+ level: "error",
56
+ code: "COLLISION_UNMANAGED_FILE",
57
+ detail: f.path,
58
+ });
59
+ }
60
+ else {
61
+ // Verify content hash matches (optional strict check)
62
+ const currentContent = fs.readFileSync(filePath, "utf8");
63
+ const currentHash = sha256(currentContent);
64
+ if (currentHash !== f.contentSha256) {
65
+ issues.push({
66
+ level: "warn",
67
+ code: "MANIFEST_FILE_MODIFIED",
68
+ detail: f.path,
69
+ });
70
+ }
71
+ }
72
+ }
73
+ }
74
+ // Check for orphan marker files in .cursor/commands (files with marker but not in manifest)
75
+ const commandsDir = path.join(cwd, COMMANDS_DIR);
76
+ if (fs.existsSync(commandsDir)) {
77
+ const manifestPaths = new Set(m.files.map((f) => f.path));
78
+ const entries = fs.readdirSync(commandsDir, { withFileTypes: true });
79
+ for (const entry of entries) {
80
+ if (entry.isFile()) {
81
+ const filePath = path.join(commandsDir, entry.name);
82
+ const relPath = path.relative(cwd, filePath).replace(/\\/g, "/");
83
+ // If file has marker but is not in manifest, it's an orphan
84
+ if (fileContainsMarker(filePath) && !manifestPaths.has(relPath)) {
85
+ issues.push({
86
+ level: "warn",
87
+ code: "ORPHAN_MARKER_FILE",
88
+ detail: relPath,
89
+ });
90
+ }
91
+ }
92
+ }
93
+ }
94
+ }
95
+ const hasErrors = issues.some(i => i.level === "error");
96
+ if (opts.json) {
97
+ console.log(JSON.stringify({ ok: !hasErrors, issues }, null, 2));
98
+ }
99
+ else {
100
+ if (issues.length === 0)
101
+ console.log("✅ doctor: OK");
102
+ else {
103
+ console.log("doctor report:");
104
+ for (const i of issues) {
105
+ console.log(`- ${i.level.toUpperCase()} ${i.code}: ${i.detail}`);
106
+ }
107
+ }
108
+ }
109
+ if (opts.strict && issues.length > 0)
110
+ process.exitCode = 1;
111
+ if (hasErrors)
112
+ process.exitCode = 1;
113
+ }
@@ -0,0 +1,43 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import crypto from "crypto";
4
+ export const MANIFEST_REL = ".cursor/4runr.manifest.json";
5
+ export function sha256(s) {
6
+ return crypto.createHash("sha256").update(s, "utf8").digest("hex");
7
+ }
8
+ export function loadManifest(cwd) {
9
+ const fp = path.join(cwd, MANIFEST_REL);
10
+ if (!fs.existsSync(fp))
11
+ return null;
12
+ return JSON.parse(fs.readFileSync(fp, "utf8"));
13
+ }
14
+ export function saveManifest(cwd, m, dryRun = false) {
15
+ if (dryRun)
16
+ return;
17
+ const fp = path.join(cwd, MANIFEST_REL);
18
+ fs.mkdirSync(path.dirname(fp), { recursive: true });
19
+ fs.writeFileSync(fp, JSON.stringify(m, null, 2) + "\n", "utf8");
20
+ }
21
+ export function createEmptyManifest(toolVersion) {
22
+ return {
23
+ schemaVersion: 1,
24
+ tool: "4runr-cursor-setup",
25
+ toolVersion,
26
+ updatedAt: new Date().toISOString(),
27
+ groups: [],
28
+ files: [],
29
+ };
30
+ }
31
+ export function upsertFile(m, rec) {
32
+ const i = m.files.findIndex(f => f.path === rec.path);
33
+ if (i >= 0)
34
+ m.files[i] = rec;
35
+ else
36
+ m.files.push(rec);
37
+ }
38
+ export function removeFile(m, relPath) {
39
+ m.files = m.files.filter(f => f.path !== relPath);
40
+ }
41
+ export function hasFile(m, relPath) {
42
+ return m.files.some(f => f.path === relPath);
43
+ }
@@ -1,35 +1,19 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import * as url from "url";
4
- function ensureDir(p) {
5
- fs.mkdirSync(p, { recursive: true });
6
- }
7
- function copyIfMissing(src, dst) {
8
- if (fs.existsSync(dst))
9
- return false;
10
- ensureDir(path.dirname(dst));
11
- fs.copyFileSync(src, dst);
12
- return true;
13
- }
14
- function getTemplatesRoot() {
15
- const here = path.dirname(url.fileURLToPath(import.meta.url));
16
- return path.resolve(here, "..", "templates");
17
- }
18
- export function ensureMemory(cwd) {
19
- const root = getTemplatesRoot();
20
- const srcBase = path.join(root, "memory");
21
- const mappings = [
22
- { src: path.join(srcBase, "docs", "status.md"), dst: path.join(cwd, "docs", "status.md") },
23
- { src: path.join(srcBase, "docs", "todo.md"), dst: path.join(cwd, "docs", "todo.md") },
24
- { src: path.join(srcBase, "docs", "decisions.md"), dst: path.join(cwd, "docs", "decisions.md") },
25
- { src: path.join(srcBase, "cursor", "project.md"), dst: path.join(cwd, ".cursor", "rules", "project.md") },
1
+ import fs from "fs";
2
+ import path from "path";
3
+ export function ensureMemory(cwd, opts) {
4
+ const files = [
5
+ "docs/status.md",
6
+ "docs/todo.md",
7
+ "docs/decisions.md",
8
+ ".cursor/rules/project.md",
26
9
  ];
27
- let created = 0;
28
- for (const m of mappings) {
29
- if (!fs.existsSync(m.src))
30
- throw new Error("Template missing: " + m.src);
31
- if (copyIfMissing(m.src, m.dst))
32
- created++;
10
+ for (const rel of files) {
11
+ const fp = path.join(cwd, rel);
12
+ if (fs.existsSync(fp))
13
+ continue;
14
+ if (!opts?.dryRun) {
15
+ fs.mkdirSync(path.dirname(fp), { recursive: true });
16
+ fs.writeFileSync(fp, "", "utf8");
17
+ }
33
18
  }
34
- console.log(`Memory ensured (created ${created} file(s), left existing untouched).`);
35
19
  }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "4runr-cursor-setup",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1",
7
+ "test": "node --test test/*.test.js",
8
8
  "dev": "tsx src/cli.ts",
9
9
  "build": "tsc",
10
10
  "prepublishOnly": "npm run build",