link-agents 0.9.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.
- package/AGENTS.md +127 -0
- package/README.md +93 -0
- package/cursor-rules-notes.md +23 -0
- package/dist/cli/interactive.d.ts +9 -0
- package/dist/cli/interactive.d.ts.map +1 -0
- package/dist/cli/interactive.js +1176 -0
- package/dist/cli/interactive.js.map +1 -0
- package/dist/cli/options.d.ts +3 -0
- package/dist/cli/options.d.ts.map +1 -0
- package/dist/cli/options.js +107 -0
- package/dist/cli/options.js.map +1 -0
- package/dist/cli/options.spec.d.ts +2 -0
- package/dist/cli/options.spec.d.ts.map +1 -0
- package/dist/cli/options.spec.js +74 -0
- package/dist/cli/options.spec.js.map +1 -0
- package/dist/clients/definitions.d.ts +5 -0
- package/dist/clients/definitions.d.ts.map +1 -0
- package/dist/clients/definitions.js +82 -0
- package/dist/clients/definitions.js.map +1 -0
- package/dist/clients/definitions.spec.d.ts +2 -0
- package/dist/clients/definitions.spec.d.ts.map +1 -0
- package/dist/clients/definitions.spec.js +135 -0
- package/dist/clients/definitions.spec.js.map +1 -0
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +81 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/restore.d.ts +3 -0
- package/dist/commands/restore.d.ts.map +1 -0
- package/dist/commands/restore.js +36 -0
- package/dist/commands/restore.js.map +1 -0
- package/dist/commands/sync.d.ts +3 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +193 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +98 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/apply.d.ts +22 -0
- package/dist/utils/apply.d.ts.map +1 -0
- package/dist/utils/apply.js +215 -0
- package/dist/utils/apply.js.map +1 -0
- package/dist/utils/bootstrap.d.ts +18 -0
- package/dist/utils/bootstrap.d.ts.map +1 -0
- package/dist/utils/bootstrap.js +31 -0
- package/dist/utils/bootstrap.js.map +1 -0
- package/dist/utils/bootstrap.spec.d.ts +2 -0
- package/dist/utils/bootstrap.spec.d.ts.map +1 -0
- package/dist/utils/bootstrap.spec.js +92 -0
- package/dist/utils/bootstrap.spec.js.map +1 -0
- package/dist/utils/canonical.d.ts +17 -0
- package/dist/utils/canonical.d.ts.map +1 -0
- package/dist/utils/canonical.js +136 -0
- package/dist/utils/canonical.js.map +1 -0
- package/dist/utils/canonicalState.d.ts +19 -0
- package/dist/utils/canonicalState.d.ts.map +1 -0
- package/dist/utils/canonicalState.js +21 -0
- package/dist/utils/canonicalState.js.map +1 -0
- package/dist/utils/cursorHistory.d.ts +7 -0
- package/dist/utils/cursorHistory.d.ts.map +1 -0
- package/dist/utils/cursorHistory.js +54 -0
- package/dist/utils/cursorHistory.js.map +1 -0
- package/dist/utils/cursorPaths.d.ts +3 -0
- package/dist/utils/cursorPaths.d.ts.map +1 -0
- package/dist/utils/cursorPaths.js +17 -0
- package/dist/utils/cursorPaths.js.map +1 -0
- package/dist/utils/discovery.d.ts +8 -0
- package/dist/utils/discovery.d.ts.map +1 -0
- package/dist/utils/discovery.js +93 -0
- package/dist/utils/discovery.js.map +1 -0
- package/dist/utils/frontmatter.d.ts +32 -0
- package/dist/utils/frontmatter.d.ts.map +1 -0
- package/dist/utils/frontmatter.js +263 -0
- package/dist/utils/frontmatter.js.map +1 -0
- package/dist/utils/frontmatter.spec.d.ts +2 -0
- package/dist/utils/frontmatter.spec.d.ts.map +1 -0
- package/dist/utils/frontmatter.spec.js +264 -0
- package/dist/utils/frontmatter.spec.js.map +1 -0
- package/dist/utils/fs.d.ts +27 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +137 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/fs.spec.d.ts +2 -0
- package/dist/utils/fs.spec.d.ts.map +1 -0
- package/dist/utils/fs.spec.js +73 -0
- package/dist/utils/fs.spec.js.map +1 -0
- package/dist/utils/gitignore.d.ts +10 -0
- package/dist/utils/gitignore.d.ts.map +1 -0
- package/dist/utils/gitignore.js +63 -0
- package/dist/utils/gitignore.js.map +1 -0
- package/dist/utils/manifest.d.ts +28 -0
- package/dist/utils/manifest.d.ts.map +1 -0
- package/dist/utils/manifest.js +89 -0
- package/dist/utils/manifest.js.map +1 -0
- package/dist/utils/mcp.d.ts +73 -0
- package/dist/utils/mcp.d.ts.map +1 -0
- package/dist/utils/mcp.js +529 -0
- package/dist/utils/mcp.js.map +1 -0
- package/dist/utils/mcp.spec.d.ts +2 -0
- package/dist/utils/mcp.spec.d.ts.map +1 -0
- package/dist/utils/mcp.spec.js +488 -0
- package/dist/utils/mcp.spec.js.map +1 -0
- package/dist/utils/merge.d.ts +17 -0
- package/dist/utils/merge.d.ts.map +1 -0
- package/dist/utils/merge.js +45 -0
- package/dist/utils/merge.js.map +1 -0
- package/dist/utils/merge.spec.d.ts +2 -0
- package/dist/utils/merge.spec.d.ts.map +1 -0
- package/dist/utils/merge.spec.js +134 -0
- package/dist/utils/merge.spec.js.map +1 -0
- package/dist/utils/paths.d.ts +11 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +164 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/paths.spec.d.ts +2 -0
- package/dist/utils/paths.spec.d.ts.map +1 -0
- package/dist/utils/paths.spec.js +282 -0
- package/dist/utils/paths.spec.js.map +1 -0
- package/dist/utils/plan.d.ts +7 -0
- package/dist/utils/plan.d.ts.map +1 -0
- package/dist/utils/plan.js +118 -0
- package/dist/utils/plan.js.map +1 -0
- package/dist/utils/plan.spec.d.ts +2 -0
- package/dist/utils/plan.spec.d.ts.map +1 -0
- package/dist/utils/plan.spec.js +420 -0
- package/dist/utils/plan.spec.js.map +1 -0
- package/dist/utils/reporting.d.ts +21 -0
- package/dist/utils/reporting.d.ts.map +1 -0
- package/dist/utils/reporting.js +82 -0
- package/dist/utils/reporting.js.map +1 -0
- package/dist/utils/reporting.spec.d.ts +2 -0
- package/dist/utils/reporting.spec.d.ts.map +1 -0
- package/dist/utils/reporting.spec.js +78 -0
- package/dist/utils/reporting.spec.js.map +1 -0
- package/dist/utils/reset.d.ts +14 -0
- package/dist/utils/reset.d.ts.map +1 -0
- package/dist/utils/reset.js +81 -0
- package/dist/utils/reset.js.map +1 -0
- package/dist/utils/revert.d.ts +30 -0
- package/dist/utils/revert.d.ts.map +1 -0
- package/dist/utils/revert.js +89 -0
- package/dist/utils/revert.js.map +1 -0
- package/dist/utils/revert.spec.d.ts +2 -0
- package/dist/utils/revert.spec.d.ts.map +1 -0
- package/dist/utils/revert.spec.js +102 -0
- package/dist/utils/revert.spec.js.map +1 -0
- package/dist/utils/similarity.d.ts +14 -0
- package/dist/utils/similarity.d.ts.map +1 -0
- package/dist/utils/similarity.js +70 -0
- package/dist/utils/similarity.js.map +1 -0
- package/dist/utils/similarity.spec.d.ts +2 -0
- package/dist/utils/similarity.spec.d.ts.map +1 -0
- package/dist/utils/similarity.spec.js +62 -0
- package/dist/utils/similarity.spec.js.map +1 -0
- package/dist/utils/snapshots.d.ts +21 -0
- package/dist/utils/snapshots.d.ts.map +1 -0
- package/dist/utils/snapshots.js +81 -0
- package/dist/utils/snapshots.js.map +1 -0
- package/dist/utils/snapshots.spec.d.ts +2 -0
- package/dist/utils/snapshots.spec.d.ts.map +1 -0
- package/dist/utils/snapshots.spec.js +56 -0
- package/dist/utils/snapshots.spec.js.map +1 -0
- package/dist/utils/syncFilters.d.ts +3 -0
- package/dist/utils/syncFilters.d.ts.map +1 -0
- package/dist/utils/syncFilters.js +8 -0
- package/dist/utils/syncFilters.js.map +1 -0
- package/dist/utils/syncRuntime.d.ts +3 -0
- package/dist/utils/syncRuntime.d.ts.map +1 -0
- package/dist/utils/syncRuntime.js +31 -0
- package/dist/utils/syncRuntime.js.map +1 -0
- package/dist/utils/validation.d.ts +3 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +19 -0
- package/dist/utils/validation.js.map +1 -0
- package/dist/utils/validation.spec.d.ts +2 -0
- package/dist/utils/validation.spec.d.ts.map +1 -0
- package/dist/utils/validation.spec.js +36 -0
- package/dist/utils/validation.spec.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { parseFrontmatter, reconstructMarkdown, transformForOpenCode, transformContentForClient, } from "./frontmatter.js";
|
|
3
|
+
describe("parseFrontmatter", () => {
|
|
4
|
+
it("returns null frontmatter for content without frontmatter", () => {
|
|
5
|
+
const content = "# Hello\n\nNo frontmatter here.";
|
|
6
|
+
const result = parseFrontmatter(content);
|
|
7
|
+
expect(result.frontmatter).toBeNull();
|
|
8
|
+
expect(result.body).toBe(content);
|
|
9
|
+
});
|
|
10
|
+
it("parses simple key-value frontmatter", () => {
|
|
11
|
+
const content = `---
|
|
12
|
+
name: test-agent
|
|
13
|
+
description: A test agent
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Content`;
|
|
17
|
+
const result = parseFrontmatter(content);
|
|
18
|
+
expect(result.frontmatter).toEqual({
|
|
19
|
+
name: "test-agent",
|
|
20
|
+
description: "A test agent",
|
|
21
|
+
});
|
|
22
|
+
expect(result.body).toBe("\n# Content");
|
|
23
|
+
});
|
|
24
|
+
it("parses boolean values", () => {
|
|
25
|
+
const content = `---
|
|
26
|
+
enabled: true
|
|
27
|
+
disabled: false
|
|
28
|
+
---
|
|
29
|
+
`;
|
|
30
|
+
const result = parseFrontmatter(content);
|
|
31
|
+
expect(result.frontmatter?.enabled).toBe(true);
|
|
32
|
+
expect(result.frontmatter?.disabled).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
it("parses numeric values", () => {
|
|
35
|
+
const content = `---
|
|
36
|
+
count: 42
|
|
37
|
+
ratio: 3.14
|
|
38
|
+
---
|
|
39
|
+
`;
|
|
40
|
+
const result = parseFrontmatter(content);
|
|
41
|
+
expect(result.frontmatter?.count).toBe(42);
|
|
42
|
+
expect(result.frontmatter?.ratio).toBe(3.14);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
describe("reconstructMarkdown", () => {
|
|
46
|
+
it("creates valid frontmatter markdown", () => {
|
|
47
|
+
const frontmatter = { name: "test", enabled: true };
|
|
48
|
+
const body = "\n# Content";
|
|
49
|
+
const result = reconstructMarkdown(frontmatter, body);
|
|
50
|
+
expect(result).toBe(`---
|
|
51
|
+
name: test
|
|
52
|
+
enabled: true
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
# Content`);
|
|
56
|
+
});
|
|
57
|
+
it("handles object values", () => {
|
|
58
|
+
const frontmatter = { tools: { Read: true, Edit: false } };
|
|
59
|
+
const body = "";
|
|
60
|
+
const result = reconstructMarkdown(frontmatter, body);
|
|
61
|
+
expect(result).toContain("tools:");
|
|
62
|
+
expect(result).toContain(" Read: true");
|
|
63
|
+
expect(result).toContain(" Edit: false");
|
|
64
|
+
});
|
|
65
|
+
it("handles array values", () => {
|
|
66
|
+
const frontmatter = { items: ["one", "two", "three"] };
|
|
67
|
+
const body = "";
|
|
68
|
+
const result = reconstructMarkdown(frontmatter, body);
|
|
69
|
+
expect(result).toContain("items:");
|
|
70
|
+
expect(result).toContain(" - one");
|
|
71
|
+
expect(result).toContain(" - two");
|
|
72
|
+
expect(result).toContain(" - three");
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe("transformForOpenCode", () => {
|
|
76
|
+
it("transforms comma-separated tools string to object", () => {
|
|
77
|
+
const content = `---
|
|
78
|
+
name: test
|
|
79
|
+
tools: Read, Edit, Bash
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
# Content`;
|
|
83
|
+
const result = transformForOpenCode(content);
|
|
84
|
+
expect(result).toContain("tools:");
|
|
85
|
+
expect(result).toContain(" Read: true");
|
|
86
|
+
expect(result).toContain(" Edit: true");
|
|
87
|
+
expect(result).toContain(" Bash: true");
|
|
88
|
+
expect(result).not.toContain("Read, Edit, Bash");
|
|
89
|
+
});
|
|
90
|
+
it("transforms color name to hex", () => {
|
|
91
|
+
const content = `---
|
|
92
|
+
name: test
|
|
93
|
+
color: purple
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
# Content`;
|
|
97
|
+
const result = transformForOpenCode(content);
|
|
98
|
+
expect(result).toContain('color: "#9B59B6"');
|
|
99
|
+
expect(result).not.toContain("color: purple");
|
|
100
|
+
});
|
|
101
|
+
it("leaves already-hex colors unchanged", () => {
|
|
102
|
+
const content = `---
|
|
103
|
+
name: test
|
|
104
|
+
color: "#FF5733"
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
# Content`;
|
|
108
|
+
const result = transformForOpenCode(content);
|
|
109
|
+
expect(result).toContain('color: "#FF5733"');
|
|
110
|
+
});
|
|
111
|
+
it("preserves content without frontmatter", () => {
|
|
112
|
+
const content = "# No frontmatter\n\nJust content.";
|
|
113
|
+
const result = transformForOpenCode(content);
|
|
114
|
+
expect(result).toBe(content);
|
|
115
|
+
});
|
|
116
|
+
it("handles missing tools field gracefully", () => {
|
|
117
|
+
const content = `---
|
|
118
|
+
name: test
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
# Content`;
|
|
122
|
+
const result = transformForOpenCode(content);
|
|
123
|
+
expect(result).toContain("name: test");
|
|
124
|
+
expect(result).not.toContain("tools:");
|
|
125
|
+
});
|
|
126
|
+
it("preserves body content after transformation", () => {
|
|
127
|
+
const content = `---
|
|
128
|
+
name: test
|
|
129
|
+
tools: Read
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
# Big Content
|
|
133
|
+
|
|
134
|
+
This is a lot of text that should be preserved.
|
|
135
|
+
|
|
136
|
+
## Section 2
|
|
137
|
+
|
|
138
|
+
More content here.`;
|
|
139
|
+
const result = transformForOpenCode(content);
|
|
140
|
+
expect(result).toContain("# Big Content");
|
|
141
|
+
expect(result).toContain("This is a lot of text that should be preserved.");
|
|
142
|
+
expect(result).toContain("## Section 2");
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
describe("transformContentForClient", () => {
|
|
146
|
+
it("transforms agents for opencode", () => {
|
|
147
|
+
const content = `---
|
|
148
|
+
tools: Read, Edit
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
# Agent`;
|
|
152
|
+
const result = transformContentForClient(content, "opencode", "agents");
|
|
153
|
+
expect(result).toContain(" Read: true");
|
|
154
|
+
expect(result).toContain(" Edit: true");
|
|
155
|
+
});
|
|
156
|
+
it("does not transform agents for other clients", () => {
|
|
157
|
+
const content = `---
|
|
158
|
+
tools: Read, Edit
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
# Agent`;
|
|
162
|
+
const result = transformContentForClient(content, "cursor", "agents");
|
|
163
|
+
expect(result).toBe(content);
|
|
164
|
+
});
|
|
165
|
+
it("does not transform non-agent assets for opencode", () => {
|
|
166
|
+
const content = `---
|
|
167
|
+
tools: Read, Edit
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
# Command`;
|
|
171
|
+
const result = transformContentForClient(content, "opencode", "commands");
|
|
172
|
+
expect(result).toBe(content);
|
|
173
|
+
});
|
|
174
|
+
describe("command frontmatter stripping", () => {
|
|
175
|
+
it("strips Cursor-specific keys when syncing commands to Claude", () => {
|
|
176
|
+
const content = `---
|
|
177
|
+
description: Test command
|
|
178
|
+
argument-hint: [plan]
|
|
179
|
+
model: opus
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
# Test Command`;
|
|
183
|
+
const result = transformContentForClient(content, "claude", "commands");
|
|
184
|
+
expect(result).toContain("description: Test command");
|
|
185
|
+
expect(result).not.toContain("argument-hint");
|
|
186
|
+
expect(result).not.toContain("model:");
|
|
187
|
+
expect(result).toContain("# Test Command");
|
|
188
|
+
});
|
|
189
|
+
it("strips Cursor-specific keys when syncing commands to Codex", () => {
|
|
190
|
+
const content = `---
|
|
191
|
+
description: Test
|
|
192
|
+
argument-hint: [file]
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
# Test`;
|
|
196
|
+
const result = transformContentForClient(content, "codex", "commands");
|
|
197
|
+
expect(result).toContain("description: Test");
|
|
198
|
+
expect(result).not.toContain("argument-hint");
|
|
199
|
+
});
|
|
200
|
+
it("preserves Cursor-specific keys when syncing to Cursor", () => {
|
|
201
|
+
const content = `---
|
|
202
|
+
description: Test
|
|
203
|
+
argument-hint: [plan]
|
|
204
|
+
model: opus
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
# Test`;
|
|
208
|
+
const result = transformContentForClient(content, "cursor", "commands");
|
|
209
|
+
expect(result).toBe(content);
|
|
210
|
+
});
|
|
211
|
+
it("preserves Cursor-specific keys when syncing to OpenCode", () => {
|
|
212
|
+
const content = `---
|
|
213
|
+
description: Test
|
|
214
|
+
argument-hint: [plan]
|
|
215
|
+
model: opus
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
# Test`;
|
|
219
|
+
const result = transformContentForClient(content, "opencode", "commands");
|
|
220
|
+
expect(result).toBe(content);
|
|
221
|
+
});
|
|
222
|
+
it("strips Claude-specific keys when syncing commands to Cursor", () => {
|
|
223
|
+
const content = `---
|
|
224
|
+
description: Test
|
|
225
|
+
allowed_tools: read,write
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
# Test`;
|
|
229
|
+
const result = transformContentForClient(content, "cursor", "commands");
|
|
230
|
+
expect(result).toContain("description: Test");
|
|
231
|
+
expect(result).not.toContain("allowed_tools");
|
|
232
|
+
});
|
|
233
|
+
it("preserves body content after stripping", () => {
|
|
234
|
+
const content = `---
|
|
235
|
+
description: Big command
|
|
236
|
+
argument-hint: [plan]
|
|
237
|
+
model: opus
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
# Main Title
|
|
241
|
+
|
|
242
|
+
## Section 1
|
|
243
|
+
|
|
244
|
+
Content here.
|
|
245
|
+
|
|
246
|
+
## Section 2
|
|
247
|
+
|
|
248
|
+
More content.`;
|
|
249
|
+
const result = transformContentForClient(content, "claude", "commands");
|
|
250
|
+
expect(result).toContain("# Main Title");
|
|
251
|
+
expect(result).toContain("## Section 1");
|
|
252
|
+
expect(result).toContain("Content here.");
|
|
253
|
+
expect(result).toContain("## Section 2");
|
|
254
|
+
});
|
|
255
|
+
it("handles commands without frontmatter", () => {
|
|
256
|
+
const content = `# No frontmatter
|
|
257
|
+
|
|
258
|
+
Just a command.`;
|
|
259
|
+
const result = transformContentForClient(content, "claude", "commands");
|
|
260
|
+
expect(result).toBe(content);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
//# sourceMappingURL=frontmatter.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontmatter.spec.js","sourceRoot":"","sources":["../../src/utils/frontmatter.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare function fileExists(filePath: string): Promise<boolean>;
|
|
2
|
+
export declare function readFileSafe(filePath: string): Promise<string | null>;
|
|
3
|
+
export declare function writeFileSafe(filePath: string, contents: string): Promise<void>;
|
|
4
|
+
export declare function hashContent(content: string): string;
|
|
5
|
+
export declare function expandHome(inputPath: string): string;
|
|
6
|
+
export declare function toFileUrl(filePath: string): string;
|
|
7
|
+
export declare function getFileMtime(filePath: string): Promise<Date | null>;
|
|
8
|
+
/**
|
|
9
|
+
* Create a backup of a file by copying it to .bak
|
|
10
|
+
* Returns the backup path if created, null if file didn't exist
|
|
11
|
+
*/
|
|
12
|
+
export declare function createBackup(filePath: string): Promise<string | null>;
|
|
13
|
+
/**
|
|
14
|
+
* Restore a file from its backup
|
|
15
|
+
* Returns true if restored, false if backup didn't exist or failed
|
|
16
|
+
*/
|
|
17
|
+
export declare function restoreBackup(backupPath: string, targetPath: string): Promise<boolean>;
|
|
18
|
+
/**
|
|
19
|
+
* Check if file content matches expected hash
|
|
20
|
+
*/
|
|
21
|
+
export declare function verifyFileHash(filePath: string, expectedContent: string): Promise<boolean>;
|
|
22
|
+
/**
|
|
23
|
+
* Check if a command exists in PATH
|
|
24
|
+
* Uses execFile to avoid shell injection
|
|
25
|
+
*/
|
|
26
|
+
export declare function commandExists(command: string): Promise<boolean>;
|
|
27
|
+
//# sourceMappingURL=fs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/utils/fs.ts"],"names":[],"mappings":"AAaA,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOnE;AAED,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAM3E;AAED,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAGf;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAKpD;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAOzE;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAyB3E;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,OAAO,CAAC,CAuBlB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,OAAO,CAAC,CAOlB;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAYrE"}
|
package/dist/utils/fs.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
const BACKUP_PREFIX = "__SYNC_AGENTS_BACKUP_V1__\n";
|
|
6
|
+
export async function fileExists(filePath) {
|
|
7
|
+
try {
|
|
8
|
+
await fs.access(filePath);
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export async function readFileSafe(filePath) {
|
|
16
|
+
try {
|
|
17
|
+
return await fs.readFile(filePath, "utf8");
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export async function writeFileSafe(filePath, contents) {
|
|
24
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
25
|
+
await fs.writeFile(filePath, contents, "utf8");
|
|
26
|
+
}
|
|
27
|
+
export function hashContent(content) {
|
|
28
|
+
return crypto.createHash("sha1").update(content).digest("hex");
|
|
29
|
+
}
|
|
30
|
+
export function expandHome(inputPath) {
|
|
31
|
+
if (inputPath.startsWith("~")) {
|
|
32
|
+
return path.join(process.env.HOME ?? "", inputPath.slice(1));
|
|
33
|
+
}
|
|
34
|
+
return inputPath;
|
|
35
|
+
}
|
|
36
|
+
export function toFileUrl(filePath) {
|
|
37
|
+
return pathToFileURL(filePath).href;
|
|
38
|
+
}
|
|
39
|
+
export async function getFileMtime(filePath) {
|
|
40
|
+
try {
|
|
41
|
+
const stats = await fs.stat(filePath);
|
|
42
|
+
return stats.mtime;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Create a backup of a file by copying it to .bak
|
|
50
|
+
* Returns the backup path if created, null if file didn't exist
|
|
51
|
+
*/
|
|
52
|
+
export async function createBackup(filePath) {
|
|
53
|
+
try {
|
|
54
|
+
const backupPath = `${filePath}.bak`;
|
|
55
|
+
const stats = await fs.lstat(filePath);
|
|
56
|
+
if (stats.isSymbolicLink()) {
|
|
57
|
+
const linkTarget = await fs.readlink(filePath);
|
|
58
|
+
await fs.writeFile(backupPath, serializeBackupPayload({ kind: "symlink", linkTarget }), "utf8");
|
|
59
|
+
return backupPath;
|
|
60
|
+
}
|
|
61
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
62
|
+
await fs.writeFile(backupPath, serializeBackupPayload({ kind: "file", content }), "utf8");
|
|
63
|
+
return backupPath;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Restore a file from its backup
|
|
71
|
+
* Returns true if restored, false if backup didn't exist or failed
|
|
72
|
+
*/
|
|
73
|
+
export async function restoreBackup(backupPath, targetPath) {
|
|
74
|
+
try {
|
|
75
|
+
const backupContent = await fs.readFile(backupPath, "utf8");
|
|
76
|
+
const payload = parseBackupPayload(backupContent);
|
|
77
|
+
if (!payload) {
|
|
78
|
+
await writeFileSafe(targetPath, backupContent);
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
await fs.rm(targetPath, { force: true, recursive: true });
|
|
82
|
+
if (payload.kind === "symlink" && payload.linkTarget) {
|
|
83
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
84
|
+
await fs.symlink(payload.linkTarget, targetPath);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
await writeFileSafe(targetPath, payload.content ?? "");
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Check if file content matches expected hash
|
|
96
|
+
*/
|
|
97
|
+
export async function verifyFileHash(filePath, expectedContent) {
|
|
98
|
+
try {
|
|
99
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
100
|
+
return hashContent(content) === hashContent(expectedContent);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Check if a command exists in PATH
|
|
108
|
+
* Uses execFile to avoid shell injection
|
|
109
|
+
*/
|
|
110
|
+
export async function commandExists(command) {
|
|
111
|
+
const { execFile } = await import("node:child_process");
|
|
112
|
+
const { promisify } = await import("node:util");
|
|
113
|
+
const execFileAsync = promisify(execFile);
|
|
114
|
+
try {
|
|
115
|
+
const whichCmd = process.platform === "win32" ? "where" : "which";
|
|
116
|
+
await execFileAsync(whichCmd, [command]);
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function serializeBackupPayload(payload) {
|
|
124
|
+
return `${BACKUP_PREFIX}${JSON.stringify(payload)}`;
|
|
125
|
+
}
|
|
126
|
+
function parseBackupPayload(content) {
|
|
127
|
+
if (!content.startsWith(BACKUP_PREFIX)) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
return JSON.parse(content.slice(BACKUP_PREFIX.length));
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=fs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/utils/fs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.spec.d.ts","sourceRoot":"","sources":["../../src/utils/fs.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { hashContent, commandExists, createBackup, restoreBackup, readFileSafe, } from "./fs.js";
|
|
6
|
+
describe("fs utilities", () => {
|
|
7
|
+
const testDir = path.join(os.tmpdir(), "link-agents-fs-test");
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
10
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
11
|
+
});
|
|
12
|
+
afterEach(async () => {
|
|
13
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
14
|
+
});
|
|
15
|
+
describe("hashContent", () => {
|
|
16
|
+
it("should generate consistent hashes for same content", () => {
|
|
17
|
+
const content = "test content";
|
|
18
|
+
const hash1 = hashContent(content);
|
|
19
|
+
const hash2 = hashContent(content);
|
|
20
|
+
expect(hash1).toBe(hash2);
|
|
21
|
+
});
|
|
22
|
+
it("should generate different hashes for different content", () => {
|
|
23
|
+
const hash1 = hashContent("content 1");
|
|
24
|
+
const hash2 = hashContent("content 2");
|
|
25
|
+
expect(hash1).not.toBe(hash2);
|
|
26
|
+
});
|
|
27
|
+
it("should handle empty strings", () => {
|
|
28
|
+
const hash = hashContent("");
|
|
29
|
+
expect(hash).toBeDefined();
|
|
30
|
+
expect(typeof hash).toBe("string");
|
|
31
|
+
expect(hash.length).toBe(40);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
describe("commandExists", () => {
|
|
35
|
+
it("should return true for common commands", async () => {
|
|
36
|
+
// 'node' should exist since we're running in Node.js
|
|
37
|
+
const exists = await commandExists("node");
|
|
38
|
+
expect(exists).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
it("should return false for non-existent commands", async () => {
|
|
41
|
+
const exists = await commandExists("definitely-not-a-real-command-12345");
|
|
42
|
+
expect(exists).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
describe("backup and restore", () => {
|
|
46
|
+
it("restores regular files from backup payloads", async () => {
|
|
47
|
+
const filePath = path.join(testDir, "agent.md");
|
|
48
|
+
await fs.writeFile(filePath, "original content", "utf8");
|
|
49
|
+
const backupPath = await createBackup(filePath);
|
|
50
|
+
expect(backupPath).toBe(`${filePath}.bak`);
|
|
51
|
+
await fs.writeFile(filePath, "changed content", "utf8");
|
|
52
|
+
const restored = await restoreBackup(backupPath, filePath);
|
|
53
|
+
expect(restored).toBe(true);
|
|
54
|
+
expect(await readFileSafe(filePath)).toBe("original content");
|
|
55
|
+
});
|
|
56
|
+
it("restores symlinks from backup payloads", async () => {
|
|
57
|
+
const sourcePath = path.join(testDir, "source.md");
|
|
58
|
+
const targetPath = path.join(testDir, "target.md");
|
|
59
|
+
await fs.writeFile(sourcePath, "source content", "utf8");
|
|
60
|
+
await fs.symlink("source.md", targetPath);
|
|
61
|
+
const backupPath = await createBackup(targetPath);
|
|
62
|
+
expect(backupPath).toBe(`${targetPath}.bak`);
|
|
63
|
+
await fs.rm(targetPath, { force: true });
|
|
64
|
+
await fs.writeFile(targetPath, "now a regular file", "utf8");
|
|
65
|
+
const restored = await restoreBackup(backupPath, targetPath);
|
|
66
|
+
expect(restored).toBe(true);
|
|
67
|
+
const stats = await fs.lstat(targetPath);
|
|
68
|
+
expect(stats.isSymbolicLink()).toBe(true);
|
|
69
|
+
expect(await fs.readlink(targetPath)).toBe("source.md");
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
//# sourceMappingURL=fs.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.spec.js","sourceRoot":"","sources":["../../src/utils/fs.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update .gitignore to include link-agents generated files.
|
|
3
|
+
* Only updates if there are project-level generated files.
|
|
4
|
+
*/
|
|
5
|
+
export declare function updateGitignore(projectRoot: string, generatedPaths: string[]): Promise<boolean>;
|
|
6
|
+
/**
|
|
7
|
+
* Remove link-agents section from .gitignore.
|
|
8
|
+
*/
|
|
9
|
+
export declare function cleanGitignore(projectRoot: string): Promise<boolean>;
|
|
10
|
+
//# sourceMappingURL=gitignore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitignore.d.ts","sourceRoot":"","sources":["../../src/utils/gitignore.ts"],"names":[],"mappings":"AAOA;;;GAGG;AACH,wBAAsB,eAAe,CACnC,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EAAE,GACvB,OAAO,CAAC,OAAO,CAAC,CAqClB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAqB1E"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileExists, readFileSafe } from "./fs.js";
|
|
4
|
+
const GITIGNORE_MARKER_START = "# link-agents generated files";
|
|
5
|
+
const GITIGNORE_MARKER_END = "# end link-agents";
|
|
6
|
+
/**
|
|
7
|
+
* Update .gitignore to include link-agents generated files.
|
|
8
|
+
* Only updates if there are project-level generated files.
|
|
9
|
+
*/
|
|
10
|
+
export async function updateGitignore(projectRoot, generatedPaths) {
|
|
11
|
+
// Only include paths relative to project root
|
|
12
|
+
const relativePaths = generatedPaths
|
|
13
|
+
.filter((p) => p.startsWith(projectRoot))
|
|
14
|
+
.map((p) => path.relative(projectRoot, p))
|
|
15
|
+
.filter((p) => p && !p.startsWith(".."));
|
|
16
|
+
if (relativePaths.length === 0) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
const gitignorePath = path.join(projectRoot, ".gitignore");
|
|
20
|
+
let content = (await readFileSafe(gitignorePath)) ?? "";
|
|
21
|
+
// Remove existing link-agents section
|
|
22
|
+
const startIdx = content.indexOf(GITIGNORE_MARKER_START);
|
|
23
|
+
const endIdx = content.indexOf(GITIGNORE_MARKER_END);
|
|
24
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
25
|
+
content =
|
|
26
|
+
content.slice(0, startIdx) +
|
|
27
|
+
content.slice(endIdx + GITIGNORE_MARKER_END.length);
|
|
28
|
+
}
|
|
29
|
+
// Build new section
|
|
30
|
+
const newSection = [
|
|
31
|
+
"",
|
|
32
|
+
GITIGNORE_MARKER_START,
|
|
33
|
+
...relativePaths.map((p) => `/${p}`),
|
|
34
|
+
GITIGNORE_MARKER_END,
|
|
35
|
+
"",
|
|
36
|
+
].join("\n");
|
|
37
|
+
// Append to content
|
|
38
|
+
content = content.trimEnd() + newSection;
|
|
39
|
+
await fs.writeFile(gitignorePath, content, "utf8");
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Remove link-agents section from .gitignore.
|
|
44
|
+
*/
|
|
45
|
+
export async function cleanGitignore(projectRoot) {
|
|
46
|
+
const gitignorePath = path.join(projectRoot, ".gitignore");
|
|
47
|
+
if (!(await fileExists(gitignorePath))) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
let content = (await readFileSafe(gitignorePath)) ?? "";
|
|
51
|
+
const startIdx = content.indexOf(GITIGNORE_MARKER_START);
|
|
52
|
+
const endIdx = content.indexOf(GITIGNORE_MARKER_END);
|
|
53
|
+
if (startIdx === -1 || endIdx === -1) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
content =
|
|
57
|
+
content.slice(0, startIdx) +
|
|
58
|
+
content.slice(endIdx + GITIGNORE_MARKER_END.length);
|
|
59
|
+
content = content.replace(/\n{3,}/g, "\n\n").trim() + "\n";
|
|
60
|
+
await fs.writeFile(gitignorePath, content, "utf8");
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=gitignore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitignore.js","sourceRoot":"","sources":["../../src/utils/gitignore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
interface Manifest {
|
|
2
|
+
version: 1;
|
|
3
|
+
lastSync: string;
|
|
4
|
+
generatedFiles: string[];
|
|
5
|
+
}
|
|
6
|
+
export declare function readManifest(): Promise<Manifest>;
|
|
7
|
+
export declare function writeManifest(manifest: Manifest): Promise<void>;
|
|
8
|
+
export declare function updateManifest(generatedFiles: string[]): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Find files that were in the previous manifest but not in the current plan.
|
|
11
|
+
* These are "stale" files that should be cleaned up.
|
|
12
|
+
*/
|
|
13
|
+
export declare function findStaleFiles(currentFiles: string[]): Promise<string[]>;
|
|
14
|
+
/**
|
|
15
|
+
* Remove stale files that are no longer in the sync plan.
|
|
16
|
+
* Returns list of removed files.
|
|
17
|
+
*/
|
|
18
|
+
export declare function pruneStaleFiles(currentFiles: string[]): Promise<string[]>;
|
|
19
|
+
/**
|
|
20
|
+
* Clear the manifest (used by reset command).
|
|
21
|
+
*/
|
|
22
|
+
export declare function clearManifest(): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Get all files from manifest (for reset command).
|
|
25
|
+
*/
|
|
26
|
+
export declare function getManifestFiles(): Promise<string[]>;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/utils/manifest.ts"],"names":[],"mappings":"AAQA,UAAU,QAAQ;IAChB,OAAO,EAAE,CAAC,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAUD,wBAAsB,YAAY,IAAI,OAAO,CAAC,QAAQ,CAAC,CAUtD;AAED,wBAAsB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAGrE;AAED,wBAAsB,cAAc,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAO5E;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,YAAY,EAAE,MAAM,EAAE,GACrB,OAAO,CAAC,MAAM,EAAE,CAAC,CAWnB;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,YAAY,EAAE,MAAM,EAAE,GACrB,OAAO,CAAC,MAAM,EAAE,CAAC,CAcnB;AAED;;GAEG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAMnD;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAG1D"}
|