oneagent 0.2.5 → 0.3.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 +355 -171
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -609,7 +609,7 @@ var import_picocolors, import_sisteransi, at = (t) => t === 161 || t === 164 ||
609
609
  ` && (r && p && (i += st(r)), n && (i += it(n))), V += h.length, m = A, A = g.next();
610
610
  }
611
611
  return i;
612
- }, At, _, bt, z, rt = (t) => ("columns" in t) && typeof t.columns == "number" ? t.columns : 80, nt = (t) => ("rows" in t) && typeof t.rows == "number" ? t.rows : 20, Vt, kt, yt, Lt, Wt;
612
+ }, At, _, bt, z, rt = (t) => ("columns" in t) && typeof t.columns == "number" ? t.columns : 80, nt = (t) => ("rows" in t) && typeof t.rows == "number" ? t.rows : 20, Vt, kt, yt, Lt, Wt, $t;
613
613
  var init_dist2 = __esm(() => {
614
614
  import_picocolors = __toESM(require_picocolors(), 1);
615
615
  import_sisteransi = __toESM(require_src(), 1);
@@ -826,6 +826,27 @@ var init_dist2 = __esm(() => {
826
826
  });
827
827
  }
828
828
  };
829
+ $t = class $t extends x {
830
+ get userInputWithCursor() {
831
+ if (this.state === "submit")
832
+ return this.userInput;
833
+ const e = this.userInput;
834
+ if (this.cursor >= e.length)
835
+ return `${this.userInput}█`;
836
+ const s = e.slice(0, this.cursor), [i, ...r] = e.slice(this.cursor);
837
+ return `${s}${import_picocolors.default.inverse(i)}${r.join("")}`;
838
+ }
839
+ get cursor() {
840
+ return this._cursor;
841
+ }
842
+ constructor(e) {
843
+ super({ ...e, initialUserInput: e.initialUserInput ?? e.initialValue }), this.on("userInput", (s) => {
844
+ this._setValue(s);
845
+ }), this.on("finalize", () => {
846
+ this.value || (this.value = e.defaultValue), this.value === undefined && (this.value = "");
847
+ });
848
+ }
849
+ };
829
850
  });
830
851
 
831
852
  // ../../node_modules/.bun/@clack+prompts@1.0.1/node_modules/@clack/prompts/dist/index.mjs
@@ -1067,7 +1088,7 @@ ${l}
1067
1088
  }
1068
1089
  }
1069
1090
  } }).prompt();
1070
- }, We = (t = "", r) => {
1091
+ }, R2, We = (t = "", r) => {
1071
1092
  (r?.output ?? process.stdout).write(`${import_picocolors2.default.gray(ht2)} ${t}
