agentloom 0.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.
- package/LICENSE +21 -0
- package/README.md +234 -0
- package/ThirdPartyNoticeText.txt +3 -0
- package/bin/cli.mjs +8 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +61 -0
- package/dist/commands/add.d.ts +2 -0
- package/dist/commands/add.js +62 -0
- package/dist/commands/mcp.d.ts +2 -0
- package/dist/commands/mcp.js +188 -0
- package/dist/commands/skills.d.ts +1 -0
- package/dist/commands/skills.js +11 -0
- package/dist/commands/sync.d.ts +2 -0
- package/dist/commands/sync.js +25 -0
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.js +71 -0
- package/dist/core/agents.d.ts +7 -0
- package/dist/core/agents.js +67 -0
- package/dist/core/argv.d.ts +5 -0
- package/dist/core/argv.js +52 -0
- package/dist/core/copy.d.ts +16 -0
- package/dist/core/copy.js +167 -0
- package/dist/core/fs.d.ts +13 -0
- package/dist/core/fs.js +70 -0
- package/dist/core/importer.d.ts +21 -0
- package/dist/core/importer.js +201 -0
- package/dist/core/lockfile.d.ts +4 -0
- package/dist/core/lockfile.js +25 -0
- package/dist/core/manifest.d.ts +3 -0
- package/dist/core/manifest.js +17 -0
- package/dist/core/mcp.d.ts +4 -0
- package/dist/core/mcp.js +73 -0
- package/dist/core/scope.d.ts +9 -0
- package/dist/core/scope.js +64 -0
- package/dist/core/settings.d.ts +6 -0
- package/dist/core/settings.js +54 -0
- package/dist/core/sources.d.ts +20 -0
- package/dist/core/sources.js +162 -0
- package/dist/core/version-notifier.d.ts +8 -0
- package/dist/core/version-notifier.js +142 -0
- package/dist/core/version.d.ts +1 -0
- package/dist/core/version.js +25 -0
- package/dist/sync/index.d.ts +15 -0
- package/dist/sync/index.js +482 -0
- package/dist/types.d.ts +73 -0
- package/dist/types.js +8 -0
- package/package.json +60 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { importSource, NonInteractiveConflictError } from "../core/importer.js";
|
|
2
|
+
import { readLockfile } from "../core/lockfile.js";
|
|
3
|
+
import { resolveScope } from "../core/scope.js";
|
|
4
|
+
import { prepareSource } from "../core/sources.js";
|
|
5
|
+
import { parseProvidersFlag } from "../core/argv.js";
|
|
6
|
+
import { getUpdateHelpText } from "../core/copy.js";
|
|
7
|
+
import { formatSyncSummary, syncFromCanonical } from "../sync/index.js";
|
|
8
|
+
export async function runUpdateCommand(argv, cwd) {
|
|
9
|
+
if (argv.help) {
|
|
10
|
+
console.log(getUpdateHelpText());
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const nonInteractive = !(process.stdin.isTTY && process.stdout.isTTY);
|
|
14
|
+
const paths = await resolveScope({
|
|
15
|
+
cwd,
|
|
16
|
+
global: Boolean(argv.global),
|
|
17
|
+
local: Boolean(argv.local),
|
|
18
|
+
interactive: !nonInteractive,
|
|
19
|
+
});
|
|
20
|
+
const lockfile = readLockfile(paths);
|
|
21
|
+
if (lockfile.entries.length === 0) {
|
|
22
|
+
console.log(`No lock entries found in ${paths.lockPath}.`);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
let updated = 0;
|
|
26
|
+
let skipped = 0;
|
|
27
|
+
for (const entry of lockfile.entries) {
|
|
28
|
+
const probe = prepareSource({
|
|
29
|
+
source: entry.source,
|
|
30
|
+
ref: entry.requestedRef,
|
|
31
|
+
subdir: entry.subdir,
|
|
32
|
+
});
|
|
33
|
+
const hasNewCommit = probe.resolvedCommit !== entry.resolvedCommit;
|
|
34
|
+
probe.cleanup();
|
|
35
|
+
if (!hasNewCommit) {
|
|
36
|
+
skipped += 1;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
await importSource({
|
|
41
|
+
source: entry.source,
|
|
42
|
+
ref: entry.requestedRef,
|
|
43
|
+
subdir: entry.subdir,
|
|
44
|
+
yes: Boolean(argv.yes),
|
|
45
|
+
nonInteractive,
|
|
46
|
+
paths,
|
|
47
|
+
});
|
|
48
|
+
updated += 1;
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
if (err instanceof NonInteractiveConflictError) {
|
|
52
|
+
console.error(err.message);
|
|
53
|
+
process.exit(2);
|
|
54
|
+
}
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
console.log(`Updated entries: ${updated}`);
|
|
59
|
+
console.log(`Unchanged entries: ${skipped}`);
|
|
60
|
+
if (updated > 0 && !argv["no-sync"]) {
|
|
61
|
+
const syncSummary = await syncFromCanonical({
|
|
62
|
+
paths,
|
|
63
|
+
providers: parseProvidersFlag(argv.providers),
|
|
64
|
+
yes: Boolean(argv.yes),
|
|
65
|
+
nonInteractive,
|
|
66
|
+
dryRun: Boolean(argv["dry-run"]),
|
|
67
|
+
});
|
|
68
|
+
console.log("");
|
|
69
|
+
console.log(formatSyncSummary(syncSummary, paths.agentsRoot));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AgentFrontmatter, CanonicalAgent, Provider } from "../types.js";
|
|
2
|
+
export declare function parseAgentsDir(agentsDir: string): CanonicalAgent[];
|
|
3
|
+
export declare function parseAgentMarkdown(raw: string, sourcePath: string, fileName?: string): CanonicalAgent;
|
|
4
|
+
export declare function buildAgentMarkdown(frontmatter: AgentFrontmatter, body: string): string;
|
|
5
|
+
export declare function targetFileNameForAgent(agent: CanonicalAgent): string;
|
|
6
|
+
export declare function getProviderConfig(frontmatter: AgentFrontmatter, provider: Provider): Record<string, unknown> | null;
|
|
7
|
+
export declare function isProviderEnabled(frontmatter: AgentFrontmatter, provider: Provider): boolean;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import matter from "gray-matter";
|
|
4
|
+
import YAML from "yaml";
|
|
5
|
+
import { isObject, slugify } from "./fs.js";
|
|
6
|
+
export function parseAgentsDir(agentsDir) {
|
|
7
|
+
if (!fs.existsSync(agentsDir))
|
|
8
|
+
return [];
|
|
9
|
+
const files = fs
|
|
10
|
+
.readdirSync(agentsDir)
|
|
11
|
+
.filter((entry) => entry.endsWith(".md"))
|
|
12
|
+
.sort();
|
|
13
|
+
return files.map((entry) => {
|
|
14
|
+
const filePath = path.join(agentsDir, entry);
|
|
15
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
16
|
+
return parseAgentMarkdown(raw, filePath, entry);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
export function parseAgentMarkdown(raw, sourcePath, fileName = path.basename(sourcePath)) {
|
|
20
|
+
const parsed = matter(raw);
|
|
21
|
+
if (!isObject(parsed.data)) {
|
|
22
|
+
throw new Error(`Invalid frontmatter in ${sourcePath}: expected object.`);
|
|
23
|
+
}
|
|
24
|
+
const frontmatter = parsed.data;
|
|
25
|
+
if (typeof frontmatter.name !== "string" || frontmatter.name.trim() === "") {
|
|
26
|
+
throw new Error(`Invalid frontmatter in ${sourcePath}: missing \`name\`.`);
|
|
27
|
+
}
|
|
28
|
+
if (typeof frontmatter.description !== "string" ||
|
|
29
|
+
frontmatter.description.trim() === "") {
|
|
30
|
+
throw new Error(`Invalid frontmatter in ${sourcePath}: missing \`description\`.`);
|
|
31
|
+
}
|
|
32
|
+
const normalizedName = frontmatter.name.trim();
|
|
33
|
+
const normalizedDescription = frontmatter.description.trim();
|
|
34
|
+
const body = parsed.content.trimStart();
|
|
35
|
+
return {
|
|
36
|
+
name: normalizedName,
|
|
37
|
+
description: normalizedDescription,
|
|
38
|
+
body,
|
|
39
|
+
frontmatter: {
|
|
40
|
+
...frontmatter,
|
|
41
|
+
name: normalizedName,
|
|
42
|
+
description: normalizedDescription,
|
|
43
|
+
},
|
|
44
|
+
sourcePath,
|
|
45
|
+
fileName,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function buildAgentMarkdown(frontmatter, body) {
|
|
49
|
+
const fm = YAML.stringify(frontmatter).trimEnd();
|
|
50
|
+
const normalizedBody = body.trimStart();
|
|
51
|
+
return `---\n${fm}\n---\n\n${normalizedBody}${normalizedBody.endsWith("\n") ? "" : "\n"}`;
|
|
52
|
+
}
|
|
53
|
+
export function targetFileNameForAgent(agent) {
|
|
54
|
+
const slug = slugify(agent.name);
|
|
55
|
+
return `${slug || "agent"}.md`;
|
|
56
|
+
}
|
|
57
|
+
export function getProviderConfig(frontmatter, provider) {
|
|
58
|
+
const value = frontmatter[provider];
|
|
59
|
+
if (value === false)
|
|
60
|
+
return null;
|
|
61
|
+
if (isObject(value))
|
|
62
|
+
return value;
|
|
63
|
+
return {};
|
|
64
|
+
}
|
|
65
|
+
export function isProviderEnabled(frontmatter, provider) {
|
|
66
|
+
return frontmatter[provider] !== false;
|
|
67
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ParsedArgs } from "minimist";
|
|
2
|
+
import type { Provider } from "../types.js";
|
|
3
|
+
export declare function parseArgs(argv: string[]): ParsedArgs;
|
|
4
|
+
export declare function parseProvidersFlag(input: unknown): Provider[] | undefined;
|
|
5
|
+
export declare function getStringArrayFlag(value: unknown, fallback?: string[]): string[];
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import minimist from "minimist";
|
|
2
|
+
export function parseArgs(argv) {
|
|
3
|
+
return minimist(argv, {
|
|
4
|
+
boolean: ["global", "local", "yes", "no-sync", "dry-run", "json", "help"],
|
|
5
|
+
string: ["ref", "subdir", "providers", "rename", "command", "url"],
|
|
6
|
+
alias: {
|
|
7
|
+
g: "global",
|
|
8
|
+
l: "local",
|
|
9
|
+
y: "yes",
|
|
10
|
+
h: "help",
|
|
11
|
+
},
|
|
12
|
+
"--": true,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
export function parseProvidersFlag(input) {
|
|
16
|
+
if (typeof input !== "string" || input.trim() === "")
|
|
17
|
+
return undefined;
|
|
18
|
+
const parsed = input
|
|
19
|
+
.split(",")
|
|
20
|
+
.map((item) => item.trim())
|
|
21
|
+
.filter(Boolean)
|
|
22
|
+
.map((item) => item.toLowerCase());
|
|
23
|
+
const validProviders = [];
|
|
24
|
+
for (const provider of parsed) {
|
|
25
|
+
if (provider === "cursor" ||
|
|
26
|
+
provider === "claude" ||
|
|
27
|
+
provider === "codex" ||
|
|
28
|
+
provider === "opencode" ||
|
|
29
|
+
provider === "gemini" ||
|
|
30
|
+
provider === "copilot") {
|
|
31
|
+
validProviders.push(provider);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
throw new Error(`Unknown provider: ${provider}. Expected one of: cursor, claude, codex, opencode, gemini, copilot.`);
|
|
35
|
+
}
|
|
36
|
+
return [...new Set(validProviders)];
|
|
37
|
+
}
|
|
38
|
+
export function getStringArrayFlag(value, fallback = []) {
|
|
39
|
+
if (Array.isArray(value)) {
|
|
40
|
+
return value
|
|
41
|
+
.flatMap((item) => String(item).split(","))
|
|
42
|
+
.map((item) => item.trim())
|
|
43
|
+
.filter(Boolean);
|
|
44
|
+
}
|
|
45
|
+
if (typeof value === "string") {
|
|
46
|
+
return value
|
|
47
|
+
.split(",")
|
|
48
|
+
.map((item) => item.trim())
|
|
49
|
+
.filter(Boolean);
|
|
50
|
+
}
|
|
51
|
+
return fallback;
|
|
52
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type UsageErrorInput = {
|
|
2
|
+
issue: string;
|
|
3
|
+
usage: string;
|
|
4
|
+
example?: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function getRootHelpText(): string;
|
|
7
|
+
export declare function getAddHelpText(): string;
|
|
8
|
+
export declare function getUpdateHelpText(): string;
|
|
9
|
+
export declare function getSyncHelpText(): string;
|
|
10
|
+
export declare function getMcpHelpText(): string;
|
|
11
|
+
export declare function getMcpAddHelpText(): string;
|
|
12
|
+
export declare function getMcpListHelpText(): string;
|
|
13
|
+
export declare function getMcpDeleteHelpText(): string;
|
|
14
|
+
export declare function formatUsageError(input: UsageErrorInput): string;
|
|
15
|
+
export declare function formatUnknownCommandError(command: string): string;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
const PROVIDERS_CSV = "cursor,claude,codex,opencode,gemini,copilot";
|
|
2
|
+
export function getRootHelpText() {
|
|
3
|
+
return `agentloom - unified agent and MCP sync CLI
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
agentloom <command> [options]
|
|
7
|
+
|
|
8
|
+
Commands:
|
|
9
|
+
skills ... Pass through to "npx skills ..." (vercel-labs/skills)
|
|
10
|
+
add <source> Import agents and MCP from a repo source
|
|
11
|
+
update Refresh lockfile-managed imports
|
|
12
|
+
sync Generate provider-specific outputs
|
|
13
|
+
mcp <add|list|delete> Manage canonical MCP servers
|
|
14
|
+
help Show this help text
|
|
15
|
+
|
|
16
|
+
Common options:
|
|
17
|
+
--local Use .agents from current workspace
|
|
18
|
+
--global Use ~/.agents
|
|
19
|
+
--yes Skip interactive confirmations
|
|
20
|
+
--no-sync Skip post-change sync (mutating commands)
|
|
21
|
+
--providers <csv> Limit sync providers (${PROVIDERS_CSV})
|
|
22
|
+
--dry-run Print planned sync changes without writing files
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
agentloom add vercel-labs/skills
|
|
26
|
+
agentloom add /repo --subdir packages/agents
|
|
27
|
+
agentloom update --local
|
|
28
|
+
agentloom sync --providers codex,claude,cursor
|
|
29
|
+
agentloom mcp add browser-tools --command npx --arg browser-tools-mcp
|
|
30
|
+
agentloom skills add vercel-labs/skills
|
|
31
|
+
`;
|
|
32
|
+
}
|
|
33
|
+
export function getAddHelpText() {
|
|
34
|
+
return `Import canonical agents and MCP from a source repository.
|
|
35
|
+
|
|
36
|
+
Usage:
|
|
37
|
+
agentloom add <source> [options]
|
|
38
|
+
|
|
39
|
+
Options:
|
|
40
|
+
--ref <ref> Git ref (branch/tag/commit) for remote sources
|
|
41
|
+
--subdir <path> Subdirectory inside source repo
|
|
42
|
+
--rename <name> Rename imported agent (single-agent import only)
|
|
43
|
+
--local | --global Choose destination scope
|
|
44
|
+
--yes Skip conflict prompts (overwrite/merge defaults)
|
|
45
|
+
--no-sync Do not run sync after import
|
|
46
|
+
--providers <csv> Providers for post-import sync (${PROVIDERS_CSV})
|
|
47
|
+
--dry-run Show sync plan without writing provider files
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
agentloom add vercel-labs/skills --subdir skills --providers codex,claude
|
|
51
|
+
`;
|
|
52
|
+
}
|
|
53
|
+
export function getUpdateHelpText() {
|
|
54
|
+
return `Refresh lockfile-managed sources and re-import updated revisions.
|
|
55
|
+
|
|
56
|
+
Usage:
|
|
57
|
+
agentloom update [options]
|
|
58
|
+
|
|
59
|
+
Options:
|
|
60
|
+
--local | --global Choose lockfile scope
|
|
61
|
+
--yes Skip conflict prompts during re-import
|
|
62
|
+
--no-sync Do not run sync after updates
|
|
63
|
+
--providers <csv> Providers for post-update sync (${PROVIDERS_CSV})
|
|
64
|
+
--dry-run Show sync plan without writing provider files
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
agentloom update --local --providers codex,cursor
|
|
68
|
+
`;
|
|
69
|
+
}
|
|
70
|
+
export function getSyncHelpText() {
|
|
71
|
+
return `Generate provider-specific agent and MCP files from canonical .agents data.
|
|
72
|
+
|
|
73
|
+
Usage:
|
|
74
|
+
agentloom sync [options]
|
|
75
|
+
|
|
76
|
+
Options:
|
|
77
|
+
--local | --global Choose canonical scope
|
|
78
|
+
--providers <csv> Limit providers (${PROVIDERS_CSV})
|
|
79
|
+
--yes Auto-delete stale generated files
|
|
80
|
+
--dry-run Show file changes without writing
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
agentloom sync --local --providers codex,claude,cursor --dry-run
|
|
84
|
+
`;
|
|
85
|
+
}
|
|
86
|
+
export function getMcpHelpText() {
|
|
87
|
+
return `Manage canonical MCP servers in .agents/mcp.json.
|
|
88
|
+
|
|
89
|
+
Usage:
|
|
90
|
+
agentloom mcp <command> [options]
|
|
91
|
+
|
|
92
|
+
Commands:
|
|
93
|
+
add <name> Add or update an MCP server
|
|
94
|
+
list List configured MCP servers
|
|
95
|
+
delete <name> Remove an MCP server
|
|
96
|
+
|
|
97
|
+
Shared options:
|
|
98
|
+
--local | --global Choose canonical scope
|
|
99
|
+
--no-sync Skip post-change sync (add/delete only)
|
|
100
|
+
--providers <csv> Providers for post-change sync (${PROVIDERS_CSV})
|
|
101
|
+
|
|
102
|
+
Examples:
|
|
103
|
+
agentloom mcp add browser --command npx --arg browser-tools-mcp
|
|
104
|
+
agentloom mcp list --json
|
|
105
|
+
agentloom mcp delete browser
|
|
106
|
+
`;
|
|
107
|
+
}
|
|
108
|
+
export function getMcpAddHelpText() {
|
|
109
|
+
return `Add or update an MCP server in canonical .agents/mcp.json.
|
|
110
|
+
|
|
111
|
+
Usage:
|
|
112
|
+
agentloom mcp add <name> (--url <url> | --command <cmd>) [options]
|
|
113
|
+
|
|
114
|
+
Options:
|
|
115
|
+
--arg <value> Repeatable command argument
|
|
116
|
+
--env KEY=VALUE Repeatable environment variable
|
|
117
|
+
--providers <csv> Provider-specific server assignment (${PROVIDERS_CSV})
|
|
118
|
+
--local | --global Choose canonical scope
|
|
119
|
+
--no-sync Skip post-change sync
|
|
120
|
+
|
|
121
|
+
Examples:
|
|
122
|
+
agentloom mcp add browser --command npx --arg browser-tools-mcp
|
|
123
|
+
agentloom mcp add docs --url https://example.com/mcp --providers codex,claude
|
|
124
|
+
`;
|
|
125
|
+
}
|
|
126
|
+
export function getMcpListHelpText() {
|
|
127
|
+
return `List canonical MCP servers.
|
|
128
|
+
|
|
129
|
+
Usage:
|
|
130
|
+
agentloom mcp list [options]
|
|
131
|
+
|
|
132
|
+
Options:
|
|
133
|
+
--json Print raw JSON
|
|
134
|
+
--local | --global Choose canonical scope
|
|
135
|
+
|
|
136
|
+
Example:
|
|
137
|
+
agentloom mcp list --json
|
|
138
|
+
`;
|
|
139
|
+
}
|
|
140
|
+
export function getMcpDeleteHelpText() {
|
|
141
|
+
return `Delete an MCP server from canonical .agents/mcp.json.
|
|
142
|
+
|
|
143
|
+
Usage:
|
|
144
|
+
agentloom mcp delete <name> [options]
|
|
145
|
+
|
|
146
|
+
Options:
|
|
147
|
+
--local | --global Choose canonical scope
|
|
148
|
+
--no-sync Skip post-change sync
|
|
149
|
+
|
|
150
|
+
Example:
|
|
151
|
+
agentloom mcp delete browser
|
|
152
|
+
`;
|
|
153
|
+
}
|
|
154
|
+
export function formatUsageError(input) {
|
|
155
|
+
const lines = [`Issue: ${input.issue}`, `Usage: ${input.usage}`];
|
|
156
|
+
if (input.example) {
|
|
157
|
+
lines.push(`Example: ${input.example}`);
|
|
158
|
+
}
|
|
159
|
+
return lines.join("\n");
|
|
160
|
+
}
|
|
161
|
+
export function formatUnknownCommandError(command) {
|
|
162
|
+
return formatUsageError({
|
|
163
|
+
issue: `Unknown command "${command}".`,
|
|
164
|
+
usage: "agentloom --help",
|
|
165
|
+
example: "agentloom sync --local",
|
|
166
|
+
});
|
|
167
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function ensureDir(dirPath: string): void;
|
|
2
|
+
export declare function readTextIfExists(filePath: string): string | null;
|
|
3
|
+
export declare function readJsonIfExists<T>(filePath: string): T | null;
|
|
4
|
+
export declare function writeJsonAtomic(filePath: string, value: unknown): void;
|
|
5
|
+
export declare function writeTextAtomic(filePath: string, content: string): void;
|
|
6
|
+
export declare function listMarkdownFiles(dirPath: string): string[];
|
|
7
|
+
export declare function hashContent(input: string): string;
|
|
8
|
+
export declare function hashFiles(filePaths: string[]): string;
|
|
9
|
+
export declare function slugify(input: string): string;
|
|
10
|
+
export declare function isObject(value: unknown): value is Record<string, unknown>;
|
|
11
|
+
export declare function toPosixPath(filePath: string): string;
|
|
12
|
+
export declare function relativePosix(fromPath: string, toPath: string): string;
|
|
13
|
+
export declare function removeFileIfExists(filePath: string): void;
|
package/dist/core/fs.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
export function ensureDir(dirPath) {
|
|
5
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
6
|
+
}
|
|
7
|
+
export function readTextIfExists(filePath) {
|
|
8
|
+
if (!fs.existsSync(filePath))
|
|
9
|
+
return null;
|
|
10
|
+
return fs.readFileSync(filePath, "utf8");
|
|
11
|
+
}
|
|
12
|
+
export function readJsonIfExists(filePath) {
|
|
13
|
+
const text = readTextIfExists(filePath);
|
|
14
|
+
if (text === null)
|
|
15
|
+
return null;
|
|
16
|
+
return JSON.parse(text);
|
|
17
|
+
}
|
|
18
|
+
export function writeJsonAtomic(filePath, value) {
|
|
19
|
+
writeTextAtomic(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
20
|
+
}
|
|
21
|
+
export function writeTextAtomic(filePath, content) {
|
|
22
|
+
ensureDir(path.dirname(filePath));
|
|
23
|
+
const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
24
|
+
fs.writeFileSync(tempPath, content, "utf8");
|
|
25
|
+
fs.renameSync(tempPath, filePath);
|
|
26
|
+
}
|
|
27
|
+
export function listMarkdownFiles(dirPath) {
|
|
28
|
+
if (!fs.existsSync(dirPath))
|
|
29
|
+
return [];
|
|
30
|
+
return fs
|
|
31
|
+
.readdirSync(dirPath)
|
|
32
|
+
.filter((entry) => entry.endsWith(".md") || entry.endsWith(".mdc"))
|
|
33
|
+
.map((entry) => path.join(dirPath, entry));
|
|
34
|
+
}
|
|
35
|
+
export function hashContent(input) {
|
|
36
|
+
return createHash("sha256").update(input).digest("hex");
|
|
37
|
+
}
|
|
38
|
+
export function hashFiles(filePaths) {
|
|
39
|
+
const hasher = createHash("sha256");
|
|
40
|
+
for (const filePath of [...filePaths].sort()) {
|
|
41
|
+
hasher.update(filePath);
|
|
42
|
+
hasher.update("\0");
|
|
43
|
+
hasher.update(fs.readFileSync(filePath));
|
|
44
|
+
hasher.update("\0");
|
|
45
|
+
}
|
|
46
|
+
return hasher.digest("hex");
|
|
47
|
+
}
|
|
48
|
+
export function slugify(input) {
|
|
49
|
+
return input
|
|
50
|
+
.trim()
|
|
51
|
+
.toLowerCase()
|
|
52
|
+
.replace(/[^a-z0-9_-]+/g, "-")
|
|
53
|
+
.replace(/-{2,}/g, "-")
|
|
54
|
+
.replace(/^-+|-+$/g, "")
|
|
55
|
+
.slice(0, 80);
|
|
56
|
+
}
|
|
57
|
+
export function isObject(value) {
|
|
58
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
59
|
+
}
|
|
60
|
+
export function toPosixPath(filePath) {
|
|
61
|
+
return filePath.split(path.sep).join("/");
|
|
62
|
+
}
|
|
63
|
+
export function relativePosix(fromPath, toPath) {
|
|
64
|
+
return toPosixPath(path.relative(fromPath, toPath));
|
|
65
|
+
}
|
|
66
|
+
export function removeFileIfExists(filePath) {
|
|
67
|
+
if (fs.existsSync(filePath)) {
|
|
68
|
+
fs.unlinkSync(filePath);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ScopePaths } from "../types.js";
|
|
2
|
+
export declare class NonInteractiveConflictError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
export interface ImportOptions {
|
|
6
|
+
source: string;
|
|
7
|
+
ref?: string;
|
|
8
|
+
subdir?: string;
|
|
9
|
+
rename?: string;
|
|
10
|
+
yes?: boolean;
|
|
11
|
+
nonInteractive?: boolean;
|
|
12
|
+
paths: ScopePaths;
|
|
13
|
+
}
|
|
14
|
+
export interface ImportSummary {
|
|
15
|
+
source: string;
|
|
16
|
+
sourceType: "local" | "github" | "git";
|
|
17
|
+
importedAgents: string[];
|
|
18
|
+
importedMcpServers: string[];
|
|
19
|
+
resolvedCommit: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function importSource(options: ImportOptions): Promise<ImportSummary>;
|