@appsforgood/next-supabase-kit 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +2 -1
  3. package/UPGRADE.md +12 -0
  4. package/USER_GUIDE.html +9 -2
  5. package/USER_GUIDE.md +1 -0
  6. package/dist/index.js +319 -242
  7. package/dist/index.js.map +1 -1
  8. package/examples/next-supabase-installed/.antigravity/runtime-skills/deslop/SKILL.md +7 -1
  9. package/examples/next-supabase-installed/.antigravity/runtime-skills/frontend-design/SKILL.md +3 -1
  10. package/examples/next-supabase-installed/.antigravity/runtime-skills/planning/SKILL.md +1 -1
  11. package/examples/next-supabase-installed/.antigravity/runtime-skills/testing-qa/SKILL.md +2 -0
  12. package/examples/next-supabase-installed/.cursor/skills/deslop/SKILL.md +7 -1
  13. package/examples/next-supabase-installed/.cursor/skills/frontend-design/SKILL.md +3 -1
  14. package/examples/next-supabase-installed/.cursor/skills/planning/SKILL.md +1 -1
  15. package/examples/next-supabase-installed/.cursor/skills/testing-qa/SKILL.md +2 -0
  16. package/examples/next-supabase-installed/USER_GUIDE.html +9 -2
  17. package/examples/next-supabase-installed/USER_GUIDE.md +1 -0
  18. package/examples/next-supabase-installed/skills/deslop/SKILL.md +7 -1
  19. package/examples/next-supabase-installed/skills/frontend-design/SKILL.md +3 -1
  20. package/examples/next-supabase-installed/skills/planning/SKILL.md +1 -1
  21. package/examples/next-supabase-installed/skills/testing-qa/SKILL.md +2 -0
  22. package/package.json +1 -1
  23. package/skills/deslop/SKILL.md +7 -1
  24. package/skills/frontend-design/SKILL.md +3 -1
  25. package/skills/planning/SKILL.md +1 -1
  26. package/skills/testing-qa/SKILL.md +2 -0
package/dist/index.js CHANGED
@@ -25,11 +25,12 @@ function findPackageRoot(start = dirname(fileURLToPath(import.meta.url))) {
25
25
  }
26
26
 
27
27
  // src/catalog.ts
28
- var cached = null;
28
+ var catalogByRoot = /* @__PURE__ */ new Map();
29
29
  function loadCatalog(packageRoot = findPackageRoot()) {
30
- if (cached && packageRoot === findPackageRoot()) return cached;
30
+ const existing = catalogByRoot.get(packageRoot);
31
+ if (existing) return existing;
31
32
  const parsed = JSON.parse(readFileSync(join2(packageRoot, "catalog.json"), "utf8"));
32
- cached = parsed;
33
+ catalogByRoot.set(packageRoot, parsed);
33
34
  return parsed;
34
35
  }