1072
1093
  `);
1073
1094
  }, Le = (t = "", r) => {
@@ -1244,7 +1265,35 @@ ${n}
1244
1265
  }
1245
1266
  }
1246
1267
  } }).prompt();
1247
- }, Qt;
1268
+ }, Qt, Ze = (t) => new $t({ validate: t.validate, placeholder: t.placeholder, defaultValue: t.defaultValue, initialValue: t.initialValue, output: t.output, signal: t.signal, input: t.input, render() {
1269
+ const r = t?.withGuide ?? _.withGuide, s = `${`${r ? `${import_picocolors2.default.gray(d)}
1270
+ ` : ""}${W2(this.state)} `}${t.message}
1271
+ `, i = t.placeholder ? import_picocolors2.default.inverse(t.placeholder[0]) + import_picocolors2.default.dim(t.placeholder.slice(1)) : import_picocolors2.default.inverse(import_picocolors2.default.hidden("_")), a = this.userInput ? this.userInputWithCursor : i, o = this.value ?? "";
1272
+ switch (this.state) {
1273
+ case "error": {
1274
+ const u = this.error ? ` ${import_picocolors2.default.yellow(this.error)}` : "", l = r ? `${import_picocolors2.default.yellow(d)} ` : "", n = r ? import_picocolors2.default.yellow(x2) : "";
1275
+ return `${s.trim()}
1276
+ ${l}${a}
1277
+ ${n}${u}
1278
+ `;
1279
+ }
1280
+ case "submit": {
1281
+ const u = o ? ` ${import_picocolors2.default.dim(o)}` : "", l = r ? import_picocolors2.default.gray(d) : "";
1282
+ return `${s}${l}${u}`;
1283
+ }
1284
+ case "cancel": {
1285
+ const u = o ? ` ${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(o))}` : "", l = r ? import_picocolors2.default.gray(d) : "";
1286
+ return `${s}${l}${u}${o.trim() ? `
1287
+ ${l}` : ""}`;
1288
+ }
1289
+ default: {
1290
+ const u = r ? `${import_picocolors2.default.cyan(d)} ` : "", l = r ? import_picocolors2.default.cyan(x2) : "";
1291
+ return `${s}${u}${a}
1292
+ ${l}
1293
+ `;
1294
+ }
1295
+ }
1296
+ } }).prompt();
1248
1297
  var init_dist3 = __esm(() => {
1249
1298
  init_dist2();
1250
1299
  init_dist2();
@@ -1286,10 +1335,58 @@ var init_dist3 = __esm(() => {
1286
1335
  Ee = { limit: 1 / 0, ellipsis: "", ellipsisWidth: 0 };
1287
1336
  St2 = `${Ae}8;;`;
1288
1337
  Ht = new RegExp(`(?:\\${kt2}(?<code>\\d+)m|\\${St2}(?<uri>.*)${Ct2})`, "y");
1338
+ R2 = { message: (t = [], { symbol: r = import_picocolors2.default.gray(d), secondarySymbol: s = import_picocolors2.default.gray(d), output: i = process.stdout, spacing: a = 1, withGuide: o } = {}) => {
1339
+ const u = [], l = o ?? _.withGuide, n = l ? s : "", c = l ? `${r} ` : "", g = l ? `${s} ` : "";
1340
+ for (let p = 0;p < a; p++)
1341
+ u.push(n);
1342
+ const F = Array.isArray(t) ? t : t.split(`
1343
+ `);
1344
+ if (F.length > 0) {
1345
+ const [p, ...E] = F;
1346
+ p.length > 0 ? u.push(`${c}${p}`) : u.push(l ? r : "");
1347
+ for (const $ of E)
1348
+ $.length > 0 ? u.push(`${g}${$}`) : u.push(l ? s : "");
1349
+ }
1350
+ i.write(`${u.join(`
1351
+ `)}
1352
+ `);
1353
+ }, info: (t, r) => {
1354
+ R2.message(t, { ...r, symbol: import_picocolors2.default.blue(ft2) });
1355
+ }, success: (t, r) => {
1356
+ R2.message(t, { ...r, symbol: import_picocolors2.default.green(Ft2) });
1357
+ }, step: (t, r) => {
1358
+ R2.message(t, { ...r, symbol: import_picocolors2.default.green(V) });
1359
+ }, warn: (t, r) => {
1360
+ R2.message(t, { ...r, symbol: import_picocolors2.default.yellow(yt2) });
1361
+ }, warning: (t, r) => {
1362
+ R2.warn(t, r);
1363
+ }, error: (t, r) => {
1364
+ R2.message(t, { ...r, symbol: import_picocolors2.default.red(Et2) });
1365
+ } };
1289
1366
  Ke = import_picocolors2.default.magenta;
1290
1367
  zt = { light: C("─", "-"), heavy: C("━", "="), block: C("█", "#") };
1291
1368
  Qt = `${import_picocolors2.default.gray(d)} `;
1292
1369
  });
1370
+
1371
+ // src/utils.ts
1372
+ function timeAgo(date) {
1373
+ const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
1374
+ if (seconds < 60)
1375
+ return `${seconds}s ago`;
1376
+ const minutes = Math.floor(seconds / 60);
1377
+ if (minutes < 60)
1378
+ return `${minutes}m ago`;
1379
+ const hours = Math.floor(minutes / 60);
1380
+ if (hours < 24)
1381
+ return `${hours}h ago`;
1382
+ const days = Math.floor(hours / 24);
1383
+ if (days < 30)
1384
+ return `${days}d ago`;
1385
+ const months = Math.floor(days / 30);
1386
+ if (months < 12)
1387
+ return `${months}mo ago`;
1388
+ return `${Math.floor(months / 12)}y ago`;
1389
+ }
1293
1390
  // ../core/src/agents.ts
1294
1391
  var AGENT_DEFINITIONS;
1295
1392
  var init_agents = __esm(() => {
@@ -1301,7 +1398,8 @@ var init_agents = __esm(() => {
1301
1398
  detectIndicators: ["CLAUDE.md", ".claude"],
1302
1399
  mainFile: "CLAUDE.md",
1303
1400
  rulesDir: ".claude/rules",
1304
- skillsDir: ".claude/skills"
1401
+ skillsDir: ".claude/skills",
1402
+ commandsDir: ".claude/commands"
1305
1403
  },
1306
1404
  {
1307
1405
  target: "cursor",
@@ -1311,7 +1409,8 @@ var init_agents = __esm(() => {
1311
1409
  mainFile: "AGENTS.md",
1312
1410
  rulesDir: ".cursor/rules",
1313
1411
  skillsDir: ".cursor/skills",
1314
- deprecatedFiles: [".cursorrules"]
1412
+ deprecatedFiles: [".cursorrules"],
1413
+ commandsDir: ".cursor/commands"
1315
1414
  },
1316
1415
  {
1317
1416
  target: "windsurf",
@@ -1326,9 +1425,12 @@ var init_agents = __esm(() => {
1326
1425
  {
1327
1426
  target: "opencode",
1328
1427
  displayName: "OpenCode",
1329
- hint: "AGENTS.md + opencode.json",
1428
+ hint: "AGENTS.md + .opencode/",
1330
1429
  detectIndicators: ["opencode.json", ".opencode"],
1331
- mainFile: "AGENTS.md"
1430
+ mainFile: "AGENTS.md",
1431
+ rulesDir: ".opencode/rules",
1432
+ skillsDir: ".opencode/skills",
1433
+ commandsDir: ".opencode/commands"
1332
1434
  },
1333
1435
  {
1334
1436
  target: "copilot",
@@ -8364,15 +8466,6 @@ async function removeDeprecatedFiles(root) {
8364
8466
  } catch {}
8365
8467
  }
8366
8468
  }
8367
- async function detectDeprecatedCommandFiles(root) {
8368
- const commandsDir = path2.join(root, ".claude/commands");
8369
- try {
8370
- const entries = await fs2.readdir(commandsDir, { withFileTypes: true });
8371
- return entries.filter((e2) => e2.isFile()).map((e2) => path2.join(".claude/commands", e2.name));
8372
- } catch {
8373
- return [];
8374
- }
8375
- }
8376
8469
  var AGENT_FILES, DEPRECATED_FILES;
8377
8470
  var init_detect = __esm(() => {
8378
8471
  init_agents();
@@ -8389,34 +8482,20 @@ var init_detect = __esm(() => {
8389
8482
  // ../core/src/rules.ts
8390
8483
  import path3 from "path";
8391
8484
  import fs3 from "fs/promises";
8392
- function parseFrontmatter(raw) {
8393
- const match = raw.match(/^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/);
8394
- if (!match)
8395
- return { applyTo: "**", content: raw };
8396
- const frontmatter = match[1] ?? "";
8397
- const content = match[2] ?? "";
8398
- const applyToMatch = frontmatter.match(/applyTo:\s*["']?([^"'\n]+)["']?/);
8399
- const applyTo = applyToMatch?.[1]?.trim() ?? "**";
8400
- return { applyTo, content };
8401
- }
8402
- async function readRuleFile(filePath) {
8403
- const raw = await fs3.readFile(filePath, "utf-8");
8404
- const { applyTo, content } = parseFrontmatter(raw);
8405
- return { name: path3.basename(filePath, ".md"), path: filePath, applyTo, content };
8406
- }
8407
8485
  async function readRules(root) {
8408
8486
  const rulesDir = path3.join(root, ".oneagent/rules");
8409
8487
  try {
8410
8488
  const files = await fs3.readdir(rulesDir);
8411
- const mdFiles = files.filter((f) => f.endsWith(".md"));
8412
- const rules = await Promise.all(mdFiles.map((f) => readRuleFile(path3.join(rulesDir, f))));
8413
- return rules.sort((a, b) => a.name.localeCompare(b.name));
8489
+ 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));
8414
8490
  } catch {
8415
8491
  return [];
8416
8492
  }
8417
8493
  }
8418
8494
  var init_rules = () => {};
8419
8495
 
8496
+ // ../core/src/commands.ts
8497
+ var init_commands = () => {};
8498
+
8420
8499
  // ../core/src/skills.ts
8421
8500
  import path4 from "path";
8422
8501
  import fs4 from "fs/promises";
@@ -8463,7 +8542,7 @@ async function ensureDir(dirPath) {
8463
8542
  async function createSymlink(symlinkPath, target) {
8464
8543
  await ensureDir(path5.dirname(symlinkPath));
8465
8544
  try {
8466
- await fs5.unlink(symlinkPath);
8545
+ await fs5.rm(symlinkPath, { recursive: true });
8467
8546
  } catch {}
8468
8547
  await fs5.symlink(target, symlinkPath);
8469
8548
  }
@@ -8500,6 +8579,13 @@ function buildSkillSymlinks(root, targets) {
8500
8579
  return { symlinkPath, target: relativeTarget(symlinkPath, targetAbs), label: d2.skillsDir };
8501
8580
  });
8502
8581
  }
8582
+ function buildCommandSymlinks(root, targets) {
8583
+ const targetAbs = path5.join(root, ".oneagent/commands");
8584
+ return AGENT_DEFINITIONS.filter((d2) => targets.includes(d2.target) && d2.commandsDir).map((d2) => {
8585
+ const symlinkPath = path5.join(root, d2.commandsDir);
8586
+ return { symlinkPath, target: relativeTarget(symlinkPath, targetAbs), label: d2.commandsDir };
8587
+ });
8588
+ }
8503
8589
  function buildAgentsDirSymlinks(root) {
8504
8590
  const symlinkPath = path5.join(root, ".agents/skills");
8505
8591
  const targetAbs = path5.join(root, ".oneagent/skills");
@@ -8555,13 +8641,21 @@ async function migrateAndRemoveDir(src, dest, root) {
8555
8641
  async function migrateRuleAndSkillFiles(root) {
8556
8642
  const destRules = path5.join(root, ".oneagent/rules");
8557
8643
  const destSkills = path5.join(root, ".oneagent/skills");
8558
- await migrateFilesFromDir(path5.join(root, ".cursor/rules"), destRules, root);
8559
- await migrateFilesFromDir(path5.join(root, ".claude/rules"), destRules, root);
8560
- await migrateFilesFromDir(path5.join(root, ".windsurf/rules"), destRules, root);
8644
+ const destCommands = path5.join(root, ".oneagent/commands");
8645
+ for (const def of AGENT_DEFINITIONS) {
8646
+ if (def.rulesDir)
8647
+ await migrateAndRemoveDir(path5.join(root, def.rulesDir), destRules, root);
8648
+ }
8561
8649
  await migrateAndRemoveDir(path5.join(root, ".agents/skills"), destSkills, root);
8650
+ for (const def of AGENT_DEFINITIONS) {
8651
+ if (def.commandsDir)
8652
+ await migrateAndRemoveDir(path5.join(root, def.commandsDir), destCommands, root);
8653
+ }
8562
8654
  }
8563
8655
  async function createAllSymlinks(entries) {
8564
- await Promise.all(entries.map((e2) => createSymlink(e2.symlinkPath, e2.target)));
8656
+ for (const e2 of entries) {
8657
+ await createSymlink(e2.symlinkPath, e2.target);
8658
+ }
8565
8659
  }
8566
8660
  async function checkSymlink(entry) {
8567
8661
  try {
@@ -8582,19 +8676,13 @@ var init_symlinks = __esm(() => {
8582
8676
  // ../core/src/copilot.ts
8583
8677
  import path6 from "path";
8584
8678
  import fs6 from "fs/promises";
8585
- function buildCopilotContent(rule) {
8586
- return `---
8587
- applyTo: "${rule.applyTo}"
8588
- ---
8589
- ${rule.content}`;
8590
- }
8591
8679
  function copilotFilePath(root, ruleName) {
8592
8680
  return path6.join(root, ".github/instructions", `${ruleName}.instructions.md`);
8593
8681
  }
8594
8682
  async function generateCopilotRule(root, rule) {
8595
- const filePath = copilotFilePath(root, rule.name);
8596
- await fs6.mkdir(path6.dirname(filePath), { recursive: true });
8597
- await fs6.writeFile(filePath, buildCopilotContent(rule));
8683
+ const destPath = copilotFilePath(root, rule.name);
8684
+ await fs6.mkdir(path6.dirname(destPath), { recursive: true });
8685
+ await fs6.copyFile(rule.path, destPath);
8598
8686
  }
8599
8687
  async function generateCopilotRules(root, rules) {
8600
8688
  await Promise.all(rules.map((rule) => generateCopilotRule(root, rule)));
@@ -8637,6 +8725,21 @@ function buildOpencodeConfig(existing) {
8637
8725
  instructions: ".oneagent/instructions.md"
8638
8726
  };
8639
8727
  }
8728
+ async function addOpenCodePlugin(root, id) {
8729
+ const filePath = path7.join(root, "opencode.json");
8730
+ let existing;
8731
+ try {
8732
+ existing = JSON.parse(await fs7.readFile(filePath, "utf-8"));
8733
+ } catch {
8734
+ return;
8735
+ }
8736
+ const current = Array.isArray(existing.plugin) ? existing.plugin : [];
8737
+ if (current.includes(id))
8738
+ return;
8739
+ existing.plugin = [...current, id];
8740
+ await fs7.writeFile(filePath, JSON.stringify(existing, null, 2) + `
8741
+ `);
8742
+ }
8640
8743
  async function writeOpencode(root, _rules) {
8641
8744
  const existing = await readOpencode(root);
8642
8745
  const config = buildOpencodeConfig(existing);
@@ -8654,7 +8757,8 @@ async function detectGenerateCollisions(root, config) {
8654
8757
  const mainEntries = buildMainSymlinks(root, targets);
8655
8758
  const ruleSkillEntries = [
8656
8759
  ...buildRulesSymlinks(root, targets),
8657
- ...buildSkillSymlinks(root, targets)
8760
+ ...buildSkillSymlinks(root, targets),
8761
+ ...buildCommandSymlinks(root, targets)
8658
8762
  ];
8659
8763
  const [mainCollisions, ruleSkillSymlinkCollisions] = await Promise.all([
8660
8764
  Promise.all(mainEntries.map((entry) => readDetectedFile(root, path8.relative(root, entry.symlinkPath)))).then((files) => files.filter((f) => f !== null)),
@@ -8666,11 +8770,14 @@ async function detectGenerateCollisions(root, config) {
8666
8770
  ...rules.map(async (rule) => {
8667
8771
  const filePath = copilotFilePath(root, rule.name);
8668
8772
  try {
8669
- const content = await fs8.readFile(filePath, "utf-8");
8670
- if (content === buildCopilotContent(rule))
8773
+ const [source, dest] = await Promise.all([
8774
+ fs8.readFile(rule.path, "utf-8"),
8775
+ fs8.readFile(filePath, "utf-8")
8776
+ ]);
8777
+ if (source === dest)
8671
8778
  return null;
8672
8779
  const stat = await fs8.lstat(filePath);
8673
- return { relativePath: path8.relative(root, filePath), absolutePath: filePath, sizeBytes: stat.size, modifiedAt: stat.mtime, content };
8780
+ return { relativePath: path8.relative(root, filePath), absolutePath: filePath, sizeBytes: stat.size, modifiedAt: stat.mtime, content: dest };
8674
8781
  } catch {
8675
8782
  return null;
8676
8783
  }
@@ -8702,7 +8809,8 @@ async function generate(root, config) {
8702
8809
  const mainSymlinks = buildMainSymlinks(root, targets);
8703
8810
  const rulesSymlinks = buildRulesSymlinks(root, targets);
8704
8811
  const skillSymlinks = await buildSkillSymlinks(root, targets);
8705
- await createAllSymlinks([...mainSymlinks, ...rulesSymlinks, ...skillSymlinks, ...buildAgentsDirSymlinks(root)]);
8812
+ const commandSymlinks = buildCommandSymlinks(root, targets);
8813
+ await createAllSymlinks([...mainSymlinks, ...rulesSymlinks, ...skillSymlinks, ...commandSymlinks, ...buildAgentsDirSymlinks(root)]);
8706
8814
  if (targets.includes("copilot")) {
8707
8815
  await Promise.all([generateCopilotRules(root, rules), generateCopilotSkills(root, skills)]);
8708
8816
  }
@@ -8724,10 +8832,12 @@ var init_generate = __esm(() => {
8724
8832
  import fs9 from "fs/promises";
8725
8833
  async function checkGeneratedFile(root, rule) {
8726
8834
  const filePath = copilotFilePath(root, rule.name);
8727
- const expected = buildCopilotContent(rule);
8728
8835
  try {
8729
- const content = await fs9.readFile(filePath, "utf-8");
8730
- return { path: filePath, exists: true, upToDate: content === expected };
8836
+ const [source, dest] = await Promise.all([
8837
+ fs9.readFile(rule.path, "utf-8"),
8838
+ fs9.readFile(filePath, "utf-8")
8839
+ ]);
8840
+ return { path: filePath, exists: true, upToDate: source === dest };
8731
8841
  } catch {
8732
8842
  return { path: filePath, exists: false, upToDate: false };
8733
8843
  }
@@ -8755,6 +8865,7 @@ async function checkStatus(root, config) {
8755
8865
  ...buildMainSymlinks(root, targets),
8756
8866
  ...buildRulesSymlinks(root, targets),
8757
8867
  ...buildSkillSymlinks(root, targets),
8868
+ ...buildCommandSymlinks(root, targets),
8758
8869
  ...buildAgentsDirSymlinks(root)
8759
8870
  ];
8760
8871
  const symlinks = await Promise.all(allEntries.map(checkSymlink));
@@ -8779,6 +8890,50 @@ import path9 from "path";
8779
8890
  import fs10 from "fs/promises";
8780
8891
  import { execFile } from "child_process";
8781
8892
  import { promisify } from "util";
8893
+ function parseTemplateYaml(yamlText, fallbackName = "custom") {
8894
+ const nameMatch = yamlText.match(/^name:\s*(.+)$/m);
8895
+ const name = nameMatch?.[1]?.trim() ?? fallbackName;
8896
+ const descMatch = yamlText.match(/^description:\s*(.+)$/m);
8897
+ const description = descMatch?.[1]?.trim() ?? "";
8898
+ const skills = parseSkillsFromYaml(yamlText);
8899
+ const plugins = parsePluginsFromYaml(yamlText);
8900
+ return { name, description, skills, plugins };
8901
+ }
8902
+ function parseSkillsFromYaml(yamlText) {
8903
+ const skills = [];
8904
+ const section = yamlText.match(/^skills:\s*\n((?:(?: -.+|\s{4}.+)\n?)*)/m);
8905
+ if (!section)
8906
+ return skills;
8907
+ const block = section[1];
8908
+ const entries = block.split(/\n(?= -)/);
8909
+ for (const entry of entries) {
8910
+ const repoMatch = entry.match(/repo:\s*(\S+)/);
8911
+ const skillMatch = entry.match(/skill:\s*(\S+)/);
8912
+ if (repoMatch && skillMatch) {
8913
+ skills.push({ repo: repoMatch[1].trim(), skill: skillMatch[1].trim() });
8914
+ }
8915
+ }
8916
+ return skills;
8917
+ }
8918
+ function parsePluginsFromYaml(yamlText) {
8919
+ const plugins = [];
8920
+ const section = yamlText.match(/^plugins:\s*\n((?:(?: -.+|\s{4}.+)\n?)*)/m);
8921
+ if (!section)
8922
+ return plugins;
8923
+ const block = section[1];
8924
+ const entries = block.split(/\n(?= -)/);
8925
+ for (const entry of entries) {
8926
+ const targetMatch = entry.match(/target:\s*(\S+)/);
8927
+ const idMatch = entry.match(/id:\s*(.+)/);
8928
+ if (targetMatch && idMatch) {
8929
+ plugins.push({
8930
+ target: targetMatch[1].trim(),
8931
+ id: idMatch[1].trim()
8932
+ });
8933
+ }
8934
+ }
8935
+ return plugins;
8936
+ }
8782
8937
  async function applyTemplateFiles(root, template) {
8783
8938
  const oneagentDir = path9.join(root, ".oneagent");
8784
8939
  await fs10.mkdir(path9.join(oneagentDir, "rules"), { recursive: true });
@@ -8788,16 +8943,54 @@ async function applyTemplateFiles(root, template) {
8788
8943
  await fs10.writeFile(path9.join(oneagentDir, "rules", `${rule.name}.md`), rule.content);
8789
8944
  }
8790
8945
  }
8791
- async function installTemplateSkills(root, template, onSkillInstalled) {
8792
- for (const identifier of template.skills) {
8946
+ async function installTemplateSkills(root, template) {
8947
+ const results = await Promise.all(template.skills.map(async (entry) => {
8793
8948
  try {
8794
- await execFileAsync("npx", ["skills", "add", identifier, "--agent", "universal", "--yes"], { cwd: root });
8795
- onSkillInstalled?.(identifier);
8949
+ await execFileAsync("npx", ["skills", "add", entry.repo, "--skill", entry.skill, "--agent", "universal", "--yes"], { cwd: root });
8950
+ return { entry, ok: true };
8951
+ } catch (err) {
8952
+ const reason = err instanceof Error ? err.message : String(err);
8953
+ return { entry, ok: false, reason };
8954
+ }
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
+ };
8960
+ }
8961
+ async function installTemplatePlugins(root, template, activeTargets2) {
8962
+ const installed = [];
8963
+ const manual = [];
8964
+ const failed = [];
8965
+ for (const plugin of template.plugins) {
8966
+ if (!activeTargets2.includes(plugin.target))
8967
+ continue;
8968
+ try {
8969
+ switch (plugin.target) {
8970
+ case "claude":
8971
+ await execFileAsync("claude", ["plugin", "install", plugin.id], { cwd: root });
8972
+ installed.push(plugin);
8973
+ break;
8974
+ case "copilot":
8975
+ await execFileAsync("copilot", ["plugin", "install", plugin.id], { cwd: root });
8976
+ installed.push(plugin);
8977
+ break;
8978
+ case "opencode":
8979
+ await addOpenCodePlugin(root, plugin.id);
8980
+ installed.push(plugin);
8981
+ break;
8982
+ case "cursor":
8983
+ manual.push(plugin);
8984
+ break;
8985
+ case "windsurf":
8986
+ break;
8987
+ }
8796
8988
  } catch (err) {
8797
- const message = err instanceof Error ? err.message : String(err);
8798
- throw new Error(`Failed to install skill "${identifier}": ${message}`);
8989
+ const reason = err instanceof Error ? err.message : String(err);
8990
+ failed.push({ plugin, reason });
8799
8991
  }
8800
8992
  }
8993
+ return { installed, manual, failed };
8801
8994
  }
8802
8995
  async function fetchTemplateFromGitHub(url) {
8803
8996
  const rawBase = githubUrlToRawBase(url);
@@ -8805,23 +8998,9 @@ async function fetchTemplateFromGitHub(url) {
8805
8998
  fetchText(`${rawBase}/template.yml`),
8806
8999
  fetchText(`${rawBase}/instructions.md`)
8807
9000
  ]);
8808
- const descMatch = yamlText.match(/^description:\s*(.+)$/m);
8809
- const description = descMatch?.[1]?.trim() ?? "";
8810
- const nameMatch = yamlText.match(/^name:\s*(.+)$/m);
8811
- const name = nameMatch?.[1]?.trim() ?? "custom";
8812
- const skills = [];
8813
- const skillsBlockMatch = yamlText.match(/^skills:\s*\n((?: - .+\n?)*)/m);
8814
- if (skillsBlockMatch) {
8815
- const lines = skillsBlockMatch[1].split(`
8816
- `).filter(Boolean);
8817
- for (const line of lines) {
8818
- const skill = line.replace(/^\s*-\s*/, "").trim();
8819
- if (skill)
8820
- skills.push(skill);
8821
- }
8822
- }
9001
+ const { name, description, skills, plugins } = parseTemplateYaml(yamlText);
8823
9002
  const rules = await fetchGitHubRules(url);
8824
- return { name, description, skills, instructions, rules };
9003
+ return { name, description, skills, plugins, instructions, rules };
8825
9004
  }
8826
9005
  async function fetchText(url) {
8827
9006
  const response = await fetch(url);
@@ -8830,20 +9009,23 @@ async function fetchText(url) {
8830
9009
  }
8831
9010
  return response.text();
8832
9011
  }
8833
- function githubUrlToRawBase(url) {
8834
- const match = url.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\/tree\/([^/]+))?(?:\/.*)?$/);
9012
+ function parseGitHubUrl(url) {
9013
+ const match = url.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\/tree\/([^/]+?)(?:\/(.+))?)?(?:\/)?$/);
8835
9014
  if (!match) {
8836
9015
  throw new Error(`Invalid GitHub URL: "${url}". Expected format: https://github.com/owner/repo`);
