@valentia-ai-skills/framework 1.0.3 → 1.0.4

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 (2) hide show
  1. package/bin/cli.js +310 -287
  2. package/package.json +2 -2
package/bin/cli.js CHANGED
@@ -1,25 +1,31 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * ai-skills CLI
5
- *
4
+ * ai-skills CLI — @valentia-ai-skills/framework
5
+ *
6
6
  * Usage:
7
- * npx ai-skills setup # First-time setup detects your AI tools and installs skills
8
- * npx ai-skills update # Pull latest skills into your project
9
- * npx ai-skills status # Show which skills are installed and which tools are configured
10
- * npx ai-skills list # List all available skills
11
- * npx ai-skills add <skill> # Add a specific skill
12
- * npx ai-skills remove <skill> # Remove a specific skill
13
- * npx ai-skills doctor # Check setup health
7
+ * npx ai-skills setup # Enter email lookup team install team's skills
8
+ * npx ai-skills update # Re-fetch skills from Supabase for your team
9
+ * npx ai-skills status # Show installed skills and team info
10
+ * npx ai-skills list # List all locally available skills
11
+ * npx ai-skills doctor # Health check
14
12
  */
15
13
 
16
14
  const fs = require("fs");
17
15
  const path = require("path");
16
+ const readline = require("readline");
17
+ const https = require("https");
18
+ const http = require("http");
18
19
 
19
20
  // ── Constants ──
20
21
 
21
- const SKILLS_SOURCE = path.join(__dirname, "..", "skills");
22
+ const SKILLS_SOURCE = path.join(__dirname, "..", "skills"); // fallback local skills
22
23
  const PROJECT_ROOT = process.cwd();
24
+ const CONFIG_PATH = path.join(PROJECT_ROOT, ".ai-skills.json");
25
+
26
+ // UPDATE THIS with your actual Supabase project URL
27
+ const SUPABASE_FUNCTION_URL =
28
+ process.env.AI_SKILLS_API_URL || "https://znshdhjquohrzvbnloki.supabase.co/functions/v1/lookup-team";
23
29
 
