@smicolon/ai-kit 0.1.1 → 0.2.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 (3) hide show
  1. package/README.md +26 -16
  2. package/dist/index.js +146 -38
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -5,7 +5,7 @@ AI coding tool pack manager. Install convention packs (agents, skills, commands,
5
5
  ## Quick Start
6
6
 
7
7
  ```bash
8
- npx @smicolon/ai-kit init
8
+ npx @smicolon/ai-kit@latest init
9
9
  ```
10
10
 
11
11
  This walks you through selecting your AI tools and stack, then installs the right files in the right places.
@@ -17,8 +17,8 @@ This walks you through selecting your AI tools and stack, then installs the righ
17
17
  Interactive first-time setup. Prompts for your AI tools, stack, and component preferences.
18
18
 
19
19
  ```bash
20
- npx @smicolon/ai-kit init
21
- npx @smicolon/ai-kit init --cwd apps/web # monorepo sub-package
20
+ npx @smicolon/ai-kit@latest init
21
+ npx @smicolon/ai-kit@latest init --cwd apps/web # monorepo sub-package
22
22
  ```
23
23
 
24
24
  ### `add <pack>`
@@ -26,11 +26,11 @@ npx @smicolon/ai-kit init --cwd apps/web # monorepo sub-package
26
26
  Add a pack to your project.
27
27
 
28
28
  ```bash
29
- npx @smicolon/ai-kit add django
30
- npx @smicolon/ai-kit add django --skills-only
31
- npx @smicolon/ai-kit add django --agents-only
32
- npx @smicolon/ai-kit add django --rules-only
33
- npx @smicolon/ai-kit add django --tools claude-code,cursor
29
+ npx @smicolon/ai-kit@latest add django
30
+ npx @smicolon/ai-kit@latest add django --skills-only
31
+ npx @smicolon/ai-kit@latest add django --agents-only
32
+ npx @smicolon/ai-kit@latest add django --rules-only
33
+ npx @smicolon/ai-kit@latest add django --tools claude-code,cursor
34
34
  ```
35
35
 
36
36
  ### `list`
@@ -38,8 +38,8 @@ npx @smicolon/ai-kit add django --tools claude-code,cursor
38
38
  Show available or installed packs.
39
39
 
40
40
  ```bash
41
- npx @smicolon/ai-kit list # available packs
42
- npx @smicolon/ai-kit list --installed # installed packs
41
+ npx @smicolon/ai-kit@latest list # available packs
42
+ npx @smicolon/ai-kit@latest list --installed # installed packs
43
43
  ```
44
44
 
45
45
  ### `remove <pack>`
@@ -47,7 +47,17 @@ npx @smicolon/ai-kit list --installed # installed packs
47
47
  Remove a pack and all its installed files.
48
48
 
49
49
  ```bash
50
- npx @smicolon/ai-kit remove django
50
+ npx @smicolon/ai-kit@latest remove django
51
+ ```
52
+
53
+ ### `search <query>`
54
+
55
+ Search packs by name or keyword.
56
+
57
+ ```bash
58
+ npx @smicolon/ai-kit@latest search auth
59
+ npx @smicolon/ai-kit@latest search frontend
60
+ npx @smicolon/ai-kit@latest search tdd
51
61
  ```
52
62
 
53
63
  ### `update [pack]`
@@ -55,8 +65,8 @@ npx @smicolon/ai-kit remove django
55
65
  Update installed packs to latest versions.
56
66
 
57
67
  ```bash
58
- npx @smicolon/ai-kit update # update all
59
- npx @smicolon/ai-kit update django # update one
68
+ npx @smicolon/ai-kit@latest update # update all
69
+ npx @smicolon/ai-kit@latest update django # update one
60
70
  ```
61
71
 
62
72
  ## Supported AI Tools
@@ -116,15 +126,15 @@ your-project/
116
126
  └── .gitignore # auto-updated
117
127
  ```
118
128
 
119
- Config is stored in `.ai-kit.json` which tracks installed packs and all created files for clean removal.
129
+ Tool preferences are stored globally at `~/.config/ai-kit/config.json` (pick once, works in all projects). Local `.ai-kit.json` tracks installed packs and files for clean removal — it's auto-added to `.gitignore`.
120
130
 
121
131
  ## Monorepo Support
122
132
 
123
133
  Use `--cwd` to target a sub-package. Both the sub-package and root `.gitignore` are updated.
124
134
 
125
135
  ```bash
126
- npx @smicolon/ai-kit init --cwd apps/web
127
- npx @smicolon/ai-kit add django --cwd apps/web
136
+ npx @smicolon/ai-kit@latest init --cwd apps/web
137
+ npx @smicolon/ai-kit@latest add django --cwd apps/web
128
138
  ```
129
139
 
130
140
  ## License
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command6 } from "commander";
4
+ import { Command as Command7 } from "commander";
5
5
 
6
6
  // src/commands/init.ts
7
7
  import { Command } from "commander";
8
8
  import * as p from "@clack/prompts";
9
9
  import pc from "picocolors";
10
- import path5 from "path";
10
+ import path6 from "path";
11
11
 
12
12
  // src/tools.ts
13
13
  var TOOL_REGISTRY = {
@@ -182,6 +182,8 @@ function resolvePack(plugin, marketplaceDir) {
182
182
  name: plugin.name,
183
183
  version: plugin.version,
184
184
  description: plugin.description,
185
+ category: plugin.category ?? "",
186
+ keywords: plugin.keywords ?? [],
185
187
  sourceDir,
186
188
  agents: resolveFiles(plugin.agents),
187
189
  commands: resolveFiles(plugin.commands),
@@ -201,10 +203,10 @@ function discoverPacks(startDir) {
201
203
  }
202
204
  const marketplaceDir = path.dirname(path.dirname(marketplacePath));
203
205
  const raw = JSON.parse(fs.readFileSync(marketplacePath, "utf-8"));
204
- return raw.plugins.map((p2) => resolvePack(p2, marketplaceDir)).filter((p2) => fs.existsSync(p2.sourceDir));
206
+ return raw.plugins.map((p3) => resolvePack(p3, marketplaceDir)).filter((p3) => fs.existsSync(p3.sourceDir));
205
207
  }
206
208
  function findPack(name, startDir) {
207
- return discoverPacks(startDir).find((p2) => p2.name === name);
209
+ return discoverPacks(startDir).find((p3) => p3.name === name);
208
210
  }
209
211
 
210
212
  // src/config.ts
@@ -298,6 +300,7 @@ ${newEntries.join("\n")}
298
300
  function updateGitignore(projectDir, dirs) {
299
301
  if (dirs.length === 0) return;
300
302
  const entries = dirs.map((d) => d.endsWith("/") ? d : `${d}/`);
303
+ entries.push(".ai-kit.json");
301
304
  const projectGitignore = path3.join(projectDir, ".gitignore");
302
305
  appendToGitignore(projectGitignore, entries);
303
306
  const gitRoot = findGitRoot(projectDir);
@@ -565,9 +568,9 @@ function removePack2(projectDir, files) {
565
568
  }
566
569
  return removed;
567
570
  }
568
- function isSymlink(p2) {
571
+ function isSymlink(p3) {
569
572
  try {
570
- return fs5.lstatSync(p2).isSymbolicLink();
573
+ return fs5.lstatSync(p3).isSymbolicLink();
571
574
  } catch {
572
575
  return false;
573
576
  }
@@ -587,10 +590,43 @@ function getWrittenDirs(tools, hadSkills) {
587
590
  return [...dirs];
588
591
  }
589
592
 
593
+ // src/global-config.ts
594
+ import fs6 from "fs";
595
+ import path5 from "path";
596
+ import os from "os";
597
+ function getConfigDir() {
598
+ return path5.join(os.homedir(), ".config", "ai-kit");
599
+ }
600
+ function getConfigPath() {
601
+ return path5.join(getConfigDir(), "config.json");
602
+ }
603
+ function readGlobalConfig() {
604
+ const fp = getConfigPath();
605
+ if (!fs6.existsSync(fp)) return null;
606
+ try {
607
+ return JSON.parse(fs6.readFileSync(fp, "utf-8"));
608
+ } catch {
609
+ return null;
610
+ }
611
+ }
612
+ function writeGlobalConfig(config) {
613
+ const dir = getConfigDir();
614
+ fs6.mkdirSync(dir, { recursive: true });
615
+ fs6.writeFileSync(getConfigPath(), JSON.stringify(config, null, 2) + "\n");
616
+ }
617
+ function getGlobalTools() {
618
+ const config = readGlobalConfig();
619
+ return config?.tools ?? null;
620
+ }
621
+ function saveGlobalTools(tools) {
622
+ const existing = readGlobalConfig();
623
+ writeGlobalConfig({ ...existing, tools });
624
+ }
625
+
590
626
  // src/commands/init.ts
591
627
  var initCommand = new Command("init").description("Interactive first-time setup").option("--cwd <dir>", "Project directory (for monorepo sub-packages)").action(async (opts) => {
592
628
  p.intro(pc.bgCyan(pc.black(" ai-kit ")));
593
- const projectDir = opts.cwd ? path5.resolve(opts.cwd) : process.cwd();
629
+ const projectDir = opts.cwd ? path6.resolve(opts.cwd) : process.cwd();
594
630
  const existing = readConfig(projectDir);
595
631
  if (existing) {
596
632
  const action = await p.select({
@@ -610,13 +646,15 @@ var initCommand = new Command("init").description("Interactive first-time setup"
610
646
  return;
611
647
  }
612
648
  }
613
- const toolSelection = await p.multiselect({
614
- message: "Which AI coding tools do you use?",
649
+ const savedTools = getGlobalTools();
650
+ const toolSelection = await p.autocompleteMultiselect({
651
+ message: "Which AI coding tools do you use? (type to filter)",
615
652
  options: TOOL_IDS.map((id) => ({
616
653
  value: id,
617
654
  label: TOOL_REGISTRY[id].label,
618
655
  hint: TOOL_REGISTRY[id].hint
619
656
  })),
657
+ initialValues: savedTools ?? [],
620
658
  required: true
621
659
  });
622
660
  if (p.isCancel(toolSelection)) {
@@ -624,6 +662,7 @@ var initCommand = new Command("init").description("Interactive first-time setup"
624
662
  return;
625
663
  }
626
664
  const selectedTools = toolSelection;
665
+ saveGlobalTools(selectedTools);
627
666
  let packs;
628
667
  try {
629
668
  packs = discoverPacks();
@@ -632,8 +671,8 @@ var initCommand = new Command("init").description("Interactive first-time setup"
632
671
  p.outro("Setup failed.");
633
672
  return;
634
673
  }
635
- const packSelection = await p.multiselect({
636
- message: "What's your stack?",
674
+ const packSelection = await p.autocompleteMultiselect({
675
+ message: "Which packs do you want to install? (type to filter)",
637
676
  options: packs.map((pack) => ({
638
677
  value: pack.name,
639
678
  label: pack.name,
@@ -682,7 +721,7 @@ var initCommand = new Command("init").description("Interactive first-time setup"
682
721
  const s = p.spinner();
683
722
  s.start("Installing packs...");
684
723
  let config = createDefaultConfig(selectedTools);
685
- const selectedPacks = packs.filter((p2) => selectedPackNames.includes(p2.name));
724
+ const selectedPacks = packs.filter((p3) => selectedPackNames.includes(p3.name));
686
725
  let hadSkills = false;
687
726
  for (const pack of selectedPacks) {
688
727
  const result = installPack({
@@ -701,31 +740,55 @@ var initCommand = new Command("init").description("Interactive first-time setup"
701
740
  for (const pack of selectedPacks) {
702
741
  p.log.message(` ${pc.green("+")} ${pack.name} ${pc.dim(`v${pack.version}`)}`);
703
742
  }
704
- p.outro(pc.dim("Config saved to .ai-kit.json"));
743
+ p.outro(pc.dim("Run ai-kit add <pack> to add more packs anytime."));
705
744
  });
706
745
 
707
746
  // src/commands/add.ts
708
747
  import { Command as Command2 } from "commander";
709
- import path6 from "path";
748
+ import path7 from "path";
710
749
  import pc2 from "picocolors";
711
- var addCommand = new Command2("add").description("Add a pack to your project").argument("<pack>", "Pack name (e.g., django, nextjs)").option("--skills-only", "Only install skills").option("--agents-only", "Only install agents").option("--rules-only", "Only install rules").option("--commands-only", "Only install commands").option("--hooks-only", "Only install hooks").option("--tools <tools>", "Comma-separated tool IDs (overrides config)").option("--cwd <dir>", "Project directory (for monorepo sub-packages)").action(async (packName, opts) => {
712
- const projectDir = opts.cwd ? path6.resolve(opts.cwd) : process.cwd();
750
+ import * as p2 from "@clack/prompts";
751
+ async function resolveTools(toolsFlag, projectDir) {
752
+ if (toolsFlag) {
753
+ return toolsFlag.split(",").map((t) => t.trim());
754
+ }
713
755
  const config = readConfig(projectDir);
714
- if (!config) {
715
- console.error(
716
- pc2.red("No .ai-kit.json found. Run ") + pc2.cyan("ai-kit init") + pc2.red(" first.")
717
- );
718
- process.exit(1);
756
+ if (config?.tools?.length) {
757
+ return config.tools;
758
+ }
759
+ const globalTools = getGlobalTools();
760
+ if (globalTools?.length) {
761
+ return globalTools;
719
762
  }
763
+ const selection = await p2.autocompleteMultiselect({
764
+ message: "Which AI coding tools do you use? (type to filter)",
765
+ options: TOOL_IDS.map((id) => ({
766
+ value: id,
767
+ label: TOOL_REGISTRY[id].label,
768
+ hint: TOOL_REGISTRY[id].hint
769
+ })),
770
+ required: true
771
+ });
772
+ if (p2.isCancel(selection)) return null;
773
+ const tools = selection;
774
+ saveGlobalTools(tools);
775
+ return tools;
776
+ }
777
+ var addCommand = new Command2("add").description("Add a pack to your project").argument("<pack>", "Pack name (e.g., django, nextjs)").option("--skills-only", "Only install skills").option("--agents-only", "Only install agents").option("--rules-only", "Only install rules").option("--commands-only", "Only install commands").option("--hooks-only", "Only install hooks").option("--tools <tools>", "Comma-separated tool IDs (overrides config)").option("--cwd <dir>", "Project directory (for monorepo sub-packages)").action(async (packName, opts) => {
778
+ const projectDir = opts.cwd ? path7.resolve(opts.cwd) : process.cwd();
720
779
  const pack = findPack(packName);
721
780
  if (!pack) {
722
- const available = discoverPacks().map((p2) => p2.name);
781
+ const available = discoverPacks().map((p3) => p3.name);
723
782
  console.error(
724
783
  pc2.red(`Pack "${packName}" not found.`) + "\nAvailable: " + available.join(", ")
725
784
  );
726
785
  process.exit(1);
727
786
  }
728
- const tools = opts.tools ? opts.tools.split(",").map((t) => t.trim()) : config.tools;
787
+ const tools = await resolveTools(opts.tools, projectDir);
788
+ if (!tools) {
789
+ console.log(pc2.dim("Cancelled."));
790
+ return;
791
+ }
729
792
  let filter;
730
793
  if (opts.skillsOnly) filter = ["skills"];
731
794
  else if (opts.agentsOnly) filter = ["agents"];
@@ -733,6 +796,8 @@ var addCommand = new Command2("add").description("Add a pack to your project").a
733
796
  else if (opts.commandsOnly) filter = ["commands"];
734
797
  else if (opts.hooksOnly) filter = ["hooks"];
735
798
  const result = installPack({ pack, tools, filter, projectDir });
799
+ let config = readConfig(projectDir) ?? createDefaultConfig(tools);
800
+ config.tools = tools;
736
801
  const updated = mergeInstall(config, result);
737
802
  updated.packs[pack.name].version = pack.version;
738
803
  writeConfig(projectDir, updated);
@@ -798,14 +863,14 @@ ${pc3.dim(`${packs.length} packs available`)}`);
798
863
 