8837
9016
  }
8838
- const [, owner, repo, branch = "main"] = match;
8839
- return `https://raw.githubusercontent.com/${owner}/${repo}/${branch}`;
9017
+ const [, owner, repo, branch = "main", subdir = ""] = match;
9018
+ return { owner, repo, branch, subdir };
9019
+ }
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;
8840
9024
  }
8841
9025
  async function fetchGitHubRules(repoUrl) {
8842
- const match = repoUrl.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\/tree\/([^/]+))?(?:\/.*)?$/);
8843
- if (!match)
8844
- return [];
8845
- const [, owner, repo, branch = "main"] = match;
8846
- const apiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/rules?ref=${branch}`;
9026
+ const { owner, repo, branch, subdir } = parseGitHubUrl(repoUrl);
9027
+ const rulesPath = subdir ? `${subdir}/rules` : "rules";
9028
+ const apiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${rulesPath}?ref=${branch}`;
8847
9029
  try {
8848
9030
  const response = await fetch(apiUrl, {
8849
9031
  headers: { Accept: "application/vnd.github.v3+json" }
@@ -8863,6 +9045,7 @@ async function fetchGitHubRules(repoUrl) {
8863
9045
  }
8864
9046
  var execFileAsync;
8865
9047
  var init_apply_template = __esm(() => {
9048
+ init_opencode();
8866
9049
  execFileAsync = promisify(execFile);
8867
9050
  });
8868
9051
 
@@ -8872,6 +9055,7 @@ var init_src = __esm(() => {
8872
9055
  init_config();
8873
9056
  init_detect();
8874
9057
  init_rules();
9058
+ init_commands();
8875
9059
  init_skills();
8876
9060
  init_symlinks();
8877
9061
  init_copilot();
@@ -8881,39 +9065,6 @@ var init_src = __esm(() => {
8881
9065
  init_apply_template();
8882
9066
  });
8883
9067
 
8884
- // src/utils.ts
8885
- async function warnDeprecatedCommandFiles(root) {
8886
- const deprecated = await detectDeprecatedCommandFiles(root);
8887
- if (deprecated.length === 0)
8888
- return;
8889
- Ve(deprecated.map((f) => ` • ${f}`).join(`
8890
- `) + `
8891
-
8892
- Move them to .oneagent/skills/ to manage them with oneagent.`, ".claude/commands/ is deprecated — use .oneagent/skills/ instead");
8893
- }
8894
- function timeAgo(date) {
8895
- const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
8896
- if (seconds < 60)
8897
- return `${seconds}s ago`;
8898
- const minutes = Math.floor(seconds / 60);
8899
- if (minutes < 60)
8900
- return `${minutes}m ago`;
8901
- const hours = Math.floor(minutes / 60);
8902
- if (hours < 24)
8903
- return `${hours}h ago`;
8904
- const days = Math.floor(hours / 24);
8905
- if (days < 30)
8906
- return `${days}d ago`;
8907
- const months = Math.floor(days / 30);
8908
- if (months < 12)
8909
- return `${months}mo ago`;
8910
- return `${Math.floor(months / 12)}y ago`;
8911
- }
8912
- var init_utils = __esm(() => {
8913
- init_dist3();
8914
- init_src();
8915
- });
8916
-
8917
9068
  // ../templates/src/index.ts
8918
9069
  import path10 from "path";
8919
9070
  import fs11 from "fs/promises";
@@ -8924,19 +9075,7 @@ async function loadTemplate(name) {
8924
9075
  fs11.readFile(path10.join(templateDir, "template.yml"), "utf-8"),
8925
9076
  fs11.readFile(path10.join(templateDir, "instructions.md"), "utf-8")
8926
9077
  ]);
8927
- const descMatch = yamlText.match(/^description:\s*(.+)$/m);
8928
- const description = descMatch?.[1]?.trim() ?? "";
8929
- const skills2 = [];
8930
- const skillsBlockMatch = yamlText.match(/^skills:\s*\n((?: - .+\n?)*)/m);
8931
- if (skillsBlockMatch) {
8932
- const lines = skillsBlockMatch[1].split(`
8933
- `).filter(Boolean);
8934
- for (const line of lines) {
8935
- const skill = line.replace(/^\s*-\s*/, "").trim();
8936
- if (skill)
8937
- skills2.push(skill);
8938
- }
8939
- }
9078
+ const { description, skills: skills2, plugins } = parseTemplateYaml(yamlText, name);
8940
9079
  const rulesDir = path10.join(templateDir, "rules");
8941
9080
  let rules2 = [];
8942
9081
  try {
@@ -8946,18 +9085,24 @@ async function loadTemplate(name) {
8946
9085
  content: await fs11.readFile(path10.join(rulesDir, f), "utf-8")
8947
9086
  })));
8948
9087
  } catch {}
8949
- return { name, description, skills: skills2, instructions, rules: rules2 };
9088
+ return { name, description, skills: skills2, plugins, instructions, rules: rules2 };
8950
9089
  }
