@tofrankie/agents-sync 0.0.1 → 0.0.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## agents-sync@0.0.3 (2026-04-22)
4
+
5
+ - Update CLI interaction
6
+
7
+ ## agents-sync@0.0.2 (2026-04-22)
8
+
9
+ - Fix config loader and sync engine bugs
10
+ - User home config file name is `.agents-sync.config.*`
11
+
3
12
  ## agents-sync@0.0.1 (2026-04-22)
4
13
 
5
14
  - Initial release
package/dist/cli.mjs CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import process$1 from "node:process";
2
+ import process from "node:process";
3
3
  import * as p from "@clack/prompts";
4
4
  import c from "ansis";
5
5
  import { Command } from "commander";
6
6
  import fs from "node:fs/promises";
7
7
  import path from "node:path";
8
- import { loadConfig } from "c12";
9
- import jiti from "jiti";
10
8
  import os from "node:os";
9
+ import { loadConfig } from "c12";
10
+ import { createJiti } from "jiti";
11
11
  //#region src/core/agent-profiles.ts
12
12
  const AGENT_PROFILES = {
13
13
  cursor: {
@@ -93,22 +93,20 @@ const DEFAULTS = {
93
93
  source: "~/.agents",
94
94
  targetAgent: "cursor"
95
95
  };
96
- function pickFirst(value) {
97
- if (Array.isArray(value)) return value[0];
98
- return value;
99
- }
100
96
  async function loadUserConfig(cwd, configPath) {
101
97
  if (configPath) {
102
- const loaded = await jiti(cwd, { interopDefault: true }).import(path.resolve(cwd, configPath));
103
- if (loaded && typeof loaded === "object" && "default" in loaded) return loaded.default ?? {};
104
- return loaded ?? {};
98
+ const resolvedConfig = path.resolve(cwd, configPath);
99
+ const loaded = await createJiti(resolvedConfig, { interopDefault: true }).import(resolvedConfig, { default: true });
100
+ return loaded && typeof loaded === "object" ? loaded : {};
105
101
  }
106
- return (await loadConfig({
107
- cwd,
108
- name: "agents-sync",
109
- defaults: {},
110
- jitiOptions: { interopDefault: true }
111
- })).config ?? {};
102
+ const projectDir = path.resolve(cwd);
103
+ const homeDir = path.resolve(os.homedir());
104
+ if (projectDir === homeDir) return loadHomeAgentsSyncConfig(homeDir);
105
+ const projectLayer = await loadAgentsSyncConfigFromDir(projectDir);
106
+ return {
107
+ ...await loadHomeAgentsSyncConfig(homeDir),
108
+ ...projectLayer
109
+ };
112
110
  }
113
111
  function resolveOptions(config, cli) {
114
112
  const sourceFromConfig = pickFirst(config.sourceDir);
@@ -122,26 +120,37 @@ function resolveOptions(config, cli) {
122
120
  yes: Boolean(cli.yes)
123
121
  };
124
122
  }
125
- //#endregion
126
- //#region src/core/sync-engine.ts
127
- async function exists(filePath) {
128
- try {
129
- await fs.access(filePath);
130
- return true;
131
- } catch {
132
- return false;
133
- }
123
+ function pickFirst(value) {
124
+ if (Array.isArray(value)) return value[0];
125
+ return value;
134
126
  }
135
- async function listFilesRecursively(root) {
136
- const out = [];
137
- const entries = await fs.readdir(root, { withFileTypes: true });
138
- for (const entry of entries) {
139
- const abs = path.join(root, entry.name);
140
- if (entry.isDirectory()) out.push(...await listFilesRecursively(abs));
141
- else out.push(abs);
142
- }
143
- return out;
127
+ async function loadAgentsSyncConfigFromDir(dir) {
128
+ return (await loadC12AgentsSync(dir)).config ?? {};
129
+ }
130
+ /** Standard `agents-sync.config.*` (+ c12 rc / layers for that name). */
131
+ async function loadC12AgentsSync(dir) {
132
+ return loadConfig({
133
+ cwd: dir,
134
+ name: "agents-sync",
135
+ defaults: {},
136
+ jitiOptions: { interopDefault: true }
137
+ });
138
+ }
139
+ /**
140
+ * User home only: hidden `.agents-sync.config.*` (not `agents-sync.config.*`).
141
+ * Keeps `name: 'agents-sync'` so `~/.agents-syncrc` still merges when present.
142
+ */
143
+ async function loadHomeAgentsSyncConfig(homeDir) {
144
+ return (await loadConfig({
145
+ cwd: homeDir,
146
+ name: "agents-sync",
147
+ configFile: ".agents-sync.config",
148
+ defaults: {},
149
+ jitiOptions: { interopDefault: true }
150
+ })).config ?? {};
144
151
  }
152
+ //#endregion
153
+ //#region src/core/sync-engine.ts
145
154
  async function discoverSkills(source) {
146
155
  const candidates = [path.join(source, "skills"), source];
147
156
  const found = [];
@@ -232,6 +241,24 @@ function renderMcpConfigByAgent(model, targetAgent) {
232
241
  if (targetAgent !== "cursor") throw new Error(`unsupported mcp target: ${targetAgent}`);
233
242
  return `${JSON.stringify(model, null, 2)}\n`;
234
243
  }
244
+ async function exists(filePath) {
245
+ try {
246
+ await fs.access(filePath);
247
+ return true;
248
+ } catch {
249
+ return false;
250
+ }
251
+ }
252
+ async function listFilesRecursively(root) {
253
+ const out = [];
254
+ const entries = await fs.readdir(root, { withFileTypes: true });
255
+ for (const entry of entries) {
256
+ const abs = path.join(root, entry.name);
257
+ if (entry.isDirectory()) out.push(...await listFilesRecursively(abs));
258
+ else out.push(abs);
259
+ }
260
+ return out;
261
+ }
235
262
  //#endregion
236
263
  //#region src/cli/commands/mcp.ts
237
264
  async function runMcpCommand(cwd, options) {
@@ -242,18 +269,18 @@ async function runMcpCommand(cwd, options) {
242
269
  }
243
270
  const sourceFile = await resolveMcpSourceFile(runtime.source);
244
271
  if (!sourceFile) {
245
- printSkip(`No MCP config file found in ${runtime.source}.`);
272
+ printSkip(`No MCP config file found in ${c.dim(runtime.source)}.`);
246
273
  return;
247
274
  }
248
275
  const rendered = renderMcpConfigByAgent(await normalizeMcpConfig(sourceFile), runtime.targetAgent);
249
276
  const targetFile = resolveMcpTargetFile(cwd);
250
277
  if (runtime.dryRun) {
251
- process.stdout.write(`dry-run: MCP sync target => ${targetFile}\n`);
278
+ process.stdout.write(`dry-run: MCP sync target => ${c.dim(targetFile)}\n`);
252
279
  return;
253
280
  }
254
281
  await fs.mkdir(path.dirname(targetFile), { recursive: true });
255
282
  await fs.writeFile(targetFile, rendered, "utf8");
256
- process.stdout.write(`done: MCP synced to ${targetFile}\n`);
283
+ process.stdout.write(`done: MCP synced to ${c.dim(targetFile)}\n`);
257
284
  }
258
285
  //#endregion
259
286
  //#region src/cli/commands/rule.ts
@@ -265,7 +292,7 @@ async function runRuleCommand(cwd, options) {
265
292
  }
266
293
  const files = await discoverRuleFiles(runtime.source);
267
294
  if (files.length === 0) {
268
- printSkip(`No rule files found in ${runtime.source}.`);
295
+ printSkip(`No rule files found in ${c.dim(runtime.source)}.`);
269
296
  return;
270
297
  }
271
298
  printSummary("rules", await copyRuleFiles(files, resolveRulesTargetDir(cwd), {
@@ -275,28 +302,36 @@ async function runRuleCommand(cwd, options) {
275
302
  }
276
303
  //#endregion
277
304
  //#region src/cli/commands/skill.ts
305
+ /** Multiselect sentinel: not a filesystem path. When selected, every skill is synced. */
306
+ const MULTISELECT_ALL_SKILLS = "__agents-sync:all-skills__";
278
307
  async function runSkillCommand(cwd, options) {
279
308
  const runtime = resolveOptions(await loadUserConfig(cwd, options.config), options);
280
309
  const skillDirs = await discoverSkills(runtime.source);
281
310
  if (skillDirs.length === 0) {
282
- printSkip(`No syncable skills found in ${runtime.source}.`);
311
+ printSkip(`No syncable skills found in ${c.dim(runtime.source)}.`);
283
312
  return;
284
313
  }
285
314
  let selected = skillDirs;
286
315
  if (!runtime.yes) {
316
+ const skillOptions = skillDirs.map((dir) => ({
317
+ label: path.basename(dir),
318
+ value: dir
319
+ }));
320
+ const options = skillDirs.length > 1 ? [{
321
+ label: "All skills",
322
+ value: MULTISELECT_ALL_SKILLS
323
+ }, ...skillOptions] : skillOptions;
287
324
  const picked = await p.multiselect({
288
- message: `Select ${c.green("skills")} to sync`,
289
- options: skillDirs.map((dir) => ({
290
- label: path.basename(dir),
291
- value: dir
292
- })),
325
+ message: `Select one or more ${c.green("skills")} to sync from ${c.dim(runtime.source)}`,
326
+ options,
293
327
  required: false
294
328
  });
295
329
  if (p.isCancel(picked)) {
296
330
  p.cancel("Operation canceled.");
297
331
  return;
298
332
  }
299
- selected = picked;
333
+ const values = picked;
334
+ selected = values.includes(MULTISELECT_ALL_SKILLS) ? skillDirs : values;
300
335
  }
301
336
  if (selected.length === 0) {
302
337
  printSkip("No skills selected.");
@@ -328,31 +363,31 @@ async function runSkillCommand(cwd, options) {
328
363
  //#endregion
329
364
  //#region package.json
330
365
  var name = "@tofrankie/agents-sync";
331
- var version = "0.0.1";
366
+ var version = "0.0.3";
332
367
  //#endregion
333
368
  //#region src/cli/index.ts
334
369
  main().catch((error) => {
335
- process$1.stdout.write("\n");
336
- process$1.stderr.write(`${String(error)}\n`);
337
- process$1.stdout.write("\n");
338
- process$1.exitCode = 1;
370
+ process.stdout.write("\n");
371
+ process.stderr.write(`${String(error)}\n`);
372
+ process.stdout.write("\n");
373
+ process.exitCode = 1;
339
374
  });
340
375
  async function main() {
341
376
  const program = new Command();
342
377
  program.name("agents-sync").description("Sync skills, cursor rules, and cursor mcp config from a source folder.").version(version, "-v, --version", "output the version number").hook("preAction", () => {
343
- process$1.stdout.write("\n");
378
+ process.stdout.write("\n");
344
379
  p.intro(`${name} ${c.dim(`v${version}`)}`);
345
380
  });
346
381
  applySharedOptions(program.command("skill").description("sync skills")).action(async (options) => {
347
- await runSkillCommand(process$1.cwd(), options);
382
+ await runSkillCommand(process.cwd(), options);
348
383
  });
349
384
  applySharedOptions(program.command("rule").description("sync rules (cursor only)")).action(async (options) => {
350
- await runRuleCommand(process$1.cwd(), options);
385
+ await runRuleCommand(process.cwd(), options);
351
386
  });
352
387
  applySharedOptions(program.command("mcp").description("sync mcp config (cursor only)")).action(async (options) => {
353
- await runMcpCommand(process$1.cwd(), options);
388
+ await runMcpCommand(process.cwd(), options);
354
389
  });
355
- await program.parseAsync(process$1.argv);
390
+ await program.parseAsync(process.argv);
356
391
  }
357
392
  function applySharedOptions(cmd) {
358
393
  return cmd.option("-s, --source <path>", "source path, default ~/.agents").option("-t, --target-agent <agent>", "target agent, default cursor").option("-c, --config <path>", "config file path").option("--dry-run", "show planned operations without writing files").option("-y, --yes", "skip prompts and overwrite conflicts");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tofrankie/agents-sync",
3
3
  "type": "module",
4
- "version": "0.0.1",
4
+ "version": "0.0.3",
5
5
  "description": "Sync skills, rules, and MCP from a configurable source into your project",
6
6
  "author": "Frankie <1426203851@qq.com> (https://github.com/tofrankie)",
7
7
  "license": "MIT",
@@ -83,6 +83,6 @@
83
83
  "typecheck": "tsc --noEmit",
84
84
  "test": "vitest run",
85
85
  "publint": "publint",
86
- "github:release": "tfr"
86
+ "github:release": "tfr --package @tofrankie/agents-sync"
87
87
  }
88
88
  }