35
36
  function agentSourcePath(packageRoot, id) {
@@ -423,8 +424,222 @@ function addSkill(cwd, skillName, options = {}) {
423
424
  }
424
425
 
425
426
  // src/install/adapter-validate.ts
427
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
428
+ import { join as join7 } from "path";
429
+
430
+ // src/config/defaults.ts
431
+ var PACKAGE_NAME = "@appsforgood/next-supabase-kit";
432
+ var PACKAGE_VERSION = "0.4.3";
433
+ var ROOT_DOCS = ["AGENTS.md", "USER_GUIDE.md", "USER_GUIDE.html"];
434
+ var CURSOR_RULE_FILE = {
435
+ source: "assistant-adapters/cursor-agent-kit.mdc",
436
+ target: ".cursor/rules/cursor-agent-kit.mdc"
437
+ };
438
+ var CLAUDE_TEMPLATE = "templates/next-supabase/CLAUDE.md";
439
+ var USER_GUIDE_SOURCE = "USER_GUIDE.md";
440
+ var USER_GUIDE_HTML_SOURCE = "USER_GUIDE.html";
441
+ var AGENTS_DOC_SOURCE = "templates/next-supabase/AGENTS.md";
442
+
443
+ // src/install/ide-activate.ts
444
+ var IDE_TARGETS = ["cursor", "claude", "codex", "copilot", "antigravity"];
445
+ var ALLOWED = new Set(IDE_TARGETS);
446
+ function isIdeTarget(value) {
447
+ return ALLOWED.has(value);
448
+ }
449
+ var InvalidActivateTargetError = class extends Error {
450
+ constructor(invalid) {
451
+ super(`Unknown --activate target(s): ${invalid.join(", ")}. Allowed: cursor, claude, codex, copilot, antigravity, all.`);
452
+ this.invalid = invalid;
453
+ this.name = "InvalidActivateTargetError";
454
+ }
455
+ invalid;
456
+ };
457
+ function parseActivateTargets(raw) {
458
+ if (!raw || raw.length === 0) return [];
459
+ return normalizeTargets(raw.flatMap((value) => value.split(",")));
460
+ }
461
+ function normalizeTargets(targets) {
462
+ const normalized = /* @__PURE__ */ new Set();
463
+ const invalid = [];
464
+ for (const target of targets) {
465
+ const value = target.trim().toLowerCase();
466
+ if (!value) continue;
467
+ if (value === "all") {
468
+ for (const item of ALLOWED) normalized.add(item);
469
+ continue;
470
+ }
471
+ if (isIdeTarget(value)) {
472
+ normalized.add(value);
473
+ } else {
474
+ invalid.push(target.trim());
475
+ }
476
+ }
477
+ if (invalid.length > 0) throw new InvalidActivateTargetError(invalid);
478
+ return [...normalized];
479
+ }
480
+ function activateIdeTargets(options) {
481
+ const cwd = options.cwd;
482
+ const packageRoot = findPackageRoot();
483
+ const targets = normalizeTargets(options.targets);
484
+ const force = Boolean(options.force);
485
+ const collector = emptyCollector();
486
+ const result = { activated: targets, ...collector };
487
+ if (targets.length === 0) return result;
488
+ if (targets.includes("cursor")) {
489
+ copyFromPackage(cwd, packageRoot, CURSOR_RULE_FILE.source, CURSOR_RULE_FILE.target, force, result);
490
+ generateCursorAgents(cwd, force, result);
491
+ generateCursorSkills(cwd, force, result);
492
+ }
493
+ if (targets.includes("claude")) {
494
+ copyFromPackage(cwd, packageRoot, CLAUDE_TEMPLATE, "CLAUDE.md", force, result);
495
+ generateClaudeAgents(cwd, force, result);
496
+ }
497
+ if (targets.includes("codex")) {
498
+ generateCodexAgents(cwd, force, result);
499
+ }
500
+ if (targets.includes("copilot")) {
501
+ generateCopilotInstructions(cwd, force, result);
502
+ }
503
+ if (targets.includes("antigravity")) {
504
+ generateAntigravityCommands(cwd, force, result);
505
+ }
506
+ return result;
507
+ }
508
+
509
+ // src/install/install.ts
426
510
  import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
511
+ import { join as join6 } from "path";
512
+
513
+ // src/install/managed-assets.ts
427
514
  import { join as join5 } from "path";
515
+ function listManagedAssets(packageRoot, options = {}) {
516
+ const assets = [
517
+ { target: "AGENTS.md", sourcePath: join5(packageRoot, AGENTS_DOC_SOURCE), category: "root-doc" },
518
+ { target: "USER_GUIDE.md", sourcePath: join5(packageRoot, USER_GUIDE_SOURCE), category: "root-doc" },
519
+ { target: "USER_GUIDE.html", sourcePath: join5(packageRoot, USER_GUIDE_HTML_SOURCE), category: "root-doc" },
520
+ { target: CURSOR_RULE_FILE.target, sourcePath: join5(packageRoot, CURSOR_RULE_FILE.source), category: "adapter" }
521
+ ];
522
+ const activated = new Set(options.activated ?? ["cursor"]);
523
+ if (activated.has("claude")) {
524
+ assets.push({
525
+ target: "CLAUDE.md",
526
+ sourcePath: join5(packageRoot, "templates/next-supabase/CLAUDE.md"),
527
+ category: "adapter"
528
+ });
529
+ }
530
+ return assets;
531
+ }
532
+
533
+ // src/install/install.ts
534
+ function initProject(options) {
535
+ const cwd = options.cwd;
536
+ const stack = options.stack ?? "next-supabase";
537
+ const packageRoot = findPackageRoot();
538
+ const force = Boolean(options.force);
539
+ ensureDir(join6(cwd, ".agent-kit", "conflicts"));
540
+ const result = {
541
+ ...emptyCollector(),
542
+ manifestPath: ".agent-kit/manifest.json"
543
+ };
544
+ const templateHashes = {};
545
+ const agentsDoc = readFileSync6(join6(packageRoot, AGENTS_DOC_SOURCE), "utf8");
546
+ const userGuide = readFileSync6(join6(packageRoot, USER_GUIDE_SOURCE), "utf8");
547
+ const userGuideHtml = readFileSync6(join6(packageRoot, USER_GUIDE_HTML_SOURCE), "utf8");
548
+ templateHashes["AGENTS.md"] = sha256(agentsDoc);
549
+ templateHashes["USER_GUIDE.md"] = sha256(userGuide);
550
+ templateHashes["USER_GUIDE.html"] = sha256(userGuideHtml);
551
+ recordCopy(
552
+ result,
553
+ copyTextWithConflict(join6(packageRoot, AGENTS_DOC_SOURCE), cwd, "AGENTS.md", {
554
+ force,
555
+ conflictRoot: join6(cwd, ".agent-kit", "conflicts")
556
+ })
557
+ );
558
+ recordCopy(
559
+ result,
560
+ copyTextWithConflict(join6(packageRoot, USER_GUIDE_SOURCE), cwd, "USER_GUIDE.md", {
561
+ force,
562
+ conflictRoot: join6(cwd, ".agent-kit", "conflicts")
563
+ })
564
+ );
565
+ recordCopy(
566
+ result,
567
+ copyTextWithConflict(join6(packageRoot, USER_GUIDE_HTML_SOURCE), cwd, "USER_GUIDE.html", {
568
+ force,
569
+ conflictRoot: join6(cwd, ".agent-kit", "conflicts")
570
+ })
571
+ );
572
+ if (options.legacyDocs) {
573
+ const legacyRoot = join6(packageRoot, "templates", stack);
574
+ const legacyDocs = ["SPEC.md", "DECISIONS.md", "DESIGN.md", "SECURITY.md", "TESTING.md", "QUALITY_GATES.md"];
575
+ for (const doc of legacyDocs) {
576
+ const source = join6(legacyRoot, doc);
577
+ if (!existsSync6(source)) continue;
578
+ recordCopy(
579
+ result,
580
+ copyTextWithConflict(source, cwd, doc, {
581
+ force,
582
+ conflictRoot: join6(cwd, ".agent-kit", "conflicts")
583
+ })
584
+ );
585
+ }
586
+ }
587
+ const activateTargets = parseActivateTargets(options.activate);
588
+ const targets = activateTargets.length > 0 ? activateTargets : ["cursor"];
589
+ result.activation = activateIdeTargets({ cwd, targets, force });
590
+ generatePortableSkills(cwd, force, result.activation);
591
+ result.copied.push(...result.activation.copied.filter((path) => !result.copied.includes(path)));
592
+ result.unchanged.push(...result.activation.unchanged.filter((path) => !result.unchanged.includes(path)));
593
+ result.conflicts.push(...result.activation.conflicts.filter((path) => !result.conflicts.includes(path)));
594
+ result.overwritten.push(...result.activation.overwritten.filter((path) => !result.overwritten.includes(path)));
595
+ const assets = listManagedAssets(packageRoot, { activated: targets });
596
+ const assetHashes = {};
597
+ for (const asset of assets) {
598
+ if (existsSync6(asset.sourcePath)) assetHashes[asset.target] = sha256(readFileSync6(asset.sourcePath, "utf8"));
599
+ }
600
+ for (const relative2 of [...result.copied, ...result.unchanged, ...result.overwritten]) {
601
+ const path = join6(cwd, relative2);
602
+ if (existsSync6(path) && !assetHashes[relative2]) {
603
+ assetHashes[relative2] = sha256(readFileSync6(path, "utf8"));
604
+ }
605
+ }
606
+ const catalog = loadCatalog(packageRoot);
607
+ const manifest = {
608
+ schemaVersion: 3,
609
+ packageName: PACKAGE_NAME,
610
+ packageVersion: PACKAGE_VERSION,
611
+ stack,
612
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
613
+ docs: [...ROOT_DOCS],
614
+ activated: targets,
615
+ templateHashes,
616
+ assetHashes
617
+ };
618
+ writeText(join6(cwd, ".agent-kit", "manifest.json"), `${JSON.stringify(manifest, null, 2)}
619
+ `);
620
+ writeText(
621
+ join6(cwd, ".agent-kit", "config.json"),
622
+ `${JSON.stringify({ stack, catalog: { defaultAgents: catalog.defaultAgents, defaultSkills: catalog.defaultSkills } }, null, 2)}
623
+ `
624
+ );
625
+ if (!targets.includes("cursor")) {
626
+ recordCopy(
627
+ result,
628
+ copyTextWithConflict(join6(packageRoot, CURSOR_RULE_FILE.source), cwd, CURSOR_RULE_FILE.target, {
629
+ force,
630
+ conflictRoot: join6(cwd, ".agent-kit", "conflicts")
631
+ })
632
+ );
633
+ }
634
+ return result;
635
+ }
636
+ function readManifest(cwd) {
637
+ const manifestPath = join6(cwd, ".agent-kit", "manifest.json");
638
+ if (!existsSync6(manifestPath)) return null;
639
+ return JSON.parse(readFileSync6(manifestPath, "utf8"));
640
+ }
641
+
642
+ // src/install/adapter-validate.ts
428
643
  function summary(findings) {
429
644
  return {
430
645
  pass: findings.filter((finding) => finding.level === "pass").length,
@@ -432,14 +647,14 @@ function summary(findings) {
432
647
  fail: findings.filter((finding) => finding.level === "fail").length
433
648
  };
434
649
  }
435
- function report(target, findings) {
436
- return { target, summary: summary(findings), findings };
650
+ function report(target, findings, validated) {
651
+ return { target, validated, summary: summary(findings), findings };
437
652
  }
438
653
  function has(cwd, relative2) {
439
- return existsSync6(join5(cwd, relative2));
654
+ return existsSync7(join7(cwd, relative2));
440
655
  }
441
656
  function text(cwd, relative2) {
442
- return readFileSync6(join5(cwd, relative2), "utf8");
657
+ return readFileSync7(join7(cwd, relative2), "utf8");
443
658
  }
444
659
  function validateCursor(cwd) {
445
660
  const catalog = loadCatalog();
@@ -527,45 +742,55 @@ function validateAntigravity(cwd) {
527
742
  }
528
743
  return findings;
529
744
  }
745
+ var validators = {
746
+ cursor: validateCursor,
747
+ claude: validateClaude,
748
+ codex: validateCodex,
749
+ copilot: validateCopilot,
750
+ antigravity: validateAntigravity
751
+ };
752
+ function resolveAdapterTargets(cwd, target) {
753
+ if (target !== "all") return [target];
754
+ const activated = (readManifest(cwd)?.activated ?? []).filter(isIdeTarget);
755
+ return activated.length > 0 ? activated : [...IDE_TARGETS];
756
+ }
530
757
  function validateAdapter(cwd, target) {
531
- if (target === "cursor") return report("cursor", validateCursor(cwd));
532
- if (target === "claude") return report("claude", validateClaude(cwd));
533
- if (target === "codex") return report("codex", validateCodex(cwd));
534
- if (target === "copilot") return report("copilot", validateCopilot(cwd));
535
- if (target === "antigravity") return report("antigravity", validateAntigravity(cwd));
536
- return report("all", [...validateCursor(cwd), ...validateClaude(cwd), ...validateCodex(cwd), ...validateCopilot(cwd), ...validateAntigravity(cwd)]);
758
+ const validated = resolveAdapterTargets(cwd, target);
759
+ const findings = validated.flatMap((ide) => validators[ide](cwd));
760
+ const label = target === "all" && validated.length < IDE_TARGETS.length ? `all (${validated.join(", ")})` : target;
761
+ return report(label, findings, validated);
537
762
  }
538
763
  function validatePackage() {
539
764
  const cwd = process.cwd();
540
765
  const findings = [];
541
- if (!existsSync6(join5(cwd, "catalog.json"))) {
766
+ if (!existsSync7(join7(cwd, "catalog.json"))) {
542
767
  findings.push({ level: "fail", area: "package", message: "catalog.json is missing." });
543
768
  } else {
544
769
  findings.push({ level: "pass", area: "package", message: "catalog.json is present." });
545
770
  }
546
- if (!existsSync6(join5(cwd, "USER_GUIDE.md"))) {
771
+ if (!existsSync7(join7(cwd, "USER_GUIDE.md"))) {
547
772
  findings.push({ level: "fail", area: "package", message: "USER_GUIDE.md is missing." });
548
- } else if (!readFileSync6(join5(cwd, "USER_GUIDE.md"), "utf8").includes("Do not review code alone")) {
773
+ } else if (!readFileSync7(join7(cwd, "USER_GUIDE.md"), "utf8").includes("Do not review code alone")) {
549
774
  findings.push({ level: "fail", area: "package", message: "USER_GUIDE.md dropped the screenshot fail-closed sentence." });
550
775
  } else {
551
776
  findings.push({ level: "pass", area: "package", message: "USER_GUIDE.md includes the screenshot rule." });
552
777
  }
553
- if (!existsSync6(join5(cwd, "USER_GUIDE.html"))) {
778
+ if (!existsSync7(join7(cwd, "USER_GUIDE.html"))) {
554
779
  findings.push({ level: "fail", area: "package", message: "USER_GUIDE.html is missing." });
555
- } else if (!readFileSync6(join5(cwd, "USER_GUIDE.html"), "utf8").includes("Do not review code alone")) {
780
+ } else if (!readFileSync7(join7(cwd, "USER_GUIDE.html"), "utf8").includes("Do not review code alone")) {
556
781
  findings.push({ level: "fail", area: "package", message: "USER_GUIDE.html dropped the screenshot fail-closed sentence." });
557
782
  } else {
558
783
  findings.push({ level: "pass", area: "package", message: "USER_GUIDE.html includes the screenshot rule." });
559
784
  }
560
- if (!existsSync6(join5(cwd, "skills/browser-qa/SKILL.md"))) {
785
+ if (!existsSync7(join7(cwd, "skills/browser-qa/SKILL.md"))) {
561
786
  findings.push({ level: "fail", area: "package", message: "skills/browser-qa/SKILL.md is missing." });
562
787
  }
563
- return report("package", findings);
788
+ return report("package", findings, []);
564
789
  }
565
790
 
566
791
  // src/install/doctor.ts
567
- import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
568
- import { join as join6 } from "path";
792
+ import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
793
+ import { join as join8 } from "path";
569
794
  var LEGACY_LEFTOVER_PATHS = [
570
795
  "AGENT_ROSTER.md",
571
796
  "COUNCIL.md",
@@ -583,11 +808,11 @@ function summarize(findings) {
583
808
  };
584
809
  }
585
810
  function read(cwd, relative2) {
586
- const path = join6(cwd, relative2);
587
- return existsSync7(path) ? readFileSync7(path, "utf8") : null;
811
+ const path = join8(cwd, relative2);
812
+ return existsSync8(path) ? readFileSync8(path, "utf8") : null;
588
813
  }
589
814
  function listLegacyLeftovers(cwd) {
590
- return LEGACY_LEFTOVER_PATHS.filter((relative2) => existsSync7(join6(cwd, relative2)));
815
+ return LEGACY_LEFTOVER_PATHS.filter((relative2) => existsSync8(join8(cwd, relative2)));
591
816
  }
592
817
  function createDoctorReport(cwd) {
593
818
  const catalog = loadCatalog();
@@ -637,9 +862,33 @@ function createDoctorReport(cwd) {
637
862
  const meta = parseFrontmatter(content);
638
863
  if (!meta.tools || meta.tools.length === 0) {
639
864
  findings.push({ level: "fail", area: "agents", message: `${id} is missing a tools list.` });
640
- } else {
641
- findings.push({ level: "pass", area: "agents", message: `${id} declares tools.` });
865
+ continue;
866
+ }
867
+ const expectedRequired = packagedRequiredTools(id);
868
+ const actualRequired = meta.requiredTools ?? [];
869
+ const missingRequired = expectedRequired.filter((tool) => !actualRequired.includes(tool));
870
+ if (missingRequired.length > 0) {
871
+ findings.push({
872
+ level: "fail",
873
+ area: "agents",
874
+ message: `${id} dropped requiredTools: ${missingRequired.join(", ")}.`
875
+ });
876
+ continue;
642
877
  }
878
+ const requiredNotAllowed = actualRequired.filter((tool) => !meta.tools?.includes(tool));
879
+ if (requiredNotAllowed.length > 0) {
880
+ findings.push({
881
+ level: "fail",
882
+ area: "agents",
883
+ message: `${id} lists required tools that are not in tools: ${requiredNotAllowed.join(", ")}.`
884
+ });
885
+ continue;
886
+ }
887
+ findings.push({
888
+ level: "pass",
889
+ area: "agents",
890
+ message: expectedRequired.length > 0 ? `${id} keeps required tools.` : `${id} declares tools.`
891
+ });
643
892
  }
644
893
  const leftovers = listLegacyLeftovers(cwd);
645
894
  if (leftovers.length > 0) {
@@ -663,225 +912,41 @@ function createDoctorReport(cwd) {
663
912
  ok: findings.every((item) => item.level !== "fail")
664
913
  };
665
914
  }
666
-
667
- // src/install/install.ts
668
- import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
669
- import { join as join8 } from "path";
670
-
671
- // src/config/defaults.ts
672
- var PACKAGE_NAME = "@appsforgood/next-supabase-kit";
673
- var PACKAGE_VERSION = "0.4.2";
674
- var ROOT_DOCS = ["AGENTS.md", "USER_GUIDE.md", "USER_GUIDE.html"];
675
- var CURSOR_RULE_FILE = {
676
- source: "assistant-adapters/cursor-agent-kit.mdc",
677
- target: ".cursor/rules/cursor-agent-kit.mdc"
678
- };
679
- var CLAUDE_TEMPLATE = "templates/next-supabase/CLAUDE.md";
680
- var USER_GUIDE_SOURCE = "USER_GUIDE.md";
681
- var USER_GUIDE_HTML_SOURCE = "USER_GUIDE.html";
682
- var AGENTS_DOC_SOURCE = "templates/next-supabase/AGENTS.md";
683
-
684
- // src/install/ide-activate.ts
685
- var ALLOWED = /* @__PURE__ */ new Set(["cursor", "claude", "codex", "copilot", "antigravity"]);
686
- var InvalidActivateTargetError = class extends Error {
687
- constructor(invalid) {
688
- super(`Unknown --activate target(s): ${invalid.join(", ")}. Allowed: cursor, claude, codex, copilot, antigravity, all.`);
689
- this.invalid = invalid;
690
- this.name = "InvalidActivateTargetError";
691
- }
692
- invalid;
693
- };
694
- function parseActivateTargets(raw) {
695
- if (!raw || raw.length === 0) return [];
696
- return normalizeTargets(raw.flatMap((value) => value.split(",")));
697
- }
698
- function normalizeTargets(targets) {
699
- const normalized = /* @__PURE__ */ new Set();
700
- const invalid = [];
701
- for (const target of targets) {
702
- const value = target.trim().toLowerCase();
703
- if (!value) continue;
704
- if (value === "all") {
705
- for (const item of ALLOWED) normalized.add(item);
706
- continue;
707
- }
708
- if (ALLOWED.has(value)) {
709
- normalized.add(value);
710
- } else {
711
- invalid.push(target.trim());
712
- }
713
- }
714
- if (invalid.length > 0) throw new InvalidActivateTargetError(invalid);
715
- return [...normalized];
716
- }
717
- function activateIdeTargets(options) {
718
- const cwd = options.cwd;
719
- const packageRoot = findPackageRoot();
720
- const targets = normalizeTargets(options.targets);
721
- const force = Boolean(options.force);
722
- const collector = emptyCollector();
723
- const result = { activated: targets, ...collector };
724
- if (targets.length === 0) return result;
725
- if (targets.includes("cursor")) {
726
- copyFromPackage(cwd, packageRoot, CURSOR_RULE_FILE.source, CURSOR_RULE_FILE.target, force, result);
727
- generateCursorAgents(cwd, force, result);
728
- generateCursorSkills(cwd, force, result);
729
- }
730
- if (targets.includes("claude")) {
731
- copyFromPackage(cwd, packageRoot, CLAUDE_TEMPLATE, "CLAUDE.md", force, result);
732
- generateClaudeAgents(cwd, force, result);
733
- }
734
- if (targets.includes("codex")) {
735
- generateCodexAgents(cwd, force, result);
736
- }
737
- if (targets.includes("copilot")) {
738
- generateCopilotInstructions(cwd, force, result);
739
- }
740
- if (targets.includes("antigravity")) {
741
- generateAntigravityCommands(cwd, force, result);
742
- }
743
- return result;
744
- }
745
-
746
- // src/install/managed-assets.ts
747
- import { join as join7 } from "path";
748
- function listManagedAssets(packageRoot, options = {}) {
749
- const assets = [
750
- { target: "AGENTS.md", sourcePath: join7(packageRoot, AGENTS_DOC_SOURCE), category: "root-doc" },
751
- { target: "USER_GUIDE.md", sourcePath: join7(packageRoot, USER_GUIDE_SOURCE), category: "root-doc" },
752
- { target: "USER_GUIDE.html", sourcePath: join7(packageRoot, USER_GUIDE_HTML_SOURCE), category: "root-doc" },
753
- { target: CURSOR_RULE_FILE.target, sourcePath: join7(packageRoot, CURSOR_RULE_FILE.source), category: "adapter" }
754
- ];
755
- const activated = new Set(options.activated ?? ["cursor"]);
756
- if (activated.has("claude")) {
757
- assets.push({
758
- target: "CLAUDE.md",
759
- sourcePath: join7(packageRoot, "templates/next-supabase/CLAUDE.md"),
760
- category: "adapter"
761
- });
915
+ function packagedRequiredTools(id) {
916
+ try {
917
+ const source = readFileSync8(agentSourcePath(findPackageRoot(), id), "utf8");
918
+ return parseFrontmatter(source).requiredTools ?? [];
919
+ } catch {
920
+ return [];
762
921
  }
763
- return assets;
764
922
  }
765
923
 
766
- // src/install/install.ts
767
- function initProject(options) {
768
- const cwd = options.cwd;
769
- const stack = options.stack ?? "next-supabase";
770
- const packageRoot = findPackageRoot();
771
- const force = Boolean(options.force);
772
- ensureDir(join8(cwd, ".agent-kit", "conflicts"));
773
- const result = {
774
- ...emptyCollector(),
775
- manifestPath: ".agent-kit/manifest.json"
776
- };
777
- const templateHashes = {};
778
- const agentsDoc = readFileSync8(join8(packageRoot, AGENTS_DOC_SOURCE), "utf8");
779
- const userGuide = readFileSync8(join8(packageRoot, USER_GUIDE_SOURCE), "utf8");
780
- const userGuideHtml = readFileSync8(join8(packageRoot, USER_GUIDE_HTML_SOURCE), "utf8");
781
- templateHashes["AGENTS.md"] = sha256(agentsDoc);
782
- templateHashes["USER_GUIDE.md"] = sha256(userGuide);
783
- templateHashes["USER_GUIDE.html"] = sha256(userGuideHtml);
784
- recordCopy(
785
- result,
786
- copyTextWithConflict(join8(packageRoot, AGENTS_DOC_SOURCE), cwd, "AGENTS.md", {
787
- force,
788
- conflictRoot: join8(cwd, ".agent-kit", "conflicts")
789
- })
790
- );
791
- recordCopy(
792
- result,
793
- copyTextWithConflict(join8(packageRoot, USER_GUIDE_SOURCE), cwd, "USER_GUIDE.md", {
794
- force,
795
- conflictRoot: join8(cwd, ".agent-kit", "conflicts")
796
- })
797
- );
798
- recordCopy(
799
- result,
800
- copyTextWithConflict(join8(packageRoot, USER_GUIDE_HTML_SOURCE), cwd, "USER_GUIDE.html", {
801
- force,
802
- conflictRoot: join8(cwd, ".agent-kit", "conflicts")
803
- })
804
- );
805
- if (options.legacyDocs) {
806
- const legacyRoot = join8(packageRoot, "templates", stack);
807
- const legacyDocs = ["SPEC.md", "DECISIONS.md", "DESIGN.md", "SECURITY.md", "TESTING.md", "QUALITY_GATES.md"];
808
- for (const doc of legacyDocs) {
809
- const source = join8(legacyRoot, doc);
810
- if (!existsSync8(source)) continue;
811
- recordCopy(
812
- result,
813
- copyTextWithConflict(source, cwd, doc, {
814
- force,
815
- conflictRoot: join8(cwd, ".agent-kit", "conflicts")
816
- })
817
- );
818
- }
819
- }
820
- const activateTargets = parseActivateTargets(options.activate);
821
- const targets = activateTargets.length > 0 ? activateTargets : ["cursor"];
822
- result.activation = activateIdeTargets({ cwd, targets, force });
823
- generatePortableSkills(cwd, force, result.activation);
824
- result.copied.push(...result.activation.copied.filter((path) => !result.copied.includes(path)));
825
- result.unchanged.push(...result.activation.unchanged.filter((path) => !result.unchanged.includes(path)));
826
- result.conflicts.push(...result.activation.conflicts.filter((path) => !result.conflicts.includes(path)));
827
- result.overwritten.push(...result.activation.overwritten.filter((path) => !result.overwritten.includes(path)));
828
- const assets = listManagedAssets(packageRoot, { activated: targets });
829
- const assetHashes = {};
830
- for (const asset of assets) {
831
- if (existsSync8(asset.sourcePath)) assetHashes[asset.target] = sha256(readFileSync8(asset.sourcePath, "utf8"));
832
- }
833
- for (const relative2 of [...result.copied, ...result.unchanged, ...result.overwritten]) {
834
- const path = join8(cwd, relative2);
835
- if (existsSync8(path) && !assetHashes[relative2]) {
836
- assetHashes[relative2] = sha256(readFileSync8(path, "utf8"));
837
- }
838
- }
839
- const catalog = loadCatalog(packageRoot);
840
- const manifest = {
841
- schemaVersion: 3,
842
- packageName: PACKAGE_NAME,
843
- packageVersion: PACKAGE_VERSION,
844
- stack,
845
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
846
- docs: [...ROOT_DOCS],
847
- activated: targets,
848
- templateHashes,
849
- assetHashes
850
- };
851
- writeText(join8(cwd, ".agent-kit", "manifest.json"), `${JSON.stringify(manifest, null, 2)}
852
- `);
853
- writeText(
854
- join8(cwd, ".agent-kit", "config.json"),
855
- `${JSON.stringify({ stack, catalog: { defaultAgents: catalog.defaultAgents, defaultSkills: catalog.defaultSkills } }, null, 2)}
856
- `
857
- );
858
- if (!targets.includes("cursor")) {
859
- recordCopy(
860
- result,
861
- copyTextWithConflict(join8(packageRoot, CURSOR_RULE_FILE.source), cwd, CURSOR_RULE_FILE.target, {
862
- force,
863
- conflictRoot: join8(cwd, ".agent-kit", "conflicts")
864
- })
865
- );
866
- }
867
- return result;
868
- }
869
- function readManifest(cwd) {
870
- const manifestPath = join8(cwd, ".agent-kit", "manifest.json");
871
- if (!existsSync8(manifestPath)) return null;
872
- return JSON.parse(readFileSync8(manifestPath, "utf8"));
924
+ // src/install/guide.ts
925
+ import { existsSync as existsSync9 } from "fs";
926
+ import { join as join9, resolve as resolve3 } from "path";
927
+ import { pathToFileURL } from "url";
928
+ function resolveUserGuideHtml(cwd) {
929
+ const local = join9(cwd, "USER_GUIDE.html");
930
+ if (existsSync9(local)) return locate(local, "cwd");
931
+ const packaged = join9(findPackageRoot(), "USER_GUIDE.html");
932
+ if (existsSync9(packaged)) return locate(packaged, "package");
933
+ throw new Error("USER_GUIDE.html is missing. Run agent-kit init, then open the file in a browser.");
934
+ }
935
+ function locate(path, source) {
936
+ const absolute = resolve3(path);
937
+ return { path: absolute, source, fileUrl: pathToFileURL(absolute).href };
873
938
  }
874
939
 
875
940
  // src/install/update.ts
876
- import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
877
- import { join as join9 } from "path";
941
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
942
+ import { join as join10 } from "path";
878
943
 
879
944
  // src/install/file-update-plan.ts
880
- import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
945
+ import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
881
946
  function planFileUpdate(input) {
882
947
  const sourceContent = readFileSync9(input.sourcePath, "utf8");
883
948
  const sourceHash = sha256(sourceContent);
884
- if (!existsSync9(input.targetPath)) {
949
+ if (!existsSync10(input.targetPath)) {
885
950
  return { target: input.target, action: "created", reason: "File is missing locally.", sourceContent, sourceHash };
886
951
  }
887
952
  const localContent = readFileSync9(input.targetPath, "utf8");
@@ -1007,7 +1072,7 @@ function updateProject(options) {
1007
1072
  const assets = listManagedAssets(packageRoot, { activated });
1008
1073
  const files = [];
1009
1074
  for (const asset of assets) {
1010
- if (!existsSync10(asset.sourcePath)) continue;
1075
+ if (!existsSync11(asset.sourcePath)) continue;
1011
1076
  const plan = planFileUpdate({
1012
1077
  target: asset.target,
1013
1078
  sourcePath: asset.sourcePath,
@@ -1041,11 +1106,11 @@ function updateProject(options) {
1041
1106
  files.push(...activationToUpdateFiles(activation));
1042
1107
  const nextHashes = { ...manifest.assetHashes };
1043
1108
  for (const asset of assets) {
1044
- if (existsSync10(asset.sourcePath)) nextHashes[asset.target] = sha256(readFileSync10(asset.sourcePath, "utf8"));
1109
+ if (existsSync11(asset.sourcePath)) nextHashes[asset.target] = sha256(readFileSync10(asset.sourcePath, "utf8"));
1045
1110
  }
1046
1111
  for (const relative2 of [...activation.copied, ...activation.unchanged, ...activation.overwritten]) {
1047
- const path = join9(cwd, relative2);
1048
- if (existsSync10(path)) nextHashes[relative2] = sha256(readFileSync10(path, "utf8"));
1112
+ const path = join10(cwd, relative2);
1113
+ if (existsSync11(path)) nextHashes[relative2] = sha256(readFileSync10(path, "utf8"));
1049
1114
  }
1050
1115
  const next = {
1051
1116
  ...manifest,
@@ -1055,7 +1120,7 @@ function updateProject(options) {
1055
1120
  docs: ["AGENTS.md", "USER_GUIDE.md", "USER_GUIDE.html"],
1056
1121
  assetHashes: nextHashes
1057
1122
  };
1058
- writeText(join9(cwd, ".agent-kit", "manifest.json"), `${JSON.stringify(next, null, 2)}
1123
+ writeText(join10(cwd, ".agent-kit", "manifest.json"), `${JSON.stringify(next, null, 2)}
1059
1124
  `);
1060
1125
  } else {
1061
1126
  files.push({
@@ -1173,7 +1238,7 @@ add.command("agent <name>").description("Install one agent into .cursor/agents a
1173
1238
  line(`${result.action} ${result.target}`);
1174
1239
  if (!options.dryRun) detail(`Available agents: ${listAgents().join(", ")}`);
1175
1240
  });
1176
- program.command("doctor").description("Check agents, skills, the screenshot QA rule, and leftover 0.3 council files.").option("--json", "Machine-readable output").action((options) => {
1241
+ program.command("doctor").description("Check agents, required tools, the screenshot QA rule, and leftover 0.3 council files.").option("--json", "Machine-readable output").action((options) => {
1177
1242
  const report2 = createDoctorReport(process.cwd());
1178
1243
  if (options.json) {
1179
1244
  printJson(report2);
@@ -1187,10 +1252,22 @@ program.command("doctor").description("Check agents, skills, the screenshot QA r
1187
1252
  if (!report2.ok) {
1188
1253
  fail("doctor found failures");
1189
1254
  process.exitCode = 1;
1255
+ } else {
1256
+ detail("Open USER_GUIDE.html in a browser. GitHub shows source, not the layout. Run agent-kit guide for the path.");
1257
+ }
1258
+ });
1259
+ program.command("guide").description("Print the path to USER_GUIDE.html. Open it in a browser; GitHub shows source.").option("--json", "Machine-readable output").action((options) => {
1260
+ const location = resolveUserGuideHtml(process.cwd());
1261
+ if (options.json) {
1262
+ printJson(location);
1263
+ return;
1190
1264
  }
1265
+ heading("user guide");
1266
+ line(location.path);
1267
+ detail("Open that file in a browser. GitHub shows HTML as source, not the layout.");
1191
1268
  });
1192
1269
  var adapter = program.command("adapter").description("Validate IDE adapter files.");
1193
- adapter.command("validate [target]").description("Validate cursor, claude, codex, copilot, antigravity, or all").option("--json", "Machine-readable output").action((rawTarget, options) => {
1270
+ adapter.command("validate [target]").description("Validate activated IDEs, or one of cursor, claude, codex, copilot, antigravity").option("--json", "Machine-readable output").action((rawTarget, options) => {
1194
1271
  const target = rawTarget ?? "all";
1195
1272
  const allowed = ["cursor", "claude", "codex", "copilot", "antigravity", "all"];
1196
1273
  if (!allowed.includes(target)) {