8951
9090
  async function resolveBuiltinTemplate(name) {
8952
9091
  if (!TEMPLATE_NAMES.includes(name))
8953
9092
  return null;
8954
9093
  return loadTemplate(name);
8955
9094
  }
8956
- var __dirname2, TEMPLATE_NAMES, BUILTIN_TEMPLATE_NAMES;
9095
+ var __dirname2, TEMPLATE_NAMES, BUILTIN_TEMPLATE_NAMES, BUILTIN_TEMPLATE_META;
8957
9096
  var init_src2 = __esm(() => {
9097
+ init_src();
8958
9098
  __dirname2 = path10.dirname(fileURLToPath(import.meta.url));
8959
9099
  TEMPLATE_NAMES = ["default", "react", "react-native"];
8960
9100
  BUILTIN_TEMPLATE_NAMES = TEMPLATE_NAMES;
9101
+ BUILTIN_TEMPLATE_META = [
9102
+ { name: "default", description: "General programming starter" },
9103
+ { name: "react", description: "React / Next.js project starter" },
9104
+ { name: "react-native", description: "React Native / Expo project starter" }
9105
+ ];
8961
9106
  });
8962
9107
 
8963
9108
  // src/commands/init.ts
@@ -9052,6 +9197,32 @@ async function backupFiles(root, files) {
9052
9197
  await fs12.writeFile(path11.join(backupDir, safeName), file.content);
9053
9198
  }
