@valentia-ai-skills/framework 1.0.14 → 2.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.
- package/bin/cli.js +620 -101
- package/package.json +6 -2
package/bin/cli.js
CHANGED
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
* ai-skills CLI — @valentia-ai-skills/framework
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
* npx ai-skills setup # Enter email → lookup team → install
|
|
8
|
-
* npx ai-skills update # Re-fetch
|
|
9
|
-
* npx ai-skills status # Show installed
|
|
7
|
+
* npx ai-skills setup # Enter email → lookup team → install full toolkit
|
|
8
|
+
* npx ai-skills update # Re-fetch toolkit from Supabase for your team
|
|
9
|
+
* npx ai-skills status # Show installed toolkit and team info
|
|
10
10
|
* npx ai-skills list # List all locally available skills
|
|
11
11
|
* npx ai-skills doctor # Health check
|
|
12
|
+
* npx ai-skills analyze # Analyze last commit against active skills
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
const fs = require("fs");
|
|
@@ -31,6 +32,111 @@ const ANALYZE_FUNCTION_URL =
|
|
|
31
32
|
const SCAN_FUNCTION_URL =
|
|
32
33
|
process.env.AI_SKILLS_SCAN_URL || "https://znshdhjquohrzvbnloki.supabase.co/functions/v1/scan-results";
|
|
33
34
|
|
|
35
|
+
// ── Language Detection ──
|
|
36
|
+
|
|
37
|
+
const LANGUAGE_DETECTORS = {
|
|
38
|
+
typescript: {
|
|
39
|
+
files: ["tsconfig.json", "tsconfig.base.json"],
|
|
40
|
+
packageDeps: ["typescript"],
|
|
41
|
+
extensions: [".ts", ".tsx"],
|
|
42
|
+
},
|
|
43
|
+
javascript: {
|
|
44
|
+
files: ["jsconfig.json"],
|
|
45
|
+
packageDeps: [],
|
|
46
|
+
extensions: [".js", ".jsx"],
|
|
47
|
+
},
|
|
48
|
+
python: {
|
|
49
|
+
files: ["requirements.txt", "Pipfile", "pyproject.toml", "setup.py", "setup.cfg"],
|
|
50
|
+
packageDeps: [],
|
|
51
|
+
extensions: [".py"],
|
|
52
|
+
},
|
|
53
|
+
golang: {
|
|
54
|
+
files: ["go.mod", "go.sum"],
|
|
55
|
+
packageDeps: [],
|
|
56
|
+
extensions: [".go"],
|
|
57
|
+
},
|
|
58
|
+
rust: {
|
|
59
|
+
files: ["Cargo.toml", "Cargo.lock"],
|
|
60
|
+
packageDeps: [],
|
|
61
|
+
extensions: [".rs"],
|
|
62
|
+
},
|
|
63
|
+
java: {
|
|
64
|
+
files: ["pom.xml", "build.gradle", "build.gradle.kts"],
|
|
65
|
+
packageDeps: [],
|
|
66
|
+
extensions: [".java"],
|
|
67
|
+
},
|
|
68
|
+
kotlin: {
|
|
69
|
+
files: ["build.gradle.kts"],
|
|
70
|
+
packageDeps: [],
|
|
71
|
+
extensions: [".kt", ".kts"],
|
|
72
|
+
},
|
|
73
|
+
swift: {
|
|
74
|
+
files: ["Package.swift", "*.xcodeproj"],
|
|
75
|
+
packageDeps: [],
|
|
76
|
+
extensions: [".swift"],
|
|
77
|
+
},
|
|
78
|
+
php: {
|
|
79
|
+
files: ["composer.json", "artisan"],
|
|
80
|
+
packageDeps: [],
|
|
81
|
+
extensions: [".php"],
|
|
82
|
+
},
|
|
83
|
+
csharp: {
|
|
84
|
+
files: ["*.csproj", "*.sln"],
|
|
85
|
+
packageDeps: [],
|
|
86
|
+
extensions: [".cs"],
|
|
87
|
+
},
|
|
88
|
+
cpp: {
|
|
89
|
+
files: ["CMakeLists.txt", "Makefile"],
|
|
90
|
+
packageDeps: [],
|
|
91
|
+
extensions: [".cpp", ".cc", ".h", ".hpp"],
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
function detectLanguages() {
|
|
96
|
+
const detected = [];
|
|
97
|
+
|
|
98
|
+
// Check package.json for TS dependency
|
|
99
|
+
let pkgDeps = {};
|
|
100
|
+
try {
|
|
101
|
+
const pkgPath = path.join(PROJECT_ROOT, "package.json");
|
|
102
|
+
if (fs.existsSync(pkgPath)) {
|
|
103
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
104
|
+
pkgDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
105
|
+
}
|
|
106
|
+
} catch { /* ignore */ }
|
|
107
|
+
|
|
108
|
+
for (const [lang, detector] of Object.entries(LANGUAGE_DETECTORS)) {
|
|
109
|
+
// Check marker files
|
|
110
|
+
const hasFile = detector.files.some((f) => {
|
|
111
|
+
if (f.includes("*")) {
|
|
112
|
+
// Glob pattern — check if any matching file exists
|
|
113
|
+
try {
|
|
114
|
+
const dir = fs.readdirSync(PROJECT_ROOT);
|
|
115
|
+
const ext = f.replace("*", "");
|
|
116
|
+
return dir.some((d) => d.endsWith(ext));
|
|
117
|
+
} catch { return false; }
|
|
118
|
+
}
|
|
119
|
+
return fs.existsSync(path.join(PROJECT_ROOT, f));
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Check package.json dependencies
|
|
123
|
+
const hasDep = detector.packageDeps.some((dep) => dep in pkgDeps);
|
|
124
|
+
|
|
125
|
+
if (hasFile || hasDep) {
|
|
126
|
+
detected.push(lang);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// If package.json exists but no typescript detected, it's javascript
|
|
131
|
+
if (fs.existsSync(path.join(PROJECT_ROOT, "package.json")) && !detected.includes("typescript") && !detected.includes("javascript")) {
|
|
132
|
+
detected.push("javascript");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return detected;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ── Tool Configs ──
|
|
139
|
+
|
|
34
140
|
const TOOL_CONFIGS = {
|
|
35
141
|
"claude-code": {
|
|
36
142
|
name: "Claude Code",
|
|
@@ -69,6 +175,7 @@ const TOOL_CONFIGS = {
|
|
|
69
175
|
const C = {
|
|
70
176
|
green: "\x1b[32m", yellow: "\x1b[33m", blue: "\x1b[34m",
|
|
71
177
|
red: "\x1b[31m", dim: "\x1b[2m", bold: "\x1b[1m", reset: "\x1b[0m",
|
|
178
|
+
cyan: "\x1b[36m", magenta: "\x1b[35m",
|
|
72
179
|
};
|
|
73
180
|
|
|
74
181
|
// ── Helpers ──
|
|
@@ -163,16 +270,47 @@ function filterSkillsForProject(skills) {
|
|
|
163
270
|
const projectName = getProjectName();
|
|
164
271
|
return skills.filter((skill) => {
|
|
165
272
|
if (skill.scope !== "project") return true;
|
|
166
|
-
// Only install project skills matching this project
|
|
167
273
|
return skill.project_name === projectName;
|
|
168
274
|
});
|
|
169
275
|
}
|
|
170
276
|
|
|
277
|
+
function extractFrontmatter(content) {
|
|
278
|
+
const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
279
|
+
if (!match) return {};
|
|
280
|
+
const fm = {};
|
|
281
|
+
for (const line of match[1].split("\n")) {
|
|
282
|
+
if (!line.startsWith(" ") && line.includes(":")) {
|
|
283
|
+
const [key, ...rest] = line.split(":");
|
|
284
|
+
fm[key.trim()] = rest.join(":").trim().replace(/^["']|["']$/g, "");
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return fm;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function getLocalSkills() {
|
|
291
|
+
const skills = [];
|
|
292
|
+
if (!fs.existsSync(SKILLS_SOURCE)) return skills;
|
|
293
|
+
for (const cat of ["global", "stack"]) {
|
|
294
|
+
const catDir = path.join(SKILLS_SOURCE, cat);
|
|
295
|
+
if (!fs.existsSync(catDir)) continue;
|
|
296
|
+
for (const item of fs.readdirSync(catDir)) {
|
|
297
|
+
const skillMd = path.join(catDir, item, "SKILL.md");
|
|
298
|
+
if (fs.existsSync(skillMd)) {
|
|
299
|
+
const content = fs.readFileSync(skillMd, "utf-8");
|
|
300
|
+
const fm = extractFrontmatter(content);
|
|
301
|
+
skills.push({ name: fm.name || item, scope: cat, stack: fm.stack || null, version: fm.version || "?", content });
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return skills;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ── Installation Functions ──
|
|
309
|
+
|
|
171
310
|
function installSkillsForTool(toolKey, skills) {
|
|
172
311
|
const tool = TOOL_CONFIGS[toolKey];
|
|
173
312
|
if (!tool) return 0;
|
|
174
313
|
|
|
175
|
-
// Filter: only install project-scoped skills that match this project
|
|
176
314
|
const filtered = filterSkillsForProject(skills);
|
|
177
315
|
|
|
178
316
|
if (tool.format === "skill-folder") {
|
|
@@ -182,7 +320,6 @@ function installSkillsForTool(toolKey, skills) {
|
|
|
182
320
|
mkdirp(dest);
|
|
183
321
|
fs.writeFileSync(path.join(dest, "SKILL.md"), skill.content);
|
|
184
322
|
|
|
185
|
-
// Write reference files if the skill has any
|
|
186
323
|
if (skill.references && typeof skill.references === "object") {
|
|
187
324
|
for (const [filePath, fileContent] of Object.entries(skill.references)) {
|
|
188
325
|
const refDest = path.join(dest, filePath);
|
|
@@ -204,14 +341,12 @@ function installSkillsForTool(toolKey, skills) {
|
|
|
204
341
|
content += `# Last updated: ${new Date().toISOString().split("T")[0]}\n\n`;
|
|
205
342
|
|
|
206
343
|
for (const skill of filtered) {
|
|
207
|
-
// Strip YAML frontmatter for rules files
|
|
208
344
|
const body = skill.content.replace(/^---[\s\S]*?---\n*/m, "").trim();
|
|
209
345
|
content += `\n${"=".repeat(60)}\n`;
|
|
210
346
|
content += `# SKILL: ${skill.name} (${skill.scope})\n`;
|
|
211
347
|
content += `${"=".repeat(60)}\n\n`;
|
|
212
348
|
content += body + "\n";
|
|
213
349
|
|
|
214
|
-
// Inline reference files for rules-file format
|
|
215
350
|
if (skill.references && typeof skill.references === "object") {
|
|
216
351
|
for (const [refPath, refContent] of Object.entries(skill.references)) {
|
|
217
352
|
content += `\n--- Reference: ${refPath} ---\n`;
|
|
@@ -227,35 +362,250 @@ function installSkillsForTool(toolKey, skills) {
|
|
|
227
362
|
return 0;
|
|
228
363
|
}
|
|
229
364
|
|
|
230
|
-
function
|
|
231
|
-
const
|
|
232
|
-
if (!
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
365
|
+
function installAgentsForTool(toolKey, agents) {
|
|
366
|
+
const tool = TOOL_CONFIGS[toolKey];
|
|
367
|
+
if (!tool || !agents || agents.length === 0) return 0;
|
|
368
|
+
|
|
369
|
+
if (tool.format === "skill-folder") {
|
|
370
|
+
// Claude Code: individual agent .md files
|
|
371
|
+
const agentsDir = path.join(PROJECT_ROOT, ".claude", "agents");
|
|
372
|
+
mkdirp(agentsDir);
|
|
373
|
+
for (const agent of agents) {
|
|
374
|
+
const content = [
|
|
375
|
+
"---",
|
|
376
|
+
`name: ${agent.name}`,
|
|
377
|
+
`description: ${agent.display_name || agent.description}`,
|
|
378
|
+
agent.model_preference ? `model: ${agent.model_preference}` : null,
|
|
379
|
+
agent.allowed_tools ? `tools: ${JSON.stringify(agent.allowed_tools)}` : null,
|
|
380
|
+
agent.max_turns ? `max_turns: ${agent.max_turns}` : null,
|
|
381
|
+
"---",
|
|
382
|
+
"",
|
|
383
|
+
agent.instructions,
|
|
384
|
+
].filter(Boolean).join("\n");
|
|
385
|
+
fs.writeFileSync(path.join(agentsDir, `${agent.name}.md`), content);
|
|
238
386
|
}
|
|
387
|
+
return agents.length;
|
|
239
388
|
}
|
|
240
|
-
|
|
389
|
+
|
|
390
|
+
if (tool.format === "rules-file") {
|
|
391
|
+
// Other tools: merge agents into a rules file
|
|
392
|
+
const rulesDir = path.dirname(path.join(PROJECT_ROOT, tool.rulesFile));
|
|
393
|
+
const agentsFile = path.join(rulesDir, "ai-agents.mdc");
|
|
394
|
+
mkdirp(rulesDir);
|
|
395
|
+
|
|
396
|
+
let content = `# AI Agent Definitions\n`;
|
|
397
|
+
content += `# Auto-generated by @valentia-ai-skills/framework\n\n`;
|
|
398
|
+
|
|
399
|
+
for (const agent of agents) {
|
|
400
|
+
content += `\n${"=".repeat(60)}\n`;
|
|
401
|
+
content += `# AGENT: ${agent.name} (${agent.scope}) — ${agent.model_preference || "sonnet"}\n`;
|
|
402
|
+
content += `${"=".repeat(60)}\n\n`;
|
|
403
|
+
content += agent.instructions + "\n";
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
fs.writeFileSync(agentsFile, content);
|
|
407
|
+
return agents.length;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return 0;
|
|
241
411
|
}
|
|
242
412
|
|
|
243
|
-
function
|
|
244
|
-
const
|
|
245
|
-
if (!
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
413
|
+
function installCommandsForTool(toolKey, commands) {
|
|
414
|
+
const tool = TOOL_CONFIGS[toolKey];
|
|
415
|
+
if (!tool || !commands || commands.length === 0) return 0;
|
|
416
|
+
|
|
417
|
+
if (tool.format === "skill-folder") {
|
|
418
|
+
// Claude Code: individual command .md files
|
|
419
|
+
const commandsDir = path.join(PROJECT_ROOT, ".claude", "commands");
|
|
420
|
+
mkdirp(commandsDir);
|
|
421
|
+
for (const cmd of commands) {
|
|
422
|
+
const content = [
|
|
423
|
+
"---",
|
|
424
|
+
`name: ${cmd.name}`,
|
|
425
|
+
`description: ${cmd.display_name || cmd.description}`,
|
|
426
|
+
cmd.agent_ref ? `agent: ${cmd.agent_ref}` : null,
|
|
427
|
+
"---",
|
|
428
|
+
"",
|
|
429
|
+
cmd.instructions,
|
|
430
|
+
].filter(Boolean).join("\n");
|
|
431
|
+
fs.writeFileSync(path.join(commandsDir, `${cmd.name}.md`), content);
|
|
432
|
+
}
|
|
433
|
+
return commands.length;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (tool.format === "rules-file") {
|
|
437
|
+
// Other tools: merge commands into rules file
|
|
438
|
+
const rulesDir = path.dirname(path.join(PROJECT_ROOT, tool.rulesFile));
|
|
439
|
+
const commandsFile = path.join(rulesDir, "ai-commands.mdc");
|
|
440
|
+
mkdirp(rulesDir);
|
|
441
|
+
|
|
442
|
+
let content = `# AI Command Definitions\n`;
|
|
443
|
+
content += `# Auto-generated by @valentia-ai-skills/framework\n\n`;
|
|
444
|
+
|
|
445
|
+
for (const cmd of commands) {
|
|
446
|
+
content += `\n${"=".repeat(60)}\n`;
|
|
447
|
+
content += `# COMMAND: /${cmd.name} (${cmd.scope})\n`;
|
|
448
|
+
content += `${"=".repeat(60)}\n\n`;
|
|
449
|
+
content += cmd.instructions + "\n";
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
fs.writeFileSync(commandsFile, content);
|
|
453
|
+
return commands.length;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return 0;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function installHooksForTool(toolKey, hooks) {
|
|
460
|
+
const tool = TOOL_CONFIGS[toolKey];
|
|
461
|
+
if (!tool || !hooks || hooks.length === 0) return 0;
|
|
462
|
+
|
|
463
|
+
if (tool.format === "skill-folder") {
|
|
464
|
+
// Claude Code: write hook scripts + settings.local.json
|
|
465
|
+
const hooksScriptDir = path.join(PROJECT_ROOT, ".ai-skills", "hooks");
|
|
466
|
+
mkdirp(hooksScriptDir);
|
|
467
|
+
|
|
468
|
+
// Write individual hook scripts
|
|
469
|
+
for (const hook of hooks) {
|
|
470
|
+
if (hook.script_content) {
|
|
471
|
+
const scriptPath = path.join(hooksScriptDir, `${hook.name}.js`);
|
|
472
|
+
fs.writeFileSync(scriptPath, hook.script_content);
|
|
255
473
|
}
|
|
256
474
|
}
|
|
475
|
+
|
|
476
|
+
// Build Claude Code hooks config for settings.local.json
|
|
477
|
+
const hooksByEvent = {};
|
|
478
|
+
for (const hook of hooks) {
|
|
479
|
+
if (!hooksByEvent[hook.event]) hooksByEvent[hook.event] = [];
|
|
480
|
+
hooksByEvent[hook.event].push({
|
|
481
|
+
matcher: hook.tool_match || "*",
|
|
482
|
+
hooks: [
|
|
483
|
+
{
|
|
484
|
+
type: hook.hook_type || "command",
|
|
485
|
+
command: hook.command,
|
|
486
|
+
...(hook.timeout_ms ? { timeout: hook.timeout_ms } : {}),
|
|
487
|
+
},
|
|
488
|
+
],
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Read existing settings.local.json or create new
|
|
493
|
+
const settingsPath = path.join(PROJECT_ROOT, ".claude", "settings.local.json");
|
|
494
|
+
let settings = {};
|
|
495
|
+
if (fs.existsSync(settingsPath)) {
|
|
496
|
+
try { settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8")); } catch { settings = {}; }
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Merge hooks into settings (preserve existing non-hook settings)
|
|
500
|
+
settings.hooks = hooksByEvent;
|
|
501
|
+
|
|
502
|
+
mkdirp(path.dirname(settingsPath));
|
|
503
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
504
|
+
|
|
505
|
+
return hooks.length;
|
|
257
506
|
}
|
|
258
|
-
|
|
507
|
+
|
|
508
|
+
// Other tools don't support hooks in the same way — skip
|
|
509
|
+
return 0;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function installRulesForTool(toolKey, rules, detectedLanguages) {
|
|
513
|
+
const tool = TOOL_CONFIGS[toolKey];
|
|
514
|
+
if (!tool || !rules || rules.length === 0) return 0;
|
|
515
|
+
|
|
516
|
+
// Filter rules by detected languages (auto_detect rules only install if language found)
|
|
517
|
+
const filtered = rules.filter((rule) => {
|
|
518
|
+
if (rule.language === "all") return true; // Universal rules always install
|
|
519
|
+
if (!rule.auto_detect) return true; // Non-auto-detect rules always install
|
|
520
|
+
return detectedLanguages.includes(rule.language); // Auto-detect: only if language found
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
if (filtered.length === 0) return 0;
|
|
524
|
+
|
|
525
|
+
if (tool.format === "skill-folder") {
|
|
526
|
+
// Claude Code: individual rule .md files
|
|
527
|
+
const rulesDir = path.join(PROJECT_ROOT, ".claude", "rules");
|
|
528
|
+
mkdirp(rulesDir);
|
|
529
|
+
for (const rule of filtered) {
|
|
530
|
+
fs.writeFileSync(path.join(rulesDir, `${rule.name}.md`), rule.content);
|
|
531
|
+
}
|
|
532
|
+
return filtered.length;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if (tool.format === "rules-file") {
|
|
536
|
+
// Other tools: merge rules into a rules file
|
|
537
|
+
const rulesDir = path.dirname(path.join(PROJECT_ROOT, tool.rulesFile));
|
|
538
|
+
const rulesFile = path.join(rulesDir, "ai-rules.mdc");
|
|
539
|
+
mkdirp(rulesDir);
|
|
540
|
+
|
|
541
|
+
let content = `# AI Coding Rules\n`;
|
|
542
|
+
content += `# Auto-generated by @valentia-ai-skills/framework\n\n`;
|
|
543
|
+
|
|
544
|
+
for (const rule of filtered) {
|
|
545
|
+
content += `\n${"=".repeat(60)}\n`;
|
|
546
|
+
content += `# RULE: ${rule.name} (${rule.language})\n`;
|
|
547
|
+
content += `${"=".repeat(60)}\n\n`;
|
|
548
|
+
content += rule.content + "\n";
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
fs.writeFileSync(rulesFile, content);
|
|
552
|
+
return filtered.length;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return 0;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function installMcpConfigsForTool(toolKey, mcpConfigs) {
|
|
559
|
+
const tool = TOOL_CONFIGS[toolKey];
|
|
560
|
+
if (!tool || !mcpConfigs || mcpConfigs.length === 0) return 0;
|
|
561
|
+
|
|
562
|
+
if (tool.format === "skill-folder") {
|
|
563
|
+
// Claude Code: merge into .claude/mcp-servers.json
|
|
564
|
+
// NOTE: We write templates with {{PLACEHOLDER}} — user must fill in actual values
|
|
565
|
+
const mcpPath = path.join(PROJECT_ROOT, ".claude", "mcp-servers.json");
|
|
566
|
+
let existing = {};
|
|
567
|
+
if (fs.existsSync(mcpPath)) {
|
|
568
|
+
try { existing = JSON.parse(fs.readFileSync(mcpPath, "utf-8")); } catch { existing = {}; }
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (!existing.mcpServers) existing.mcpServers = {};
|
|
572
|
+
|
|
573
|
+
for (const mcp of mcpConfigs) {
|
|
574
|
+
// Only add if not already configured (don't overwrite user's actual values)
|
|
575
|
+
if (!existing.mcpServers[mcp.name]) {
|
|
576
|
+
existing.mcpServers[mcp.name] = mcp.config_template;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
mkdirp(path.dirname(mcpPath));
|
|
581
|
+
fs.writeFileSync(mcpPath, JSON.stringify(existing, null, 2) + "\n");
|
|
582
|
+
return mcpConfigs.length;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Other tools: write a reference file listing available MCP configs
|
|
586
|
+
if (tool.format === "rules-file") {
|
|
587
|
+
const rulesDir = path.dirname(path.join(PROJECT_ROOT, tool.rulesFile));
|
|
588
|
+
const mcpFile = path.join(rulesDir, "ai-mcp-configs.md");
|
|
589
|
+
mkdirp(rulesDir);
|
|
590
|
+
|
|
591
|
+
let content = `# Available MCP Server Configurations\n`;
|
|
592
|
+
content += `# These MCP servers are recommended for your team's toolkit\n\n`;
|
|
593
|
+
|
|
594
|
+
for (const mcp of mcpConfigs) {
|
|
595
|
+
content += `## ${mcp.display_name || mcp.name}\n`;
|
|
596
|
+
content += `${mcp.description}\n`;
|
|
597
|
+
content += `- Package: \`${mcp.package_name}\`\n`;
|
|
598
|
+
if (mcp.required_env_vars?.length) {
|
|
599
|
+
content += `- Required env vars: ${mcp.required_env_vars.join(", ")}\n`;
|
|
600
|
+
}
|
|
601
|
+
content += `\n`;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
fs.writeFileSync(mcpFile, content);
|
|
605
|
+
return mcpConfigs.length;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return 0;
|
|
259
609
|
}
|
|
260
610
|
|
|
261
611
|
// ── Email + OTP Verification ──
|
|
@@ -290,11 +640,9 @@ async function requestOtpForEmail(emailInput) {
|
|
|
290
640
|
throw new Error(response.error);
|
|
291
641
|
}
|
|
292
642
|
|
|
293
|
-
// OTP sent successfully
|
|
294
643
|
console.log(c("green", `\n✓ Found: ${response.user_name}`));
|
|
295
644
|
|
|
296
645
|
if (response.fallback_code) {
|
|
297
|
-
// Email delivery not configured — show code in terminal
|
|
298
646
|
console.log(c("yellow", `\n Your verification code: ${c("bold", response.fallback_code)}\n`));
|
|
299
647
|
} else {
|
|
300
648
|
console.log(c("dim", ` A verification code has been sent to ${email}\n`));
|
|
@@ -340,22 +688,29 @@ async function cmdSetup() {
|
|
|
340
688
|
console.log(c("yellow", "No AI coding tools detected. Will create a generic .ai-rules file.\n"));
|
|
341
689
|
tools.push("generic");
|
|
342
690
|
} else {
|
|
343
|
-
console.log(
|
|
691
|
+
console.log(`${c("bold", "AI Tools:")} ${tools.map((t) => c("green", TOOL_CONFIGS[t]?.name || t)).join(", ")}`);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// 2. Detect languages
|
|
695
|
+
const detectedLanguages = detectLanguages();
|
|
696
|
+
if (detectedLanguages.length > 0) {
|
|
697
|
+
console.log(`${c("bold", "Languages:")} ${detectedLanguages.map((l) => c("cyan", l)).join(", ")}`);
|
|
344
698
|
}
|
|
699
|
+
console.log("");
|
|
345
700
|
|
|
346
|
-
//
|
|
701
|
+
// 3. Ask for email
|
|
347
702
|
let email = await ask(`${c("bold", "Enter your work email:")} `);
|
|
348
703
|
|
|
349
|
-
let
|
|
704
|
+
let response;
|
|
350
705
|
let teamName = null;
|
|
351
706
|
|
|
352
707
|
try {
|
|
353
|
-
//
|
|
708
|
+
// 4. Request OTP
|
|
354
709
|
const otpResult = await requestOtpForEmail(email);
|
|
355
710
|
email = otpResult.email;
|
|
356
711
|
|
|
357
|
-
//
|
|
358
|
-
|
|
712
|
+
// 5. Verify OTP and get full toolkit
|
|
713
|
+
response = await verifyOtp(email);
|
|
359
714
|
|
|
360
715
|
if (response.team) {
|
|
361
716
|
teamName = response.team.name;
|
|
@@ -369,60 +724,108 @@ async function cmdSetup() {
|
|
|
369
724
|
} else if (response.message) {
|
|
370
725
|
console.log(c("yellow", `⚠ ${response.message}`));
|
|
371
726
|
}
|
|
372
|
-
|
|
373
|
-
skills = response.skills || [];
|
|
374
|
-
|
|
375
|
-
if (skills.length === 0) {
|
|
376
|
-
console.log(c("yellow", "\n⚠ No skills are enabled for your team. Contact your Team Lead."));
|
|
377
|
-
process.exit(1);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// Show skill counts with project context
|
|
381
|
-
const projectName = getProjectName();
|
|
382
|
-
const teamSkillCount = skills.filter((s) => s.scope !== "project").length;
|
|
383
|
-
const projectSkillCount = skills.filter((s) => s.scope === "project" && s.project_name === projectName).length;
|
|
384
|
-
const skippedProjectCount = skills.filter((s) => s.scope === "project" && s.project_name !== projectName).length;
|
|
385
|
-
|
|
386
|
-
console.log(` ${c("bold", teamSkillCount)} team skill(s)`);
|
|
387
|
-
if (projectSkillCount > 0) {
|
|
388
|
-
console.log(` ${c("bold", projectSkillCount)} project skill(s) for ${c("green", projectName)}`);
|
|
389
|
-
}
|
|
390
|
-
if (skippedProjectCount > 0) {
|
|
391
|
-
console.log(c("dim", ` ${skippedProjectCount} project skill(s) skipped (different project)`));
|
|
392
|
-
}
|
|
393
|
-
console.log("");
|
|
394
|
-
|
|
395
727
|
} catch (err) {
|
|
396
728
|
console.log(c("red", `\n✗ Could not complete verification: ${err.message}`));
|
|
397
729
|
console.log(c("dim", " Please check your internet connection and try again.\n"));
|
|
398
730
|
process.exit(1);
|
|
399
731
|
}
|
|
400
732
|
|
|
401
|
-
|
|
402
|
-
|
|
733
|
+
// Extract all entity types from response
|
|
734
|
+
const skills = response.skills || [];
|
|
735
|
+
const agents = response.agents || [];
|
|
736
|
+
const commands = response.commands || [];
|
|
737
|
+
const hooks = response.hooks || [];
|
|
738
|
+
const rules = response.rules || [];
|
|
739
|
+
const mcpConfigs = response.mcp_configs || [];
|
|
740
|
+
|
|
741
|
+
const totalEntities = skills.length + agents.length + commands.length + hooks.length + rules.length + mcpConfigs.length;
|
|
742
|
+
|
|
743
|
+
if (totalEntities === 0) {
|
|
744
|
+
console.log(c("yellow", "\n⚠ No toolkit items are enabled for your team. Contact your Team Lead."));
|
|
403
745
|
process.exit(1);
|
|
404
746
|
}
|
|
405
747
|
|
|
406
|
-
//
|
|
407
|
-
|
|
748
|
+
// Show toolkit summary
|
|
749
|
+
console.log(c("blue", "\n Toolkit Summary:"));
|
|
750
|
+
if (skills.length > 0) console.log(` ${c("green", skills.length.toString().padStart(2))} skill(s)`);
|
|
751
|
+
if (agents.length > 0) console.log(` ${c("green", agents.length.toString().padStart(2))} agent(s)`);
|
|
752
|
+
if (commands.length > 0) console.log(` ${c("green", commands.length.toString().padStart(2))} command(s)`);
|
|
753
|
+
if (hooks.length > 0) console.log(` ${c("green", hooks.length.toString().padStart(2))} hook(s)`);
|
|
754
|
+
if (rules.length > 0) console.log(` ${c("green", rules.length.toString().padStart(2))} rule(s)`);
|
|
755
|
+
if (mcpConfigs.length > 0) console.log(` ${c("green", mcpConfigs.length.toString().padStart(2))} MCP config(s)`);
|
|
756
|
+
console.log("");
|
|
757
|
+
|
|
758
|
+
// 6. Install for each tool
|
|
408
759
|
for (const toolKey of tools) {
|
|
409
760
|
const tool = TOOL_CONFIGS[toolKey];
|
|
410
761
|
if (!tool) continue;
|
|
411
762
|
console.log(c("yellow", `Installing for ${tool.name}...`));
|
|
412
|
-
const count = installSkillsForTool(toolKey, skills);
|
|
413
763
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
764
|
+
// Skills
|
|
765
|
+
const skillCount = installSkillsForTool(toolKey, skills);
|
|
766
|
+
if (skillCount > 0) {
|
|
767
|
+
if (tool.format === "skill-folder") {
|
|
768
|
+
for (const s of filterSkillsForProject(skills)) {
|
|
769
|
+
console.log(` ${c("green", "✓")} ${c("dim", "skill:")} ${s.name}`);
|
|
770
|
+
}
|
|
771
|
+
} else {
|
|
772
|
+
console.log(` ${c("green", "✓")} ${skillCount} skills merged into ${tool.rulesFile}`);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Agents
|
|
777
|
+
const agentCount = installAgentsForTool(toolKey, agents);
|
|
778
|
+
if (agentCount > 0) {
|
|
779
|
+
if (tool.format === "skill-folder") {
|
|
780
|
+
for (const a of agents) console.log(` ${c("green", "✓")} ${c("dim", "agent:")} ${a.name}`);
|
|
781
|
+
} else {
|
|
782
|
+
console.log(` ${c("green", "✓")} ${agentCount} agents merged`);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// Commands
|
|
787
|
+
const cmdCount = installCommandsForTool(toolKey, commands);
|
|
788
|
+
if (cmdCount > 0) {
|
|
789
|
+
if (tool.format === "skill-folder") {
|
|
790
|
+
for (const cmd of commands) console.log(` ${c("green", "✓")} ${c("dim", "command:")} /${cmd.name}`);
|
|
791
|
+
} else {
|
|
792
|
+
console.log(` ${c("green", "✓")} ${cmdCount} commands merged`);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// Hooks (Claude Code only)
|
|
797
|
+
const hookCount = installHooksForTool(toolKey, hooks);
|
|
798
|
+
if (hookCount > 0) {
|
|
799
|
+
for (const h of hooks) console.log(` ${c("green", "✓")} ${c("dim", "hook:")} ${h.name} (${h.event})`);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// Rules (filtered by detected languages)
|
|
803
|
+
const ruleCount = installRulesForTool(toolKey, rules, detectedLanguages);
|
|
804
|
+
if (ruleCount > 0) {
|
|
805
|
+
if (tool.format === "skill-folder") {
|
|
806
|
+
const installed = rules.filter((r) => r.language === "all" || !r.auto_detect || detectedLanguages.includes(r.language));
|
|
807
|
+
for (const r of installed) console.log(` ${c("green", "✓")} ${c("dim", "rule:")} ${r.name}`);
|
|
808
|
+
} else {
|
|
809
|
+
console.log(` ${c("green", "✓")} ${ruleCount} rules merged`);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// MCP Configs
|
|
814
|
+
const mcpCount = installMcpConfigsForTool(toolKey, mcpConfigs);
|
|
815
|
+
if (mcpCount > 0) {
|
|
816
|
+
for (const m of mcpConfigs) {
|
|
817
|
+
const envNote = m.required_env_vars?.length ? c("yellow", ` (needs: ${m.required_env_vars.join(", ")})`) : "";
|
|
818
|
+
console.log(` ${c("green", "✓")} ${c("dim", "mcp:")} ${m.name}${envNote}`);
|
|
418
819
|
}
|
|
419
|
-
} else {
|
|
420
|
-
console.log(` ${c("green", "✓")} ${tool.rulesFile} (${count} skills merged)`);
|
|
421
820
|
}
|
|
821
|
+
|
|
422
822
|
console.log("");
|
|
423
823
|
}
|
|
424
824
|
|
|
425
|
-
//
|
|
825
|
+
// 7. Save config
|
|
826
|
+
const filteredSkills = filterSkillsForProject(skills);
|
|
827
|
+
const installedRules = rules.filter((r) => r.language === "all" || !r.auto_detect || detectedLanguages.includes(r.language));
|
|
828
|
+
|
|
426
829
|
const config = {
|
|
427
830
|
version: require(path.join(__dirname, "..", "package.json")).version,
|
|
428
831
|
email,
|
|
@@ -431,13 +834,19 @@ async function cmdSetup() {
|
|
|
431
834
|
source: "supabase",
|
|
432
835
|
analyzeUrl: ANALYZE_FUNCTION_URL,
|
|
433
836
|
scanUrl: SCAN_FUNCTION_URL,
|
|
837
|
+
detectedLanguages,
|
|
434
838
|
tools,
|
|
435
839
|
skills: filteredSkills.map((s) => ({ name: s.name, scope: s.scope, version: s.version, ...(s.project_name ? { project_name: s.project_name } : {}) })),
|
|
840
|
+
agents: agents.map((a) => ({ name: a.name, scope: a.scope, version: a.version })),
|
|
841
|
+
commands: commands.map((cmd) => ({ name: cmd.name, scope: cmd.scope, version: cmd.version })),
|
|
842
|
+
hooks: hooks.map((h) => ({ name: h.name, scope: h.scope, version: h.version })),
|
|
843
|
+
rules: installedRules.map((r) => ({ name: r.name, scope: r.scope, version: r.version, language: r.language })),
|
|
844
|
+
mcpConfigs: mcpConfigs.map((m) => ({ name: m.name, scope: m.scope, version: m.version })),
|
|
436
845
|
installedAt: new Date().toISOString(),
|
|
437
846
|
};
|
|
438
847
|
saveConfig(config);
|
|
439
848
|
|
|
440
|
-
//
|
|
849
|
+
// 8. Install post-commit analysis hook
|
|
441
850
|
try {
|
|
442
851
|
const { installHook } = require(path.join(__dirname, "..", "hooks", "install-hook.js"));
|
|
443
852
|
const hookResult = installHook(PROJECT_ROOT);
|
|
@@ -445,14 +854,15 @@ async function cmdSetup() {
|
|
|
445
854
|
console.log(`${c("green", "✓")} Post-commit analysis hook installed`);
|
|
446
855
|
}
|
|
447
856
|
} catch {
|
|
448
|
-
// Hook installation is optional
|
|
857
|
+
// Hook installation is optional
|
|
449
858
|
}
|
|
450
859
|
|
|
451
|
-
//
|
|
860
|
+
// 9. Summary
|
|
452
861
|
console.log(c("blue", "\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
|
|
453
|
-
console.log(c("green", "
|
|
454
|
-
console.log(` ${
|
|
862
|
+
console.log(c("green", "Setup complete!"));
|
|
863
|
+
console.log(` ${totalEntities} items installed for ${tools.length} tool(s)`);
|
|
455
864
|
if (teamName) console.log(` Team: ${teamName}`);
|
|
865
|
+
if (detectedLanguages.length > 0) console.log(` Languages: ${detectedLanguages.join(", ")}`);
|
|
456
866
|
console.log(`\n Config saved to ${c("dim", ".ai-skills.json")}`);
|
|
457
867
|
console.log(` To update: ${c("bold", "npx ai-skills update")}\n`);
|
|
458
868
|
}
|
|
@@ -466,24 +876,20 @@ async function cmdUpdate() {
|
|
|
466
876
|
process.exit(1);
|
|
467
877
|
}
|
|
468
878
|
|
|
469
|
-
|
|
879
|
+
let email = config.email;
|
|
470
880
|
console.log(c("dim", `Updating for ${email}...`));
|
|
471
881
|
|
|
472
|
-
let
|
|
882
|
+
let response;
|
|
473
883
|
let teamName = config.team;
|
|
474
884
|
|
|
475
885
|
try {
|
|
476
|
-
// Request OTP for the saved email
|
|
477
886
|
const otpResult = await requestOtpForEmail(email);
|
|
478
887
|
email = otpResult.email;
|
|
479
|
-
|
|
480
|
-
// Verify OTP
|
|
481
|
-
const response = await verifyOtp(email);
|
|
482
|
-
skills = response.skills || [];
|
|
888
|
+
response = await verifyOtp(email);
|
|
483
889
|
teamName = response.team?.name || config.team;
|
|
484
890
|
|
|
485
891
|
if (response.team) {
|
|
486
|
-
console.log(c("green", `✓ Team: ${teamName}
|
|
892
|
+
console.log(c("green", `✓ Team: ${teamName}`));
|
|
487
893
|
} else {
|
|
488
894
|
console.log(c("yellow", `⚠ ${response.message || "No team found."}`));
|
|
489
895
|
}
|
|
@@ -493,26 +899,55 @@ async function cmdUpdate() {
|
|
|
493
899
|
process.exit(1);
|
|
494
900
|
}
|
|
495
901
|
|
|
902
|
+
const skills = response.skills || [];
|
|
903
|
+
const agents = response.agents || [];
|
|
904
|
+
const commands = response.commands || [];
|
|
905
|
+
const hooks = response.hooks || [];
|
|
906
|
+
const rules = response.rules || [];
|
|
907
|
+
const mcpConfigs = response.mcp_configs || [];
|
|
908
|
+
const detectedLanguages = detectLanguages();
|
|
909
|
+
|
|
496
910
|
const tools = config.tools || detectTools();
|
|
497
911
|
for (const toolKey of tools) {
|
|
498
912
|
const tool = TOOL_CONFIGS[toolKey];
|
|
499
913
|
if (!tool) continue;
|
|
500
|
-
console.log(c("yellow",
|
|
914
|
+
console.log(c("yellow", `\nUpdating ${tool.name}...`));
|
|
915
|
+
|
|
501
916
|
installSkillsForTool(toolKey, skills);
|
|
917
|
+
installAgentsForTool(toolKey, agents);
|
|
918
|
+
installCommandsForTool(toolKey, commands);
|
|
919
|
+
installHooksForTool(toolKey, hooks);
|
|
920
|
+
installRulesForTool(toolKey, rules, detectedLanguages);
|
|
921
|
+
installMcpConfigsForTool(toolKey, mcpConfigs);
|
|
502
922
|
|
|
503
923
|
if (tool.format === "skill-folder") {
|
|
504
|
-
|
|
924
|
+
if (skills.length > 0) console.log(` ${c("green", "✓")} ${skills.length} skills`);
|
|
925
|
+
if (agents.length > 0) console.log(` ${c("green", "✓")} ${agents.length} agents`);
|
|
926
|
+
if (commands.length > 0) console.log(` ${c("green", "✓")} ${commands.length} commands`);
|
|
927
|
+
if (hooks.length > 0) console.log(` ${c("green", "✓")} ${hooks.length} hooks`);
|
|
928
|
+
const installedRules = rules.filter((r) => r.language === "all" || !r.auto_detect || detectedLanguages.includes(r.language));
|
|
929
|
+
if (installedRules.length > 0) console.log(` ${c("green", "✓")} ${installedRules.length} rules`);
|
|
930
|
+
if (mcpConfigs.length > 0) console.log(` ${c("green", "✓")} ${mcpConfigs.length} MCP configs`);
|
|
505
931
|
} else {
|
|
506
932
|
console.log(` ${c("green", "✓")} ${tool.rulesFile} updated`);
|
|
507
933
|
}
|
|
508
934
|
}
|
|
509
935
|
|
|
936
|
+
const installedRules = rules.filter((r) => r.language === "all" || !r.auto_detect || detectedLanguages.includes(r.language));
|
|
937
|
+
const totalEntities = skills.length + agents.length + commands.length + hooks.length + installedRules.length + mcpConfigs.length;
|
|
938
|
+
|
|
510
939
|
config.updatedAt = new Date().toISOString();
|
|
511
940
|
config.team = teamName;
|
|
941
|
+
config.detectedLanguages = detectedLanguages;
|
|
512
942
|
config.skills = skills.map((s) => ({ name: s.name, scope: s.scope, version: s.version }));
|
|
943
|
+
config.agents = agents.map((a) => ({ name: a.name, scope: a.scope, version: a.version }));
|
|
944
|
+
config.commands = commands.map((cmd) => ({ name: cmd.name, scope: cmd.scope, version: cmd.version }));
|
|
945
|
+
config.hooks = hooks.map((h) => ({ name: h.name, scope: h.scope, version: h.version }));
|
|
946
|
+
config.rules = installedRules.map((r) => ({ name: r.name, scope: r.scope, version: r.version, language: r.language }));
|
|
947
|
+
config.mcpConfigs = mcpConfigs.map((m) => ({ name: m.name, scope: m.scope, version: m.version }));
|
|
513
948
|
saveConfig(config);
|
|
514
949
|
|
|
515
|
-
console.log(c("green", `\
|
|
950
|
+
console.log(c("green", `\nUpdated! ${totalEntities} items across ${tools.length} tool(s).\n`));
|
|
516
951
|
}
|
|
517
952
|
|
|
518
953
|
function cmdStatus() {
|
|
@@ -526,13 +961,34 @@ function cmdStatus() {
|
|
|
526
961
|
if (config) {
|
|
527
962
|
console.log(`Email: ${c("bold", config.email)}`);
|
|
528
963
|
console.log(`Team: ${config.team ? c("green", config.team) : c("yellow", "none")}`);
|
|
964
|
+
console.log(`Project: ${c("bold", config.project || getProjectName())}`);
|
|
529
965
|
console.log(`Source: ${config.source === "supabase" ? c("green", "Supabase (live)") : c("yellow", "local fallback")}`);
|
|
530
966
|
console.log(`Installed: ${config.installedAt?.split("T")[0]}`);
|
|
531
967
|
if (config.updatedAt) console.log(`Updated: ${config.updatedAt.split("T")[0]}`);
|
|
532
968
|
console.log(`Tools: ${config.tools?.map((t) => TOOL_CONFIGS[t]?.name || t).join(", ")}`);
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
969
|
+
if (config.detectedLanguages?.length) {
|
|
970
|
+
console.log(`Languages: ${config.detectedLanguages.join(", ")}`);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// Entity summary table
|
|
974
|
+
const entities = [
|
|
975
|
+
{ label: "Skills", icon: "📝", items: config.skills },
|
|
976
|
+
{ label: "Agents", icon: "🤖", items: config.agents },
|
|
977
|
+
{ label: "Commands", icon: "⚡", items: config.commands },
|
|
978
|
+
{ label: "Hooks", icon: "🪝", items: config.hooks },
|
|
979
|
+
{ label: "Rules", icon: "📏", items: config.rules },
|
|
980
|
+
{ label: "MCP Configs", icon: "🔌", items: config.mcpConfigs },
|
|
981
|
+
];
|
|
982
|
+
|
|
983
|
+
console.log("");
|
|
984
|
+
for (const entity of entities) {
|
|
985
|
+
const items = entity.items || [];
|
|
986
|
+
if (items.length === 0) continue;
|
|
987
|
+
console.log(`${c("bold", entity.label)} (${items.length}):`);
|
|
988
|
+
for (const item of items) {
|
|
989
|
+
const extra = item.language ? ` ${c("dim", `[${item.language}]`)}` : "";
|
|
990
|
+
console.log(` ${c("green", "✓")} ${item.name} ${c("dim", `v${item.version}`)} ${c("dim", `(${item.scope})`)}${extra}`);
|
|
991
|
+
}
|
|
536
992
|
}
|
|
537
993
|
} else {
|
|
538
994
|
console.log(`Installed: ${c("red", "No")} — run 'npx ai-skills setup'`);
|
|
@@ -542,6 +998,15 @@ function cmdStatus() {
|
|
|
542
998
|
for (const [key, tool] of Object.entries(TOOL_CONFIGS)) {
|
|
543
999
|
console.log(` ${tool.detect() ? c("green", "●") : c("dim", "○")} ${tool.name}`);
|
|
544
1000
|
}
|
|
1001
|
+
|
|
1002
|
+
const languages = detectLanguages();
|
|
1003
|
+
if (languages.length > 0) {
|
|
1004
|
+
console.log(`\nDetected languages:`);
|
|
1005
|
+
for (const lang of languages) {
|
|
1006
|
+
console.log(` ${c("cyan", "●")} ${lang}`);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
|
|
545
1010
|
console.log("");
|
|
546
1011
|
}
|
|
547
1012
|
|
|
@@ -558,7 +1023,7 @@ function cmdList() {
|
|
|
558
1023
|
console.log(` ${s.name.padEnd(22)} ${c("dim", `v${s.version}`)} ${c("dim", `(${s.scope})`)}`);
|
|
559
1024
|
}
|
|
560
1025
|
console.log(c("dim", `\n ${skills.length} skills available`));
|
|
561
|
-
console.log(c("dim", " Note: 'npx ai-skills setup' installs team-specific
|
|
1026
|
+
console.log(c("dim", " Note: 'npx ai-skills setup' installs team-specific toolkit from Supabase\n"));
|
|
562
1027
|
}
|
|
563
1028
|
|
|
564
1029
|
function cmdDoctor() {
|
|
@@ -573,6 +1038,18 @@ function cmdDoctor() {
|
|
|
573
1038
|
} else {
|
|
574
1039
|
console.log(c("green", `✓ Config found (${config.email}, team: ${config.team || "none"})`));
|
|
575
1040
|
|
|
1041
|
+
// Count entities
|
|
1042
|
+
const entityCounts = [];
|
|
1043
|
+
if (config.skills?.length) entityCounts.push(`${config.skills.length} skills`);
|
|
1044
|
+
if (config.agents?.length) entityCounts.push(`${config.agents.length} agents`);
|
|
1045
|
+
if (config.commands?.length) entityCounts.push(`${config.commands.length} commands`);
|
|
1046
|
+
if (config.hooks?.length) entityCounts.push(`${config.hooks.length} hooks`);
|
|
1047
|
+
if (config.rules?.length) entityCounts.push(`${config.rules.length} rules`);
|
|
1048
|
+
if (config.mcpConfigs?.length) entityCounts.push(`${config.mcpConfigs.length} MCP configs`);
|
|
1049
|
+
if (entityCounts.length > 0) {
|
|
1050
|
+
console.log(c("green", `✓ Installed: ${entityCounts.join(", ")}`));
|
|
1051
|
+
}
|
|
1052
|
+
|
|
576
1053
|
// Check tool files
|
|
577
1054
|
for (const toolKey of config.tools || []) {
|
|
578
1055
|
const tool = TOOL_CONFIGS[toolKey];
|
|
@@ -588,6 +1065,50 @@ function cmdDoctor() {
|
|
|
588
1065
|
console.log(c("red", `✗ ${tool.name}: ${tool.skillsDir}/ missing`));
|
|
589
1066
|
issues++;
|
|
590
1067
|
}
|
|
1068
|
+
|
|
1069
|
+
// Check agents dir
|
|
1070
|
+
const agentsDir = path.join(PROJECT_ROOT, ".claude", "agents");
|
|
1071
|
+
if (fs.existsSync(agentsDir)) {
|
|
1072
|
+
const agentCount = fs.readdirSync(agentsDir).filter((f) => f.endsWith(".md")).length;
|
|
1073
|
+
console.log(c("green", `✓ ${tool.name}: ${agentCount} agents in .claude/agents/`));
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// Check commands dir
|
|
1077
|
+
const commandsDir = path.join(PROJECT_ROOT, ".claude", "commands");
|
|
1078
|
+
if (fs.existsSync(commandsDir)) {
|
|
1079
|
+
const cmdCount = fs.readdirSync(commandsDir).filter((f) => f.endsWith(".md")).length;
|
|
1080
|
+
console.log(c("green", `✓ ${tool.name}: ${cmdCount} commands in .claude/commands/`));
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// Check rules dir
|
|
1084
|
+
const rulesDir = path.join(PROJECT_ROOT, ".claude", "rules");
|
|
1085
|
+
if (fs.existsSync(rulesDir)) {
|
|
1086
|
+
const ruleCount = fs.readdirSync(rulesDir).filter((f) => f.endsWith(".md")).length;
|
|
1087
|
+
console.log(c("green", `✓ ${tool.name}: ${ruleCount} rules in .claude/rules/`));
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// Check hooks
|
|
1091
|
+
const settingsPath = path.join(PROJECT_ROOT, ".claude", "settings.local.json");
|
|
1092
|
+
if (fs.existsSync(settingsPath)) {
|
|
1093
|
+
try {
|
|
1094
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
1095
|
+
if (settings.hooks) {
|
|
1096
|
+
const hookCount = Object.values(settings.hooks).flat().length;
|
|
1097
|
+
console.log(c("green", `✓ ${tool.name}: ${hookCount} hooks in settings.local.json`));
|
|
1098
|
+
}
|
|
1099
|
+
} catch { /* ignore */ }
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// Check MCP configs
|
|
1103
|
+
const mcpPath = path.join(PROJECT_ROOT, ".claude", "mcp-servers.json");
|
|
1104
|
+
if (fs.existsSync(mcpPath)) {
|
|
1105
|
+
try {
|
|
1106
|
+
const mcp = JSON.parse(fs.readFileSync(mcpPath, "utf-8"));
|
|
1107
|
+
const mcpCount = Object.keys(mcp.mcpServers || mcp).length;
|
|
1108
|
+
console.log(c("green", `✓ ${tool.name}: ${mcpCount} MCP servers configured`));
|
|
1109
|
+
} catch { /* ignore */ }
|
|
1110
|
+
}
|
|
1111
|
+
|
|
591
1112
|
} else if (tool.rulesFile) {
|
|
592
1113
|
const rulesPath = path.join(PROJECT_ROOT, tool.rulesFile);
|
|
593
1114
|
if (fs.existsSync(rulesPath)) {
|
|
@@ -620,7 +1141,7 @@ function cmdDoctor() {
|
|
|
620
1141
|
function finish() {
|
|
621
1142
|
console.log(
|
|
622
1143
|
issues === 0
|
|
623
|
-
? c("green", "\
|
|
1144
|
+
? c("green", "\nEverything looks good!\n")
|
|
624
1145
|
: c("yellow", `\n⚠ ${issues} issue(s) found.\n`)
|
|
625
1146
|
);
|
|
626
1147
|
}
|
|
@@ -666,7 +1187,6 @@ async function cmdAnalyze() {
|
|
|
666
1187
|
commitMessage = require("child_process").execSync('git log -1 --format="%s"', execOpt).trim();
|
|
667
1188
|
filesChanged = require("child_process").execSync("git diff --name-only HEAD~1 HEAD", execOpt).trim().split("\n").filter(Boolean);
|
|
668
1189
|
} else {
|
|
669
|
-
// Analyze staged changes or last commit
|
|
670
1190
|
diff = require("child_process").execSync("git diff --cached", execOpt).trim();
|
|
671
1191
|
if (!diff) {
|
|
672
1192
|
diff = require("child_process").execSync("git diff HEAD~1 HEAD", execOpt).trim();
|
|
@@ -690,7 +1210,6 @@ async function cmdAnalyze() {
|
|
|
690
1210
|
process.exit(0);
|
|
691
1211
|
}
|
|
692
1212
|
|
|
693
|
-
// Truncate diff if needed
|
|
694
1213
|
if (diff.length > 10000) {
|
|
695
1214
|
diff = diff.slice(0, 4000) + "\n\n[...truncated...]\n\n" + diff.slice(-4000);
|
|
696
1215
|
}
|
|
@@ -699,7 +1218,6 @@ async function cmdAnalyze() {
|
|
|
699
1218
|
console.log(c("dim", `Commit: ${commitHash?.slice(0, 8)} — ${commitMessage}`));
|
|
700
1219
|
console.log(c("dim", `Branch: ${branch}\n`));
|
|
701
1220
|
|
|
702
|
-
// Get project name
|
|
703
1221
|
let projectName = null;
|
|
704
1222
|
try {
|
|
705
1223
|
const pkgPath = path.join(PROJECT_ROOT, "package.json");
|
|
@@ -726,7 +1244,6 @@ async function cmdAnalyze() {
|
|
|
726
1244
|
throw new Error(result.error);
|
|
727
1245
|
}
|
|
728
1246
|
|
|
729
|
-
// Display results
|
|
730
1247
|
console.log(c("blue", "━━━ Analysis Results ━━━\n"));
|
|
731
1248
|
const score = result.overall_score || 0;
|
|
732
1249
|
const scoreColor = score >= 80 ? "green" : score >= 50 ? "yellow" : "red";
|
|
@@ -778,9 +1295,9 @@ switch (command) {
|
|
|
778
1295
|
${c("blue", "AI Skills Framework")} — @valentia-ai-skills/framework
|
|
779
1296
|
|
|
780
1297
|
Usage:
|
|
781
|
-
npx ai-skills setup Enter email → fetch team's
|
|
782
|
-
npx ai-skills update Re-fetch and update
|
|
783
|
-
npx ai-skills status Show installed
|
|
1298
|
+
npx ai-skills setup Enter email → fetch team's toolkit → install
|
|
1299
|
+
npx ai-skills update Re-fetch and update toolkit for your team
|
|
1300
|
+
npx ai-skills status Show installed toolkit, team, and tools
|
|
784
1301
|
npx ai-skills list List locally bundled skills
|
|
785
1302
|
npx ai-skills analyze Analyze last commit against active skills
|
|
786
1303
|
npx ai-skills doctor Health check (config + API + tools)
|
|
@@ -788,6 +1305,8 @@ Usage:
|
|
|
788
1305
|
Flags:
|
|
789
1306
|
analyze --last Analyze the most recent commit
|
|
790
1307
|
|
|
1308
|
+
Toolkit includes: skills, agents, commands, hooks, rules, MCP configs.
|
|
1309
|
+
|
|
791
1310
|
Environment:
|
|
792
1311
|
AI_SKILLS_API_URL Override the Supabase Edge Function URL
|
|
793
1312
|
`); break;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@valentia-ai-skills/framework",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "AI development skills framework ÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂâÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂÃÂàcentralized coding standards, security patterns, and SOPs for AI-assisted development. Works with Claude Code, Cursor, Copilot, Windsurf, and any AI coding tool.",
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "AI development skills framework centralized coding standards, security patterns, and SOPs for AI-assisted development. Works with Claude Code, Cursor, Copilot, Windsurf, and any AI coding tool.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-skills",
|
|
7
7
|
"claude-code",
|
|
@@ -33,6 +33,10 @@
|
|
|
33
33
|
"bin/",
|
|
34
34
|
"src/",
|
|
35
35
|
"skills/",
|
|
36
|
+
"agents/",
|
|
37
|
+
"commands/",
|
|
38
|
+
"rules/",
|
|
39
|
+
"mcp-configs/",
|
|
36
40
|
"scripts/",
|
|
37
41
|
"hooks/",
|
|
38
42
|
"README.md"
|