@clubmatto/ai-kit 0.0.1

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 (70) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +65 -0
  3. package/dist/scripts/fetch-playwright-skills.js +63 -0
  4. package/dist/src/cmd/sync.js +109 -0
  5. package/dist/src/commands/sync.js +111 -0
  6. package/dist/src/content.js +99 -0
  7. package/dist/src/index.js +19 -0
  8. package/dist/src/logger.js +2 -0
  9. package/dist/src/manifest.js +24 -0
  10. package/dist/src/output.js +46 -0
  11. package/dist/src/reader.js +99 -0
  12. package/dist/src/template.js +10 -0
  13. package/dist/tests/content.test.js +141 -0
  14. package/dist/tests/integration/cli.test.js +43 -0
  15. package/dist/tests/output.js +36 -0
  16. package/dist/tests/reader.test.js +141 -0
  17. package/dist/tests/sync.test.js +90 -0
  18. package/dist/tests/utils.js +20 -0
  19. package/dist/vitest.config.js +9 -0
  20. package/docs/roadmap.md +16 -0
  21. package/eslint.config.mjs +38 -0
  22. package/package.json +78 -0
  23. package/scripts/fetch-playwright-skills.ts +79 -0
  24. package/src/agents/monorepo.md +30 -0
  25. package/src/agents/opencode.json +31 -0
  26. package/src/cmd/sync.ts +158 -0
  27. package/src/commands/commit.md +43 -0
  28. package/src/commands/interview.md +92 -0
  29. package/src/commands/synth.md +45 -0
  30. package/src/index.ts +24 -0
  31. package/src/logger.ts +10 -0
  32. package/src/manifest.ts +29 -0
  33. package/src/output.ts +66 -0
  34. package/src/reader.ts +114 -0
  35. package/src/rules/go.md +306 -0
  36. package/src/rules/kotlin.md +177 -0
  37. package/src/rules/plan-mode.md +7 -0
  38. package/src/rules/spring-boot.md +549 -0
  39. package/src/rules/typescript.md +302 -0
  40. package/src/rules/unsure.md +9 -0
  41. package/src/skills/image-gen/SKILL.md +50 -0
  42. package/src/skills/image-gen/scripts/generate.js +166 -0
  43. package/src/skills/playwright-cli/SKILL.md +279 -0
  44. package/src/skills/playwright-cli/references/request-mocking.md +87 -0
  45. package/src/skills/playwright-cli/references/running-code.md +232 -0
  46. package/src/skills/playwright-cli/references/session-management.md +170 -0
  47. package/src/skills/playwright-cli/references/storage-state.md +275 -0
  48. package/src/skills/playwright-cli/references/test-generation.md +88 -0
  49. package/src/skills/playwright-cli/references/tracing.md +142 -0
  50. package/src/skills/playwright-cli/references/video-recording.md +43 -0
  51. package/src/template.ts +14 -0
  52. package/tests/fixtures/agents/another.json +4 -0
  53. package/tests/fixtures/agents/monorepo.md +5 -0
  54. package/tests/fixtures/agents/opencode.json +4 -0
  55. package/tests/fixtures/commands/another.md +5 -0
  56. package/tests/fixtures/commands/commit.md +7 -0
  57. package/tests/fixtures/commands/test.md +13 -0
  58. package/tests/fixtures/rules/nested/nested-rule.md +3 -0
  59. package/tests/fixtures/rules/test-rule.md +5 -0
  60. package/tests/fixtures/rules/typescript.md +5 -0
  61. package/tests/fixtures/skills/test-skill/SKILL.md +7 -0
  62. package/tests/fixtures/skills/test-skill/nested-refs/doc.md +3 -0
  63. package/tests/fixtures/skills/test-skill/skill-details.md +7 -0
  64. package/tests/integration/cli.test.ts +55 -0
  65. package/tests/output.ts +37 -0
  66. package/tests/reader.test.ts +193 -0
  67. package/tests/sync.test.ts +136 -0
  68. package/tests/utils.ts +17 -0
  69. package/tsconfig.json +23 -0
  70. package/vitest.config.ts +8 -0