9054
9199
  }
9200
+ async function pickTemplateInteractively() {
9201
+ const result = await Je({
9202
+ message: "Which template would you like to use?",
9203
+ options: [
9204
+ ...BUILTIN_TEMPLATE_META.map((t) => ({
9205
+ value: t.name,
9206
+ label: t.name,
9207
+ hint: t.description
9208
+ })),
9209
+ { value: "__github__", label: "Custom GitHub template", hint: "Enter a GitHub URL" }
9210
+ ]
9211
+ });
9212
+ if (Ct(result))
9213
+ cancelAndExit();
9214
+ if (result === "__github__") {
9215
+ const url = await Ze({
9216
+ message: "GitHub URL:",
9217
+ placeholder: "https://github.com/owner/repo",
9218
+ validate: (v) => !v || !v.startsWith("https://github.com/") ? "Must be a GitHub URL" : undefined
9219
+ });
9220
+ if (Ct(url))
9221
+ cancelAndExit();
9222
+ return url;
9223
+ }
9224
+ return result;
9225
+ }
9055
9226
  async function resolveTemplate(templateArg) {
9056
9227
  if (templateArg.startsWith("https://github.com/")) {
9057
9228
  return fetchTemplateFromGitHub(templateArg);
@@ -9061,34 +9232,13 @@ async function resolveTemplate(templateArg) {
9061
9232
  return builtin;
9062
9233
  throw new Error(`Unknown template "${templateArg}". Use one of: ${BUILTIN_TEMPLATE_NAMES.join(", ")} — or a GitHub URL.`);
9063
9234
  }
9064
- var DOTAI_META_RULE = `---
9065
- applyTo: "**"
9066
- ---
9067
- # oneagent
9068
-
9069
- This project uses [oneagent](https://github.com/moskalakamil/oneagent) to manage AI agent configuration.
9070
-
9071
- Rules are stored in \`.oneagent/rules/\` and distributed to agents automatically via symlinks or generated files.
9072
-
9073
- To add a new rule, create a \`.md\` file in \`.oneagent/rules/\` with optional frontmatter:
9074
-
9075
- \`\`\`md
9076
- ---
9077
- applyTo: "**/*.ts"
9078
- ---
9079
- # Rule name
9080
-
9081
- Rule content here.
9082
- \`\`\`
9083
-
9084
- Then run \`dotai generate\` to distribute the rule to all configured agents.
9085
- `, init_default;
9235
+ var ABOUT_ONEAGENT_RULE_PATH, init_default;
9086
9236
  var init_init = __esm(() => {
9087
9237
  init_dist();
9088
9238
  init_dist3();
9089
- init_utils();
9090
9239
  init_src();
9091
9240
  init_src2();
9241
+ ABOUT_ONEAGENT_RULE_PATH = new URL("../assets/about-oneagent.md", import.meta.url);
9092
9242
  init_default = defineCommand2({
9093
9243
  meta: {
9094
9244
  name: "init",
@@ -9111,11 +9261,14 @@ var init_init = __esm(() => {
9111
9261
  const detected = await detectExistingFiles(root);
9112
9262
  let template = null;
9113
9263
  let importedContent = "";
9114
- if (args.template) {
9264
+ const templateFlagPresent = process.argv.includes("--template");
9265
+ const templateValue = typeof args.template === "string" && args.template.length > 0 ? args.template : null;
9266
+ const templateArg = templateValue || (templateFlagPresent ? await pickTemplateInteractively() : null);
9267
+ if (templateArg) {
9115
9268
  const s3 = bt2();
9116
- s3.start(`Resolving template "${args.template}"...`);
9269
+ s3.start(`Resolving template "${templateArg}"...`);
9117
9270
  try {
9118
- template = await resolveTemplate(args.template);
9271
+ template = await resolveTemplate(templateArg);
9119
9272
  s3.stop(`Template "${template.name}" ready.`);
9120
9273
  } catch (err) {
9121
9274
  s3.stop("Failed.");
@@ -9132,9 +9285,9 @@ var init_init = __esm(() => {
9132
9285
  s.start("Setting up .oneagent/ directory...");
9133
9286
  await fs12.mkdir(path11.join(root, ".oneagent/rules"), { recursive: true });
9134
9287
  await fs12.mkdir(path11.join(root, ".oneagent/skills"), { recursive: true });
9288
+ await fs12.mkdir(path11.join(root, ".oneagent/commands"), { recursive: true });
9135
9289
  await backupFiles(root, detected);
9136
9290
  await removeDeprecatedFiles(root);
9137
- await warnDeprecatedCommandFiles(root);
9138
9291
  await migrateRuleAndSkillFiles(root);
9139
9292
  const config2 = { version: 1, targets: makeTargets(...selectedTargets) };
9140
9293
  await writeConfig(root, config2);
@@ -9147,31 +9300,64 @@ Add your AI instructions here.
9147
9300
  `;
9148
9301
  await fs12.writeFile(path11.join(root, ".oneagent/instructions.md"), instructionsContent);
9149
9302
  }
9150
- await fs12.writeFile(path11.join(root, ".oneagent/rules/oneagent.md"), DOTAI_META_RULE);
9303
+ await fs12.copyFile(ABOUT_ONEAGENT_RULE_PATH, path11.join(root, ".oneagent/rules/about-oneagent.md"));
9151
9304
  s.stop("Directory structure created.");
9305
+ const commandFiles = await fs12.readdir(path11.join(root, ".oneagent/commands")).catch(() => []);
9306
+ 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.");
9308
+ }
9309
+ if (commandFiles.some((f) => f.endsWith(".md"))) {
9310
+ const commandsSupported = new Set(AGENT_DEFINITIONS.filter((d2) => d2.commandsDir).map((d2) => d2.target));
9311
+ const unsupported = selectedTargets.filter((t) => !commandsSupported.has(t));
9312
+ if (unsupported.length > 0) {
9313
+ 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.`);
9315
+ }
9316
+ }
9152
9317
  const s2 = bt2();
9153
9318
  s2.start("Generating symlinks and agent files...");
9154
9319
  await generate(root, config2);
9155
9320
  s2.stop("Done.");
9156
- const fetchedSkills = [];
9321
+ let skillResult = { installed: [], failed: [] };
9157
9322
  if (template && template.skills.length > 0) {
9158
9323
  const s3 = bt2();
9159
9324
  s3.start("Installing skills...");
9160
- await installTemplateSkills(root, template, (id) => fetchedSkills.push(id));
9161
- s3.stop(`Installed ${fetchedSkills.length} skill(s).`);
9325
+ skillResult = await installTemplateSkills(root, template);
9326
+ s3.stop(`Installed ${skillResult.installed.length} skill(s).`);
9327
+ for (const f of skillResult.failed) {
9328
+ R2.warn(`Skill "${f.entry.skill}" (${f.entry.repo}) could not be installed and was skipped.`);
9329
+ }
9330
+ }
9331
+ let pluginResult = { installed: [], manual: [], failed: [] };
9332
+ if (template && template.plugins.length > 0) {
9333
+ const s4 = bt2();
9334
+ s4.start("Installing plugins...");
9335
+ pluginResult = await installTemplatePlugins(root, template, selectedTargets);
9336
+ s4.stop(`Installed ${pluginResult.installed.length} plugin(s).`);
9337
+ for (const f of pluginResult.failed) {
9338
+ R2.warn(`Plugin "${f.plugin.id}" (${f.plugin.target}) could not be installed and was skipped.`);
9339
+ }
9162
9340
  }
9163
9341
  const lines = [
9164
9342
  ...template ? [
9165
9343
  `Template: ${template.name} — ${template.description}`,
9166
- ...fetchedSkills.length > 0 ? [`Fetched ${fetchedSkills.length} skill(s): ${fetchedSkills.join(", ")}`] : [],
9167
- ...template.rules.length > 0 ? [`Added ${template.rules.length} rule(s) from template`] : []
9344
+ ...skillResult.installed.length > 0 ? [`Installed ${skillResult.installed.length} skill(s): ${skillResult.installed.map((s3) => s3.skill).join(", ")}`] : [],
9345
+ ...template.rules.length > 0 ? [`Added ${template.rules.length} rule(s) from template`] : [],
9346
+ ...pluginResult.installed.length > 0 ? [`Installed ${pluginResult.installed.length} plugin(s): ${pluginResult.installed.map((p) => p.id).join(", ")}`] : []
9168
9347
  ] : ["Created .oneagent/instructions.md"],
9169
- "Created .oneagent/rules/oneagent.md",
9348
+ "Created .oneagent/rules/about-oneagent.md",
9170
9349
  ...selectedTargets.map((t) => `Configured: ${t}`),
9171
9350
  ...detected.length > 0 ? [`Backed up ${detected.length} file(s) to .oneagent/backup/`] : []
9172
9351
  ];
9173
9352
  Ve(lines.map((l) => ` • ${l}`).join(`
9174
9353
  `), "Setup complete");
9354
+ if (pluginResult.manual.length > 0) {
9355
+ Ve(`This template includes ${pluginResult.manual.length} Cursor plugin(s).
9356
+ To install them, run each command in the Cursor chat:
9357
+
9358
+ ${pluginResult.manual.map((p) => ` /add-plugin ${p.id}`).join(`
9359
+ `)}`, "Action required: Cursor plugins");
9360
+ }
9175
9361
  Le("Run `oneagent status` to verify your setup.");
9176
9362
  }
9177
9363
  });
@@ -9188,7 +9374,6 @@ var generate_default;
9188
9374
  var init_generate2 = __esm(() => {
9189
9375
  init_dist();
9190
9376
  init_dist3();
9191
- init_utils();
9192
9377
  init_src();
9193
9378
  generate_default = defineCommand2({
9194
9379
  meta: {
@@ -9213,7 +9398,6 @@ var init_generate2 = __esm(() => {
9213
9398
  await fs13.writeFile(path12.join(backupDir, safeName), file.content);
9214
9399
  }
9215
9400
  }
9216
- await warnDeprecatedCommandFiles(root);
9217
9401
  if (ruleSkillFiles.length > 0) {
9218
9402
  Ve(ruleSkillFiles.map((f) => ` • ${f.relativePath}`).join(`
9219
9403
  `), "These rule/skill files are not dotai symlinks");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oneagent",
3
- "version": "0.2.5",
3
+ "version": "0.3.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",