@clubmatto/ai-kit 0.0.5 → 0.0.6
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.
- package/package.json +1 -1
- package/src/cmd/sync.ts +0 -158
- package/src/gradient-string.d.ts +0 -1
- package/src/index.ts +0 -24
- package/src/logger.ts +0 -10
- package/src/manifest.ts +0 -29
- package/src/output.ts +0 -66
- package/src/reader.ts +0 -114
- package/src/template.ts +0 -14
package/package.json
CHANGED
package/src/cmd/sync.ts
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
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
|
-
}
|
package/src/gradient-string.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
declare module 'gradient-string';
|
package/src/index.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
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
|
-
}
|
package/src/manifest.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
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
|
-
};
|
package/src/reader.ts
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { readdirSync, readFileSync } from "fs";
|
|
2
|
-
import { join } from "path";
|
|
3
|
-
|
|
4
|
-
type SyncType = "commands" | "rules" | "skills" | "config";
|
|
5
|
-
|
|
6
|
-
export interface SyncItem {
|
|
7
|
-
type: SyncType;
|
|
8
|
-
name: string;
|
|
9
|
-
content: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface CommandConfig {
|
|
13
|
-
template: string;
|
|
14
|
-
description: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function parseFrontmatter(content: string): Record<string, string> {
|
|
18
|
-
const result: Record<string, string> = {};
|
|
19
|
-
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
20
|
-
if (!match) return result;
|
|
21
|
-
|
|
22
|
-
const frontmatter = match[1];
|
|
23
|
-
for (const line of frontmatter.split("\n")) {
|
|
24
|
-
const colonIndex = line.indexOf(":");
|
|
25
|
-
if (colonIndex === -1) continue;
|
|
26
|
-
const key = line.slice(0, colonIndex).trim();
|
|
27
|
-
result[key] = line.slice(colonIndex + 1).trim();
|
|
28
|
-
}
|
|
29
|
-
return result;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function getCommandConfig(
|
|
33
|
-
commandsDir: string,
|
|
34
|
-
): Record<string, CommandConfig> {
|
|
35
|
-
const config: Record<string, CommandConfig> = {};
|
|
36
|
-
|
|
37
|
-
try {
|
|
38
|
-
const files = readdirSync(commandsDir);
|
|
39
|
-
for (const file of files) {
|
|
40
|
-
if (!file.endsWith(".md")) continue;
|
|
41
|
-
const filePath = join(commandsDir, file);
|
|
42
|
-
const content = readFileSync(filePath, "utf-8");
|
|
43
|
-
const frontmatter = parseFrontmatter(content);
|
|
44
|
-
const name = file.replace(/\.md$/, "");
|
|
45
|
-
const body = content.replace(/^---[\s\S]*?---\n/, "").trim();
|
|
46
|
-
config[name] = {
|
|
47
|
-
description: frontmatter.description || "",
|
|
48
|
-
template: body,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
} catch {
|
|
52
|
-
return config;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return config;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function readFiles(dir: string, type: SyncType, baseDir?: string): SyncItem[] {
|
|
59
|
-
if (!readdirSync(dir, { withFileTypes: true }).length) {
|
|
60
|
-
return [];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const base = baseDir || dir;
|
|
64
|
-
|
|
65
|
-
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
66
|
-
const path = join(dir, entry.name);
|
|
67
|
-
const relativePath = path.slice(base.length + 1);
|
|
68
|
-
if (entry.isDirectory()) {
|
|
69
|
-
return readFiles(path, type, base);
|
|
70
|
-
}
|
|
71
|
-
if (entry.name.endsWith(".md")) {
|
|
72
|
-
return [
|
|
73
|
-
{
|
|
74
|
-
type,
|
|
75
|
-
name: relativePath,
|
|
76
|
-
content: readFileSync(path, "utf-8"),
|
|
77
|
-
},
|
|
78
|
-
];
|
|
79
|
-
}
|
|
80
|
-
return [];
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export function readContent(rulesDir: string, skillsDir: string): SyncItem[] {
|
|
85
|
-
return [...readFiles(rulesDir, "rules"), ...readFiles(skillsDir, "skills")];
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function readConfigs(agentsDir: string): SyncItem[] {
|
|
89
|
-
try {
|
|
90
|
-
return readdirSync(agentsDir, { withFileTypes: true })
|
|
91
|
-
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
|
|
92
|
-
.map((entry) => ({
|
|
93
|
-
type: "config",
|
|
94
|
-
name: entry.name,
|
|
95
|
-
content: readFileSync(join(agentsDir, entry.name), "utf-8"),
|
|
96
|
-
}));
|
|
97
|
-
} catch {
|
|
98
|
-
return [];
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function readAgents(agentsDir: string): SyncItem | null {
|
|
103
|
-
const sourcePath = join(agentsDir, "monorepo.md");
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
return {
|
|
107
|
-
type: "config",
|
|
108
|
-
name: "AGENTS.md",
|
|
109
|
-
content: readFileSync(sourcePath, "utf-8"),
|
|
110
|
-
};
|
|
111
|
-
} catch {
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
}
|
package/src/template.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export function processTemplate(content: string): string {
|
|
2
|
-
const now = new Date();
|
|
3
|
-
const isoDate = now.toISOString().split("T")[0];
|
|
4
|
-
|
|
5
|
-
return content
|
|
6
|
-
.replace(
|
|
7
|
-
/\{\{FOOTER}}/g,
|
|
8
|
-
`Last updated: ${isoDate}. This file extends the global rules in @AGENTS.md. Always check both files.`,
|
|
9
|
-
)
|
|
10
|
-
.replace(
|
|
11
|
-
/\{\{AGENTS_FOOTER}}/g,
|
|
12
|
-
`This file was last updated: ${isoDate}. Always check the \`.agents/rules/\` directory for the most current language-specific guidelines.`,
|
|
13
|
-
);
|
|
14
|
-
}
|