agents-sync 0.1.0 → 0.2.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/dist/cli.js +9 -1
- package/dist/index.js +14 -108
- package/dist/init.js +62 -0
- package/dist/postinstall.js +36 -0
- package/dist/sync.js +113 -0
- package/package.json +3 -2
- package/readme.md +24 -0
package/dist/cli.js
CHANGED
|
@@ -1,2 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
const args = process.argv.slice(2);
|
|
3
|
+
if (args.includes("init")) {
|
|
4
|
+
const { runInit } = await import("./init.js");
|
|
5
|
+
await runInit({ cwd: process.cwd() });
|
|
6
|
+
}
|
|
7
|
+
else {
|
|
8
|
+
await import("./index.js");
|
|
9
|
+
}
|
|
10
|
+
export {};
|
package/dist/index.js
CHANGED
|
@@ -1,129 +1,35 @@
|
|
|
1
1
|
import chokidar from "chokidar";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import fs from "node:fs/promises";
|
|
4
2
|
import { existsSync } from "node:fs";
|
|
5
|
-
import {
|
|
6
|
-
import { parseAgentsMarkdown, inferKindFromPath } from "./schema.js";
|
|
7
|
-
import { translators } from "./translation/index.js";
|
|
8
|
-
const CWD = process.cwd();
|
|
9
|
-
const CONFIG_PATH = path.join(CWD, "agents.config.json");
|
|
10
|
-
const ensureDir = async (dirPath) => {
|
|
11
|
-
await fs.mkdir(dirPath, { recursive: true });
|
|
12
|
-
};
|
|
13
|
-
const writeIfChanged = async (filePath, content) => {
|
|
14
|
-
let existing = null;
|
|
15
|
-
try {
|
|
16
|
-
existing = await fs.readFile(filePath, "utf8");
|
|
17
|
-
}
|
|
18
|
-
catch {
|
|
19
|
-
existing = null;
|
|
20
|
-
}
|
|
21
|
-
if (existing === content)
|
|
22
|
-
return;
|
|
23
|
-
await ensureDir(path.dirname(filePath));
|
|
24
|
-
await fs.writeFile(filePath, content, "utf8");
|
|
25
|
-
};
|
|
26
|
-
const walkFiles = async (dirPath) => {
|
|
27
|
-
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
28
|
-
const files = [];
|
|
29
|
-
for (const entry of entries) {
|
|
30
|
-
const full = path.join(dirPath, entry.name);
|
|
31
|
-
if (entry.isDirectory()) {
|
|
32
|
-
files.push(...(await walkFiles(full)));
|
|
33
|
-
}
|
|
34
|
-
else if (entry.isFile()) {
|
|
35
|
-
files.push(full);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return files;
|
|
39
|
-
};
|
|
3
|
+
import { createSyncContext, ensureTargetDirs, walkFiles, syncSourceFile, removeSourceFile, syncRootDoc } from "./sync.js";
|
|
40
4
|
const main = async () => {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
for (const [, target] of targetEntries) {
|
|
46
|
-
await ensureDir(resolveTargetAgentsDir(CWD, target, config.agentsSubdir));
|
|
47
|
-
}
|
|
48
|
-
const syncSourceFile = async (filePath) => {
|
|
49
|
-
const rel = path.relative(sourceDir, filePath);
|
|
50
|
-
const kind = inferKindFromPath(rel);
|
|
51
|
-
if (!kind)
|
|
52
|
-
return;
|
|
53
|
-
const id = path.basename(rel, path.extname(rel));
|
|
54
|
-
const content = await fs.readFile(filePath, "utf8");
|
|
55
|
-
const doc = parseAgentsMarkdown(content, { kind, id });
|
|
56
|
-
await Promise.all(targetEntries.map(async ([targetKey, target]) => {
|
|
57
|
-
const translator = translators[targetKey];
|
|
58
|
-
if (!translator)
|
|
59
|
-
return;
|
|
60
|
-
const baseDir = resolveTargetAgentsDir(CWD, target, config.agentsSubdir);
|
|
61
|
-
const subdir = translator.subdir(doc, target);
|
|
62
|
-
const ext = translator.fileExtension(doc, target);
|
|
63
|
-
const outPath = path.join(baseDir, subdir, `${doc.id}${ext}`);
|
|
64
|
-
const translated = translator.renderDoc(doc, target);
|
|
65
|
-
await writeIfChanged(outPath, translated);
|
|
66
|
-
}));
|
|
67
|
-
};
|
|
68
|
-
const removeSourceFile = async (filePath) => {
|
|
69
|
-
const rel = path.relative(sourceDir, filePath);
|
|
70
|
-
const kind = inferKindFromPath(rel);
|
|
71
|
-
if (!kind)
|
|
72
|
-
return;
|
|
73
|
-
const id = path.basename(rel, path.extname(rel));
|
|
74
|
-
const doc = parseAgentsMarkdown("", { kind, id });
|
|
75
|
-
await Promise.all(targetEntries.map(async ([targetKey, target]) => {
|
|
76
|
-
const translator = translators[targetKey];
|
|
77
|
-
if (!translator)
|
|
78
|
-
return;
|
|
79
|
-
const baseDir = resolveTargetAgentsDir(CWD, target, config.agentsSubdir);
|
|
80
|
-
const subdir = translator.subdir(doc, target);
|
|
81
|
-
const ext = translator.fileExtension(doc, target);
|
|
82
|
-
const outPath = path.join(baseDir, subdir, `${id}${ext}`);
|
|
83
|
-
await fs.rm(outPath, { force: true });
|
|
84
|
-
}));
|
|
85
|
-
};
|
|
86
|
-
const syncRootDoc = async () => {
|
|
87
|
-
if (!existsSync(rootDoc))
|
|
88
|
-
return;
|
|
89
|
-
const content = await fs.readFile(rootDoc, "utf8");
|
|
90
|
-
const doc = parseAgentsMarkdown(content, { kind: "config", id: "root" });
|
|
91
|
-
const rootTargets = config.rootTargets;
|
|
92
|
-
await Promise.all(Object.entries(rootTargets).map(async ([targetKey, outName]) => {
|
|
93
|
-
const translator = translators[targetKey];
|
|
94
|
-
if (!translator)
|
|
95
|
-
return;
|
|
96
|
-
const outPath = path.resolve(CWD, outName);
|
|
97
|
-
const translated = translator.renderDoc(doc, config.targets[targetKey]);
|
|
98
|
-
await writeIfChanged(outPath, translated);
|
|
99
|
-
}));
|
|
100
|
-
};
|
|
101
|
-
if (existsSync(sourceDir)) {
|
|
102
|
-
const files = await walkFiles(sourceDir);
|
|
5
|
+
const ctx = await createSyncContext(process.cwd());
|
|
6
|
+
await ensureTargetDirs(ctx);
|
|
7
|
+
if (existsSync(ctx.sourceDir)) {
|
|
8
|
+
const files = await walkFiles(ctx.sourceDir);
|
|
103
9
|
for (const filePath of files) {
|
|
104
|
-
await syncSourceFile(filePath);
|
|
10
|
+
await syncSourceFile(ctx, filePath);
|
|
105
11
|
}
|
|
106
12
|
}
|
|
107
|
-
await syncRootDoc();
|
|
108
|
-
const agentsWatcher = chokidar.watch(sourceDir, {
|
|
13
|
+
await syncRootDoc(ctx);
|
|
14
|
+
const agentsWatcher = chokidar.watch(ctx.sourceDir, {
|
|
109
15
|
ignoreInitial: true,
|
|
110
16
|
awaitWriteFinish: {
|
|
111
17
|
stabilityThreshold: 150,
|
|
112
18
|
pollInterval: 50
|
|
113
19
|
}
|
|
114
20
|
});
|
|
115
|
-
agentsWatcher.on("add", syncSourceFile);
|
|
116
|
-
agentsWatcher.on("change", syncSourceFile);
|
|
117
|
-
agentsWatcher.on("unlink", removeSourceFile);
|
|
118
|
-
const rootWatcher = chokidar.watch(rootDoc, {
|
|
21
|
+
agentsWatcher.on("add", (filePath) => syncSourceFile(ctx, filePath));
|
|
22
|
+
agentsWatcher.on("change", (filePath) => syncSourceFile(ctx, filePath));
|
|
23
|
+
agentsWatcher.on("unlink", (filePath) => removeSourceFile(ctx, filePath));
|
|
24
|
+
const rootWatcher = chokidar.watch(ctx.rootDoc, {
|
|
119
25
|
ignoreInitial: true,
|
|
120
26
|
awaitWriteFinish: {
|
|
121
27
|
stabilityThreshold: 150,
|
|
122
28
|
pollInterval: 50
|
|
123
29
|
}
|
|
124
30
|
});
|
|
125
|
-
rootWatcher.on("add", syncRootDoc);
|
|
126
|
-
rootWatcher.on("change", syncRootDoc);
|
|
31
|
+
rootWatcher.on("add", () => syncRootDoc(ctx));
|
|
32
|
+
rootWatcher.on("change", () => syncRootDoc(ctx));
|
|
127
33
|
process.stdout.write("agents-sync watching .agents and AGENTS.MD\n");
|
|
128
34
|
};
|
|
129
35
|
main().catch((err) => {
|
package/dist/init.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { ensureDir, syncOnce } from "./sync.js";
|
|
6
|
+
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
|
+
const DEFAULT_CONFIG_PATH = path.join(PACKAGE_ROOT, "agents.config.json");
|
|
8
|
+
const DEFAULT_SCHEMA_PATH = path.join(PACKAGE_ROOT, "SCHEMA.MD");
|
|
9
|
+
const DEFAULT_SCHEMA_COMMAND = `\`\`\`agents
|
|
10
|
+
id: schema
|
|
11
|
+
type: command
|
|
12
|
+
title: Schema Guide
|
|
13
|
+
description: How to write and update the canonical agents schema.
|
|
14
|
+
aliases: agents-schema
|
|
15
|
+
\`\`\`
|
|
16
|
+
|
|
17
|
+
Use \`.agents/SCHEMA.MD\` as the source of truth for the canonical schema.
|
|
18
|
+
|
|
19
|
+
When you create or update commands, hooks, or skills, follow the format
|
|
20
|
+
defined in \`.agents/SCHEMA.MD\`. If you need to extend or change the schema,
|
|
21
|
+
update \`.agents/SCHEMA.MD\` and keep examples consistent.`;
|
|
22
|
+
const writeIfMissing = async (filePath, content) => {
|
|
23
|
+
if (existsSync(filePath))
|
|
24
|
+
return false;
|
|
25
|
+
await ensureDir(path.dirname(filePath));
|
|
26
|
+
await fs.writeFile(filePath, content, "utf8");
|
|
27
|
+
return true;
|
|
28
|
+
};
|
|
29
|
+
const copyIfMissing = async (fromPath, toPath) => {
|
|
30
|
+
if (existsSync(toPath))
|
|
31
|
+
return false;
|
|
32
|
+
const content = await fs.readFile(fromPath, "utf8");
|
|
33
|
+
await ensureDir(path.dirname(toPath));
|
|
34
|
+
await fs.writeFile(toPath, content, "utf8");
|
|
35
|
+
return true;
|
|
36
|
+
};
|
|
37
|
+
export const runInit = async ({ cwd, quiet }) => {
|
|
38
|
+
const created = [];
|
|
39
|
+
if (await copyIfMissing(DEFAULT_CONFIG_PATH, path.join(cwd, "agents.config.json"))) {
|
|
40
|
+
created.push("agents.config.json");
|
|
41
|
+
}
|
|
42
|
+
const agentsDir = path.join(cwd, ".agents");
|
|
43
|
+
await ensureDir(agentsDir);
|
|
44
|
+
await ensureDir(path.join(agentsDir, "commands"));
|
|
45
|
+
await ensureDir(path.join(agentsDir, "hooks"));
|
|
46
|
+
await ensureDir(path.join(agentsDir, "skills"));
|
|
47
|
+
if (await copyIfMissing(DEFAULT_SCHEMA_PATH, path.join(agentsDir, "SCHEMA.MD"))) {
|
|
48
|
+
created.push(".agents/SCHEMA.MD");
|
|
49
|
+
}
|
|
50
|
+
if (await writeIfMissing(path.join(agentsDir, "commands", "schema.md"), DEFAULT_SCHEMA_COMMAND)) {
|
|
51
|
+
created.push(".agents/commands/schema.md");
|
|
52
|
+
}
|
|
53
|
+
await syncOnce(cwd);
|
|
54
|
+
if (!quiet) {
|
|
55
|
+
if (created.length) {
|
|
56
|
+
console.log(`agents-sync init created: ${created.join(", ")}`);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
console.log("agents-sync init: nothing to do");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { runInit } from "./init.js";
|
|
5
|
+
const shouldSkip = async () => {
|
|
6
|
+
if (process.env.AGENTS_SYNC_SKIP_INIT === "1")
|
|
7
|
+
return true;
|
|
8
|
+
if (process.env.npm_config_global === "true")
|
|
9
|
+
return true;
|
|
10
|
+
const initCwd = process.env.INIT_CWD;
|
|
11
|
+
if (!initCwd)
|
|
12
|
+
return true;
|
|
13
|
+
const pkgPath = path.join(initCwd, "package.json");
|
|
14
|
+
if (!existsSync(pkgPath))
|
|
15
|
+
return true;
|
|
16
|
+
try {
|
|
17
|
+
const raw = await fs.readFile(pkgPath, "utf8");
|
|
18
|
+
const pkg = JSON.parse(raw);
|
|
19
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
20
|
+
if (!deps["agents-sync"])
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
};
|
|
28
|
+
const main = async () => {
|
|
29
|
+
if (await shouldSkip())
|
|
30
|
+
return;
|
|
31
|
+
const cwd = process.env.INIT_CWD ?? process.cwd();
|
|
32
|
+
await runInit({ cwd, quiet: true });
|
|
33
|
+
};
|
|
34
|
+
main().catch((err) => {
|
|
35
|
+
console.warn("[agents-sync] init skipped:", err?.message ?? err);
|
|
36
|
+
});
|
package/dist/sync.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { loadConfig, resolveTargetAgentsDir } from "./config.js";
|
|
5
|
+
import { parseAgentsMarkdown, inferKindFromPath } from "./schema.js";
|
|
6
|
+
import { translators } from "./translation/index.js";
|
|
7
|
+
export const ensureDir = async (dirPath) => {
|
|
8
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
9
|
+
};
|
|
10
|
+
export const writeIfChanged = async (filePath, content) => {
|
|
11
|
+
let existing = null;
|
|
12
|
+
try {
|
|
13
|
+
existing = await fs.readFile(filePath, "utf8");
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
existing = null;
|
|
17
|
+
}
|
|
18
|
+
if (existing === content)
|
|
19
|
+
return;
|
|
20
|
+
await ensureDir(path.dirname(filePath));
|
|
21
|
+
await fs.writeFile(filePath, content, "utf8");
|
|
22
|
+
};
|
|
23
|
+
export const walkFiles = async (dirPath) => {
|
|
24
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
25
|
+
const files = [];
|
|
26
|
+
for (const entry of entries) {
|
|
27
|
+
const full = path.join(dirPath, entry.name);
|
|
28
|
+
if (entry.isDirectory()) {
|
|
29
|
+
files.push(...(await walkFiles(full)));
|
|
30
|
+
}
|
|
31
|
+
else if (entry.isFile()) {
|
|
32
|
+
files.push(full);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return files;
|
|
36
|
+
};
|
|
37
|
+
export const createSyncContext = async (cwd, configPath = path.join(cwd, "agents.config.json")) => {
|
|
38
|
+
const config = await loadConfig(configPath);
|
|
39
|
+
const sourceDir = path.resolve(cwd, config.sourceDir);
|
|
40
|
+
const rootDoc = path.resolve(cwd, config.rootDoc);
|
|
41
|
+
const targetEntries = Object.entries(config.targets);
|
|
42
|
+
return { cwd, config, sourceDir, rootDoc, targetEntries };
|
|
43
|
+
};
|
|
44
|
+
export const ensureTargetDirs = async (ctx) => {
|
|
45
|
+
for (const [, target] of ctx.targetEntries) {
|
|
46
|
+
await ensureDir(resolveTargetAgentsDir(ctx.cwd, target, ctx.config.agentsSubdir));
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
export const syncSourceFile = async (ctx, filePath) => {
|
|
50
|
+
const rel = path.relative(ctx.sourceDir, filePath);
|
|
51
|
+
const kind = inferKindFromPath(rel);
|
|
52
|
+
if (!kind)
|
|
53
|
+
return;
|
|
54
|
+
const id = path.basename(rel, path.extname(rel));
|
|
55
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
56
|
+
const doc = parseAgentsMarkdown(content, { kind, id });
|
|
57
|
+
await Promise.all(ctx.targetEntries.map(async ([targetKey, target]) => {
|
|
58
|
+
const translator = translators[targetKey];
|
|
59
|
+
if (!translator)
|
|
60
|
+
return;
|
|
61
|
+
const baseDir = resolveTargetAgentsDir(ctx.cwd, target, ctx.config.agentsSubdir);
|
|
62
|
+
const subdir = translator.subdir(doc, target);
|
|
63
|
+
const ext = translator.fileExtension(doc, target);
|
|
64
|
+
const outPath = path.join(baseDir, subdir, `${doc.id}${ext}`);
|
|
65
|
+
const translated = translator.renderDoc(doc, target);
|
|
66
|
+
await writeIfChanged(outPath, translated);
|
|
67
|
+
}));
|
|
68
|
+
};
|
|
69
|
+
export const removeSourceFile = async (ctx, filePath) => {
|
|
70
|
+
const rel = path.relative(ctx.sourceDir, filePath);
|
|
71
|
+
const kind = inferKindFromPath(rel);
|
|
72
|
+
if (!kind)
|
|
73
|
+
return;
|
|
74
|
+
const id = path.basename(rel, path.extname(rel));
|
|
75
|
+
const doc = parseAgentsMarkdown("", { kind, id });
|
|
76
|
+
await Promise.all(ctx.targetEntries.map(async ([targetKey, target]) => {
|
|
77
|
+
const translator = translators[targetKey];
|
|
78
|
+
if (!translator)
|
|
79
|
+
return;
|
|
80
|
+
const baseDir = resolveTargetAgentsDir(ctx.cwd, target, ctx.config.agentsSubdir);
|
|
81
|
+
const subdir = translator.subdir(doc, target);
|
|
82
|
+
const ext = translator.fileExtension(doc, target);
|
|
83
|
+
const outPath = path.join(baseDir, subdir, `${id}${ext}`);
|
|
84
|
+
await fs.rm(outPath, { force: true });
|
|
85
|
+
}));
|
|
86
|
+
};
|
|
87
|
+
export const syncRootDoc = async (ctx) => {
|
|
88
|
+
if (!existsSync(ctx.rootDoc))
|
|
89
|
+
return;
|
|
90
|
+
const content = await fs.readFile(ctx.rootDoc, "utf8");
|
|
91
|
+
const doc = parseAgentsMarkdown(content, { kind: "config", id: "root" });
|
|
92
|
+
const rootTargets = ctx.config.rootTargets;
|
|
93
|
+
await Promise.all(Object.entries(rootTargets).map(async ([targetKey, outName]) => {
|
|
94
|
+
const translator = translators[targetKey];
|
|
95
|
+
if (!translator)
|
|
96
|
+
return;
|
|
97
|
+
const outPath = path.resolve(ctx.cwd, outName);
|
|
98
|
+
const translated = translator.renderDoc(doc, ctx.config.targets[targetKey]);
|
|
99
|
+
await writeIfChanged(outPath, translated);
|
|
100
|
+
}));
|
|
101
|
+
};
|
|
102
|
+
export const syncOnce = async (cwd, configPath = path.join(cwd, "agents.config.json")) => {
|
|
103
|
+
const ctx = await createSyncContext(cwd, configPath);
|
|
104
|
+
await ensureTargetDirs(ctx);
|
|
105
|
+
if (existsSync(ctx.sourceDir)) {
|
|
106
|
+
const files = await walkFiles(ctx.sourceDir);
|
|
107
|
+
for (const filePath of files) {
|
|
108
|
+
await syncSourceFile(ctx, filePath);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
await syncRootDoc(ctx);
|
|
112
|
+
return ctx;
|
|
113
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agents-sync",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"build": "tsc -p tsconfig.json",
|
|
17
17
|
"postbuild": "node scripts/add-shebang.mjs",
|
|
18
18
|
"start": "node dist/index.js",
|
|
19
|
-
"prepare": "npm run build"
|
|
19
|
+
"prepare": "npm run build",
|
|
20
|
+
"postinstall": "node dist/postinstall.js"
|
|
20
21
|
},
|
|
21
22
|
"dependencies": {
|
|
22
23
|
"chokidar": "^3.6.0"
|
package/readme.md
CHANGED
|
@@ -26,6 +26,30 @@ npm i -D agents-sync
|
|
|
26
26
|
npx agents-sync
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
### Auto Init On Install
|
|
30
|
+
|
|
31
|
+
When installed as a direct dependency, `agents-sync` will scaffold:
|
|
32
|
+
|
|
33
|
+
- `agents.config.json`
|
|
34
|
+
- `.agents/SCHEMA.MD`
|
|
35
|
+
- `.agents/commands/schema.md`
|
|
36
|
+
- `.agents/commands`, `.agents/hooks`, `.agents/skills`
|
|
37
|
+
|
|
38
|
+
It then runs a one-time sync to create the target folders and the `schema`
|
|
39
|
+
command in each tool folder.
|
|
40
|
+
|
|
41
|
+
Skip this behavior with:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
AGENTS_SYNC_SKIP_INIT=1 npm i -D agents-sync
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
You can also run it manually:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npx agents-sync init
|
|
51
|
+
```
|
|
52
|
+
|
|
29
53
|
## Canonical Schema
|
|
30
54
|
|
|
31
55
|
The canonical format is Markdown with an optional `agents` metadata fence. See `SCHEMA.MD` for the full schema and translation rules.
|