24
30
  const TOOL_CONFIGS = {
25
31
  "claude-code": {
@@ -27,7 +33,7 @@ const TOOL_CONFIGS = {
27
33
  skillsDir: ".claude/skills",
28
34
  rulesFile: null,
29
35
  detect: () => fs.existsSync(path.join(PROJECT_ROOT, ".claude")) || shellHas("claude"),
30
- format: "skill-folder", // copies full SKILL.md folder structure
36
+ format: "skill-folder",
31
37
  },
32
38
  cursor: {
33
39
  name: "Cursor",
@@ -36,7 +42,7 @@ const TOOL_CONFIGS = {
36
42
  detect: () =>
37
43
  fs.existsSync(path.join(PROJECT_ROOT, ".cursor")) ||
38
44
  fs.existsSync(path.join(PROJECT_ROOT, ".cursorules")),
39
- format: "rules-file", // merges into a single rules file
45
+ format: "rules-file",
40
46
  },
41
47
  copilot: {
42
48
  name: "GitHub Copilot",
@@ -54,362 +60,376 @@ const TOOL_CONFIGS = {
54
60
  detect: () => fs.existsSync(path.join(PROJECT_ROOT, ".windsurfrules")),
55
61
  format: "rules-file",
56
62
  },
57
- generic: {
58
- name: "Generic (.ai-rules)",
59
- skillsDir: null,
60
- rulesFile: ".ai-rules",
61
- detect: () => true, // always available as fallback
62
- format: "rules-file",
63
- },
64
63
  };
65
64
 
66
- const COLORS = {
67
- green: "\x1b[32m",
68
- yellow: "\x1b[33m",
69
- blue: "\x1b[34m",
70
- red: "\x1b[31m",
71
- dim: "\x1b[2m",
72
- bold: "\x1b[1m",
73
- reset: "\x1b[0m",
65
+ const C = {
66
+ green: "\x1b[32m", yellow: "\x1b[33m", blue: "\x1b[34m",
67
+ red: "\x1b[31m", dim: "\x1b[2m", bold: "\x1b[1m", reset: "\x1b[0m",
74
68
  };
75
69
 
76
70
  // ── Helpers ──
77
71
 
78
- function c(color, text) {
79
- return `${COLORS[color]}${text}${COLORS.reset}`;
80
- }
72
+ function c(color, text) { return `${C[color]}${text}${C.reset}`; }
81
73
 
82
74
  function shellHas(cmd) {
83
- try {
84
- require("child_process").execSync(`which ${cmd} 2>/dev/null`, { stdio: "ignore" });
85
- return true;
86
- } catch {
87
- return false;
88
- }
75
+ try { require("child_process").execSync(`which ${cmd} 2>/dev/null`, { stdio: "ignore" }); return true; }
76
+ catch { return false; }
89
77
  }
90
78
 
91
- function getAllSkills() {
92
- const skills = [];
93
- if (!fs.existsSync(SKILLS_SOURCE)) return skills;
94
-
95
- for (const category of ["global", "stack", "onboarding"]) {
96
- const catDir = path.join(SKILLS_SOURCE, category);
97
- if (!fs.existsSync(catDir)) continue;
79
+ function mkdirp(dirPath) {
80
+ if (!fs.existsSync(dirPath)) fs.mkdirSync(dirPath, { recursive: true });
81
+ }
98
82
 
99
- if (category === "onboarding") {
100
- if (fs.existsSync(path.join(catDir, "SKILL.md"))) {
101
- skills.push({ name: "onboarding", category, path: catDir });
102
- }
103
- continue;
104
- }
83
+ function ask(question) {
84
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
85
+ return new Promise((resolve) => {
86
+ rl.question(question, (answer) => { rl.close(); resolve(answer.trim()); });
87
+ });
88
+ }
105
89
 
106
- for (const item of fs.readdirSync(catDir)) {
107
- const itemPath = path.join(catDir, item);
108
- if (fs.statSync(itemPath).isDirectory() && fs.existsSync(path.join(itemPath, "SKILL.md"))) {
109
- skills.push({ name: item, category, path: itemPath });
90
+ function fetchJSON(url, body) {
91
+ return new Promise((resolve, reject) => {
92
+ const parsed = new URL(url);
93
+ const mod = parsed.protocol === "https:" ? https : http;
94
+ const postData = JSON.stringify(body);
95
+
96
+ const req = mod.request(
97
+ {
98
+ hostname: parsed.hostname,
99
+ port: parsed.port,
100
+ path: parsed.pathname + parsed.search,
101
+ method: "POST",
102
+ headers: {
103
+ "Content-Type": "application/json",
104
+ "Content-Length": Buffer.byteLength(postData),
105
+ },
106
+ },
107
+ (res) => {
108
+ let data = "";
109
+ res.on("data", (chunk) => (data += chunk));
110
+ res.on("end", () => {
111
+ try {
112
+ const json = JSON.parse(data);
113
+ if (res.statusCode >= 400) {
114
+ reject(new Error(json.error || `HTTP ${res.statusCode}`));
115
+ } else {
116
+ resolve(json);
117
+ }
118
+ } catch {
119
+ reject(new Error(`Invalid response: ${data.slice(0, 200)}`));
120
+ }
121
+ });
110
122
  }
111
- }
112
- }
113
- return skills;
123
+ );
124
+ req.on("error", reject);
125
+ req.write(postData);
126
+ req.end();
127
+ });
114
128
  }
115
129
 
116
- function readSkillContent(skillPath) {
117
- const skillMd = path.join(skillPath, "SKILL.md");
118
- if (!fs.existsSync(skillMd)) return null;
119
- return fs.readFileSync(skillMd, "utf-8");
130
+ function loadConfig() {
131
+ if (fs.existsSync(CONFIG_PATH)) return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
132
+ return null;
120
133
  }
121
134
 
122
- function extractSkillRules(content) {
123
- // Remove YAML frontmatter
124
- const withoutFrontmatter = content.replace(/^---[\s\S]*?---\n*/m, "");
125
- return withoutFrontmatter.trim();
135
+ function saveConfig(config) {
136
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
126
137
  }
127
138
 
128
- function mkdirp(dirPath) {
129
- if (!fs.existsSync(dirPath)) {
130
- fs.mkdirSync(dirPath, { recursive: true });
139
+ function detectTools() {
140
+ const detected = [];
141
+ for (const [key, config] of Object.entries(TOOL_CONFIGS)) {
142
+ if (config.detect()) detected.push(key);
131
143
  }
144
+ return detected;
132
145
  }
133
146
 
134
- function copyDir(src, dest) {
135
- mkdirp(dest);
136
- for (const item of fs.readdirSync(src)) {
137
- const srcItem = path.join(src, item);
138
- const destItem = path.join(dest, item);
139
- if (item === "tests") continue; // skip test prompts in installed version
140
- if (fs.statSync(srcItem).isDirectory()) {
141
- copyDir(srcItem, destItem);
142
- } else {
143
- fs.copyFileSync(srcItem, destItem);
147
+ function installSkillsForTool(toolKey, skills) {
148
+ const tool = TOOL_CONFIGS[toolKey];
149
+ if (!tool) return 0;
150
+
151
+ if (tool.format === "skill-folder") {
152
+ const skillsDir = path.join(PROJECT_ROOT, tool.skillsDir);
153
+ for (const skill of skills) {
154
+ const dest = path.join(skillsDir, skill.name);
155
+ mkdirp(dest);
156
+ fs.writeFileSync(path.join(dest, "SKILL.md"), skill.content);
144
157
  }
158
+ return skills.length;
145
159
  }
146
- }
147
160
 
148
- function detectTools() {
149
- const detected = [];
150
- for (const [key, config] of Object.entries(TOOL_CONFIGS)) {
151
- if (key === "generic") continue;
152
- if (config.detect()) {
153
- detected.push(key);
161
+ if (tool.format === "rules-file") {
162
+ const rulesPath = path.join(PROJECT_ROOT, tool.rulesFile);
163
+ mkdirp(path.dirname(rulesPath));
164
+
165
+ let content = `# AI Development Skills Framework\n`;
166
+ content += `# Auto-generated by @valentia-ai-skills/framework\n`;
167
+ content += `# Do not edit manually — run 'npx ai-skills update' to refresh\n`;
168
+ content += `# Last updated: ${new Date().toISOString().split("T")[0]}\n\n`;
169
+
170
+ for (const skill of skills) {
171
+ // Strip YAML frontmatter for rules files
172
+ const body = skill.content.replace(/^---[\s\S]*?---\n*/m, "").trim();
173
+ content += `\n${"=".repeat(60)}\n`;
174
+ content += `# SKILL: ${skill.name} (${skill.scope})\n`;
175
+ content += `${"=".repeat(60)}\n\n`;
176
+ content += body + "\n";
154
177
  }
178
+
179
+ fs.writeFileSync(rulesPath, content);
180
+ return skills.length;
155
181
  }
156
- return detected;
182
+
183
+ return 0;
157
184
  }
158
185
 
159
- function loadProjectConfig() {
160
- const configPath = path.join(PROJECT_ROOT, ".ai-skills.json");
161
- if (fs.existsSync(configPath)) {
162
- return JSON.parse(fs.readFileSync(configPath, "utf-8"));
186
+ function extractFrontmatter(content) {
187
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
188
+ if (!match) return {};
189
+ const fm = {};
190
+ for (const line of match[1].split("\n")) {
191
+ if (!line.startsWith(" ") && line.includes(":")) {
192
+ const [key, ...rest] = line.split(":");
193
+ fm[key.trim()] = rest.join(":").trim().replace(/^["']|["']$/g, "");
194
+ }
163
195
  }
164
- return null;
196
+ return fm;
165
197
  }
166
198
 
167
- function saveProjectConfig(config) {
168
- const configPath = path.join(PROJECT_ROOT, ".ai-skills.json");
169
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
199
+ function getLocalSkills() {
200
+ const skills = [];
201
+ if (!fs.existsSync(SKILLS_SOURCE)) return skills;
202
+ for (const cat of ["global", "stack"]) {
203
+ const catDir = path.join(SKILLS_SOURCE, cat);
204
+ if (!fs.existsSync(catDir)) continue;
205
+ for (const item of fs.readdirSync(catDir)) {
206
+ const skillMd = path.join(catDir, item, "SKILL.md");
207
+ if (fs.existsSync(skillMd)) {
208
+ const content = fs.readFileSync(skillMd, "utf-8");
209
+ const fm = extractFrontmatter(content);
210
+ skills.push({ name: fm.name || item, scope: cat, stack: fm.stack || null, version: fm.version || "?", content });
211
+ }
212
+ }
213
+ }
214
+ return skills;
170
215
  }
171
216
 
172
217
  // ── Commands ──
173
218
 
174
- function cmdSetup() {
219
+ async function cmdSetup() {
175
220
  console.log(c("blue", "\n━━━ AI Skills Framework — Setup ━━━\n"));
176
221
 
177
222
  // 1. Detect AI tools
178
223
  const tools = detectTools();
179
224
  if (tools.length === 0) {
180
- console.log(c("yellow", "No AI coding tools detected in this project."));
181
- console.log("Will create a generic .ai-rules file that works with any tool.\n");
225
+ console.log(c("yellow", "No AI coding tools detected. Will create a generic .ai-rules file.\n"));
182
226
  tools.push("generic");
183
227
  } else {
184
- console.log(`Detected AI tools: ${tools.map((t) => c("green", TOOL_CONFIGS[t].name)).join(", ")}\n`);
228
+ console.log(`Detected: ${tools.map((t) => c("green", TOOL_CONFIGS[t]?.name || t)).join(", ")}\n`);
185
229
  }
186
230
 
187
- // 2. Get all available skills
188
- const allSkills = getAllSkills();
189
- const globalSkills = allSkills.filter((s) => s.category === "global");
190
- const stackSkills = allSkills.filter((s) => s.category === "stack");
231
+ // 2. Ask for email
232
+ const email = await ask(`${c("bold", "Enter your work email:")} `);
233
+ if (!email || !email.includes("@")) {
234
+ console.log(c("red", "Invalid email. Please try again."));
235
+ process.exit(1);
236
+ }
191
237
 
192
- console.log(`Available skills: ${c("bold", allSkills.length)}`);
193
- console.log(` Global: ${globalSkills.map((s) => s.name).join(", ")}`);
194
- console.log(` Stack: ${stackSkills.map((s) => s.name).join(", ")}\n`);
238
+ // 3. Lookup team from Supabase
239
+ console.log(c("dim", "\nLooking up your team..."));
195
240
 
196
- // 3. Install for each detected tool
197
- let installedCount = 0;
241
+ let response;
242
+ let skills;
243
+ let teamName = null;
244
+ let useRemote = true;
198
245
 
199
- for (const toolKey of tools) {
200
- const tool = TOOL_CONFIGS[toolKey];
201
- console.log(c("yellow", `Setting up for ${tool.name}...`));
246
+ try {
247
+ response = await fetchJSON(SUPABASE_FUNCTION_URL, { email });
202
248
 
203
- if (tool.format === "skill-folder") {
204
- // Claude Code: copy individual SKILL.md folders
205
- const skillsDir = path.join(PROJECT_ROOT, tool.skillsDir);
206
- for (const skill of allSkills) {
207
- const dest = path.join(skillsDir, skill.name);
208
- copyDir(skill.path, dest);
209
- console.log(` ${c("green", "✓")} ${skill.name}`);
210
- installedCount++;
249
+ if (response.team) {
250
+ teamName = response.team.name;
251
+ console.log(c("green", `\n✓ Team: ${teamName}`));
252
+ if (response.user) {
253
+ console.log(c("dim", ` Welcome, ${response.user.name} (${response.user.role})`));
211
254
  }
212
- } else if (tool.format === "rules-file") {
213
- // Cursor / Copilot / Windsurf / Generic: merge into single rules file
214
- const rulesPath = path.join(PROJECT_ROOT, tool.rulesFile);
215
- mkdirp(path.dirname(rulesPath));
216
-
217
- let rulesContent = `# AI Development Skills Framework\n`;
218
- rulesContent += `# Auto-generated by @valentia-ai-skills/framework\n`;
219
- rulesContent += `# Do not edit manually — run 'npx ai-skills update' to refresh\n`;
220
- rulesContent += `# Last updated: ${new Date().toISOString().split("T")[0]}\n\n`;
221
-
222
- for (const skill of allSkills) {
223
- const content = readSkillContent(skill.path);
224
- if (content) {
225
- const rules = extractSkillRules(content);
226
- rulesContent += `\n${"=".repeat(60)}\n`;
227
- rulesContent += `# SKILL: ${skill.name} (${skill.category})\n`;
228
- rulesContent += `${"=".repeat(60)}\n\n`;
229
- rulesContent += rules + "\n";
230
- }
255
+ if (response.team.stack_tags?.length) {
256
+ console.log(c("dim", ` Stack: ${response.team.stack_tags.join(", ")}`));
231
257
  }
258
+ } else {
259
+ console.log(c("yellow", `\n⚠ ${response.message || "No team found for this email."}`));
260
+ }
232
261
 
233
- fs.writeFileSync(rulesPath, rulesContent);
234
- console.log(` ${c("green", "✓")} Written to ${tool.rulesFile} (${allSkills.length} skills merged)`);
235
- installedCount += allSkills.length;
262
+ skills = response.skills || [];
263
+ console.log(` ${c("bold", skills.length)} skill(s) to install\n`);
264
+
265
+ } catch (err) {
266
+ console.log(c("yellow", `\n⚠ Could not reach skills server: ${err.message}`));
267
+ console.log(c("dim", " Falling back to local skills from npm package...\n"));
268
+
269
+ skills = getLocalSkills();
270
+ useRemote = false;
271
+ }
272
+
273
+ if (skills.length === 0) {
274
+ console.log(c("red", "No skills available. Contact your Framework Admin."));
275
+ process.exit(1);
276
+ }
277
+
278
+ // 4. Install for each tool
279
+ for (const toolKey of tools) {
280
+ const tool = TOOL_CONFIGS[toolKey];
281
+ if (!tool) continue;
282
+ console.log(c("yellow", `Installing for ${tool.name}...`));
283
+ const count = installSkillsForTool(toolKey, skills);
284
+
285
+ if (tool.format === "skill-folder") {
286
+ for (const s of skills) console.log(` ${c("green", "✓")} ${s.name}`);
287
+ } else {
288
+ console.log(` ${c("green", "✓")} ${tool.rulesFile} (${count} skills merged)`);
236
289
  }
237
290
  console.log("");
238
291
  }
239
292
 
240
- // 4. Save project config
293
+ // 5. Save config
241
294
  const config = {
242
- version: "1.0.0",
243
- installedAt: new Date().toISOString(),
295
+ version: require(path.join(__dirname, "..", "package.json")).version,
296
+ email,
297
+ team: teamName,
298
+ source: useRemote ? "supabase" : "local",
244
299
  tools,
245
- skills: allSkills.map((s) => ({ name: s.name, category: s.category })),
300
+ skills: skills.map((s) => ({ name: s.name, scope: s.scope, version: s.version })),
301
+ installedAt: new Date().toISOString(),
246
302
  };
247
- saveProjectConfig(config);
303
+ saveConfig(config);
248
304
 
249
- // 5. Summary
305
+ // 6. Summary
250
306
  console.log(c("blue", "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
251
- console.log(c("green", `✅ Setup complete!`));
252
- console.log(` ${allSkills.length} skills installed for ${tools.length} tool(s)\n`);
253
- console.log(`Config saved to ${c("dim", ".ai-skills.json")}`);
254
- console.log(`To update later: ${c("bold", "npx ai-skills update")}`);
255
- console.log(`To check status: ${c("bold", "npx ai-skills status")}\n`);
307
+ console.log(c("green", "✅ Setup complete!"));
308
+ console.log(` ${skills.length} skills installed for ${tools.length} tool(s)`);
309
+ if (teamName) console.log(` Team: ${teamName}`);
310
+ console.log(`\n Config saved to ${c("dim", ".ai-skills.json")}`);
311
+ console.log(` To update: ${c("bold", "npx ai-skills update")}\n`);
256
312
  }
257
313
 
258
- function cmdUpdate() {
314
+ async function cmdUpdate() {
259
315
  console.log(c("blue", "\n━━━ AI Skills Framework — Update ━━━\n"));
260
316
 
261
- const config = loadProjectConfig();
262
- if (!config) {
317
+ const config = loadConfig();
318
+ if (!config || !config.email) {
263
319
  console.log(c("yellow", "No .ai-skills.json found. Run 'npx ai-skills setup' first."));
264
320
  process.exit(1);
265
321
  }
266
322
 
267
- // Re-run setup with saved tool preferences
268
- const tools = config.tools || detectTools();
269
- const allSkills = getAllSkills();
323
+ console.log(c("dim", `Updating for ${config.email}...`));
324
+
325
+ let skills;
326
+ let teamName = config.team;
327
+
328
+ try {
329
+ const response = await fetchJSON(SUPABASE_FUNCTION_URL, { email: config.email });
330
+ skills = response.skills || [];
331
+ teamName = response.team?.name || config.team;
332
+
333
+ if (response.team) {
334
+ console.log(c("green", `✓ Team: ${teamName} (${skills.length} skills)`));
335
+ } else {
336
+ console.log(c("yellow", `⚠ ${response.message || "No team found."}`));
337
+ }
338
+ } catch (err) {
339
+ console.log(c("yellow", `⚠ Server unreachable: ${err.message}`));
340
+ console.log(c("dim", " Using local fallback...\n"));
341
+ skills = getLocalSkills();
342
+ }
270
343
 
344
+ const tools = config.tools || detectTools();
271
345
  for (const toolKey of tools) {
272
346
  const tool = TOOL_CONFIGS[toolKey];
273
347
  if (!tool) continue;
274
-
275
348
  console.log(c("yellow", `Updating ${tool.name}...`));
349
+ installSkillsForTool(toolKey, skills);
276
350
 
277
351
  if (tool.format === "skill-folder") {
278
- const skillsDir = path.join(PROJECT_ROOT, tool.skillsDir);
279
- for (const skill of allSkills) {
280
- const dest = path.join(skillsDir, skill.name);
281
- copyDir(skill.path, dest);
282
- console.log(` ${c("green", "✓")} ${skill.name}`);
283
- }
284
- } else if (tool.format === "rules-file") {
285
- const rulesPath = path.join(PROJECT_ROOT, tool.rulesFile);
286
- mkdirp(path.dirname(rulesPath));
287
-
288
- let rulesContent = `# AI Development Skills Framework\n`;
289
- rulesContent += `# Auto-generated by @valentia-ai-skills/framework\n`;
290
- rulesContent += `# Do not edit manually — run 'npx ai-skills update' to refresh\n`;
291
- rulesContent += `# Last updated: ${new Date().toISOString().split("T")[0]}\n\n`;
292
-
293
- for (const skill of allSkills) {
294
- const content = readSkillContent(skill.path);
295
- if (content) {
296
- const rules = extractSkillRules(content);
297
- rulesContent += `\n${"=".repeat(60)}\n`;
298
- rulesContent += `# SKILL: ${skill.name} (${skill.category})\n`;
299
- rulesContent += `${"=".repeat(60)}\n\n`;
300
- rulesContent += rules + "\n";
301
- }
302
- }
303
-
304
- fs.writeFileSync(rulesPath, rulesContent);
352
+ for (const s of skills) console.log(` ${c("green", "✓")} ${s.name}`);
353
+ } else {
305
354
  console.log(` ${c("green", "✓")} ${tool.rulesFile} updated`);
306
355
  }
307
356
  }
308
357
 
309
- // Update config
310
358
  config.updatedAt = new Date().toISOString();
311
- config.skills = allSkills.map((s) => ({ name: s.name, category: s.category }));
312
- saveProjectConfig(config);
359
+ config.team = teamName;
360
+ config.skills = skills.map((s) => ({ name: s.name, scope: s.scope, version: s.version }));
361
+ saveConfig(config);
313
362
 
314
- console.log(c("green", `\n✅ Updated ${allSkills.length} skills!\n`));
363
+ console.log(c("green", `\n✅ Updated ${skills.length} skills!\n`));
315
364
  }
316
365
 
317
366
  function cmdStatus() {
318
367
  console.log(c("blue", "\n━━━ AI Skills Framework — Status ━━━\n"));
319
368
 
320
- const config = loadProjectConfig();
321
- const allSkills = getAllSkills();
322
- const tools = detectTools();
323
-
324
- // Package version
325
369
  const pkg = require(path.join(__dirname, "..", "package.json"));
326
- console.log(`Package version: ${c("bold", pkg.version)}`);
327
- console.log(`Skills available: ${c("bold", allSkills.length)}`);
370
+ const config = loadConfig();
371
+
372
+ console.log(`Package: ${c("bold", pkg.name)} v${pkg.version}`);
328
373
 
329
- // Installed status
330
374
  if (config) {
331
- console.log(`Installed: ${c("green", "Yes")} (${config.installedAt.split("T")[0]})`);
332
- if (config.updatedAt) {
333
- console.log(`Last updated: ${config.updatedAt.split("T")[0]}`);
375
+ console.log(`Email: ${c("bold", config.email)}`);
376
+ console.log(`Team: ${config.team ? c("green", config.team) : c("yellow", "none")}`);
377
+ console.log(`Source: ${config.source === "supabase" ? c("green", "Supabase (live)") : c("yellow", "local fallback")}`);
378
+ console.log(`Installed: ${config.installedAt?.split("T")[0]}`);
379
+ if (config.updatedAt) console.log(`Updated: ${config.updatedAt.split("T")[0]}`);
380
+ console.log(`Tools: ${config.tools?.map((t) => TOOL_CONFIGS[t]?.name || t).join(", ")}`);
381
+ console.log(`\nSkills (${config.skills?.length || 0}):`);
382
+ for (const s of config.skills || []) {
383
+ console.log(` ${c("green", "✓")} ${s.name} ${c("dim", `v${s.version}`)} ${c("dim", `(${s.scope})`)}`);
334
384
  }
335
- console.log(`Tools configured: ${config.tools.map((t) => TOOL_CONFIGS[t]?.name || t).join(", ")}`);
336
385
  } else {
337
386
  console.log(`Installed: ${c("red", "No")} — run 'npx ai-skills setup'`);
338
387
  }
339
388
 
340
- // Detected tools
341
- console.log(`\nDetected AI tools in this project:`);
342
- for (const [key, toolConfig] of Object.entries(TOOL_CONFIGS)) {
343
- if (key === "generic") continue;
344
- const detected = toolConfig.detect();
345
- console.log(` ${detected ? c("green", "●") : c("dim", "○")} ${toolConfig.name}`);
346
- }
347
-
348
- // Skill inventory
349
- console.log(`\nSkill inventory:`);
350
- for (const skill of allSkills) {
351
- const content = readSkillContent(skill.path);
352
- const versionMatch = content?.match(/version:\s*["']?([^"'\n]+)/);
353
- const version = versionMatch ? versionMatch[1].trim() : "?";
354
- console.log(` ${c("green", "✓")} ${skill.name} ${c("dim", `v${version}`)} ${c("dim", `(${skill.category})`)}`);
389
+ console.log(`\nDetected AI tools:`);
390
+ for (const [key, tool] of Object.entries(TOOL_CONFIGS)) {
391
+ console.log(` ${tool.detect() ? c("green", "●") : c("dim", "○")} ${tool.name}`);
355
392
  }
356
393
  console.log("");
357
394
  }
358
395
 
359
396
  function cmdList() {
360
- const allSkills = getAllSkills();
361
- console.log(c("blue", "\n━━━ Available Skills ━━━\n"));
397
+ const skills = getLocalSkills();
398
+ console.log(c("blue", "\n━━━ Available Skills (local) ━━━\n"));
362
399
 
363
- const categories = {};
364
- for (const skill of allSkills) {
365
- if (!categories[skill.category]) categories[skill.category] = [];
366
- categories[skill.category].push(skill);
400
+ if (skills.length === 0) {
401
+ console.log("No local skills found in package.");
402
+ return;
367
403
  }
368
404
 
369
- for (const [cat, skills] of Object.entries(categories)) {
370
- console.log(c("yellow", `${cat.toUpperCase()}`));
371
- for (const s of skills) {
372
- const content = readSkillContent(s.path);
373
- const descMatch = content?.match(/description:\s*>?\s*\n?\s*(.+)/);
374
- const desc = descMatch ? descMatch[1].trim().slice(0, 70) + "..." : "";
375
- console.log(` ${s.name.padEnd(22)} ${c("dim", desc)}`);
376
- }
377
- console.log("");
405
+ for (const s of skills) {
406
+ console.log(` ${s.name.padEnd(22)} ${c("dim", `v${s.version}`)} ${c("dim", `(${s.scope})`)}`);
378
407
  }
408
+ console.log(c("dim", `\n ${skills.length} skills available`));
409
+ console.log(c("dim", " Note: 'npx ai-skills setup' installs team-specific skills from Supabase\n"));
379
410
  }
380
411
 
381
412
  function cmdDoctor() {
382
413
  console.log(c("blue", "\n━━━ AI Skills — Health Check ━━━\n"));
383
-
384
414
  let issues = 0;
385
- const allSkills = getAllSkills();
386
-
387
- // Check skills source exists
388
- if (allSkills.length === 0) {
389
- console.log(c("red", "✗ No skills found in package. Package may be corrupted."));
390
- issues++;
391
- } else {
392
- console.log(c("green", `✓ ${allSkills.length} skills found in package`));
393
- }
394
415
 
395
- // Check project config
396
- const config = loadProjectConfig();
416
+ // Check config
417
+ const config = loadConfig();
397
418
  if (!config) {
398
- console.log(c("yellow", " No .ai-skills.json — run 'npx ai-skills setup'"));
419
+ console.log(c("red", " No .ai-skills.json — run 'npx ai-skills setup'"));
399
420
  issues++;
400
421
  } else {
401
- console.log(c("green", `✓ Project configured (${config.tools.length} tool(s))`));
422
+ console.log(c("green", `✓ Config found (${config.email}, team: ${config.team || "none"})`));
402
423
 
403
- // Check each tool's files exist
404
- for (const toolKey of config.tools) {
424
+ // Check tool files
425
+ for (const toolKey of config.tools || []) {
405
426
  const tool = TOOL_CONFIGS[toolKey];
406
427
  if (!tool) continue;
407
-
408
428
  if (tool.format === "skill-folder") {
409
429
  const dir = path.join(PROJECT_ROOT, tool.skillsDir);
410
430
  if (fs.existsSync(dir)) {
411
- const count = fs.readdirSync(dir).filter(
412
- (f) => fs.existsSync(path.join(dir, f, "SKILL.md"))
431
+ const count = fs.readdirSync(dir).filter((f) =>
432
+ fs.existsSync(path.join(dir, f, "SKILL.md"))
413
433
  ).length;
414
434
  console.log(c("green", `✓ ${tool.name}: ${count} skills in ${tool.skillsDir}/`));
415
435
  } else {
@@ -428,10 +448,30 @@ function cmdDoctor() {
428
448
  }
429
449
  }
430
450
 
431
- console.log(issues === 0
432
- ? c("green", "\n✅ Everything looks good!\n")
433
- : c("yellow", `\n⚠ ${issues} issue(s) found. Run 'npx ai-skills setup' to fix.\n`)
434
- );
451
+ // Check API connectivity
452
+ console.log(c("dim", "\nChecking API connectivity..."));
453
+ fetchJSON(SUPABASE_FUNCTION_URL, { email: "healthcheck@test.com" })
454
+ .then(() => {
455
+ console.log(c("green", "✓ Supabase Edge Function reachable"));
456
+ finish();
457
+ })
458
+ .catch((err) => {
459
+ if (SUPABASE_FUNCTION_URL.includes("YOUR_PROJECT")) {
460
+ console.log(c("yellow", "⚠ API URL not configured — update SUPABASE_FUNCTION_URL in bin/cli.js"));
461
+ issues++;
462
+ } else {
463
+ console.log(c("yellow", `⚠ API unreachable: ${err.message} (will use local fallback)`));
464
+ }
465
+ finish();
466
+ });
467
+
468
+ function finish() {
469
+ console.log(
470
+ issues === 0
471
+ ? c("green", "\n✅ Everything looks good!\n")
472
+ : c("yellow", `\n⚠ ${issues} issue(s) found.\n`)
473
+ );
474
+ }
435
475
  }
436
476
 
437
477
  // ── Main ──
@@ -439,44 +479,27 @@ function cmdDoctor() {
439
479
  const command = process.argv[2] || "setup";
440
480
 
441
481
  switch (command) {
442
- case "setup":
443
- cmdSetup();
444
- break;
445
- case "update":
446
- cmdUpdate();
447
- break;
448
- case "status":
449
- cmdStatus();
450
- break;
451
- case "list":
452
- cmdList();
453
- break;
454
- case "doctor":
455
- cmdDoctor();
456
- break;
457
- case "help":
458
- case "--help":
459
- case "-h":
482
+ case "setup": cmdSetup(); break;
483
+ case "update": cmdUpdate(); break;
484
+ case "status": cmdStatus(); break;
485
+ case "list": cmdList(); break;
486
+ case "doctor": cmdDoctor(); break;
487
+ case "help": case "--help": case "-h":
460
488
  console.log(`
461
- ${c("blue", "AI Skills Framework")}
489
+ ${c("blue", "AI Skills Framework")} — @valentia-ai-skills/framework
462
490
 
463
491
  Usage:
464
- npx ai-skills setup Setup skills for detected AI tools
465
- npx ai-skills update Update skills to latest version
466
- npx ai-skills status Show installed skills and tools
467
- npx ai-skills list List all available skills
468
- npx ai-skills doctor Check setup health
469
- npx ai-skills help Show this help
470
-
471
- How it works:
472
- 1. Install: npm install --save-dev @valentia-ai-skills/framework
473
- 2. Setup: npx ai-skills setup
474
- 3. Code: Your AI tools now follow your org's standards
475
- 4. Update: npm update @valentia-ai-skills/framework && npx ai-skills update
476
- `);
477
- break;
492
+ npx ai-skills setup Enter email fetch team's skills → install
493
+ npx ai-skills update Re-fetch and update skills for your team
494
+ npx ai-skills status Show installed skills, team, and tools
495
+ npx ai-skills list List locally bundled skills
496
+ npx ai-skills doctor Health check (config + API + tools)
497
+
498
+ Environment:
499
+ AI_SKILLS_API_URL Override the Supabase Edge Function URL
500
+ `); break;
478
501
  default:
479
502
  console.log(c("red", `Unknown command: ${command}`));
480
- console.log("Run 'npx ai-skills help' for usage info.");
503
+ console.log("Run 'npx ai-skills help' for usage.");
481
504
  process.exit(1);
482
505
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@valentia-ai-skills/framework",
3
- "version": "1.0.3",
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": "1.0.4",
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",