codemaxxing 0.1.14 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,241 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync } from "fs";
2
+ import { join, basename } from "path";
3
+ import { homedir } from "os";
4
+ import { REGISTRY, type RegistrySkill } from "../skills/registry.js";
5
+
6
+ const SKILLS_DIR = join(homedir(), ".codemaxxing", "skills");
7
+
8
+ export interface SkillMeta {
9
+ name: string;
10
+ description: string;
11
+ version: string;
12
+ author: string;
13
+ tags: string[];
14
+ }
15
+
16
+ /**
17
+ * Ensure the skills directory exists
18
+ */
19
+ function ensureSkillsDir(): void {
20
+ if (!existsSync(SKILLS_DIR)) {
21
+ mkdirSync(SKILLS_DIR, { recursive: true });
22
+ }
23
+ }
24
+
25
+ /**
26
+ * List all installed skills by scanning ~/.codemaxxing/skills/
27
+ */
28
+ export function listInstalledSkills(): SkillMeta[] {
29
+ ensureSkillsDir();
30
+ const skills: SkillMeta[] = [];
31
+
32
+ try {
33
+ const entries = readdirSync(SKILLS_DIR, { withFileTypes: true });
34
+ for (const entry of entries) {
35
+ if (!entry.isDirectory()) continue;
36
+ const metaPath = join(SKILLS_DIR, entry.name, "skill.json");
37
+ if (!existsSync(metaPath)) continue;
38
+ try {
39
+ const meta = JSON.parse(readFileSync(metaPath, "utf-8"));
40
+ skills.push({
41
+ name: meta.name ?? entry.name,
42
+ description: meta.description ?? "",
43
+ version: meta.version ?? "0.0.0",
44
+ author: meta.author ?? "unknown",
45
+ tags: meta.tags ?? [],
46
+ });
47
+ } catch {
48
+ // skip malformed skill.json
49
+ }
50
+ }
51
+ } catch {
52
+ // directory doesn't exist or can't be read
53
+ }
54
+
55
+ return skills;
56
+ }
57
+
58
+ /**
59
+ * Install a skill from the built-in registry
60
+ */
61
+ export function installSkill(name: string): { ok: boolean; message: string } {
62
+ const skill = REGISTRY.find((s) => s.name === name);
63
+ if (!skill) {
64
+ return { ok: false, message: `Skill "${name}" not found in registry` };
65
+ }
66
+
67
+ ensureSkillsDir();
68
+ const skillDir = join(SKILLS_DIR, name);
69
+
70
+ if (existsSync(skillDir)) {
71
+ return { ok: false, message: `Skill "${name}" is already installed` };
72
+ }
73
+
74
+ mkdirSync(skillDir, { recursive: true });
75
+ mkdirSync(join(skillDir, "examples"), { recursive: true });
76
+
77
+ // Write skill.json
78
+ const meta: SkillMeta = {
79
+ name: skill.name,
80
+ description: skill.description,
81
+ version: skill.version,
82
+ author: skill.author,
83
+ tags: skill.tags,
84
+ };
85
+ writeFileSync(join(skillDir, "skill.json"), JSON.stringify(meta, null, 2));
86
+
87
+ // Write prompt.md
88
+ writeFileSync(join(skillDir, "prompt.md"), skill.prompt);
89
+
90
+ return { ok: true, message: `Installed skill: ${skill.name}` };
91
+ }
92
+
93
+ /**
94
+ * Remove an installed skill
95
+ */
96
+ export function removeSkill(name: string): { ok: boolean; message: string } {
97
+ const skillDir = join(SKILLS_DIR, name);
98
+ if (!existsSync(skillDir)) {
99
+ return { ok: false, message: `Skill "${name}" is not installed` };
100
+ }
101
+
102
+ rmSync(skillDir, { recursive: true, force: true });
103
+ return { ok: true, message: `Removed skill: ${name}` };
104
+ }
105
+
106
+ /**
107
+ * Get the prompt.md content for an installed skill
108
+ */
109
+ export function getSkillPrompt(name: string): string | null {
110
+ const promptPath = join(SKILLS_DIR, name, "prompt.md");
111
+ if (!existsSync(promptPath)) return null;
112
+ return readFileSync(promptPath, "utf-8");
113
+ }
114
+
115
+ /**
116
+ * Get examples from a skill's examples/ directory
117
+ */
118
+ function getSkillExamples(name: string): string[] {
119
+ const examplesDir = join(SKILLS_DIR, name, "examples");
120
+ if (!existsSync(examplesDir)) return [];
121
+
122
+ try {
123
+ return readdirSync(examplesDir)
124
+ .filter((f) => f.endsWith(".md"))
125
+ .map((f) => readFileSync(join(examplesDir, f), "utf-8"));
126
+ } catch {
127
+ return [];
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Get skills that should be active for the given project directory.
133
+ * If .codemaxxing/skills.json exists in the project, only those skills are active.
134
+ * Otherwise, all installed skills are active.
135
+ */
136
+ export function getActiveSkills(cwd: string, sessionDisabled: Set<string> = new Set()): string[] {
137
+ const installed = listInstalledSkills().map((s) => s.name);
138
+ let active: string[];
139
+
140
+ const projectConfig = join(cwd, ".codemaxxing", "skills.json");
141
+ if (existsSync(projectConfig)) {
142
+ try {
143
+ const config = JSON.parse(readFileSync(projectConfig, "utf-8"));
144
+ const projectSkills: string[] = config.skills ?? [];
145
+ // Only include project skills that are actually installed
146
+ active = projectSkills.filter((s) => installed.includes(s));
147
+ } catch {
148
+ active = installed;
149
+ }
150
+ } else {
151
+ active = installed;
152
+ }
153
+
154
+ // Filter out session-disabled skills
155
+ return active.filter((s) => !sessionDisabled.has(s));
156
+ }
157
+
158
+ /**
159
+ * Build the skill prompt blocks to inject into the system prompt
160
+ */
161
+ export function buildSkillPrompts(cwd: string, sessionDisabled: Set<string> = new Set()): string {
162
+ const activeSkills = getActiveSkills(cwd, sessionDisabled);
163
+ if (activeSkills.length === 0) return "";
164
+
165
+ const blocks: string[] = [];
166
+ for (const name of activeSkills) {
167
+ const prompt = getSkillPrompt(name);
168
+ if (!prompt) continue;
169
+
170
+ blocks.push(`\n--- Skill: ${name} ---`);
171
+ blocks.push(prompt.trim());
172
+
173
+ // Include examples if any
174
+ const examples = getSkillExamples(name);
175
+ for (const example of examples) {
176
+ blocks.push(`\n### Example:\n${example.trim()}`);
177
+ }
178
+
179
+ blocks.push(`--- End Skill ---`);
180
+ }
181
+
182
+ return blocks.join("\n");
183
+ }
184
+
185
+ /**
186
+ * Create a scaffold for a new custom skill
187
+ */
188
+ export function createSkillScaffold(name: string): { ok: boolean; message: string; path?: string } {
189
+ ensureSkillsDir();
190
+ const skillDir = join(SKILLS_DIR, name);
191
+
192
+ if (existsSync(skillDir)) {
193
+ return { ok: false, message: `Skill "${name}" already exists` };
194
+ }
195
+
196
+ mkdirSync(skillDir, { recursive: true });
197
+ mkdirSync(join(skillDir, "examples"), { recursive: true });
198
+
199
+ const meta: SkillMeta = {
200
+ name,
201
+ description: "A custom skill",
202
+ version: "1.0.0",
203
+ author: "you",
204
+ tags: [],
205
+ };
206
+ writeFileSync(join(skillDir, "skill.json"), JSON.stringify(meta, null, 2));
207
+
208
+ writeFileSync(
209
+ join(skillDir, "prompt.md"),
210
+ `# ${name}\n\nAdd your skill prompt here. This content will be injected into the system prompt.\n\n## Guidelines\n- Be specific and actionable\n- Include best practices\n- List anti-patterns to avoid\n`,
211
+ );
212
+
213
+ return { ok: true, message: `Created skill scaffold: ${name}`, path: skillDir };
214
+ }
215
+
216
+ /**
217
+ * Search the built-in registry by name, tags, or description
218
+ */
219
+ export function searchRegistry(query: string): RegistrySkill[] {
220
+ const q = query.toLowerCase();
221
+ return REGISTRY.filter(
222
+ (s) =>
223
+ s.name.toLowerCase().includes(q) ||
224
+ s.description.toLowerCase().includes(q) ||
225
+ s.tags.some((t) => t.toLowerCase().includes(q)),
226
+ );
227
+ }
228
+
229
+ /**
230
+ * Return all skills from the built-in registry
231
+ */
232
+ export function getRegistrySkills(): RegistrySkill[] {
233
+ return REGISTRY;
234
+ }
235
+
236
+ /**
237
+ * Get the count of active skills
238
+ */
239
+ export function getActiveSkillCount(cwd: string, sessionDisabled: Set<string> = new Set()): number {
240
+ return getActiveSkills(cwd, sessionDisabled).length;
241
+ }