oneagent 0.3.0 → 0.4.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.
Files changed (2) hide show
  1. package/dist/index.js +470 -85
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1387,6 +1387,9 @@ function timeAgo(date) {
1387
1387
  return `${months}mo ago`;
1388
1388
  return `${Math.floor(months / 12)}y ago`;
1389
1389
  }
1390
+
1391
+ // ../core/src/constants.ts
1392
+ var ONEAGENT_DIR = ".oneagent";
1390
1393
  // ../core/src/agents.ts
1391
1394
  var AGENT_DEFINITIONS;
1392
1395
  var init_agents = __esm(() => {
@@ -1436,7 +1439,7 @@ var init_agents = __esm(() => {
1436
1439
  target: "copilot",
1437
1440
  displayName: "GitHub Copilot",
1438
1441
  hint: ".github/instructions/*.instructions.md",
1439
- detectIndicators: [".github/copilot-instructions.md", ".github"],
1442
+ detectIndicators: [".github/copilot-instructions.md", ".github/instructions"],
1440
1443
  mainFile: ".github/copilot-instructions.md",
1441
1444
  skillsDir: ".github/skills"
1442
1445
  }
@@ -8415,9 +8418,10 @@ async function writeConfig(root, config) {
8415
8418
  await fs.mkdir(path.dirname(filePath), { recursive: true });
8416
8419
  await fs.writeFile(filePath, $stringify(config));
8417
8420
  }
8418
- var CONFIG_REL = ".oneagent/config.yml", ALL_AGENT_TARGETS;
8421
+ var CONFIG_REL, ALL_AGENT_TARGETS;
8419
8422
  var init_config = __esm(() => {
8420
8423
  init_dist4();
8424
+ CONFIG_REL = `${ONEAGENT_DIR}/config.yml`;
8421
8425
  ALL_AGENT_TARGETS = ["claude", "cursor", "windsurf", "opencode", "copilot"];
8422
8426
  });
8423
8427
 
@@ -8431,7 +8435,7 @@ async function readDetectedFile(root, rel) {
8431
8435
  if (stat.isSymbolicLink()) {
8432
8436
  const linkTarget = await fs2.readlink(absolutePath);
8433
8437
  const resolved = path2.resolve(path2.dirname(absolutePath), linkTarget);
8434
- if (resolved.startsWith(path2.join(root, ".one")))
8438
+ if (resolved.startsWith(path2.join(root, ONEAGENT_DIR)))
8435
8439
  return null;
8436
8440
  }
8437
8441
  const content = await fs2.readFile(absolutePath, "utf-8");
@@ -8483,7 +8487,7 @@ var init_detect = __esm(() => {
8483
8487
  import path3 from "path";
8484
8488
  import fs3 from "fs/promises";
8485
8489
  async function readRules(root) {
8486
- const rulesDir = path3.join(root, ".oneagent/rules");
8490
+ const rulesDir = path3.join(root, ONEAGENT_DIR, "rules");
8487
8491
  try {
8488
8492
  const files = await fs3.readdir(rulesDir);
8489
8493
  return files.filter((f) => f.endsWith(".md")).map((f) => ({ name: path3.basename(f, ".md"), path: path3.join(rulesDir, f) })).sort((a, b) => a.name.localeCompare(b.name));
@@ -8518,7 +8522,7 @@ async function readSkillFile(filePath) {
8518
8522
  return { name: path4.basename(filePath, ".md"), path: filePath, description, mode, content };
8519
8523
  }
8520
8524
  async function readSkills(root) {
8521
- const skillsDir = path4.join(root, ".oneagent/skills");
8525
+ const skillsDir = path4.join(root, ONEAGENT_DIR, "skills");
8522
8526
  try {
8523
8527
  const files = await fs4.readdir(skillsDir);
8524
8528
  const mdFiles = files.filter((f) => f.endsWith(".md"));
@@ -8542,15 +8546,26 @@ async function ensureDir(dirPath) {
8542
8546
  async function createSymlink(symlinkPath, target) {
8543
8547
  await ensureDir(path5.dirname(symlinkPath));
8544
8548
  try {
8545
- await fs5.rm(symlinkPath, { recursive: true });
8549
+ const stat = await fs5.lstat(symlinkPath);
8550
+ if (stat.isSymbolicLink() && await fs5.readlink(symlinkPath) === target)
8551
+ return;
8546
8552
  } catch {}
8547
- await fs5.symlink(target, symlinkPath);
8553
+ for (let attempt = 0;attempt < 3; attempt++) {
8554
+ await fs5.rm(symlinkPath, { recursive: true, force: true });
8555
+ try {
8556
+ await fs5.symlink(target, symlinkPath);
8557
+ return;
8558
+ } catch (err) {
8559
+ if (err.code !== "EEXIST" || attempt === 2)
8560
+ throw err;
8561
+ }
8562
+ }
8548
8563
  }
8549
8564
  function relativeTarget(symlinkPath, targetAbsPath) {
8550
8565
  return path5.relative(path5.dirname(symlinkPath), targetAbsPath);
8551
8566
  }
8552
8567
  function buildMainSymlinks(root, targets) {
8553
- const instructionsAbs = path5.join(root, ".oneagent/instructions.md");
8568
+ const instructionsAbs = path5.join(root, ONEAGENT_DIR, "instructions.md");
8554
8569
  const seen = new Map;
8555
8570
  for (const target of targets) {
8556
8571
  const def = AGENT_DEFINITIONS.find((d2) => d2.target === target);
@@ -8566,21 +8581,21 @@ function buildMainSymlinks(root, targets) {
8566
8581
  return Array.from(seen.values());
8567
8582
  }
8568
8583
  function buildRulesSymlinks(root, targets) {
8569
- const targetAbs = path5.join(root, ".oneagent/rules");
8584
+ const targetAbs = path5.join(root, ONEAGENT_DIR, "rules");
8570
8585
  return AGENT_DEFINITIONS.filter((d2) => targets.includes(d2.target) && d2.rulesDir).map((d2) => {
8571
8586
  const symlinkPath = path5.join(root, d2.rulesDir);
8572
8587
  return { symlinkPath, target: relativeTarget(symlinkPath, targetAbs), label: d2.rulesDir };
8573
8588
  });
8574
8589
  }
8575
8590
  function buildSkillSymlinks(root, targets) {
8576
- const targetAbs = path5.join(root, ".oneagent/skills");
8591
+ const targetAbs = path5.join(root, ONEAGENT_DIR, "skills");
8577
8592
  return AGENT_DEFINITIONS.filter((d2) => targets.includes(d2.target) && d2.skillsDir).map((d2) => {
8578
8593
  const symlinkPath = path5.join(root, d2.skillsDir);
8579
8594
  return { symlinkPath, target: relativeTarget(symlinkPath, targetAbs), label: d2.skillsDir };
8580
8595
  });
8581
8596
  }
8582
8597
  function buildCommandSymlinks(root, targets) {
8583
- const targetAbs = path5.join(root, ".oneagent/commands");
8598
+ const targetAbs = path5.join(root, ONEAGENT_DIR, "commands");
8584
8599
  return AGENT_DEFINITIONS.filter((d2) => targets.includes(d2.target) && d2.commandsDir).map((d2) => {
8585
8600
  const symlinkPath = path5.join(root, d2.commandsDir);
8586
8601
  return { symlinkPath, target: relativeTarget(symlinkPath, targetAbs), label: d2.commandsDir };
@@ -8588,7 +8603,7 @@ function buildCommandSymlinks(root, targets) {
8588
8603
  }
8589
8604
  function buildAgentsDirSymlinks(root) {
8590
8605
  const symlinkPath = path5.join(root, ".agents/skills");
8591
- const targetAbs = path5.join(root, ".oneagent/skills");
8606
+ const targetAbs = path5.join(root, ONEAGENT_DIR, "skills");
8592
8607
  return [{ symlinkPath, target: relativeTarget(symlinkPath, targetAbs), label: ".agents/skills" }];
8593
8608
  }
8594
8609
  async function migrateFilesFromDir(srcDir, destDir, root) {
@@ -8600,9 +8615,22 @@ async function migrateFilesFromDir(srcDir, destDir, root) {
8600
8615
  return;
8601
8616
  }
8602
8617
  for (const entry of entries) {
8603
- if (!entry.isFile())
8604
- continue;
8605
8618
  const srcFile = path5.join(srcDir, entry.name);
8619
+ const fileStat = await fs5.lstat(srcFile);
8620
+ if (fileStat.isSymbolicLink())
8621
+ continue;
8622
+ if (fileStat.isDirectory()) {
8623
+ const destSub = path5.join(destDir, entry.name);
8624
+ try {
8625
+ await fs5.access(destSub);
8626
+ } catch {
8627
+ await fs5.mkdir(destDir, { recursive: true });
8628
+ await fs5.rename(srcFile, destSub);
8629
+ }
8630
+ continue;
8631
+ }
8632
+ if (!fileStat.isFile())
8633
+ continue;
8606
8634
  const destFile = path5.join(destDir, entry.name);
8607
8635
  let destExists = false;
8608
8636
  try {
@@ -8615,7 +8643,7 @@ async function migrateFilesFromDir(srcDir, destDir, root) {
8615
8643
  fs5.readFile(destFile, "utf-8")
8616
8644
  ]);
8617
8645
  if (srcContent !== destContent) {
8618
- const backupDir = path5.join(root, ".oneagent/backup");
8646
+ const backupDir = path5.join(root, ONEAGENT_DIR, "backup");
8619
8647
  await fs5.mkdir(backupDir, { recursive: true });
8620
8648
  const safeName = path5.relative(root, srcFile).replace(/\//g, "_");
8621
8649
  await fs5.writeFile(path5.join(backupDir, safeName), srcContent);
@@ -8639,21 +8667,75 @@ async function migrateAndRemoveDir(src, dest, root) {
8639
8667
  await fs5.rm(src, { recursive: true, force: true });
8640
8668
  }
8641
8669
  async function migrateRuleAndSkillFiles(root) {
8642
- const destRules = path5.join(root, ".oneagent/rules");
8643
- const destSkills = path5.join(root, ".oneagent/skills");
8644
- const destCommands = path5.join(root, ".oneagent/commands");
8670
+ const destRules = path5.join(root, ONEAGENT_DIR, "rules");
8671
+ const destSkills = path5.join(root, ONEAGENT_DIR, "skills");
8672
+ const destCommands = path5.join(root, ONEAGENT_DIR, "commands");
8645
8673
  for (const def of AGENT_DEFINITIONS) {
8646
8674
  if (def.rulesDir)
8647
8675
  await migrateAndRemoveDir(path5.join(root, def.rulesDir), destRules, root);
8648
8676
  }
8677
+ for (const def of AGENT_DEFINITIONS) {
8678
+ if (def.skillsDir)
8679
+ await migrateAndRemoveDir(path5.join(root, def.skillsDir), destSkills, root);
8680
+ }
8649
8681
  await migrateAndRemoveDir(path5.join(root, ".agents/skills"), destSkills, root);
8650
8682
  for (const def of AGENT_DEFINITIONS) {
8651
8683
  if (def.commandsDir)
8652
8684
  await migrateAndRemoveDir(path5.join(root, def.commandsDir), destCommands, root);
8653
8685
  }
8654
8686
  }
8687
+ async function backupDirRecursive(srcDir, backupDir, prefix) {
8688
+ let entries;
8689
+ try {
8690
+ entries = await fs5.readdir(srcDir, { withFileTypes: true });
8691
+ } catch {
8692
+ return;
8693
+ }
8694
+ for (const entry of entries) {
8695
+ const srcPath = path5.join(srcDir, entry.name);
8696
+ const lstat = await fs5.lstat(srcPath);
8697
+ if (lstat.isSymbolicLink())
8698
+ continue;
8699
+ if (lstat.isDirectory()) {
8700
+ await backupDirRecursive(srcPath, backupDir, `${prefix}_${entry.name}`);
8701
+ } else if (lstat.isFile()) {
8702
+ await fs5.mkdir(backupDir, { recursive: true });
8703
+ await fs5.copyFile(srcPath, path5.join(backupDir, `${prefix}_${entry.name}`));
8704
+ }
8705
+ }
8706
+ }
8707
+ async function cleanupAgentDir(root, target) {
8708
+ const def = AGENT_DEFINITIONS.find((d2) => d2.target === target);
8709
+ const backupDir = path5.join(root, ONEAGENT_DIR, "backup");
8710
+ const agentDir = [def.rulesDir, def.skillsDir, def.commandsDir].filter(Boolean).map((d2) => d2.split("/")[0]).find((d2) => d2 !== ".github");
8711
+ if (agentDir) {
8712
+ const agentDirAbs = path5.join(root, agentDir);
8713
+ let stat;
8714
+ try {
8715
+ stat = await fs5.lstat(agentDirAbs);
8716
+ } catch {}
8717
+ if (stat && stat.isDirectory() && !stat.isSymbolicLink()) {
8718
+ await backupDirRecursive(agentDirAbs, backupDir, agentDir);
8719
+ await fs5.rm(agentDirAbs, { recursive: true, force: true });
8720
+ }
8721
+ }
8722
+ if (target === "opencode") {
8723
+ const opPath = path5.join(root, "opencode.json");
8724
+ try {
8725
+ const content = await fs5.readFile(opPath, "utf-8");
8726
+ await fs5.mkdir(backupDir, { recursive: true });
8727
+ await fs5.writeFile(path5.join(backupDir, "opencode.json"), content);
8728
+ } catch {}
8729
+ try {
8730
+ await fs5.unlink(opPath);
8731
+ } catch {}
8732
+ }
8733
+ }
8655
8734
  async function createAllSymlinks(entries) {
8656
- for (const e2 of entries) {
8735
+ const deduped = new Map;
8736
+ for (const e2 of entries)
8737
+ deduped.set(e2.symlinkPath, e2);
8738
+ for (const e2 of deduped.values()) {
8657
8739
  await createSymlink(e2.symlinkPath, e2.target);
8658
8740
  }
8659
8741
  }
@@ -8722,7 +8804,7 @@ async function readOpencode(root) {
8722
8804
  function buildOpencodeConfig(existing) {
8723
8805
  return {
8724
8806
  ...existing,
8725
- instructions: ".oneagent/instructions.md"
8807
+ instructions: `${ONEAGENT_DIR}/instructions.md`
8726
8808
  };
8727
8809
  }
8728
8810
  async function addOpenCodePlugin(root, id) {
@@ -8846,7 +8928,7 @@ async function checkOpencodeStatus(root, _rules) {
8846
8928
  const existing = await readOpencode(root);
8847
8929
  if (!existing)
8848
8930
  return { exists: false, valid: false };
8849
- return { exists: true, valid: existing["instructions"] === ".oneagent/instructions.md" };
8931
+ return { exists: true, valid: existing["instructions"] === `${ONEAGENT_DIR}/instructions.md` };
8850
8932
  }
8851
8933
  async function checkCopilotPrompt(root, skill) {
8852
8934
  const filePath = copilotPromptFilePath(root, skill.name);
@@ -8895,9 +8977,11 @@ function parseTemplateYaml(yamlText, fallbackName = "custom") {
8895
8977
  const name = nameMatch?.[1]?.trim() ?? fallbackName;
8896
8978
  const descMatch = yamlText.match(/^description:\s*(.+)$/m);
8897
8979
  const description = descMatch?.[1]?.trim() ?? "";
8980
+ const extendsMatch = yamlText.match(/^extends:\s*(.+)$/m);
8981
+ const extendsValue = extendsMatch?.[1]?.trim();
8898
8982
  const skills = parseSkillsFromYaml(yamlText);
8899
8983
  const plugins = parsePluginsFromYaml(yamlText);
8900
- return { name, description, skills, plugins };
8984
+ return { name, description, skills, plugins, ...extendsValue ? { extends: extendsValue } : {} };
8901
8985
  }
8902
8986
  function parseSkillsFromYaml(yamlText) {
8903
8987
  const skills = [];
@@ -8934,8 +9018,31 @@ function parsePluginsFromYaml(yamlText) {
8934
9018
  }
8935
9019
  return plugins;
8936
9020
  }
9021
+ async function resolveExtends(child, resolveBuiltin) {
9022
+ if (!child.extends)
9023
+ return child;
9024
+ let parent;
9025
+ if (child.extends.startsWith("https://")) {
9026
+ parent = await fetchTemplateFromGitHub(child.extends);
9027
+ } else if (resolveBuiltin) {
9028
+ const resolved = await resolveBuiltin(child.extends);
9029
+ if (!resolved)
9030
+ throw new Error(`Unknown builtin template: "${child.extends}"`);
9031
+ parent = resolved;
9032
+ } else {
9033
+ throw new Error(`Cannot resolve extends: "${child.extends}"`);
9034
+ }
9035
+ return {
9036
+ name: child.name,
9037
+ description: child.description,
9038
+ instructions: child.instructions,
9039
+ skills: [...parent.skills, ...child.skills],
9040
+ plugins: [...parent.plugins, ...child.plugins],
9041
+ rules: [...parent.rules, ...child.rules]
9042
+ };
9043
+ }
8937
9044
  async function applyTemplateFiles(root, template) {
8938
- const oneagentDir = path9.join(root, ".oneagent");
9045
+ const oneagentDir = path9.join(root, ONEAGENT_DIR);
8939
9046
  await fs10.mkdir(path9.join(oneagentDir, "rules"), { recursive: true });
8940
9047
  await fs10.mkdir(path9.join(oneagentDir, "skills"), { recursive: true });
8941
9048
  await fs10.writeFile(path9.join(oneagentDir, "instructions.md"), template.instructions);
@@ -8944,19 +9051,26 @@ async function applyTemplateFiles(root, template) {
8944
9051
  }
8945
9052
  }
8946
9053
  async function installTemplateSkills(root, template) {
8947
- const results = await Promise.all(template.skills.map(async (entry) => {
9054
+ const installed = [];
9055
+ const failed = [];
9056
+ for (const entry of template.skills) {
8948
9057
  try {
8949
9058
  await execFileAsync("npx", ["skills", "add", entry.repo, "--skill", entry.skill, "--agent", "universal", "--yes"], { cwd: root });
8950
- return { entry, ok: true };
9059
+ installed.push(entry);
8951
9060
  } catch (err) {
8952
9061
  const reason = err instanceof Error ? err.message : String(err);
8953
- return { entry, ok: false, reason };
9062
+ failed.push({ entry, reason });
8954
9063
  }
8955
- }));
8956
- return {
8957
- installed: results.filter((r) => r.ok).map((r) => r.entry),
8958
- failed: results.filter((r) => !r.ok).map((r) => ({ entry: r.entry, reason: r.reason }))
8959
- };
9064
+ }
9065
+ return { installed, failed };
9066
+ }
9067
+ async function installBuiltinSkill(root) {
9068
+ try {
9069
+ await execFileAsync("npx", ["skills", "add", BUILTIN_SKILL_REPO, "--skill", BUILTIN_SKILL_NAME, "--agent", "universal", "--yes"], { cwd: root });
9070
+ return true;
9071
+ } catch {
9072
+ return false;
9073
+ }
8960
9074
  }
8961
9075
  async function installTemplatePlugins(root, template, activeTargets2) {
8962
9076
  const installed = [];
@@ -8993,14 +9107,31 @@ async function installTemplatePlugins(root, template, activeTargets2) {
8993
9107
  return { installed, manual, failed };
8994
9108
  }
8995
9109
  async function fetchTemplateFromGitHub(url) {
8996
- const rawBase = githubUrlToRawBase(url);
9110
+ const { owner, repo, branch, subdir } = parseGitHubUrl(url);
9111
+ const branchExplicit = url.includes("/tree/");
9112
+ const resolvedBranch = branchExplicit ? branch : await fetchDefaultBranch(owner, repo);
9113
+ const rawBase = `https://raw.githubusercontent.com/${owner}/${repo}/${resolvedBranch}${subdir ? `/${subdir}` : ""}`;
8997
9114
  const [yamlText, instructions] = await Promise.all([
8998
9115
  fetchText(`${rawBase}/template.yml`),
8999
9116
  fetchText(`${rawBase}/instructions.md`)
9000
9117
  ]);
9001
- const { name, description, skills, plugins } = parseTemplateYaml(yamlText);
9118
+ const parsed = parseTemplateYaml(yamlText);
9002
9119
  const rules = await fetchGitHubRules(url);
9003
- return { name, description, skills, plugins, instructions, rules };
9120
+ const base = { ...parsed, instructions, rules };
9121
+ return resolveExtends(base);
9122
+ }
9123
+ async function fetchDefaultBranch(owner, repo) {
9124
+ try {
9125
+ const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
9126
+ headers: { Accept: "application/vnd.github.v3+json" }
9127
+ });
9128
+ if (!response.ok)
9129
+ return "main";
9130
+ const data = await response.json();
9131
+ return data.default_branch ?? "main";
9132
+ } catch {
9133
+ return "main";
9134
+ }
9004
9135
  }
9005
9136
  async function fetchText(url) {
9006
9137
  const response = await fetch(url);
@@ -9017,11 +9148,6 @@ function parseGitHubUrl(url) {
9017
9148
  const [, owner, repo, branch = "main", subdir = ""] = match;
9018
9149
  return { owner, repo, branch, subdir };
9019
9150
  }
9020
- function githubUrlToRawBase(url) {
9021
- const { owner, repo, branch, subdir } = parseGitHubUrl(url);
9022
- const base = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}`;
9023
- return subdir ? `${base}/${subdir}` : base;
9024
- }
9025
9151
  async function fetchGitHubRules(repoUrl) {
9026
9152
  const { owner, repo, branch, subdir } = parseGitHubUrl(repoUrl);
9027
9153
  const rulesPath = subdir ? `${subdir}/rules` : "rules";
@@ -9043,7 +9169,7 @@ async function fetchGitHubRules(repoUrl) {
9043
9169
  return [];
9044
9170
  }
9045
9171
  }
9046
- var execFileAsync;
9172
+ var execFileAsync, BUILTIN_SKILL_REPO = "https://github.com/moskalakamil/oneagent", BUILTIN_SKILL_NAME = "oneagent";
9047
9173
  var init_apply_template = __esm(() => {
9048
9174
  init_opencode();
9049
9175
  execFileAsync = promisify(execFile);
@@ -9075,7 +9201,7 @@ async function loadTemplate(name) {
9075
9201
  fs11.readFile(path10.join(templateDir, "template.yml"), "utf-8"),
9076
9202
  fs11.readFile(path10.join(templateDir, "instructions.md"), "utf-8")
9077
9203
  ]);
9078
- const { description, skills: skills2, plugins } = parseTemplateYaml(yamlText, name);
9204
+ const parsed = parseTemplateYaml(yamlText, name);
9079
9205
  const rulesDir = path10.join(templateDir, "rules");
9080
9206
  let rules2 = [];
9081
9207
  try {
@@ -9085,7 +9211,8 @@ async function loadTemplate(name) {
9085
9211
  content: await fs11.readFile(path10.join(rulesDir, f), "utf-8")
9086
9212
  })));
9087
9213
  } catch {}
9088
- return { name, description, skills: skills2, plugins, instructions, rules: rules2 };
9214
+ const base = { ...parsed, instructions, rules: rules2 };
9215
+ return resolveExtends(base, (n) => resolveBuiltinTemplate(n));
9089
9216
  }
9090
9217
  async function resolveBuiltinTemplate(name) {
9091
9218
  if (!TEMPLATE_NAMES.includes(name))
@@ -9121,20 +9248,12 @@ async function chooseContent(detected) {
9121
9248
  return "";
9122
9249
  if (detected.length === 1) {
9123
9250
  const file = detected[0];
9124
- const result2 = await Re({
9125
- message: `Found ${file.relativePath} (${timeAgo(file.modifiedAt)}). Import its content into .oneagent/instructions.md?`
9126
- });
9127
- if (Ct(result2))
9128
- cancelAndExit();
9129
- return result2 ? file.content : "";
9251
+ R2.info(`Found ${file.relativePath} (${timeAgo(file.modifiedAt)}) importing into ${ONEAGENT_DIR}/instructions.md`);
9252
+ return file.content;
9130
9253
  }
9131
9254
  if (filesHaveSameContent(detected)) {
9132
- const result2 = await Re({
9133
- message: `Found ${detected.length} files with identical content. Import?`
9134
- });
9135
- if (Ct(result2))
9136
- cancelAndExit();
9137
- return result2 ? detected[0].content : "";
9255
+ R2.info(`Found ${detected.length} files with identical content — importing into ${ONEAGENT_DIR}/instructions.md`);
9256
+ return detected[0].content;
9138
9257
  }
9139
9258
  Ve(detected.map((f) => ` • ${f.relativePath} ${timeAgo(f.modifiedAt)}`).join(`
9140
9259
  `), "Multiple files with different content found");
@@ -9190,13 +9309,19 @@ async function pickTargets(initialValues) {
9190
9309
  async function backupFiles(root, files) {
9191
9310
  if (files.length === 0)
9192
9311
  return;
9193
- const backupDir = path11.join(root, ".oneagent/backup");
9312
+ const backupDir = path11.join(root, ONEAGENT_DIR, "backup");
9194
9313
  await fs12.mkdir(backupDir, { recursive: true });
9195
9314
  for (const file of files) {
9196
9315
  const safeName = file.relativePath.replace(/\//g, "_");
9197
9316
  await fs12.writeFile(path11.join(backupDir, safeName), file.content);
9198
9317
  }
9199
9318
  }
9319
+ async function cleanupUnselectedAgentDirs(root, presentTargets, selectedTargets) {
9320
+ const unselected = presentTargets.filter((t) => !selectedTargets.includes(t));
9321
+ for (const target of unselected) {
9322
+ await cleanupAgentDir(root, target);
9323
+ }
9324
+ }
9200
9325
  async function pickTemplateInteractively() {
9201
9326
  const result = await Je({
9202
9327
  message: "Which template would you like to use?",
@@ -9232,13 +9357,12 @@ async function resolveTemplate(templateArg) {
9232
9357
  return builtin;
9233
9358
  throw new Error(`Unknown template "${templateArg}". Use one of: ${BUILTIN_TEMPLATE_NAMES.join(", ")} — or a GitHub URL.`);
9234
9359
  }
9235
- var ABOUT_ONEAGENT_RULE_PATH, init_default;
9360
+ var init_default;
9236
9361
  var init_init = __esm(() => {
9237
9362
  init_dist();
9238
9363
  init_dist3();
9239
9364
  init_src();
9240
9365
  init_src2();
9241
- ABOUT_ONEAGENT_RULE_PATH = new URL("../assets/about-oneagent.md", import.meta.url);
9242
9366
  init_default = defineCommand2({
9243
9367
  meta: {
9244
9368
  name: "init",
@@ -9248,6 +9372,12 @@ var init_init = __esm(() => {
9248
9372
  template: {
9249
9373
  type: "string",
9250
9374
  description: `Template to use: builtin name (${BUILTIN_TEMPLATE_NAMES.join("/")}) or GitHub URL`
9375
+ },
9376
+ yes: {
9377
+ type: "boolean",
9378
+ alias: "y",
9379
+ description: "Skip prompts: auto-import detected files and select detected agents",
9380
+ default: false
9251
9381
  }
9252
9382
  },
9253
9383
  async run({ args }) {
@@ -9276,19 +9406,26 @@ var init_init = __esm(() => {
9276
9406
  Ve(message, "Error");
9277
9407
  process.exit(1);
9278
9408
  }
9409
+ } else if (args.yes) {
9410
+ if (detected.length > 0) {
9411
+ const newest = detected.toSorted((a, b) => b.modifiedAt.getTime() - a.modifiedAt.getTime())[0];
9412
+ importedContent = newest.content;
9413
+ R2.info(`Auto-importing content from ${newest.relativePath}`);
9414
+ }
9279
9415
  } else {
9280
9416
  importedContent = await chooseContent(detected);
9281
9417
  }
9282
9418
  const presentTargets = await detectPresentTargets(root);
9283
- const selectedTargets = await pickTargets(presentTargets);
9419
+ const selectedTargets = args.yes ? presentTargets.length > 0 ? presentTargets : ALL_AGENT_TARGETS.slice(0, 1) : await pickTargets(presentTargets);
9284
9420
  const s = bt2();
9285
- s.start("Setting up .oneagent/ directory...");
9286
- await fs12.mkdir(path11.join(root, ".oneagent/rules"), { recursive: true });
9287
- await fs12.mkdir(path11.join(root, ".oneagent/skills"), { recursive: true });
9288
- await fs12.mkdir(path11.join(root, ".oneagent/commands"), { recursive: true });
9421
+ s.start(`Setting up ${ONEAGENT_DIR}/ directory...`);
9422
+ await fs12.mkdir(path11.join(root, ONEAGENT_DIR, "rules"), { recursive: true });
9423
+ await fs12.mkdir(path11.join(root, ONEAGENT_DIR, "skills"), { recursive: true });
9424
+ await fs12.mkdir(path11.join(root, ONEAGENT_DIR, "commands"), { recursive: true });
9289
9425
  await backupFiles(root, detected);
9290
9426
  await removeDeprecatedFiles(root);
9291
9427
  await migrateRuleAndSkillFiles(root);
9428
+ await cleanupUnselectedAgentDirs(root, presentTargets, selectedTargets);
9292
9429
  const config2 = { version: 1, targets: makeTargets(...selectedTargets) };
9293
9430
  await writeConfig(root, config2);
9294
9431
  if (template) {
@@ -9298,30 +9435,37 @@ var init_init = __esm(() => {
9298
9435
 
9299
9436
  Add your AI instructions here.
9300
9437
  `;
9301
- await fs12.writeFile(path11.join(root, ".oneagent/instructions.md"), instructionsContent);
9438
+ await fs12.writeFile(path11.join(root, ONEAGENT_DIR, "instructions.md"), instructionsContent);
9302
9439
  }
9303
- await fs12.copyFile(ABOUT_ONEAGENT_RULE_PATH, path11.join(root, ".oneagent/rules/about-oneagent.md"));
9304
9440
  s.stop("Directory structure created.");
9305
- const commandFiles = await fs12.readdir(path11.join(root, ".oneagent/commands")).catch(() => []);
9441
+ const commandFiles = await fs12.readdir(path11.join(root, ONEAGENT_DIR, "commands")).catch(() => []);
9306
9442
  if (commandFiles.some((f) => f.endsWith(".md"))) {
9307
- R2.warn("Commands detected in .oneagent/commands/. Consider migrating to .oneagent/skills/ — skills are distributed to more agents and support richer features.");
9443
+ R2.warn(`Commands detected in ${ONEAGENT_DIR}/commands/. Consider migrating to ${ONEAGENT_DIR}/skills/ — skills are distributed to more agents and support richer features.`);
9308
9444
  }
9309
9445
  if (commandFiles.some((f) => f.endsWith(".md"))) {
9310
9446
  const commandsSupported = new Set(AGENT_DEFINITIONS.filter((d2) => d2.commandsDir).map((d2) => d2.target));
9311
9447
  const unsupported = selectedTargets.filter((t) => !commandsSupported.has(t));
9312
9448
  if (unsupported.length > 0) {
9313
9449
  const names = unsupported.map((t) => AGENT_DEFINITIONS.find((d2) => d2.target === t).displayName).join(", ");
9314
- R2.warn(`Commands in .oneagent/commands/ will not be available in: ${names} — these agents do not support custom slash commands.`);
9450
+ R2.warn(`Commands in ${ONEAGENT_DIR}/commands/ will not be available in: ${names} — these agents do not support custom slash commands.`);
9315
9451
  }
9316
9452
  }
9317
9453
  const s2 = bt2();
9318
9454
  s2.start("Generating symlinks and agent files...");
9319
9455
  await generate(root, config2);
9320
9456
  s2.stop("Done.");
9457
+ const sSkill = bt2();
9458
+ sSkill.start("Installing oneagent skill...");
9459
+ const builtinInstalled = await installBuiltinSkill(root);
9460
+ if (builtinInstalled) {
9461
+ sSkill.stop("Installed oneagent skill.");
9462
+ } else {
9463
+ sSkill.stop("Could not install oneagent skill (skipped).");
9464
+ }
9321
9465
  let skillResult = { installed: [], failed: [] };
9322
9466
  if (template && template.skills.length > 0) {
9323
9467
  const s3 = bt2();
9324
- s3.start("Installing skills...");
9468
+ s3.start("Installing template skills...");
9325
9469
  skillResult = await installTemplateSkills(root, template);
9326
9470
  s3.stop(`Installed ${skillResult.installed.length} skill(s).`);
9327
9471
  for (const f of skillResult.failed) {
@@ -9344,10 +9488,10 @@ Add your AI instructions here.
9344
9488
  ...skillResult.installed.length > 0 ? [`Installed ${skillResult.installed.length} skill(s): ${skillResult.installed.map((s3) => s3.skill).join(", ")}`] : [],
9345
9489
  ...template.rules.length > 0 ? [`Added ${template.rules.length} rule(s) from template`] : [],
9346
9490
  ...pluginResult.installed.length > 0 ? [`Installed ${pluginResult.installed.length} plugin(s): ${pluginResult.installed.map((p) => p.id).join(", ")}`] : []
9347
- ] : ["Created .oneagent/instructions.md"],
9348
- "Created .oneagent/rules/about-oneagent.md",
9491
+ ] : [`Created ${ONEAGENT_DIR}/instructions.md`],
9492
+ ...builtinInstalled ? [`Installed oneagent skill`] : [],
9349
9493
  ...selectedTargets.map((t) => `Configured: ${t}`),
9350
- ...detected.length > 0 ? [`Backed up ${detected.length} file(s) to .oneagent/backup/`] : []
9494
+ ...detected.length > 0 ? [`Backed up ${detected.length} file(s) to ${ONEAGENT_DIR}/backup/`] : []
9351
9495
  ];
9352
9496
  Ve(lines.map((l) => ` • ${l}`).join(`
9353
9497
  `), "Setup complete");
@@ -9380,18 +9524,25 @@ var init_generate2 = __esm(() => {
9380
9524
  name: "generate",
9381
9525
  description: "Generate symlinks and agent-specific files"
9382
9526
  },
9383
- async run() {
9527
+ args: {
9528
+ yes: {
9529
+ type: "boolean",
9530
+ alias: "y",
9531
+ description: "Skip confirmation prompts and auto-migrate colliding files"
9532
+ }
9533
+ },
9534
+ async run({ args }) {
9384
9535
  const root = process.cwd();
9385
9536
  let config2;
9386
9537
  try {
9387
9538
  config2 = await readConfig(root);
9388
9539
  } catch {
9389
- console.error("Error: No .oneagent/config.yml found. Run `oneagent init` first.");
9540
+ console.error(`Error: No ${ONEAGENT_DIR}/config.yml found. Run \`oneagent init\` first.`);
9390
9541
  process.exit(1);
9391
9542
  }
9392
9543
  const { mainFiles, ruleSkillFiles } = await detectGenerateCollisions(root, config2);
9393
9544
  if (mainFiles.length > 0) {
9394
- const backupDir = path12.join(root, ".oneagent/backup");
9545
+ const backupDir = path12.join(root, ONEAGENT_DIR, "backup");
9395
9546
  await fs13.mkdir(backupDir, { recursive: true });
9396
9547
  for (const file of mainFiles) {
9397
9548
  const safeName = file.relativePath.replace(/\//g, "_");
@@ -9399,16 +9550,20 @@ var init_generate2 = __esm(() => {
9399
9550
  }
9400
9551
  }
9401
9552
  if (ruleSkillFiles.length > 0) {
9402
- Ve(ruleSkillFiles.map((f) => ` • ${f.relativePath}`).join(`
9403
- `), "These rule/skill files are not dotai symlinks");
9404
- const proceed = await Re({
9405
- message: "Move them to .oneagent/ and replace with symlinks?"
9406
- });
9407
- if (Ct(proceed) || !proceed) {
9408
- Le("Aborted.");
9409
- process.exit(0);
9553
+ if (args.yes) {
9554
+ await migrateRuleAndSkillFiles(root);
9555
+ } else {
9556
+ Ve(ruleSkillFiles.map((f) => ` • ${f.relativePath}`).join(`
9557
+ `), "These rule/skill files are not oneagent symlinks");
9558
+ const proceed = await Re({
9559
+ message: `Move them to ${ONEAGENT_DIR}/ and replace with symlinks?`
9560
+ });
9561
+ if (Ct(proceed) || !proceed) {
9562
+ Le("Aborted.");
9563
+ process.exit(0);
9564
+ }
9565
+ await migrateRuleAndSkillFiles(root);
9410
9566
  }
9411
- await migrateRuleAndSkillFiles(root);
9412
9567
  }
9413
9568
  const s = bt2();
9414
9569
  s.start("Generating...");
@@ -9444,7 +9599,7 @@ var init_status2 = __esm(() => {
9444
9599
  try {
9445
9600
  config2 = await readConfig(root);
9446
9601
  } catch {
9447
- console.error("Error: No .oneagent/config.yml found. Run `oneagent init` first.");
9602
+ console.error(`Error: No ${ONEAGENT_DIR}/config.yml found. Run \`oneagent init\` first.`);
9448
9603
  process.exit(1);
9449
9604
  }
9450
9605
  const status2 = await checkStatus(root, config2);
@@ -9477,6 +9632,233 @@ OpenCode:
9477
9632
  });
9478
9633
  });
9479
9634
 
9635
+ // src/commands/targets.ts
9636
+ var exports_targets = {};
9637
+ __export(exports_targets, {
9638
+ default: () => targets_default
9639
+ });
9640
+ import path13 from "path";
9641
+ import fs14 from "fs/promises";
9642
+ function cancelAndExit2() {
9643
+ Le("Cancelled.");
9644
+ process.exit(0);
9645
+ }
9646
+ async function detectPresentTargets2(root) {
9647
+ const results = await Promise.all(AGENT_DEFINITIONS.map(async (def) => {
9648
+ for (const indicator of def.detectIndicators) {
9649
+ try {
9650
+ await fs14.access(path13.join(root, indicator));
9651
+ return def.target;
9652
+ } catch {}
9653
+ }
9654
+ return null;
9655
+ }));
9656
+ return results.filter((t) => t !== null);
9657
+ }
9658
+ var targets_default;
9659
+ var init_targets = __esm(() => {
9660
+ init_dist();
9661
+ init_dist3();
9662
+ init_src();
9663
+ targets_default = defineCommand2({
9664
+ meta: {
9665
+ name: "targets",
9666
+ description: "Add or remove AI agent targets"
9667
+ },
9668
+ async run() {
9669
+ const root = process.cwd();
9670
+ let config2;
9671
+ try {
9672
+ config2 = await readConfig(root);
9673
+ } catch {
9674
+ console.error(`Error: No ${ONEAGENT_DIR}/config.yml found. Run \`oneagent init\` first.`);
9675
+ process.exit(1);
9676
+ }
9677
+ const current = activeTargets(config2);
9678
+ const presentTargets = await detectPresentTargets2(root);
9679
+ const initialValues = [...new Set([...current, ...presentTargets])];
9680
+ Ve(current.map((t) => ` • ${AGENT_DEFINITIONS.find((d2) => d2.target === t).displayName}`).join(`
9681
+ `), "Currently configured targets");
9682
+ const result = await je({
9683
+ message: `Which AI agents do you want to support?
9684
+ \x1B[90m · Space to toggle · Enter to confirm\x1B[39m`,
9685
+ options: AGENT_DEFINITIONS.map((d2) => ({ value: d2.target, label: d2.displayName, hint: d2.hint })),
9686
+ initialValues,
9687
+ required: true
9688
+ });
9689
+ if (Ct(result))
9690
+ cancelAndExit2();
9691
+ const selected = result;
9692
+ const removed = current.filter((t) => !selected.includes(t));
9693
+ const added = selected.filter((t) => !current.includes(t));
9694
+ if (removed.length === 0 && added.length === 0) {
9695
+ Le("No changes.");
9696
+ return;
9697
+ }
9698
+ const s = bt2();
9699
+ s.start("Updating targets...");
9700
+ for (const target of removed) {
9701
+ await cleanupAgentDir(root, target);
9702
+ }
9703
+ config2.targets = makeTargets(...selected);
9704
+ await writeConfig(root, config2);
9705
+ await generate(root, config2);
9706
+ s.stop("Done.");
9707
+ const lines = [
9708
+ ...added.length > 0 ? [`Added: ${added.map((t) => AGENT_DEFINITIONS.find((d2) => d2.target === t).displayName).join(", ")}`] : [],
9709
+ ...removed.length > 0 ? [`Removed: ${removed.map((t) => AGENT_DEFINITIONS.find((d2) => d2.target === t).displayName).join(", ")}`] : []
9710
+ ];
9711
+ Ve(lines.map((l) => ` • ${l}`).join(`
9712
+ `), "Targets updated");
9713
+ if (removed.length > 0) {
9714
+ R2.info(`Removed agent files backed up to ${ONEAGENT_DIR}/backup/`);
9715
+ }
9716
+ Le("Run `oneagent status` to verify your setup.");
9717
+ }
9718
+ });
9719
+ });
9720
+
9721
+ // src/commands/add.ts
9722
+ var exports_add = {};
9723
+ __export(exports_add, {
9724
+ default: () => add_default
9725
+ });
9726
+ var add_default;
9727
+ var init_add = __esm(() => {
9728
+ init_dist();
9729
+ init_dist3();
9730
+ init_src();
9731
+ add_default = defineCommand2({
9732
+ meta: {
9733
+ name: "add",
9734
+ description: "Add an AI agent target"
9735
+ },
9736
+ args: {
9737
+ target: {
9738
+ type: "positional",
9739
+ description: "Agent to add (claude, cursor, windsurf, opencode, copilot)",
9740
+ required: false
9741
+ }
9742
+ },
9743
+ async run({ args }) {
9744
+ const root = process.cwd();
9745
+ let config2;
9746
+ try {
9747
+ config2 = await readConfig(root);
9748
+ } catch {
9749
+ console.error(`Error: No ${ONEAGENT_DIR}/config.yml found. Run \`oneagent init\` first.`);
9750
+ process.exit(1);
9751
+ }
9752
+ const current = activeTargets(config2);
9753
+ const available = ALL_AGENT_TARGETS.filter((t) => !current.includes(t));
9754
+ if (available.length === 0) {
9755
+ R2.info("All agents are already configured.");
9756
+ return;
9757
+ }
9758
+ let target;
9759
+ if (args.target) {
9760
+ if (!ALL_AGENT_TARGETS.includes(args.target)) {
9761
+ console.error(`Unknown target "${args.target}". Available: ${ALL_AGENT_TARGETS.join(", ")}`);
9762
+ process.exit(1);
9763
+ }
9764
+ target = args.target;
9765
+ if (current.includes(target)) {
9766
+ R2.info(`${AGENT_DEFINITIONS.find((d2) => d2.target === target).displayName} is already configured.`);
9767
+ return;
9768
+ }
9769
+ } else {
9770
+ const result = await Je({
9771
+ message: "Which agent do you want to add?",
9772
+ options: available.map((t) => {
9773
+ const def = AGENT_DEFINITIONS.find((d2) => d2.target === t);
9774
+ return { value: t, label: def.displayName, hint: def.hint };
9775
+ })
9776
+ });
9777
+ if (Ct(result)) {
9778
+ Le("Cancelled.");
9779
+ process.exit(0);
9780
+ }
9781
+ target = result;
9782
+ }
9783
+ config2.targets[target] = true;
9784
+ await writeConfig(root, config2);
9785
+ await generate(root, config2);
9786
+ R2.success(`Added ${AGENT_DEFINITIONS.find((d2) => d2.target === target).displayName}`);
9787
+ }
9788
+ });
9789
+ });
9790
+
9791
+ // src/commands/remove.ts
9792
+ var exports_remove = {};
9793
+ __export(exports_remove, {
9794
+ default: () => remove_default
9795
+ });
9796
+ var remove_default;
9797
+ var init_remove = __esm(() => {
9798
+ init_dist();
9799
+ init_dist3();
9800
+ init_src();
9801
+ remove_default = defineCommand2({
9802
+ meta: {
9803
+ name: "remove",
9804
+ description: "Remove an AI agent target"
9805
+ },
9806
+ args: {
9807
+ target: {
9808
+ type: "positional",
9809
+ description: "Agent to remove (claude, cursor, windsurf, opencode, copilot)",
9810
+ required: false
9811
+ }
9812
+ },
9813
+ async run({ args }) {
9814
+ const root = process.cwd();
9815
+ let config2;
9816
+ try {
9817
+ config2 = await readConfig(root);
9818
+ } catch {
9819
+ console.error(`Error: No ${ONEAGENT_DIR}/config.yml found. Run \`oneagent init\` first.`);
9820
+ process.exit(1);
9821
+ }
9822
+ const current = activeTargets(config2);
9823
+ if (current.length === 1) {
9824
+ console.error("Cannot remove the last target. At least one agent must be configured.");
9825
+ process.exit(1);
9826
+ }
9827
+ let target;
9828
+ if (args.target) {
9829
+ if (!ALL_AGENT_TARGETS.includes(args.target)) {
9830
+ console.error(`Unknown target "${args.target}". Available: ${ALL_AGENT_TARGETS.join(", ")}`);
9831
+ process.exit(1);
9832
+ }
9833
+ target = args.target;
9834
+ if (!current.includes(target)) {
9835
+ R2.info(`${AGENT_DEFINITIONS.find((d2) => d2.target === target).displayName} is not configured.`);
9836
+ return;
9837
+ }
9838
+ } else {
9839
+ const result = await Je({
9840
+ message: "Which agent do you want to remove?",
9841
+ options: current.map((t) => {
9842
+ const def = AGENT_DEFINITIONS.find((d2) => d2.target === t);
9843
+ return { value: t, label: def.displayName, hint: def.hint };
9844
+ })
9845
+ });
9846
+ if (Ct(result)) {
9847
+ Le("Cancelled.");
9848
+ process.exit(0);
9849
+ }
9850
+ target = result;
9851
+ }
9852
+ await cleanupAgentDir(root, target);
9853
+ config2.targets[target] = false;
9854
+ await writeConfig(root, config2);
9855
+ await generate(root, config2);
9856
+ R2.success(`Removed ${AGENT_DEFINITIONS.find((d2) => d2.target === target).displayName}`);
9857
+ R2.info(`Files backed up to ${ONEAGENT_DIR}/backup/`);
9858
+ }
9859
+ });
9860
+ });
9861
+
9480
9862
  // ../../node_modules/.bun/citty@0.2.1/node_modules/citty/dist/index.mjs
9481
9863
  init_scule();
9482
9864
  import { parseArgs as parseArgs$1 } from "node:util";
@@ -9831,7 +10213,10 @@ var main = defineCommand({
9831
10213
  subCommands: {
9832
10214
  init: () => Promise.resolve().then(() => (init_init(), exports_init)).then((r) => r.default),
9833
10215
  generate: () => Promise.resolve().then(() => (init_generate2(), exports_generate)).then((r) => r.default),
9834
- status: () => Promise.resolve().then(() => (init_status2(), exports_status)).then((r) => r.default)
10216
+ status: () => Promise.resolve().then(() => (init_status2(), exports_status)).then((r) => r.default),
10217
+ targets: () => Promise.resolve().then(() => (init_targets(), exports_targets)).then((r) => r.default),
10218
+ add: () => Promise.resolve().then(() => (init_add(), exports_add)).then((r) => r.default),
10219
+ remove: () => Promise.resolve().then(() => (init_remove(), exports_remove)).then((r) => r.default)
9835
10220
  }
9836
10221
  });
9837
10222
  runMain(main);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oneagent",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "One source of truth for AI agent rules — distributed via symlinks to Claude, Cursor, Windsurf, Copilot, OpenCode",
6
6
  "license": "MIT",