@acta-dev/cli 1.1.0 → 1.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 +336 -58
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // src/index.ts
4
4
  import { realpathSync } from "fs";
5
5
  import { fileURLToPath } from "url";
6
- import { defineCommand as defineCommand10, runMain } from "citty";
6
+ import { defineCommand as defineCommand11, runMain } from "citty";
7
7
 
8
8
  // src/commands/build.ts
9
9
  import { buildArtifacts } from "@acta-dev/core";
@@ -222,14 +222,17 @@ var graphCommand = defineCommand2({
222
222
  });
223
223
 
224
224
  // src/commands/init.ts
225
- import { existsSync as existsSync2 } from "fs";
226
- import { mkdir, writeFile } from "fs/promises";
227
- import { join as join2, resolve as resolve2 } from "path";
225
+ import { existsSync as existsSync3 } from "fs";
226
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
227
+ import { join as join3, resolve as resolve2 } from "path";
228
228
  import { createInterface } from "readline";
229
229
  import { resolveConfig as resolveConfig3 } from "@acta-dev/core";
230
230
  import { defineCommand as defineCommand3 } from "citty";
231
231
 
232
232
  // src/skill.ts
233
+ import { existsSync as existsSync2 } from "fs";
234
+ import { mkdir, readFile, writeFile } from "fs/promises";
235
+ import { join as join2 } from "path";
233
236
  import {
234
237
  adrStatuses,
235
238
  documentKinds,
@@ -239,6 +242,7 @@ import {
239
242
  specStatuses
240
243
  } from "@acta-dev/core";
241
244
  var SKILL_NAME = "acta-document";
245
+ var skillFormats = ["codex", "claude", "both"];
242
246
  var LINK_DESCRIPTIONS = {
243
247
  related: "Loosely related documents.",
244
248
  supersedes: "This document supersedes the target; mirror with `replacedBy`.",
@@ -250,9 +254,9 @@ var LINK_DESCRIPTIONS = {
250
254
  };
251
255
  function table(header, rows) {
252
256
  const head = `| ${header.join(" | ")} |`;
253
- const sep = `| ${header.map(() => "---").join(" | ")} |`;
257
+ const sep2 = `| ${header.map(() => "---").join(" | ")} |`;
254
258
  const body = rows.map((r) => `| ${r.join(" | ")} |`).join("\n");
255
- return [head, sep, body].join("\n");
259
+ return [head, sep2, body].join("\n");
256
260
  }
257
261
  function renderSkill() {
258
262
  const config = resolveConfig2({}, { rootDir: process.cwd() });
@@ -388,6 +392,31 @@ ${block}
388
392
  ` : `${block}
389
393
  `;
390
394
  }
395
+ async function installAgentSkill(cwd, format) {
396
+ const skillPaths = [];
397
+ const skillContent = renderSkill();
398
+ if (format === "codex" || format === "both") {
399
+ const skillDir = join2(cwd, ".agents", "skills", SKILL_NAME);
400
+ await mkdir(skillDir, { recursive: true });
401
+ const skillPath = join2(skillDir, "SKILL.md");
402
+ await writeFile(skillPath, skillContent, "utf8");
403
+ skillPaths.push(skillPath);
404
+ const agentsPath = join2(cwd, "AGENTS.md");
405
+ const existing = existsSync2(agentsPath) ? await readFile(agentsPath, "utf8") : "";
406
+ await writeFile(agentsPath, upsertAgentsBlock(existing), "utf8");
407
+ if (format === "codex") {
408
+ return { skillPaths, agentsPath };
409
+ }
410
+ }
411
+ if (format === "claude" || format === "both") {
412
+ const skillDir = join2(cwd, ".claude", "skills", SKILL_NAME);
413
+ await mkdir(skillDir, { recursive: true });
414
+ const skillPath = join2(skillDir, "SKILL.md");
415
+ await writeFile(skillPath, skillContent, "utf8");
416
+ skillPaths.push(skillPath);
417
+ }
418
+ return format === "both" ? { skillPaths, agentsPath: join2(cwd, "AGENTS.md") } : { skillPaths };
419
+ }
391
420
 
392
421
  // src/commands/init.ts
393
422
  var ADR_TEMPLATE = `---
@@ -566,7 +595,7 @@ async function confirm(message) {
566
595
  });
567
596
  }
568
597
  async function safeWriteFile(filePath, content, yes) {
569
- if (existsSync2(filePath)) {
598
+ if (existsSync3(filePath)) {
570
599
  if (!yes) {
571
600
  const ok = await confirm(` Overwrite ${filePath}?`);
572
601
  if (!ok) {
@@ -577,7 +606,7 @@ async function safeWriteFile(filePath, content, yes) {
577
606
  printWarn(`Overwriting ${filePath}`);
578
607
  }
579
608
  }
580
- await writeFile(filePath, content, "utf8");
609
+ await writeFile2(filePath, content, "utf8");
581
610
  return true;
582
611
  }
583
612
  var initCommand = defineCommand3({
@@ -604,7 +633,7 @@ var initCommand = defineCommand3({
604
633
  },
605
634
  skill: {
606
635
  type: "boolean",
607
- description: "Install the acta-document agent skill and AGENTS.md guidance",
636
+ description: "Compatibility alias for `acta skill --init` after scaffolding",
608
637
  default: false
609
638
  },
610
639
  config: {
@@ -619,7 +648,7 @@ var initCommand = defineCommand3({
619
648
  const config = resolveConfig3({}, { rootDir: cwd });
620
649
  printLine("Initializing Acta docs structure...");
621
650
  printLine();
622
- const configPath = join2(cwd, "acta.config.ts");
651
+ const configPath = join3(cwd, "acta.config.ts");
623
652
  const configWritten = await safeWriteFile(configPath, CONFIG_TEMPLATE, yes);
624
653
  if (configWritten) printSuccess(`Created ${configPath}`);
625
654
  const dirs = [
@@ -628,47 +657,44 @@ var initCommand = defineCommand3({
628
657
  config.resolvedDocs.templatesDir
629
658
  ];
630
659
  for (const dir of dirs) {
631
- await mkdir(dir, { recursive: true });
660
+ await mkdir2(dir, { recursive: true });
632
661
  printSuccess(`Created dir ${dir}`);
633
662
  }
634
- const adrTplPath = join2(config.resolvedDocs.templatesDir, "adr.md");
635
- const specTplPath = join2(config.resolvedDocs.templatesDir, "spec.md");
663
+ const adrTplPath = join3(config.resolvedDocs.templatesDir, "adr.md");
664
+ const specTplPath = join3(config.resolvedDocs.templatesDir, "spec.md");
636
665
  const adrWritten = await safeWriteFile(adrTplPath, ADR_TEMPLATE, yes);
637
666
  if (adrWritten) printSuccess(`Created ${adrTplPath}`);
638
667
  const specWritten = await safeWriteFile(specTplPath, SPEC_TEMPLATE, yes);
639
668
  if (specWritten) printSuccess(`Created ${specTplPath}`);
640
- const gitignorePath = join2(cwd, ".gitignore");
641
- if (existsSync2(gitignorePath)) {
642
- const { readFile: readFile3, appendFile } = await import("fs/promises");
643
- const content = await readFile3(gitignorePath, "utf8");
669
+ const gitignorePath = join3(cwd, ".gitignore");
670
+ if (existsSync3(gitignorePath)) {
671
+ const { readFile: readFile4, appendFile } = await import("fs/promises");
672
+ const content = await readFile4(gitignorePath, "utf8");
644
673
  if (!content.includes(".acta/")) {
645
674
  await appendFile(gitignorePath, "\n# Acta build artifacts\n.acta/\n");
646
675
  printSuccess(`Added .acta/ to .gitignore`);
647
676
  }
648
677
  }
649
678
  if (args.hooks) {
650
- const lefthookPath = join2(cwd, "lefthook.yml");
679
+ const lefthookPath = join3(cwd, "lefthook.yml");
651
680
  const lefthookWritten = await safeWriteFile(lefthookPath, LEFTHOOK_TEMPLATE, yes);
652
681
  if (lefthookWritten) printSuccess(`Created ${lefthookPath}`);
653
682
  }
654
683
  if (args["github-action"]) {
655
- const workflowsDir = join2(cwd, ".github", "workflows");
656
- await mkdir(workflowsDir, { recursive: true });
657
- const workflowPath = join2(workflowsDir, "acta-ci.yml");
684
+ const workflowsDir = join3(cwd, ".github", "workflows");
685
+ await mkdir2(workflowsDir, { recursive: true });
686
+ const workflowPath = join3(workflowsDir, "acta-ci.yml");
658
687
  const workflowWritten = await safeWriteFile(workflowPath, GITHUB_ACTION_TEMPLATE, yes);
659
688
  if (workflowWritten) printSuccess(`Created ${workflowPath}`);
660
689
  }
661
690
  if (args.skill) {
662
- const skillDir = join2(cwd, ".claude", "skills", "acta-document");
663
- await mkdir(skillDir, { recursive: true });
664
- const skillPath = join2(skillDir, "SKILL.md");
665
- const skillWritten = await safeWriteFile(skillPath, renderSkill(), yes);
666
- if (skillWritten) printSuccess(`Created ${skillPath}`);
667
- const { readFile: readFile3 } = await import("fs/promises");
668
- const agentsPath = join2(cwd, "AGENTS.md");
669
- const existing = existsSync2(agentsPath) ? await readFile3(agentsPath, "utf8") : "";
670
- await writeFile(agentsPath, upsertAgentsBlock(existing), "utf8");
671
- printSuccess(`Updated ${agentsPath} with Acta agent guidance`);
691
+ const result = await installAgentSkill(cwd, "both");
692
+ for (const skillPath of result.skillPaths) {
693
+ printSuccess(`Installed ${skillPath}`);
694
+ }
695
+ if (result.agentsPath) {
696
+ printSuccess(`Updated ${result.agentsPath} with Acta agent guidance`);
697
+ }
672
698
  }
673
699
  printLine();
674
700
  printSuccess("Acta initialized. Run `acta validate` to check your documents.");
@@ -782,9 +808,9 @@ var listCommand = defineCommand4({
782
808
  });
783
809
 
784
810
  // src/commands/new.ts
785
- import { existsSync as existsSync3 } from "fs";
786
- import { writeFile as writeFile2 } from "fs/promises";
787
- import { join as join4, relative } from "path";
811
+ import { existsSync as existsSync4 } from "fs";
812
+ import { writeFile as writeFile3 } from "fs/promises";
813
+ import { join as join5, relative } from "path";
788
814
  import { adrStatuses as adrStatuses2, specStatuses as specStatuses2 } from "@acta-dev/core";
789
815
  import { defineCommand as defineCommand5 } from "citty";
790
816
 
@@ -805,11 +831,11 @@ function titleToSlug(title) {
805
831
  }
806
832
 
807
833
  // src/template.ts
808
- import { readFile } from "fs/promises";
809
- import { join as join3 } from "path";
834
+ import { readFile as readFile2 } from "fs/promises";
835
+ import { join as join4 } from "path";
810
836
  async function renderTemplate(kind, vars, config) {
811
- const templateFile = join3(config.resolvedDocs.templatesDir, `${kind}.md`);
812
- const raw = await readFile(templateFile, "utf8");
837
+ const templateFile = join4(config.resolvedDocs.templatesDir, `${kind}.md`);
838
+ const raw = await readFile2(templateFile, "utf8");
813
839
  return interpolate(raw, vars);
814
840
  }
815
841
  function interpolate(raw, vars) {
@@ -854,8 +880,8 @@ async function createDocument(kind, title, opts) {
854
880
  const slug = titleToSlug(title.trim());
855
881
  const filename = `${id}-${slug}.md`;
856
882
  const dir = kind === "adr" ? config.resolvedDocs.adrDir : config.resolvedDocs.specDir;
857
- const destPath = join4(dir, filename);
858
- if (existsSync3(destPath)) {
883
+ const destPath = join5(dir, filename);
884
+ if (existsSync4(destPath)) {
859
885
  exitFailure(`File already exists: ${destPath}`);
860
886
  }
861
887
  const content = await renderTemplate(
@@ -863,7 +889,7 @@ async function createDocument(kind, title, opts) {
863
889
  { id, title: title.trim(), date: nowIsoDateTime(), status, tags: parseTags(opts.tags) },
864
890
  config
865
891
  );
866
- await writeFile2(destPath, content, "utf8");
892
+ await writeFile3(destPath, content, "utf8");
867
893
  if (opts.json) {
868
894
  printJson({
869
895
  id,
@@ -971,8 +997,8 @@ function parseTags(value) {
971
997
  }
972
998
 
973
999
  // src/commands/renumber.ts
974
- import { readFile as readFile2, rename, writeFile as writeFile3 } from "fs/promises";
975
- import { basename, dirname, join as join5 } from "path";
1000
+ import { readFile as readFile3, rename, writeFile as writeFile4 } from "fs/promises";
1001
+ import { basename, dirname, join as join6 } from "path";
976
1002
  import { internalLinkKeys as internalLinkKeys2, loadProject as loadProject3 } from "@acta-dev/core";
977
1003
  import { defineCommand as defineCommand6 } from "citty";
978
1004
  import kleur4 from "kleur";
@@ -996,7 +1022,7 @@ function buildRenumberPlan(fromId, toId, project) {
996
1022
  const oldFilename = basename(target.file.path);
997
1023
  const oldSlug = oldFilename.replace(`${target.id}-`, "").replace(/\.md$/, "");
998
1024
  const newFilename = `${toId}-${oldSlug}.md`;
999
- const newPath = join5(dirname(target.file.path), newFilename);
1025
+ const newPath = join6(dirname(target.file.path), newFilename);
1000
1026
  const affectedDocs = project.documents.filter((d) => d.id !== target.id).filter((d) => internalLinkKeys2.some((key) => d.links[key].includes(target.id))).map((d) => ({ doc: d, path: d.file.path }));
1001
1027
  return {
1002
1028
  target,
@@ -1007,7 +1033,7 @@ function buildRenumberPlan(fromId, toId, project) {
1007
1033
  };
1008
1034
  }
1009
1035
  async function rewriteDocument(filePath, oldId, newId, isTarget) {
1010
- const raw = await readFile2(filePath, "utf8");
1036
+ const raw = await readFile3(filePath, "utf8");
1011
1037
  const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
1012
1038
  if (!match) {
1013
1039
  throw new Error(`Cannot parse frontmatter in ${filePath}`);
@@ -1091,11 +1117,11 @@ var renumberCommand = defineCommand6({
1091
1117
  printLine();
1092
1118
  for (const { doc, path } of plan.affectedDocs) {
1093
1119
  const rewritten = await rewriteDocument(path, fromId, toId, false);
1094
- await writeFile3(path, rewritten, "utf8");
1120
+ await writeFile4(path, rewritten, "utf8");
1095
1121
  printSuccess(`Updated links in ${doc.id}`);
1096
1122
  }
1097
1123
  const rewrittenTarget = await rewriteDocument(plan.oldPath, fromId, toId, true);
1098
- await writeFile3(plan.oldPath, rewrittenTarget, "utf8");
1124
+ await writeFile4(plan.oldPath, rewrittenTarget, "utf8");
1099
1125
  await rename(plan.oldPath, plan.newPath);
1100
1126
  printSuccess(`Renamed ${basename(plan.oldPath)} \u2192 ${plan.newFilename}`);
1101
1127
  printLine();
@@ -1217,8 +1243,11 @@ function formatShowDate(value) {
1217
1243
 
1218
1244
  // src/commands/site.ts
1219
1245
  import { spawn } from "child_process";
1246
+ import { createReadStream } from "fs";
1247
+ import { stat } from "fs/promises";
1248
+ import { createServer } from "http";
1220
1249
  import { createRequire } from "module";
1221
- import { dirname as dirname2, join as join6, resolve as resolve3 } from "path";
1250
+ import { dirname as dirname2, extname, join as join7, relative as relative2, resolve as resolve3, sep } from "path";
1222
1251
  import { buildArtifacts as buildArtifacts2 } from "@acta-dev/core";
1223
1252
  import { defineCommand as defineCommand8 } from "citty";
1224
1253
  import kleur6 from "kleur";
@@ -1229,6 +1258,16 @@ function resolveSiteOptions(config, args, cwd = process.cwd()) {
1229
1258
  site: args.site ?? config.site.url
1230
1259
  };
1231
1260
  }
1261
+ function resolveSiteServeOptions(args) {
1262
+ const port = args.port === void 0 ? 4321 : Number(args.port);
1263
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
1264
+ throw new Error("Expected --port to be an integer between 0 and 65535.");
1265
+ }
1266
+ return {
1267
+ host: args.host ?? "127.0.0.1",
1268
+ port
1269
+ };
1270
+ }
1232
1271
  function buildSiteEnv(config, options) {
1233
1272
  const env = {
1234
1273
  ...process.env,
@@ -1264,6 +1303,19 @@ var siteCommand = defineCommand8({
1264
1303
  description: "Reuse existing artifacts instead of running `acta build` first",
1265
1304
  default: false
1266
1305
  },
1306
+ serve: {
1307
+ type: "boolean",
1308
+ description: "Serve the generated site locally after building it",
1309
+ default: false
1310
+ },
1311
+ host: {
1312
+ type: "string",
1313
+ description: "Host for --serve (default: 127.0.0.1)"
1314
+ },
1315
+ port: {
1316
+ type: "string",
1317
+ description: "Port for --serve (default: 4321)"
1318
+ },
1267
1319
  config: {
1268
1320
  type: "string",
1269
1321
  alias: "c",
@@ -1278,6 +1330,18 @@ var siteCommand = defineCommand8({
1278
1330
  async run({ args }) {
1279
1331
  const { config } = await resolveContext({ config: args.config });
1280
1332
  const json = Boolean(args.json);
1333
+ const serve = Boolean(args.serve);
1334
+ if (json && serve) {
1335
+ return exitUsage("`acta site --serve` cannot be combined with --json.");
1336
+ }
1337
+ let serveOptions;
1338
+ if (serve) {
1339
+ try {
1340
+ serveOptions = resolveSiteServeOptions(args);
1341
+ } catch (error) {
1342
+ return exitUsage(error instanceof Error ? error.message : String(error));
1343
+ }
1344
+ }
1281
1345
  let documentCount = 0;
1282
1346
  if (!args["skip-build"]) {
1283
1347
  if (!json) printLine("Building artifacts...");
@@ -1320,6 +1384,23 @@ var siteCommand = defineCommand8({
1320
1384
  printSuccess("Site built");
1321
1385
  printLine(` ${kleur6.bold("Output:")} ${options.outDir}`);
1322
1386
  if (options.base) printLine(` ${kleur6.bold("Base:")} ${options.base}`);
1387
+ if (serveOptions) {
1388
+ try {
1389
+ const preview = await serveStaticSite({
1390
+ root: options.outDir,
1391
+ base: options.base,
1392
+ ...serveOptions
1393
+ });
1394
+ printLine(` ${kleur6.bold("Serving:")} ${preview.url}`);
1395
+ printLine();
1396
+ printLine(kleur6.dim("Press Ctrl+C to stop the preview server."));
1397
+ await waitForShutdown(preview);
1398
+ return;
1399
+ } catch (error) {
1400
+ const message = error instanceof Error ? error.message : String(error);
1401
+ return exitFailure(message);
1402
+ }
1403
+ }
1323
1404
  printLine();
1324
1405
  printLine(kleur6.dim("Deploy the contents of the output directory to any static host."));
1325
1406
  }
@@ -1333,13 +1414,13 @@ function resolveWebPackageDir() {
1333
1414
  }
1334
1415
  }
1335
1416
  function resolveAstroBin(webDir) {
1336
- const require2 = createRequire(join6(webDir, "package.json"));
1417
+ const require2 = createRequire(join7(webDir, "package.json"));
1337
1418
  try {
1338
1419
  const astroPkgJsonPath = require2.resolve("astro/package.json");
1339
1420
  const astroPkg = require2("astro/package.json");
1340
1421
  const binRel = typeof astroPkg.bin === "string" ? astroPkg.bin : astroPkg.bin?.astro;
1341
1422
  if (!binRel) return void 0;
1342
- return join6(dirname2(astroPkgJsonPath), binRel);
1423
+ return join7(dirname2(astroPkgJsonPath), binRel);
1343
1424
  } catch {
1344
1425
  return void 0;
1345
1426
  }
@@ -1356,14 +1437,210 @@ function runAstroBuild(astroBin, webDir, env, json) {
1356
1437
  child.on("error", () => resolvePromise(1));
1357
1438
  });
1358
1439
  }
1440
+ var mimeTypes = {
1441
+ ".css": "text/css; charset=utf-8",
1442
+ ".html": "text/html; charset=utf-8",
1443
+ ".ico": "image/x-icon",
1444
+ ".jpeg": "image/jpeg",
1445
+ ".jpg": "image/jpeg",
1446
+ ".js": "text/javascript; charset=utf-8",
1447
+ ".json": "application/json; charset=utf-8",
1448
+ ".mjs": "text/javascript; charset=utf-8",
1449
+ ".png": "image/png",
1450
+ ".svg": "image/svg+xml",
1451
+ ".txt": "text/plain; charset=utf-8",
1452
+ ".wasm": "application/wasm",
1453
+ ".webp": "image/webp"
1454
+ };
1455
+ function previewUrl(host, port, base) {
1456
+ const basePath = normalizeBasePath(base);
1457
+ const path = basePath === "/" ? "/" : `${basePath}/`;
1458
+ return `http://${host}:${port}${path}`;
1459
+ }
1460
+ async function serveStaticSite(options) {
1461
+ const root = resolve3(options.root);
1462
+ const basePath = normalizeBasePath(options.base);
1463
+ const server = createServer(async (request, response) => {
1464
+ const result = await resolveStaticSiteResponse({
1465
+ root,
1466
+ base: basePath,
1467
+ method: request.method ?? "GET",
1468
+ url: request.url ?? "/"
1469
+ });
1470
+ if (!result.path) {
1471
+ if (request.method === "HEAD") {
1472
+ response.statusCode = result.status;
1473
+ response.setHeader("Content-Type", result.contentType);
1474
+ response.end();
1475
+ return;
1476
+ }
1477
+ sendText(response, result.status, result.text ?? "Not Found");
1478
+ return;
1479
+ }
1480
+ response.statusCode = result.status;
1481
+ response.setHeader("Content-Type", result.contentType);
1482
+ if (result.contentLength !== void 0) {
1483
+ response.setHeader("Content-Length", String(result.contentLength));
1484
+ }
1485
+ if (request.method === "HEAD") {
1486
+ response.end();
1487
+ return;
1488
+ }
1489
+ createReadStream(result.path).pipe(response);
1490
+ });
1491
+ await new Promise((resolvePromise, reject) => {
1492
+ server.once("error", reject);
1493
+ server.listen(options.port, options.host, () => {
1494
+ server.off("error", reject);
1495
+ resolvePromise();
1496
+ });
1497
+ }).catch((error) => {
1498
+ if (isAddressInUse(error)) {
1499
+ throw new Error(
1500
+ `Port ${options.port} is already in use on ${options.host}. Try a different --port.`
1501
+ );
1502
+ }
1503
+ throw error;
1504
+ });
1505
+ const address = server.address();
1506
+ const port = typeof address === "object" && address ? address.port : options.port;
1507
+ return {
1508
+ server,
1509
+ url: previewUrl(options.host, port, options.base),
1510
+ close: () => new Promise((resolvePromise, reject) => {
1511
+ server.close((error) => error ? reject(error) : resolvePromise());
1512
+ })
1513
+ };
1514
+ }
1515
+ async function resolveStaticSiteResponse(options) {
1516
+ if (options.method !== "GET" && options.method !== "HEAD") {
1517
+ return {
1518
+ status: 405,
1519
+ contentType: "text/plain; charset=utf-8",
1520
+ text: "Method Not Allowed"
1521
+ };
1522
+ }
1523
+ const root = resolve3(options.root);
1524
+ const basePath = normalizeBasePath(options.base);
1525
+ const filePath = await resolveRequestPath(root, basePath, options.url);
1526
+ if (!filePath) {
1527
+ return { status: 404, contentType: "text/plain; charset=utf-8", text: "Not Found" };
1528
+ }
1529
+ try {
1530
+ const fileStat = await stat(filePath);
1531
+ const finalPath = fileStat.isDirectory() ? join7(filePath, "index.html") : filePath;
1532
+ const finalStat = fileStat.isDirectory() ? await stat(finalPath) : fileStat;
1533
+ if (!finalStat.isFile()) {
1534
+ return { status: 404, contentType: "text/plain; charset=utf-8", text: "Not Found" };
1535
+ }
1536
+ return {
1537
+ status: 200,
1538
+ contentType: mimeTypes[extname(finalPath)] ?? "application/octet-stream",
1539
+ contentLength: finalStat.size,
1540
+ path: finalPath
1541
+ };
1542
+ } catch {
1543
+ return { status: 404, contentType: "text/plain; charset=utf-8", text: "Not Found" };
1544
+ }
1545
+ }
1546
+ async function resolveRequestPath(root, basePath, requestUrl) {
1547
+ let pathname;
1548
+ try {
1549
+ pathname = decodeURIComponent(new URL(requestUrl, "http://localhost").pathname);
1550
+ } catch {
1551
+ return void 0;
1552
+ }
1553
+ if (basePath !== "/") {
1554
+ if (pathname === basePath) {
1555
+ pathname = "/";
1556
+ } else if (pathname.startsWith(`${basePath}/`)) {
1557
+ pathname = pathname.slice(basePath.length);
1558
+ } else {
1559
+ return void 0;
1560
+ }
1561
+ }
1562
+ const normalized = pathname.startsWith("/") ? pathname : `/${pathname}`;
1563
+ const candidate = resolve3(root, `.${normalized}`);
1564
+ const rel = relative2(root, candidate);
1565
+ if (rel === ".." || rel.startsWith(`..${sep}`) || rel === "" || rel.startsWith("/") || rel === ".") {
1566
+ return rel === "." || rel === "" ? root : void 0;
1567
+ }
1568
+ return candidate;
1569
+ }
1570
+ function sendText(response, status, text) {
1571
+ response.statusCode = status;
1572
+ response.setHeader("Content-Type", "text/plain; charset=utf-8");
1573
+ response.end(text);
1574
+ }
1575
+ function normalizeBasePath(base) {
1576
+ if (!base || base === "/") return "/";
1577
+ const withLeading = base.startsWith("/") ? base : `/${base}`;
1578
+ return withLeading.replace(/\/+$/, "") || "/";
1579
+ }
1580
+ function waitForShutdown(preview) {
1581
+ return new Promise((resolvePromise) => {
1582
+ const shutdown = async () => {
1583
+ process.off("SIGINT", shutdown);
1584
+ process.off("SIGTERM", shutdown);
1585
+ await preview.close();
1586
+ resolvePromise();
1587
+ };
1588
+ process.once("SIGINT", shutdown);
1589
+ process.once("SIGTERM", shutdown);
1590
+ });
1591
+ }
1592
+ function isAddressInUse(error) {
1593
+ return typeof error === "object" && error !== null && "code" in error && error.code === "EADDRINUSE";
1594
+ }
1595
+
1596
+ // src/commands/skill.ts
1597
+ import { resolve as resolve4 } from "path";
1598
+ import { defineCommand as defineCommand9 } from "citty";
1599
+ function parseSkillFormat(value) {
1600
+ const format = value ?? "both";
1601
+ if (!skillFormats.includes(format)) {
1602
+ exitUsage(`Unknown skill format "${format}". Use: codex, claude, both`);
1603
+ }
1604
+ return format;
1605
+ }
1606
+ var skillCommand = defineCommand9({
1607
+ meta: {
1608
+ name: "skill",
1609
+ description: "Install or refresh the bundled acta-document agent skill"
1610
+ },
1611
+ args: {
1612
+ init: {
1613
+ type: "boolean",
1614
+ description: "Install or overwrite the bundled acta-document skill",
1615
+ default: false
1616
+ },
1617
+ format: {
1618
+ type: "string",
1619
+ description: "Skill target format: codex | claude | both (default: both)"
1620
+ }
1621
+ },
1622
+ async run({ args }) {
1623
+ if (!args.init) {
1624
+ exitUsage("Usage: acta skill --init [--format codex|claude|both]");
1625
+ }
1626
+ const cwd = resolve4(process.cwd());
1627
+ const result = await installAgentSkill(cwd, parseSkillFormat(args.format));
1628
+ for (const skillPath of result.skillPaths) {
1629
+ printSuccess(`Installed ${skillPath}`);
1630
+ }
1631
+ if (result.agentsPath) {
1632
+ printSuccess(`Updated ${result.agentsPath} with Acta agent guidance`);
1633
+ }
1634
+ }
1635
+ });
1359
1636
 
1360
1637
  // src/commands/validate.ts
1361
- import { mkdir as mkdir2, writeFile as writeFile4 } from "fs/promises";
1362
- import { join as join7 } from "path";
1638
+ import { mkdir as mkdir3, writeFile as writeFile5 } from "fs/promises";
1639
+ import { join as join8 } from "path";
1363
1640
  import { validateLoadedProject } from "@acta-dev/core";
1364
- import { defineCommand as defineCommand9 } from "citty";
1641
+ import { defineCommand as defineCommand10 } from "citty";
1365
1642
  import kleur7 from "kleur";
1366
- var validateCommand = defineCommand9({
1643
+ var validateCommand = defineCommand10({
1367
1644
  meta: {
1368
1645
  name: "validate",
1369
1646
  description: "Validate frontmatter, IDs, links, sections and repository rules"
@@ -1394,9 +1671,9 @@ var validateCommand = defineCommand9({
1394
1671
  return;
1395
1672
  }
1396
1673
  if (args.ci) {
1397
- await mkdir2(config.resolvedBuild.outDir, { recursive: true });
1398
- const outPath = join7(config.resolvedBuild.outDir, "validation.json");
1399
- await writeFile4(outPath, `${JSON.stringify(result, null, 2)}
1674
+ await mkdir3(config.resolvedBuild.outDir, { recursive: true });
1675
+ const outPath = join8(config.resolvedBuild.outDir, "validation.json");
1676
+ await writeFile5(outPath, `${JSON.stringify(result, null, 2)}
1400
1677
  `, "utf8");
1401
1678
  if (result.errors.length > 0) {
1402
1679
  for (const issue of result.errors) {
@@ -1433,7 +1710,7 @@ var validateCommand = defineCommand9({
1433
1710
  });
1434
1711
 
1435
1712
  // src/index.ts
1436
- var main = defineCommand10({
1713
+ var main = defineCommand11({
1437
1714
  meta: {
1438
1715
  name: "acta",
1439
1716
  version: "0.0.0",
@@ -1448,6 +1725,7 @@ var main = defineCommand10({
1448
1725
  graph: graphCommand,
1449
1726
  build: buildCommand,
1450
1727
  site: siteCommand,
1728
+ skill: skillCommand,
1451
1729
  renumber: renumberCommand
1452
1730
  }
1453
1731
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@acta-dev/cli",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Acta CLI — TypeScript-first docs-as-code tooling for ADR and spec documents in Git. Provides the `acta` binary.",
5
5
  "keywords": [
6
6
  "adr",