@xera-ai/cli 0.12.1 → 0.12.2

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 +91 -53
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -210,21 +210,34 @@ async function runChecks(cwd) {
210
210
  checks.push({ name: "xera skills present", ok: false, message: "run `xera init`" });
211
211
  } else {
212
212
  const required = [
213
- "xera-run.md",
214
- "xera-fetch.md",
215
- "xera-feature.md",
216
- "xera-script.md",
217
- "xera-exec.md",
218
- "xera-report.md",
219
- "xera-promote.md"
213
+ "xera-run",
214
+ "xera-fetch",
215
+ "xera-feature",
216
+ "xera-script",
217
+ "xera-exec",
218
+ "xera-report",
219
+ "xera-promote"
220
220
  ];
221
- const missing = required.filter((n) => !existsSync(join(skillsDir, n)));
221
+ const missing = [];
222
+ const legacyFlat = [];
223
+ for (const base of required) {
224
+ if (existsSync(join(skillsDir, base, "SKILL.md")))
225
+ continue;
226
+ if (existsSync(join(skillsDir, `${base}.md`))) {
227
+ legacyFlat.push(base);
228
+ } else {
229
+ missing.push(base);
230
+ }
231
+ }
222
232
  const skillsCheck = {
223
233
  name: "xera skills present",
224
- ok: missing.length === 0
234
+ ok: missing.length === 0 && legacyFlat.length === 0
225
235
  };
226
- if (missing.length)
227
- skillsCheck.message = `missing: ${missing.join(", ")}`;
236
+ if (missing.length) {
237
+ skillsCheck.message = `missing: ${missing.map((b) => `${b}/SKILL.md`).join(", ")}`;
238
+ } else if (legacyFlat.length) {
239
+ skillsCheck.message = `legacy flat layout in .claude/skills/ \u2014 run \`xera init --update\` to migrate to <name>/SKILL.md (Claude Code Skill tool requires the directory layout)`;
240
+ }
228
241
  checks.push(skillsCheck);
229
242
  }
230
243
  return checks;
@@ -261,9 +274,16 @@ async function doctorCommand(opts) {
261
274
  }
262
275
 
263
276
  // src/commands/init.ts
