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,82 @@
|
|
|
1
|
+
const TYPE_ORDER = ["agents", "commands", "skills", "mcp"];
|
|
2
|
+
const TYPE_LABELS = {
|
|
3
|
+
agents: "agents",
|
|
4
|
+
commands: "commands",
|
|
5
|
+
skills: "skills",
|
|
6
|
+
mcp: "mcp",
|
|
7
|
+
};
|
|
8
|
+
export function buildSyncPreflightLines(input) {
|
|
9
|
+
const types = input.types?.length
|
|
10
|
+
? input.types.join(", ")
|
|
11
|
+
: "agents, commands, skills, mcp";
|
|
12
|
+
const targets = input.targets.length > 0 ? input.targets.join(", ") : "none";
|
|
13
|
+
return [
|
|
14
|
+
`Mode: ${input.dryRun ? "dry-run" : "apply"}`,
|
|
15
|
+
`Write mode: ${input.writeMode}`,
|
|
16
|
+
`Canonical assets: ${input.canonicalCount}`,
|
|
17
|
+
`Bootstrap actions: ${input.bootstrapCount}`,
|
|
18
|
+
`Ignored legacy inputs: ${input.ignoredCount}`,
|
|
19
|
+
`Targets: ${targets}`,
|
|
20
|
+
`Managed types: ${types}`,
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
export function buildSyncPlanSummaryLines(plan) {
|
|
24
|
+
const bootstrapEntries = plan.filter((entry) => entry.reason === "bootstrap");
|
|
25
|
+
const fanoutEntries = plan.filter((entry) => entry.reason !== "bootstrap");
|
|
26
|
+
const lines = [];
|
|
27
|
+
if (bootstrapEntries.length > 0) {
|
|
28
|
+
lines.push(`bootstrap ${formatEntrySummary(bootstrapEntries)}`);
|
|
29
|
+
}
|
|
30
|
+
const byClient = new Map();
|
|
31
|
+
for (const entry of fanoutEntries) {
|
|
32
|
+
const entries = byClient.get(entry.targetClient) ?? [];
|
|
33
|
+
entries.push(entry);
|
|
34
|
+
byClient.set(entry.targetClient, entries);
|
|
35
|
+
}
|
|
36
|
+
for (const [client, entries] of [...byClient.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
37
|
+
lines.push(`fanout ${client}: ${formatEntrySummary(entries)}`);
|
|
38
|
+
}
|
|
39
|
+
return lines;
|
|
40
|
+
}
|
|
41
|
+
export function buildDetailedPlanLines(plan) {
|
|
42
|
+
return plan.map((entry) => {
|
|
43
|
+
const phase = entry.reason === "bootstrap" ? "bootstrap" : "fanout";
|
|
44
|
+
return `${phase.padEnd(10)} ${entry.targetPath}`;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
export function formatIssueSection(title, items) {
|
|
48
|
+
if (items.length === 0) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
return [
|
|
52
|
+
`${title} (${items.length})`,
|
|
53
|
+
...items.map((item) => ` ${item}`),
|
|
54
|
+
"",
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
export function formatSnapshotList(snapshots) {
|
|
58
|
+
return snapshots.map((snapshot) => `${snapshot.id} ${snapshot.createdAt} (${snapshot.entries.length} paths)`);
|
|
59
|
+
}
|
|
60
|
+
function formatEntrySummary(entries) {
|
|
61
|
+
const counts = new Map();
|
|
62
|
+
for (const type of TYPE_ORDER) {
|
|
63
|
+
counts.set(type, 0);
|
|
64
|
+
}
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
if (isManagedAssetType(entry.asset.type)) {
|
|
67
|
+
counts.set(entry.asset.type, (counts.get(entry.asset.type) ?? 0) + 1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const details = TYPE_ORDER.map((type) => {
|
|
71
|
+
const count = counts.get(type) ?? 0;
|
|
72
|
+
if (count === 0) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
return `${TYPE_LABELS[type]} ${count}`;
|
|
76
|
+
}).filter((item) => item !== null);
|
|
77
|
+
return `${entries.length} change${entries.length === 1 ? "" : "s"} (${details.join(", ")})`;
|
|
78
|
+
}
|
|
79
|
+
function isManagedAssetType(type) {
|
|
80
|
+
return TYPE_ORDER.includes(type);
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=reporting.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporting.js","sourceRoot":"","sources":["../../src/utils/reporting.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporting.spec.d.ts","sourceRoot":"","sources":["../../src/utils/reporting.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { hashContent } from "./fs.js";
|
|
3
|
+
import { buildDetailedPlanLines, buildSyncPlanSummaryLines, buildSyncPreflightLines, formatIssueSection, formatSnapshotList, } from "./reporting.js";
|
|
4
|
+
function makeEntry(type, targetClient, targetPath, reason) {
|
|
5
|
+
const canonicalPath = type === "mcp"
|
|
6
|
+
? "mcp.json"
|
|
7
|
+
: type === "agents"
|
|
8
|
+
? "AGENTS.md"
|
|
9
|
+
: `${type}/demo.md`;
|
|
10
|
+
return {
|
|
11
|
+
asset: {
|
|
12
|
+
client: "project",
|
|
13
|
+
type,
|
|
14
|
+
path: `/repo/.agents/${canonicalPath}`,
|
|
15
|
+
relativePath: canonicalPath,
|
|
16
|
+
canonicalPath,
|
|
17
|
+
name: canonicalPath,
|
|
18
|
+
content: canonicalPath,
|
|
19
|
+
hash: hashContent(canonicalPath),
|
|
20
|
+
},
|
|
21
|
+
targetClient,
|
|
22
|
+
targetPath,
|
|
23
|
+
action: "create",
|
|
24
|
+
reason,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
describe("reporting", () => {
|
|
28
|
+
it("formats sync preflight lines", () => {
|
|
29
|
+
expect(buildSyncPreflightLines({
|
|
30
|
+
canonicalCount: 4,
|
|
31
|
+
bootstrapCount: 1,
|
|
32
|
+
ignoredCount: 2,
|
|
33
|
+
targets: ["claude", "cursor"],
|
|
34
|
+
writeMode: "copy",
|
|
35
|
+
dryRun: true,
|
|
36
|
+
types: ["agents", "commands"],
|
|
37
|
+
})).toEqual([
|
|
38
|
+
"Mode: dry-run",
|
|
39
|
+
"Write mode: copy",
|
|
40
|
+
"Canonical assets: 4",
|
|
41
|
+
"Bootstrap actions: 1",
|
|
42
|
+
"Ignored legacy inputs: 2",
|
|
43
|
+
"Targets: claude, cursor",
|
|
44
|
+
"Managed types: agents, commands",
|
|
45
|
+
]);
|
|
46
|
+
});
|
|
47
|
+
it("groups sync plan lines by phase and client", () => {
|
|
48
|
+
const lines = buildSyncPlanSummaryLines([
|
|
49
|
+
makeEntry("agents", "project", "/repo/.agents/AGENTS.md", "bootstrap"),
|
|
50
|
+
makeEntry("commands", "codex", "/home/.codex/skills/commands/review/SKILL.md", "fanout"),
|
|
51
|
+
makeEntry("mcp", "cursor", "/home/.cursor/mcp.json", "fanout"),
|
|
52
|
+
makeEntry("agents", "cursor", "/home/.cursor/AGENTS.md", "fanout"),
|
|
53
|
+
]);
|
|
54
|
+
expect(lines).toEqual([
|
|
55
|
+
"bootstrap 1 change (agents 1)",
|
|
56
|
+
"fanout codex: 1 change (commands 1)",
|
|
57
|
+
"fanout cursor: 2 changes (agents 1, mcp 1)",
|
|
58
|
+
]);
|
|
59
|
+
});
|
|
60
|
+
it("formats detailed plan lines", () => {
|
|
61
|
+
expect(buildDetailedPlanLines([
|
|
62
|
+
makeEntry("agents", "project", "/repo/.agents/AGENTS.md", "bootstrap"),
|
|
63
|
+
makeEntry("commands", "claude", "/home/.claude/commands/review.md", "fanout"),
|
|
64
|
+
])).toEqual([
|
|
65
|
+
"bootstrap /repo/.agents/AGENTS.md",
|
|
66
|
+
"fanout /home/.claude/commands/review.md",
|
|
67
|
+
]);
|
|
68
|
+
});
|
|
69
|
+
it("formats issue sections with counts", () => {
|
|
70
|
+
expect(formatIssueSection("Ignored legacy inputs", ["/tmp/a", "/tmp/b"])).toEqual(["Ignored legacy inputs (2)", " /tmp/a", " /tmp/b", ""]);
|
|
71
|
+
});
|
|
72
|
+
it("formats snapshot lists with path counts", () => {
|
|
73
|
+
expect(formatSnapshotList([
|
|
74
|
+
{ id: "snap-1", createdAt: "2026-03-13T11:00:00Z", entries: [{}, {}] },
|
|
75
|
+
])).toEqual(["snap-1 2026-03-13T11:00:00Z (2 paths)"]);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
//# sourceMappingURL=reporting.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporting.spec.js","sourceRoot":"","sources":["../../src/utils/reporting.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ResetResult {
|
|
2
|
+
removedFiles: string[];
|
|
3
|
+
removedBackups: boolean;
|
|
4
|
+
cleanedGitignore: boolean;
|
|
5
|
+
clearedManifest: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Reset link-agents: remove all generated files, backups, and manifest.
|
|
9
|
+
*/
|
|
10
|
+
export declare function performReset(projectRoot: string, options?: {
|
|
11
|
+
dryRun?: boolean;
|
|
12
|
+
verbose?: boolean;
|
|
13
|
+
}): Promise<ResetResult>;
|
|
14
|
+
//# sourceMappingURL=reset.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reset.d.ts","sourceRoot":"","sources":["../../src/utils/reset.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,EAAE,OAAO,CAAC;IACxB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GACpD,OAAO,CAAC,WAAW,CAAC,CA2EtB"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { fileExists } from "./fs.js";
|
|
6
|
+
import { getManifestFiles, clearManifest } from "./manifest.js";
|
|
7
|
+
import { cleanGitignore } from "./gitignore.js";
|
|
8
|
+
const SYNC_AGENTS_DIR = path.join(os.homedir(), ".link-agents");
|
|
9
|
+
/**
|
|
10
|
+
* Reset link-agents: remove all generated files, backups, and manifest.
|
|
11
|
+
*/
|
|
12
|
+
export async function performReset(projectRoot, options = {}) {
|
|
13
|
+
const result = {
|
|
14
|
+
removedFiles: [],
|
|
15
|
+
removedBackups: false,
|
|
16
|
+
cleanedGitignore: false,
|
|
17
|
+
clearedManifest: false,
|
|
18
|
+
};
|
|
19
|
+
// Get files from manifest
|
|
20
|
+
const manifestFiles = await getManifestFiles();
|
|
21
|
+
console.log(chalk.yellow("Resetting link-agents..."));
|
|
22
|
+
// Remove generated files
|
|
23
|
+
for (const file of manifestFiles) {
|
|
24
|
+
if (await fileExists(file)) {
|
|
25
|
+
if (options.dryRun) {
|
|
26
|
+
console.log(chalk.dim(` would remove: ${file}`));
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
try {
|
|
30
|
+
await fs.unlink(file);
|
|
31
|
+
if (options.verbose) {
|
|
32
|
+
console.log(chalk.dim(` removed: ${file}`));
|
|
33
|
+
}
|
|
34
|
+
result.removedFiles.push(file);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// File may have been manually deleted
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Remove .link-agents directory (includes backups and manifest)
|
|
43
|
+
if (await fileExists(SYNC_AGENTS_DIR)) {
|
|
44
|
+
if (options.dryRun) {
|
|
45
|
+
console.log(chalk.dim(` would remove: ${SYNC_AGENTS_DIR}`));
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
try {
|
|
49
|
+
await fs.rm(SYNC_AGENTS_DIR, { recursive: true });
|
|
50
|
+
result.removedBackups = true;
|
|
51
|
+
result.clearedManifest = true;
|
|
52
|
+
if (options.verbose) {
|
|
53
|
+
console.log(chalk.dim(` removed: ${SYNC_AGENTS_DIR}`));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Directory may not exist
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Clean .gitignore
|
|
62
|
+
if (options.dryRun) {
|
|
63
|
+
console.log(chalk.dim(` would clean: ${projectRoot}/.gitignore`));
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
const cleaned = await cleanGitignore(projectRoot);
|
|
67
|
+
result.cleanedGitignore = cleaned;
|
|
68
|
+
if (cleaned && options.verbose) {
|
|
69
|
+
console.log(chalk.dim(` cleaned: ${projectRoot}/.gitignore`));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Clear manifest (already done if we removed the directory)
|
|
73
|
+
if (!result.clearedManifest && !options.dryRun) {
|
|
74
|
+
await clearManifest();
|
|
75
|
+
result.clearedManifest = true;
|
|
76
|
+
}
|
|
77
|
+
const action = options.dryRun ? "Would remove" : "Removed";
|
|
78
|
+
console.log(chalk.green(`${action} ${result.removedFiles.length} file(s), backups, and manifest.`));
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=reset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reset.js","sourceRoot":"","sources":["../../src/utils/reset.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface BackupInfo {
|
|
2
|
+
originalPath: string;
|
|
3
|
+
backupPath: string;
|
|
4
|
+
exists: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface RevertResult {
|
|
7
|
+
restored: string[];
|
|
8
|
+
failed: string[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Find all .bak files for files in the manifest.
|
|
12
|
+
*/
|
|
13
|
+
export declare function listBackups(): Promise<BackupInfo[]>;
|
|
14
|
+
/**
|
|
15
|
+
* Find backups that actually exist (can be restored).
|
|
16
|
+
*/
|
|
17
|
+
export declare function listAvailableBackups(): Promise<BackupInfo[]>;
|
|
18
|
+
/**
|
|
19
|
+
* Revert files from their .bak backups.
|
|
20
|
+
*/
|
|
21
|
+
export declare function performRevert(options?: {
|
|
22
|
+
dryRun?: boolean;
|
|
23
|
+
verbose?: boolean;
|
|
24
|
+
files?: string[];
|
|
25
|
+
}): Promise<RevertResult>;
|
|
26
|
+
/**
|
|
27
|
+
* Show available backups.
|
|
28
|
+
*/
|
|
29
|
+
export declare function showBackupStatus(): Promise<void>;
|
|
30
|
+
//# sourceMappingURL=revert.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"revert.d.ts","sourceRoot":"","sources":["../../src/utils/revert.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAYzD;AAED;;GAEG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAGlE;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GACtE,OAAO,CAAC,YAAY,CAAC,CAmEvB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAatD"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { fileExists, readFileSafe, writeFileSafe } from "./fs.js";
|
|
3
|
+
import { readManifest } from "./manifest.js";
|
|
4
|
+
/**
|
|
5
|
+
* Find all .bak files for files in the manifest.
|
|
6
|
+
*/
|
|
7
|
+
export async function listBackups() {
|
|
8
|
+
const manifest = await readManifest();
|
|
9
|
+
const backups = await Promise.all(manifest.generatedFiles.map(async (filePath) => {
|
|
10
|
+
const backupPath = `${filePath}.bak`;
|
|
11
|
+
const exists = await fileExists(backupPath);
|
|
12
|
+
return { originalPath: filePath, backupPath, exists };
|
|
13
|
+
}));
|
|
14
|
+
return backups;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Find backups that actually exist (can be restored).
|
|
18
|
+
*/
|
|
19
|
+
export async function listAvailableBackups() {
|
|
20
|
+
const all = await listBackups();
|
|
21
|
+
return all.filter((b) => b.exists);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Revert files from their .bak backups.
|
|
25
|
+
*/
|
|
26
|
+
export async function performRevert(options = {}) {
|
|
27
|
+
const result = {
|
|
28
|
+
restored: [],
|
|
29
|
+
failed: [],
|
|
30
|
+
};
|
|
31
|
+
const available = await listAvailableBackups();
|
|
32
|
+
if (available.length === 0) {
|
|
33
|
+
console.log(chalk.yellow("No backups available to restore."));
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
// Filter to specific files if requested
|
|
37
|
+
const toRestore = options.files
|
|
38
|
+
? available.filter((b) => options.files.some((f) => b.originalPath.includes(f) || b.backupPath.includes(f)))
|
|
39
|
+
: available;
|
|
40
|
+
if (toRestore.length === 0) {
|
|
41
|
+
console.log(chalk.yellow("No matching backups found."));
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
console.log(chalk.yellow(`Reverting ${toRestore.length} file(s)...`));
|
|
45
|
+
for (const backup of toRestore) {
|
|
46
|
+
if (options.dryRun) {
|
|
47
|
+
console.log(chalk.dim(` would restore: ${backup.originalPath}`));
|
|
48
|
+
result.restored.push(backup.originalPath);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const backupContent = await readFileSafe(backup.backupPath);
|
|
53
|
+
if (!backupContent) {
|
|
54
|
+
result.failed.push(backup.originalPath);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
await writeFileSafe(backup.originalPath, backupContent);
|
|
58
|
+
if (options.verbose) {
|
|
59
|
+
console.log(chalk.dim(` restored: ${backup.originalPath}`));
|
|
60
|
+
}
|
|
61
|
+
result.restored.push(backup.originalPath);
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
console.log(chalk.red(` failed: ${backup.originalPath} - ${String(err)}`));
|
|
65
|
+
result.failed.push(backup.originalPath);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const action = options.dryRun ? "Would restore" : "Restored";
|
|
69
|
+
console.log(chalk.green(`${action} ${result.restored.length} file(s).`));
|
|
70
|
+
if (result.failed.length > 0) {
|
|
71
|
+
console.log(chalk.red(`Failed to restore ${result.failed.length} file(s).`));
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Show available backups.
|
|
77
|
+
*/
|
|
78
|
+
export async function showBackupStatus() {
|
|
79
|
+
const available = await listAvailableBackups();
|
|
80
|
+
if (available.length === 0) {
|
|
81
|
+
console.log(chalk.yellow("No backups available."));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
console.log(chalk.cyan(`Found ${available.length} backup(s):\n`));
|
|
85
|
+
for (const backup of available) {
|
|
86
|
+
console.log(` ${backup.originalPath}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=revert.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"revert.js","sourceRoot":"","sources":["../../src/utils/revert.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"revert.spec.d.ts","sourceRoot":"","sources":["../../src/utils/revert.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import { listBackups, listAvailableBackups } from "./revert.js";
|
|
6
|
+
describe("revert", () => {
|
|
7
|
+
const testDir = path.join(os.tmpdir(), "link-agents-revert-test");
|
|
8
|
+
const manifestDir = path.join(os.homedir(), ".link-agents");
|
|
9
|
+
const manifestPath = path.join(manifestDir, "manifest.json");
|
|
10
|
+
let originalManifest = null;
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
13
|
+
// Save original manifest if it exists
|
|
14
|
+
try {
|
|
15
|
+
originalManifest = await fs.readFile(manifestPath, "utf8");
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
originalManifest = null;
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
afterEach(async () => {
|
|
22
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
23
|
+
// Restore original manifest
|
|
24
|
+
if (originalManifest) {
|
|
25
|
+
await fs.writeFile(manifestPath, originalManifest, "utf8");
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
try {
|
|
29
|
+
await fs.unlink(manifestPath);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// ignore
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
describe("listBackups", () => {
|
|
37
|
+
it("returns empty array when no manifest exists", async () => {
|
|
38
|
+
// Remove manifest if exists
|
|
39
|
+
try {
|
|
40
|
+
await fs.unlink(manifestPath);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// ignore
|
|
44
|
+
}
|
|
45
|
+
const backups = await listBackups();
|
|
46
|
+
expect(backups).toEqual([]);
|
|
47
|
+
});
|
|
48
|
+
it("returns backup info for files in manifest", async () => {
|
|
49
|
+
const testFile = path.join(testDir, "test.md");
|
|
50
|
+
const backupFile = `${testFile}.bak`;
|
|
51
|
+
// Create files
|
|
52
|
+
await fs.writeFile(testFile, "current content", "utf8");
|
|
53
|
+
await fs.writeFile(backupFile, "backup content", "utf8");
|
|
54
|
+
// Create manifest with test file
|
|
55
|
+
await fs.mkdir(manifestDir, { recursive: true });
|
|
56
|
+
await fs.writeFile(manifestPath, JSON.stringify({
|
|
57
|
+
version: 1,
|
|
58
|
+
lastSync: new Date().toISOString(),
|
|
59
|
+
generatedFiles: [testFile],
|
|
60
|
+
}), "utf8");
|
|
61
|
+
const backups = await listBackups();
|
|
62
|
+
expect(backups).toHaveLength(1);
|
|
63
|
+
expect(backups[0]).toEqual({
|
|
64
|
+
originalPath: testFile,
|
|
65
|
+
backupPath: backupFile,
|
|
66
|
+
exists: true,
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
it("marks backup as not existing when .bak file missing", async () => {
|
|
70
|
+
const testFile = path.join(testDir, "no-backup.md");
|
|
71
|
+
await fs.writeFile(testFile, "content", "utf8");
|
|
72
|
+
await fs.mkdir(manifestDir, { recursive: true });
|
|
73
|
+
await fs.writeFile(manifestPath, JSON.stringify({
|
|
74
|
+
version: 1,
|
|
75
|
+
lastSync: new Date().toISOString(),
|
|
76
|
+
generatedFiles: [testFile],
|
|
77
|
+
}), "utf8");
|
|
78
|
+
const backups = await listBackups();
|
|
79
|
+
expect(backups).toHaveLength(1);
|
|
80
|
+
expect(backups[0].exists).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
describe("listAvailableBackups", () => {
|
|
84
|
+
it("filters to only existing backups", async () => {
|
|
85
|
+
const fileWithBackup = path.join(testDir, "has-backup.md");
|
|
86
|
+
const fileWithoutBackup = path.join(testDir, "no-backup.md");
|
|
87
|
+
await fs.writeFile(fileWithBackup, "content", "utf8");
|
|
88
|
+
await fs.writeFile(`${fileWithBackup}.bak`, "backup", "utf8");
|
|
89
|
+
await fs.writeFile(fileWithoutBackup, "content", "utf8");
|
|
90
|
+
await fs.mkdir(manifestDir, { recursive: true });
|
|
91
|
+
await fs.writeFile(manifestPath, JSON.stringify({
|
|
92
|
+
version: 1,
|
|
93
|
+
lastSync: new Date().toISOString(),
|
|
94
|
+
generatedFiles: [fileWithBackup, fileWithoutBackup],
|
|
95
|
+
}), "utf8");
|
|
96
|
+
const available = await listAvailableBackups();
|
|
97
|
+
expect(available).toHaveLength(1);
|
|
98
|
+
expect(available[0].originalPath).toBe(fileWithBackup);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
//# sourceMappingURL=revert.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"revert.spec.js","sourceRoot":"","sources":["../../src/utils/revert.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculate similarity between two strings using Jaccard similarity on word sets
|
|
3
|
+
* Returns a value between 0 (completely different) and 1 (identical)
|
|
4
|
+
*/
|
|
5
|
+
export declare function calculateSimilarity(a: string, b: string): number;
|
|
6
|
+
/**
|
|
7
|
+
* Get human-readable similarity label
|
|
8
|
+
*/
|
|
9
|
+
export declare function getSimilarityLabel(score: number): string;
|
|
10
|
+
/**
|
|
11
|
+
* Format relative time (e.g., "2 hours ago", "3 days ago")
|
|
12
|
+
*/
|
|
13
|
+
export declare function formatRelativeTime(date: Date | undefined): string;
|
|
14
|
+
//# sourceMappingURL=similarity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"similarity.d.ts","sourceRoot":"","sources":["../../src/utils/similarity.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAchE;AAaD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMxD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,GAAG,MAAM,CAiBjE"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculate similarity between two strings using Jaccard similarity on word sets
|
|
3
|
+
* Returns a value between 0 (completely different) and 1 (identical)
|
|
4
|
+
*/
|
|
5
|
+
export function calculateSimilarity(a, b) {
|
|
6
|
+
if (a === b)
|
|
7
|
+
return 1;
|
|
8
|
+
if (!a || !b)
|
|
9
|
+
return 0;
|
|
10
|
+
const wordsA = new Set(tokenize(a));
|
|
11
|
+
const wordsB = new Set(tokenize(b));
|
|
12
|
+
if (wordsA.size === 0 && wordsB.size === 0)
|
|
13
|
+
return 1;
|
|
14
|
+
if (wordsA.size === 0 || wordsB.size === 0)
|
|
15
|
+
return 0;
|
|
16
|
+
const intersection = new Set([...wordsA].filter((w) => wordsB.has(w)));
|
|
17
|
+
const union = new Set([...wordsA, ...wordsB]);
|
|
18
|
+
return intersection.size / union.size;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Tokenize text into words (lowercase, alphanumeric only)
|
|
22
|
+
*/
|
|
23
|
+
function tokenize(text) {
|
|
24
|
+
return text
|
|
25
|
+
.toLowerCase()
|
|
26
|
+
.split(/\s+/)
|
|
27
|
+
.map((w) => w.replace(/[^a-z0-9]/g, ""))
|
|
28
|
+
.filter((w) => w.length > 2);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get human-readable similarity label
|
|
32
|
+
*/
|
|
33
|
+
export function getSimilarityLabel(score) {
|
|
34
|
+
if (score >= 0.9)
|
|
35
|
+
return "nearly identical";
|
|
36
|
+
if (score >= 0.7)
|
|
37
|
+
return "very similar";
|
|
38
|
+
if (score >= 0.5)
|
|
39
|
+
return "similar";
|
|
40
|
+
if (score >= 0.3)
|
|
41
|
+
return "somewhat different";
|
|
42
|
+
return "very different";
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Format relative time (e.g., "2 hours ago", "3 days ago")
|
|
46
|
+
*/
|
|
47
|
+
export function formatRelativeTime(date) {
|
|
48
|
+
if (!date)
|
|
49
|
+
return "unknown";
|
|
50
|
+
const now = new Date();
|
|
51
|
+
const diffMs = now.getTime() - date.getTime();
|
|
52
|
+
const diffSecs = Math.floor(diffMs / 1000);
|
|
53
|
+
const diffMins = Math.floor(diffSecs / 60);
|
|
54
|
+
const diffHours = Math.floor(diffMins / 60);
|
|
55
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
56
|
+
if (diffSecs < 60)
|
|
57
|
+
return "just now";
|
|
58
|
+
if (diffMins < 60)
|
|
59
|
+
return `${diffMins}m ago`;
|
|
60
|
+
if (diffHours < 24)
|
|
61
|
+
return `${diffHours}h ago`;
|
|
62
|
+
if (diffDays < 7)
|
|
63
|
+
return `${diffDays}d ago`;
|
|
64
|
+
if (diffDays < 30)
|
|
65
|
+
return `${Math.floor(diffDays / 7)}w ago`;
|
|
66
|
+
if (diffDays < 365)
|
|
67
|
+
return `${Math.floor(diffDays / 30)}mo ago`;
|
|
68
|
+
return `${Math.floor(diffDays / 365)}y ago`;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=similarity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"similarity.js","sourceRoot":"","sources":["../../src/utils/similarity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"similarity.spec.d.ts","sourceRoot":"","sources":["../../src/utils/similarity.spec.ts"],"names":[],"mappings":""}
|