@@ -0,0 +1,79 @@
1
+ import { mkdirSync, writeFileSync } from "fs";
2
+ import { join } from "path";
3
+
4
+ const rootDir = join(__dirname, "..");
5
+ const skillsDir = join(rootDir, "src", "skills", "playwright-cli");
6
+
7
+ const GITHUB_API = "https://api.github.com";
8
+ const REPO = "microsoft/playwright-cli";
9
+
10
+ const args = process.argv.slice(2);
11
+ const VERSION = args[0] || "v0.1.1";
12
+
13
+ interface GitHubFile {
14
+ name: string;
15
+ path: string;
16
+ type: "file" | "dir";
17
+ download_url?: string;
18
+ url?: string;
19
+ }
20
+
21
+ async function fetchJson(url: string): Promise<GitHubFile[]> {
22
+ const res = await fetch(url, {
23
+ headers: {
24
+ Accept: "application/vnd.github.v3+json",
25
+ "User-Agent": "ai-kit",
26
+ },
27
+ });
28
+ if (!res.ok) {
29
+ throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
30
+ }
31
+ return (await res.json()) as GitHubFile[];
32
+ }
33
+
34
+ async function fetchFile(url: string): Promise<string> {
35
+ const res = await fetch(url, {
36
+ headers: {
37
+ Accept: "application/vnd.github.v3.raw",
38
+ "User-Agent": "ai-kit",
39
+ },
40
+ });
41
+ if (!res.ok) {
42
+ throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
43
+ }
44
+ return res.text();
45
+ }
46
+
47
+ async function main() {
48
+ console.log(`Fetching playwright-cli skills from ${REPO}@${VERSION}...`);
49
+
50
+ const baseUrl = `${GITHUB_API}/repos/${REPO}/contents/skills/playwright-cli?ref=${VERSION}`;
51
+ const files = await fetchJson(baseUrl);
52
+
53
+ mkdirSync(skillsDir, { recursive: true });
54
+ mkdirSync(join(skillsDir, "references"), { recursive: true });
55
+
56
+ for (const file of files) {
57
+ if (file.type === "dir" && file.name === "references") {
58
+ const refs = await fetchJson(file.url!);
59
+ for (const ref of refs) {
60
+ const content = await fetchFile(ref.download_url!);
61
+ const targetPath = join(skillsDir, "references", ref.name);
62
+ writeFileSync(targetPath, content);
63
+ console.log(` - references/${ref.name}`);
64
+ }
65
+ } else if (file.type === "file") {
66
+ const content = await fetchFile(file.download_url!);
67
+ const targetPath = join(skillsDir, file.name);
68
+ writeFileSync(targetPath, content);
69
+ console.log(` - ${file.name}`);
70
+ }
71
+ }
72
+
73
+ console.log(`\nDone! Skills written to src/skills/playwright-cli/`);
74
+ }
75
+
76
+ main().catch((err) => {
77
+ console.error(err);
78
+ process.exit(1);
79
+ });
@@ -0,0 +1,30 @@
1
+ # Agents.md
2
+
3
+ This is a monorepo containing apps in many languages.
4
+
5
+ You MUST follow the specific rules for each language. **ALWAYS** start from
6
+ checking out README.md.
7
+
8
+ ## Build Systems
9
+
10
+ - **Kotlin/Java**: Gradle (Kotlin DSL). Use `./gradlew` commands.
11
+ - **Go**: Go modules. Use `go` commands.
12
+ - **TypeScript**: Use `npm`.
13
+
14
+ ## How to Find the Right Rules
15
+
16
+ 1. **Identify the primary language** of the file(s) you're working with.
17
+ 2. **Navigate to `.agents/rules/`** and open the corresponding file:
18
+ - TypeScript → `typescript.md`
19
+ - Go → `go.md`
20
+ - Kotlin → `kotlin.md`
21
+ - etc.
22
+
23
+ CRITICAL: When you encounter a file reference (e.g., `@.ai/rules/go.md`), use the Read tool to load it on a need-to-know basis.
24
+
25
+ ## Additional Guidelines
26
+
27
+ - [Plan Mode](.agents/rules/plan-mode.md)
28
+ - [When You're Unsure](.agents/rules/unsure.md)
29
+
30
+ _{{AGENTS_FOOTER}}_
@@ -0,0 +1,31 @@
1
+ {
2
+ "$schema": "https://opencode.ai/config.json",
3
+ "mcp": {
4
+ "context7": {
5
+ "type": "remote",
6
+ "url": "https://mcp.context7.com/mcp",
7
+ "headers": {
8
+ "CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}"
9
+ },
10
+ "enabled": false
11
+ },
12
+ "circleci": {
13
+ "type": "local",
14
+ "command": ["npx", "-y", "@circleci/mcp-server-circleci@latest"],
15
+ "environment": {
16
+ "CIRCLECI_TOKEN": "{env:CIRCLECI_TOKEN}",
17
+ "CIRCLECI_BASE_URL": "https://circleci.com"
18
+ },
19
+ "enabled": false
20
+ },
21
+ "MiniMax": {
22
+ "type": "local",
23
+ "command": ["uvx", "minimax-coding-plan-mcp", "-y"],
24
+ "environment": {
25
+ "MINIMAX_API_KEY": "{env:MINIMAX_API_KEY}",
26
+ "MINIMAX_API_HOST": "https://api.minimax.io"
27
+ },
28
+ "enabled": false
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,158 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "fs";
2
+ import { dirname, join } from "path";
3
+ import {
4
+ readAgents,
5
+ getCommandConfig,
6
+ readContent,
7
+ readConfigs,
8
+ SyncItem,
9
+ } from "../reader";
10
+ import { readManifest, writeManifest } from "../manifest";
11
+ import { processTemplate } from "../template";
12
+ import { log, SyncStats } from "../output";
13
+ import { Logger } from "../logger";
14
+
15
+ const rootDir = join(__dirname, "..", "..", "..");
16
+
17
+ export interface SourceDirs {
18
+ rules: string;
19
+ skills: string;
20
+ agents: string;
21
+ commands: string;
22
+ }
23
+
24
+ const defaultSourceDirs: SourceDirs = {
25
+ rules: join(rootDir, "src", "rules"),
26
+ skills: join(rootDir, "src", "skills"),
27
+ agents: join(rootDir, "src", "agents"),
28
+ commands: join(rootDir, "src", "commands"),
29
+ };
30
+
31
+ interface SyncOptions {
32
+ skipOpencode?: boolean;
33
+ }
34
+
35
+ export async function sync(
36
+ cwd: string,
37
+ version: string,
38
+ options: SyncOptions,
39
+ logger: Logger = log,
40
+ sourceDirs: SourceDirs = defaultSourceDirs,
41
+ ): Promise<void> {
42
+ const manifest = readManifest(cwd);
43
+ logger.logo(version);
44
+
45
+ if (manifest && manifest.version === version) {
46
+ logger.success(`Already at latest version (${version})`);
47
+ return;
48
+ }
49
+
50
+ logger.welcome();
51
+ const counts = await doSync(cwd, version, options, logger, sourceDirs);
52
+ logger.summary(counts);
53
+ }
54
+
55
+ function writeItem(aiDir: string, file: SyncItem): void {
56
+ const targetDir = join(aiDir, file.type);
57
+ if (!existsSync(targetDir)) {
58
+ mkdirSync(targetDir, { recursive: true });
59
+ }
60
+ const targetPath = join(targetDir, file.name);
61
+ const parentDir = dirname(targetPath);
62
+ if (!existsSync(parentDir)) {
63
+ mkdirSync(parentDir, { recursive: true });
64
+ }
65
+ writeFileSync(targetPath, processTemplate(file.content));
66
+ }
67
+
68
+ async function doSync(
69
+ cwd: string,
70
+ version: string,
71
+ options: SyncOptions,
72
+ logger: Logger,
73
+ sourceDirs: SourceDirs,
74
+ ): Promise<SyncStats> {
75
+ const aiDir = join(cwd, ".agents");
76
+
77
+ if (!existsSync(aiDir)) {
78
+ mkdirSync(aiDir, { recursive: true });
79
+ }
80
+
81
+ const contentFiles = readContent(sourceDirs.rules, sourceDirs.skills);
82
+ const rootFiles = readConfigs(sourceDirs.agents);
83
+ const agentsFile = readAgents(sourceDirs.agents);
84
+
85
+ const rules = contentFiles.filter((f) => f.type === "rules");
86
+
87
+ const stats: SyncStats = { rules: 0, skills: 0, commands: 0 };
88
+
89
+ const installedRootFiles: string[] = [];
90
+ if (!options.skipOpencode) {
91
+ const commandConfig = getCommandConfig(sourceDirs.commands);
92
+ stats.commands = Object.keys(commandConfig).length;
93
+
94
+ if (Object.keys(commandConfig).length > 0) {
95
+ logger.section("commands");
96
+ for (const name of Object.keys(commandConfig)) {
97
+ logger.success(`${name}.md`);
98
+ }
99
+ }
100
+
101
+ if (rootFiles.length > 0) {
102
+ logger.section("configs");
103
+ for (const file of rootFiles) {
104
+ let content = file.content;
105
+ if (
106
+ file.name === "opencode.json" &&
107
+ Object.keys(commandConfig).length > 0
108
+ ) {
109
+ const config = JSON.parse(content);
110
+ config.command = commandConfig;
111
+ content = JSON.stringify(config, null, 2) + "\n";
112
+ }
113
+ const targetPath = join(cwd, file.name);
114
+ writeFileSync(targetPath, content);
115
+ logger.success(`${file.name}`);
116
+ installedRootFiles.push(file.name);
117
+ }
118
+ }
119
+ }
120
+
121
+ if (agentsFile) {
122
+ const targetPath = join(cwd, agentsFile.name);
123
+ writeFileSync(targetPath, processTemplate(agentsFile.content));
124
+ logger.success(`${agentsFile.name}`);
125
+ }
126
+
127
+ if (rules.length > 0) {
128
+ logger.section("rules");
129
+ for (const file of rules) {
130
+ writeItem(aiDir, file);
131
+ logger.success(`${file.name}`);
132
+ stats.rules++;
133
+ }
134
+ }
135
+
136
+ const skills = contentFiles.filter((f) => f.type === "skills");
137
+
138
+ if (skills.length > 0) {
139
+ logger.section("skills");
140
+ const skillDirs = [...new Set(skills.map((f) => f.name.split("/")[0]))];
141
+ stats.skills = skillDirs.length;
142
+ for (const dir of skillDirs) {
143
+ const dirFiles = skills.filter((f) => f.name.startsWith(dir + "/"));
144
+ logger.success(`${dir} (${dirFiles.length} files)`);
145
+ for (const file of dirFiles) {
146
+ writeItem(aiDir, file);
147
+ }
148
+ }
149
+ }
150
+
151
+ writeManifest(cwd, {
152
+ version,
153
+ installedAt: new Date().toISOString(),
154
+ rootFiles: installedRootFiles,
155
+ });
156
+
157
+ return stats;
158
+ }
@@ -0,0 +1,43 @@
1
+ ---
2
+ description: Commit the work done in this session with a structured commit message.
3
+ ---
4
+
5
+ Create a commit with the following format:
6
+
7
+ ## Commit Message Format
8
+
9
+ **First line (one-liner):**
10
+
11
+ - Use conventional commits format: `<type>: <description>`
12
+ - Examples: `feat: add init command`, `fix: resolve path issue`, `docs: update README`
13
+
14
+ **Body (bullet list):**
15
+
16
+ - List the main changes made in this session
17
+ - Each item should be a brief description of a specific change
18
+
19
+ **Sign-off:**
20
+
21
+ - End with: `created with the help of <MODEL>`
22
+ - Use the current model name (e.g. "MiniMax", "GPT-4", "Claude")
23
+
24
+ ## Example
25
+
26
+ ```
27
+ feat: add init and update commands
28
+
29
+ - Created init command for first-time setup
30
+ - Added manifest tracking in .ai/.ai-kit
31
+ - Implemented update command for version sync
32
+ - Added --skip-opencode option
33
+
34
+ created with the help of MiniMax
35
+ ```
36
+
37
+ ## Process
38
+
39
+ 1. First, review all changes with `git status` and `git diff`
40
+ 2. Write a concise one-liner following conventional commits
41
+ 3. List the key changes as bullet points
42
+ 4. Add the sign-off line with the current model
43
+ 5. Commit with `git commit -m "your message"`
@@ -0,0 +1,92 @@
1
+ ---
2
+ description: Interview the user to define a feature, then generate a task‑oriented implementation plan as a markdown document.
3
+ ---
4
+
5
+ # Implementation Plan Generation
6
+
7
+ You are a technical architect. Your task is to conduct a structured interview
8
+ with the user, then produce a \*
9
+ \*task‑oriented implementation plan\*\* — not a narrative PRD. The final output
10
+ must be a series of concrete, actionable
11
+ tasks, each with explicit sequential actions, and verification steps.
12
+
13
+ Follow the process below. Skip steps only if clearly irrelevant.
14
+
15
+ ## Process
16
+
17
+ ### 1. Problem Elicitation
18
+
19
+ Ask the user for a detailed description of the problem they want to solve. Probe
20
+ for:
21
+
22
+ - Current pain points or limitations
23
+ - User impact and severity
24
+ - Any initial solution ideas they have in mind
25
+
26
+ ### 2. Context Verification
27
+
28
+ Explore the codebase to verify the user's assertions. Understand the current
29
+ state of relevant modules, existing
30
+ patterns, and potential constraints. **Do not ask the user for this
31
+ information—investigate it yourself.**
32
+
33
+ ### 3. Option Exploration
34
+
35
+ Ask whether the user has considered alternative approaches. Based on your
36
+ understanding of the codebase, present 1-3
37
+ other viable options with brief trade-offs (complexity, effort,
38
+ maintainability).
39
+
40
+ ### 4. Deep Dive Interview
41
+
42
+ Conduct a thorough interview about the proposed implementation. Your goal is to
43
+ gather **everything needed to write
44
+ concrete tasks**. Cover:
45
+
46
+ - Exact files to create/modify (capture paths **relative to project root**)
47
+ - Specific dependencies to add (with version research notes)
48
+ - Precise code changes (what, where, why)
49
+ - Edge cases and error states
50
+ - Performance expectations
51
+ - Compatibility requirements
52
+ - Security considerations
53
+
54
+ ### 5. Scope Definition
55
+
56
+ Explicitly define what is **in scope** and what is **out of scope** for this
57
+ plan. Confirm with the user.
58
+
59
+ ### 6. Task Breakdown
60
+
61
+ Decompose the implementation into **discrete, executable tasks**. Each task must
62
+ follow this exact structure:
63
+
64
+ ## Tasks
65
+
66
+ ### N. [Task Title]
67
+
68
+ **Actions:**
69
+
70
+ 1. [Specific, sequential action]
71
+ 2. [Next action, with code block if needed]
72
+ 3. [Etc.]
73
+
74
+ **Verification:
75
+ ** [Exact command to run or observable outcome that confirms this task is complete]
76
+
77
+ **Rules for tasks:**
78
+
79
+ - File paths must be **relative to project root** (no absolute paths, no
80
+ `/Users/...`)
81
+ - Actions must be **atomic**—one logical change per numbered step
82
+ - Include inline code blocks where syntax matters
83
+ - Verification must be **executable** (a command, a test, a visible UI state)
84
+ - Tasks should be **orderable**—later tasks may depend on earlier ones
85
+
86
+ ### 7. Plan Generation
87
+
88
+ Once all information is gathered, generate the complete implementation plan
89
+ using the format above. The plan should be
90
+ ready to paste directly into a markdown document.
91
+
92
+ Begin the interview now.
@@ -0,0 +1,45 @@
1
+ ---
2
+ description: To consolidate the key learnings, patterns, successful code structures, and conceptual insights from the current session into a reusable, LLM-friendly reference document.
3
+ ---
4
+
5
+ Synthesize the core work, discoveries, and decisions from this development
6
+ session into a structured, clear, and
7
+ future-oriented document. This document should enable an LLM or a future
8
+ developer to quickly understand and reuse the
9
+ outcomes of this session.
10
+
11
+ ## 1. Core Task Summary
12
+
13
+ Summarize the central problem or goal of this session in 1-2 sentences.
14
+
15
+ ## 2. Key Outcomes & Solutions
16
+
17
+ List the main features implemented, critical bugs fixed, or core modules
18
+ completed. For complex items, provide **code
19
+ snippets, architectural descriptions**, or key configurations.
20
+
21
+ ## 3. Learnings & Discoveries
22
+
23
+ Document important insights gained during this session, such as:
24
+
25
+ - New usages or best practices for specific libraries, APIs, or tools.
26
+ - New understandings about system design or data flow.
27
+ - Pitfalls encountered and their solutions.
28
+
29
+ ## 4. Reusable Patterns & Code Templates
30
+
31
+ Extract patterns, code templates, or configuration snippets that can be \*
32
+ \*directly applied to future projects or similar
33
+ tasks\*\*. Include brief context and usage instructions.
34
+
35
+ ## 5. LLM Collaboration Notes (Optional)
36
+
37
+ If this session involved prompt techniques, instruction patterns, or context
38
+ management methods particularly useful for
39
+ collaborating with an LLM, document them here.
40
+
41
+ ## Synthesis Process
42
+
43
+ 1. First, review the entire session's conversation history and code changes.
44
+ 2. Then, synthesize the content strictly following the section structure above.
45
+ 3. The final output must be a complete, standalone Markdown document.
package/src/index.ts ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { sync } from "./cmd/sync";
4
+ import { readFileSync } from "fs";
5
+ import { join } from "path";
6
+
7
+ const version = JSON.parse(
8
+ readFileSync(join(__dirname, "..", "..", "package.json"), "utf-8"),
9
+ ).version;
10
+
11
+ const program = new Command();
12
+
13
+ program
14
+ .name("@clubmatto/ai-kit")
15
+ .description("The AI configuration CLI from Club Matto")
16
+ .version(version)
17
+ .option("--skip-opencode", "Skip installing opencode.json to project root");
18
+
19
+ program
20
+ .command("sync")
21
+ .description("Initialize or update AI configuration")
22
+ .action(() => sync(process.cwd(), version, program.opts()));
23
+
24
+ program.parse();
package/src/logger.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { SyncStats } from "./output";
2
+
3
+ export interface Logger {
4
+ logo: (version: string) => void;
5
+ welcome: () => void;
6
+ section: (msg: string) => void;
7
+ success: (msg: string) => void;
8
+ final: (msg: string) => void;
9
+ summary: (counts: SyncStats) => void;
10
+ }
@@ -0,0 +1,29 @@
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
2
+ import { join } from "path";
3
+
4
+ interface Manifest {
5
+ version: string;
6
+ installedAt: string;
7
+ rootFiles?: string[];
8
+ }
9
+
10
+ const AI_DIR = ".agents";
11
+ const MANIFEST_FILE = ".ai-kit";
12
+
13
+ function getManifestPath(cwd: string): string {
14
+ return join(cwd, AI_DIR, MANIFEST_FILE);
15
+ }
16
+
17
+ export function readManifest(cwd: string): Manifest | null {
18
+ const path = getManifestPath(cwd);
19
+ if (!existsSync(path)) return null;
20
+ return JSON.parse(readFileSync(path, "utf-8"));
21
+ }
22
+
23
+ export function writeManifest(cwd: string, manifest: Manifest): void {
24
+ const dir = join(cwd, AI_DIR);
25
+ if (!existsSync(dir)) {
26
+ mkdirSync(dir, { recursive: true });
27
+ }
28
+ writeFileSync(getManifestPath(cwd), JSON.stringify(manifest, null, 2));
29
+ }
package/src/output.ts ADDED
@@ -0,0 +1,66 @@
1
+ import gradient from "gradient-string";
2
+
3
+ const brand = gradient(["#ff006e", "#fb5607", "#ffbe0b", "#8338ec", "#3a86ff"]);
4
+
5
+ type Color = "green" | "cyan" | "yellow" | "red" | "dim" | "white" | "reset";
6
+
7
+ const colors: Record<Color, string> = {
8
+ green: "\x1b[32m",
9
+ cyan: "\x1b[36m",
10
+ yellow: "\x1b[33m",
11
+ red: "\x1b[31m",
12
+ dim: "\x1b[90m",
13
+ white: "\x1b[37m",
14
+ reset: "\x1b[0m",
15
+ };
16
+
17
+ function colorize(text: string, color: Color): string {
18
+ return `${colors[color]}${text}${colors.reset}`;
19
+ }
20
+
21
+ export interface SyncStats {
22
+ rules: number;
23
+ skills: number;
24
+ commands: number;
25
+ }
26
+
27
+ export const log = {
28
+ logo: (version: string) => {
29
+ console.log(
30
+ brand(`ai-kit v${version}`) +
31
+ " " +
32
+ colorize("The AI configuration CLI", "dim"),
33
+ );
34
+ console.log(colorize("from Club Matto\n", "dim"));
35
+ },
36
+
37
+ welcome: () => {
38
+ console.log(
39
+ colorize(
40
+ " Syncing AI rules, skills, and commands to your project...\n",
41
+ "dim",
42
+ ),
43
+ );
44
+ },
45
+
46
+ section: (msg: string) => console.log(colorize(` → ${msg}`, "cyan")),
47
+
48
+ success: (msg: string) => console.log(colorize(` ✓ ${msg}`, "green")),
49
+
50
+ final: (msg: string) => console.log(colorize(` ✓ ${msg}`, "green")),
51
+
52
+ summary: (counts: SyncStats) => {
53
+ console.log(colorize("\n ✓ Done!", "green"));
54
+ console.log(
55
+ colorize(` → `, "dim") +
56
+ colorize(counts.commands.toString(), "white") +
57
+ colorize(` commands`, "dim") +
58
+ colorize(`, `, "dim") +
59
+ colorize(counts.rules.toString(), "white") +
60
+ colorize(` rules`, "dim") +
61
+ colorize(`, `, "dim") +
62
+ colorize(counts.skills.toString(), "white") +
63
+ colorize(` skills`, "dim"),
64
+ );
65
+ },
66
+ };