264
- import { appendFileSync, existsSync as existsSync3, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
277
+ import {
278
+ appendFileSync,
279
+ existsSync as existsSync3,
280
+ mkdirSync as mkdirSync2,
281
+ readdirSync as readdirSync2,
282
+ readFileSync as readFileSync3,
283
+ writeFileSync as writeFileSync2
284
+ } from "fs";
265
285
  import { createRequire } from "module";
266
- import { join as join3 } from "path";
286
+ import { dirname as dirname2, join as join3 } from "path";
267
287
  import * as p from "@clack/prompts";
268
288
  import { generateKey } from "@xera-ai/core";
269
289
  import pc2 from "picocolors";
@@ -293,17 +313,6 @@ function scaffoldFile(targetPath, templateName, vars) {
293
313
  mkdirSync(dirname(targetPath), { recursive: true });
294
314
  writeFileSync(targetPath, render(tmpl, vars));
295
315
  }
296
- function copyDir(src, dest) {
297
- mkdirSync(dest, { recursive: true });
298
- for (const entry of readdirSync(src, { withFileTypes: true })) {
299
- const s = join2(src, entry.name);
300
- const d = join2(dest, entry.name);
301
- if (entry.isDirectory())
302
- copyDir(s, d);
303
- else
304
- writeFileSync(d, readFileSync2(s));
305
- }
306
- }
307
316
 
308
317
  // src/commands/init.ts
309
318
  var require2 = createRequire(import.meta.url);
@@ -446,15 +455,22 @@ async function initCommand(opts) {
446
455
  writeFileSync2(gitignorePath, `${gitignoreAdditions.trim()}
447
456
  `);
448
457
  }
449
- const skillsSrc = require2.resolve("@xera-ai/skills/package.json");
450
- const skillsDir = join3(skillsSrc, "..");
451
- for (const target of [".claude/skills", ".claude/commands"]) {
452
- copyDir(skillsDir, join3(cwd, target));
453
- for (const name of ["package.json", "version.json", "CHANGELOG.md"]) {
454
- const f = join3(cwd, target, name);
455
- if (existsSync3(f))
456
- unlinkSync(f);
457
- }
458
+ const skillsPkgPath = require2.resolve("@xera-ai/skills/package.json");
459
+ const skillsSrcDir = join3(skillsPkgPath, "..");
460
+ const SKILL_IGNORE = new Set(["package.json", "version.json", "CHANGELOG.md"]);
461
+ for (const name of readdirSync2(skillsSrcDir)) {
462
+ if (SKILL_IGNORE.has(name))
463
+ continue;
464
+ if (!name.endsWith(".md"))
465
+ continue;
466
+ const content = readFileSync3(join3(skillsSrcDir, name));
467
+ const base = name.replace(/\.md$/, "");
468
+ const skillFile = join3(cwd, ".claude/skills", base, "SKILL.md");
469
+ mkdirSync2(dirname2(skillFile), { recursive: true });
470
+ writeFileSync2(skillFile, content);
471
+ const cmdFile = join3(cwd, ".claude/commands", name);
472
+ mkdirSync2(dirname2(cmdFile), { recursive: true });
473
+ writeFileSync2(cmdFile, content);
458
474
  }
459
475
  const pkgPath = join3(cwd, "package.json");
460
476
  const pkg = existsSync3(pkgPath) ? JSON.parse(readFileSync3(pkgPath, "utf8")) : { name: "xera-project", private: true, type: "module" };
@@ -528,13 +544,14 @@ Next:
528
544
  import {
529
545
  copyFileSync,
530
546
  existsSync as existsSync4,
531
- mkdirSync as mkdirSync2,
532
- readdirSync as readdirSync2,
547
+ mkdirSync as mkdirSync3,
548
+ readdirSync as readdirSync3,
533
549
  readFileSync as readFileSync4,
550
+ unlinkSync,
534
551
  writeFileSync as writeFileSync3
535
552
  } from "fs";
536
553
  import { createRequire as createRequire2 } from "module";
537
- import { dirname as dirname2, join as join4 } from "path";
554
+ import { dirname as dirname3, join as join4 } from "path";
538
555
  import * as p2 from "@clack/prompts";
539
556
  import pc3 from "picocolors";
540
557
  var require3 = createRequire2(import.meta.url);
@@ -647,7 +664,7 @@ async function initUpdateCommand(opts) {
647
664
  pkg.scripts["xera:disputes"] = "xera-internal disputes";
648
665
  writeFileSync3(pkgPath, JSON.stringify(pkg, null, 2));
649
666
  const wfDir = join4(cwd, ".github/workflows");
650
- mkdirSync2(wfDir, { recursive: true });
667
+ mkdirSync3(wfDir, { recursive: true });
651
668
  try {
652
669
  const cliPkgPath = require3.resolve("@xera-ai/cli/package.json");
653
670
  const cliTplPath = join4(cliPkgPath, "..", "templates/xera-graph.yml.template");
@@ -658,28 +675,47 @@ async function initUpdateCommand(opts) {
658
675
  }
659
676
  const skillsSrc = require3.resolve("@xera-ai/skills/package.json");
660
677
  const newSkillsDir = join4(skillsSrc, "..");
661
- const targetDirs = [join4(cwd, ".claude/skills"), join4(cwd, ".claude/commands")];
662
- for (const name of readdirSync2(newSkillsDir)) {
678
+ const SKILL_IGNORE = new Set(["package.json", "version.json", "CHANGELOG.md"]);
679
+ for (const name of readdirSync3(newSkillsDir)) {
680
+ if (SKILL_IGNORE.has(name))
681
+ continue;
663
682
  if (!name.endsWith(".md"))
664
683
  continue;
665
684
  const newContent = readFileSync4(join4(newSkillsDir, name), "utf8");
666
- const localStates = targetDirs.map((dir) => {
667
- const path = join4(dir, name);
668
- if (!existsSync4(path))
669
- return { path, state: "missing" };
670
- const content = readFileSync4(path, "utf8");
671
- return { path, state: content === newContent ? "same" : "diff" };
672
- });
673
- if (localStates.every((s) => s.state === "missing")) {
674
- for (const { path } of localStates) {
675
- mkdirSync2(dirname2(path), { recursive: true });
685
+ const base = name.replace(/\.md$/, "");
686
+ const skillPath = join4(cwd, ".claude/skills", base, "SKILL.md");
687
+ const legacyFlatSkillPath = join4(cwd, ".claude/skills", name);
688
+ const cmdPath = join4(cwd, ".claude/commands", name);
689
+ let migratedLegacy = false;
690
+ if (existsSync4(legacyFlatSkillPath) && !existsSync4(skillPath)) {
691
+ const legacyContent = readFileSync4(legacyFlatSkillPath, "utf8");
692
+ mkdirSync3(dirname3(skillPath), { recursive: true });
693
+ writeFileSync3(skillPath, legacyContent);
694
+ unlinkSync(legacyFlatSkillPath);
695
+ migratedLegacy = true;
696
+ }
697
+ const targets = [];
698
+ for (const path of [skillPath, cmdPath]) {
699
+ if (!existsSync4(path)) {
700
+ targets.push({ path, state: "missing" });
701
+ } else {
702
+ const content = readFileSync4(path, "utf8");
703
+ targets.push({ path, state: content === newContent ? "same" : "diff" });
704
+ }
705
+ }
706
+ if (targets.every((s) => s.state === "missing")) {
707
+ for (const { path } of targets) {
708
+ mkdirSync3(dirname3(path), { recursive: true });
676
709
  writeFileSync3(path, newContent);
677
710
  }
678
711
  p2.log.info(`+ ${name}`);
679
712
  continue;
680
713
  }
681
- if (localStates.every((s) => s.state === "same")) {
682
- p2.log.info(`= ${name}`);
714
+ if (targets.every((s) => s.state === "same")) {
715
+ if (migratedLegacy)
716
+ p2.log.success(`migrated ${name} to .claude/skills/${base}/SKILL.md`);
717
+ else
718
+ p2.log.info(`= ${name}`);
683
719
  continue;
684
720
  }
685
721
  const choice = await p2.select({
@@ -690,12 +726,14 @@ async function initUpdateCommand(opts) {
690
726
  ]
691
727
  });
692
728
  if (choice === "overwrite") {
693
- for (const { path } of localStates) {
694
- mkdirSync2(dirname2(path), { recursive: true });
729
+ for (const { path } of targets) {
730
+ mkdirSync3(dirname3(path), { recursive: true });
695
731
  writeFileSync3(path, newContent);
696
732
  }
697
733
  p2.log.success(`overwrote ${name}`);
698
734
  } else {
735
+ if (migratedLegacy)
736
+ p2.log.success(`migrated ${name} to .claude/skills/${base}/SKILL.md`);
699
737
  p2.log.warn(`kept local ${name}`);
700
738
  }
701
739
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xera-ai/cli",
3
- "version": "0.12.1",
3
+ "version": "0.12.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "xera": "./bin/xera"
@@ -15,8 +15,8 @@
15
15
  "typecheck": "tsc --noEmit"
16
16
  },
17
17
  "dependencies": {
18
- "@xera-ai/core": "^0.12.1",
19
- "@xera-ai/skills": "^0.12.1",
18
+ "@xera-ai/core": "^0.12.2",
19
+ "@xera-ai/skills": "^0.12.2",
20
20
  "@clack/prompts": "1.4.0",
21
21
  "cac": "7.0.0",
22
22
  "picocolors": "1.1.1"