799
864
  // src/commands/remove.ts
800
865
  import { Command as Command4 } from "commander";
801
- import path7 from "path";
866
+ import path8 from "path";
802
867
  import pc4 from "picocolors";
803
868
  var removeCommand = new Command4("remove").description("Remove a pack from your project").argument("<pack>", "Pack name to remove").option("--cwd <dir>", "Project directory").action((packName, opts) => {
804
- const projectDir = opts.cwd ? path7.resolve(opts.cwd) : process.cwd();
869
+ const projectDir = opts.cwd ? path8.resolve(opts.cwd) : process.cwd();
805
870
  const config = readConfig(projectDir);
806
871
  if (!config) {
807
- console.error(pc4.red("No .ai-kit.json found."));
808
- process.exit(1);
872
+ console.log(pc4.dim("No packs installed."));
873
+ return;
809
874
  }
810
875
  const packConfig = config.packs[packName];
811
876
  if (!packConfig) {
@@ -828,20 +893,17 @@ var removeCommand = new Command4("remove").description("Remove a pack from your
828
893
 
829
894
  // src/commands/update.ts
830
895
  import { Command as Command5 } from "commander";
831
- import path8 from "path";
896
+ import path9 from "path";
832
897
  import pc5 from "picocolors";
833
- var updateCommand = new Command5("update").description("Update installed packs").argument("[pack]", "Pack name (omit to update all)").option("--cwd <dir>", "Project directory").action((packName, opts) => {
834
- const projectDir = opts.cwd ? path8.resolve(opts.cwd) : process.cwd();
898
+ var updateCommand = new Command5("update").description("Update installed packs").argument("[pack]", "Pack name (omit to update all)").option("--cwd <dir>", "Project directory").action(async (packName, opts) => {
899
+ const projectDir = opts.cwd ? path9.resolve(opts.cwd) : process.cwd();
835
900
  const config = readConfig(projectDir);
836
- if (!config) {
837
- console.error(pc5.red("No .ai-kit.json found. Run ") + pc5.cyan("ai-kit init") + pc5.red(" first."));
838
- process.exit(1);
839
- }
840
- const packsToUpdate = packName ? [packName] : Object.keys(config.packs);
841
- if (packsToUpdate.length === 0) {
842
- console.log(pc5.dim("No packs installed."));
901
+ if (!config || Object.keys(config.packs).length === 0) {
902
+ console.log(pc5.dim("No packs installed yet. Starting setup...\n"));
903
+ await initCommand.parseAsync(["init", ...opts.cwd ? ["--cwd", opts.cwd] : []], { from: "user" });
843
904
  return;
844
905
  }
906
+ const packsToUpdate = packName ? [packName] : Object.keys(config.packs);
845
907
  let updated = 0;
846
908
  let skipped = 0;
847
909
  let hadSkills = false;
@@ -891,11 +953,57 @@ Updated ${updated} pack(s).`));
891
953
  }
892
954
  });
893
955
 
956
+ // src/commands/search.ts
957
+ import { Command as Command6 } from "commander";
958
+ import pc6 from "picocolors";
959
+ var searchCommand = new Command6("search").description("Search available packs by name or keyword").argument("<query>", "Search term (matches name, description, keywords)").action((query) => {
960
+ let packs;
961
+ try {
962
+ packs = discoverPacks();
963
+ } catch {
964
+ console.error(pc6.red("Could not find marketplace.json."));
965
+ process.exit(1);
966
+ }
967
+ const q = query.toLowerCase();
968
+ const matches = packs.filter((pack) => {
969
+ const haystack = [
970
+ pack.name,
971
+ pack.description,
972
+ pack.category,
973
+ ...pack.keywords
974
+ ].join(" ").toLowerCase();
975
+ return haystack.includes(q);
976
+ });
977
+ if (matches.length === 0) {
978
+ console.log(pc6.dim(`No packs matching "${query}".`));
979
+ console.log(pc6.dim(`
980
+ All packs: ${packs.map((p3) => p3.name).join(", ")}`));
981
+ return;
982
+ }
983
+ console.log(pc6.bold(`Found ${matches.length} pack(s):
984
+ `));
985
+ for (const pack of matches) {
986
+ const counts = [];
987
+ if (pack.agents.length) counts.push(`${pack.agents.length} agents`);
988
+ if (pack.skills.length) counts.push(`${pack.skills.length} skills`);
989
+ if (pack.commands.length) counts.push(`${pack.commands.length} commands`);
990
+ if (pack.rules.length) counts.push(`${pack.rules.length} rules`);
991
+ console.log(
992
+ ` ${pc6.cyan(pack.name)} ${pc6.dim(`v${pack.version}`)}
993
+ ${pack.description}
994
+ ${pc6.dim(counts.join(", "))}
995
+ ${pc6.dim(`Install: npx @smicolon/ai-kit@latest add ${pack.name}`)}
996
+ `
997
+ );
998
+ }
999
+ });
1000
+
894
1001
  // src/index.ts
895
- var program = new Command6().name("ai-kit").description("AI coding tool pack manager").version("0.0.1");
1002
+ var program = new Command7().name("ai-kit").description("AI coding tool pack manager").version("0.0.1");
896
1003
  program.addCommand(initCommand);
897
1004
  program.addCommand(addCommand);
898
1005
  program.addCommand(listCommand);
899
1006
  program.addCommand(removeCommand);
900
1007
  program.addCommand(updateCommand);
1008
+ program.addCommand(searchCommand);
901
1009
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smicolon/ai-kit",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "AI coding tool pack manager for Smicolon standards",
5
5
  "license": "MIT",
6
6
  "repository": {