smartclaudo 0.1.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.
- package/bin/smartclaudo.js +240 -0
- package/package.json +30 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const https = require('https');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const REPO = 'sharklandy/SmartClaudo';
|
|
8
|
+
const BRANCH = 'main';
|
|
9
|
+
const RAW_BASE = `https://raw.githubusercontent.com/${REPO}/${BRANCH}`;
|
|
10
|
+
|
|
11
|
+
const SKILLS = [
|
|
12
|
+
'smartclaudo', 'idea', 'pre-change', 'arch',
|
|
13
|
+
'checkpoint', 'resume', 'git-summary', 'pr-description', 'stack-explain'
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const HOOKS = ['lint-on-save', 'test-on-edit', 'no-direct-main', 'session-memory'];
|
|
17
|
+
|
|
18
|
+
const SNIPPETS = [
|
|
19
|
+
'smartclaudo-guard', 'post-skill-update', 'auto-idea-interview',
|
|
20
|
+
'auto-pre-change', 'auto-checkpoint', 'session-memory-loader', 'compact-mode'
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
// ─── utils ────────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
function fetchUrl(url) {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
https.get(url, (res) => {
|
|
28
|
+
if (res.statusCode === 301 || res.statusCode === 302) {
|
|
29
|
+
return fetchUrl(res.headers.location).then(resolve).catch(reject);
|
|
30
|
+
}
|
|
31
|
+
let data = '';
|
|
32
|
+
res.on('data', (chunk) => (data += chunk));
|
|
33
|
+
res.on('end', () => {
|
|
34
|
+
if (res.statusCode === 200) resolve(data);
|
|
35
|
+
else reject(new Error(`HTTP ${res.statusCode} — ${url}`));
|
|
36
|
+
});
|
|
37
|
+
}).on('error', reject);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function ensureDir(dir) {
|
|
42
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function log(symbol, msg) {
|
|
46
|
+
console.log(` ${symbol} ${msg}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ─── installers ───────────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
async function installSkill(name) {
|
|
52
|
+
const url = `${RAW_BASE}/skills/${name}.md`;
|
|
53
|
+
const content = await fetchUrl(url);
|
|
54
|
+
const claudeDir = path.join(process.cwd(), '.claude');
|
|
55
|
+
ensureDir(claudeDir);
|
|
56
|
+
fs.writeFileSync(path.join(claudeDir, `${name}.md`), content);
|
|
57
|
+
log('✓', `skill:${name} → .claude/${name}.md`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function installHook(name) {
|
|
61
|
+
const url = `${RAW_BASE}/hooks/${name}/hook.json`;
|
|
62
|
+
const content = await fetchUrl(url);
|
|
63
|
+
const hookConfig = JSON.parse(content);
|
|
64
|
+
|
|
65
|
+
const claudeDir = path.join(process.cwd(), '.claude');
|
|
66
|
+
ensureDir(claudeDir);
|
|
67
|
+
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
68
|
+
|
|
69
|
+
let settings = {};
|
|
70
|
+
if (fs.existsSync(settingsPath)) {
|
|
71
|
+
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); }
|
|
72
|
+
catch { settings = {}; }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!settings.hooks) settings.hooks = {};
|
|
76
|
+
|
|
77
|
+
for (const [event, entries] of Object.entries(hookConfig.hooks || {})) {
|
|
78
|
+
if (!settings.hooks[event]) settings.hooks[event] = [];
|
|
79
|
+
// avoid duplicates by checking matcher+command
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
const alreadyExists = settings.hooks[event].some(
|
|
82
|
+
(e) => JSON.stringify(e) === JSON.stringify(entry)
|
|
83
|
+
);
|
|
84
|
+
if (!alreadyExists) settings.hooks[event].push(entry);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
89
|
+
log('✓', `hook:${name} → .claude/settings.json`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function installSnippet(name) {
|
|
93
|
+
const url = `${RAW_BASE}/templates/claude-md/${name}.md`;
|
|
94
|
+
const content = await fetchUrl(url);
|
|
95
|
+
|
|
96
|
+
// extract content after the first --- divider (skip the meta header)
|
|
97
|
+
const dividerIndex = content.indexOf('\n---\n');
|
|
98
|
+
const snippet = (dividerIndex !== -1 ? content.slice(dividerIndex + 5) : content).trim();
|
|
99
|
+
|
|
100
|
+
const claudeMdPath = path.join(process.cwd(), 'CLAUDE.md');
|
|
101
|
+
let existing = fs.existsSync(claudeMdPath) ? fs.readFileSync(claudeMdPath, 'utf8') : '';
|
|
102
|
+
|
|
103
|
+
// skip if section already present (check first non-empty line of snippet)
|
|
104
|
+
const firstHeader = snippet.split('\n').find((l) => l.startsWith('#'));
|
|
105
|
+
if (firstHeader && existing.includes(firstHeader)) {
|
|
106
|
+
log('~', `template:${name} already in CLAUDE.md, skipped`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const separator = existing.trimEnd() ? '\n\n---\n\n' : '';
|
|
111
|
+
fs.writeFileSync(claudeMdPath, existing.trimEnd() + separator + snippet + '\n');
|
|
112
|
+
log('✓', `template:${name} → CLAUDE.md`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ─── commands ─────────────────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
function printHelp() {
|
|
118
|
+
console.log(`
|
|
119
|
+
SmartClaudo — Claude Code skill installer
|
|
120
|
+
https://github.com/sharklandy/SmartClaudo
|
|
121
|
+
|
|
122
|
+
Usage:
|
|
123
|
+
npx smartclaudo@latest add all Install everything (recommended)
|
|
124
|
+
npx smartclaudo@latest add skill:<name> Install a specific skill into .claude/
|
|
125
|
+
npx smartclaudo@latest add hook:<name> Merge a hook into .claude/settings.json
|
|
126
|
+
npx smartclaudo@latest add template:<name> Inject a snippet into CLAUDE.md
|
|
127
|
+
npx smartclaudo@latest list List all available resources
|
|
128
|
+
|
|
129
|
+
Skills:
|
|
130
|
+
${SKILLS.join(', ')}
|
|
131
|
+
|
|
132
|
+
Hooks:
|
|
133
|
+
${HOOKS.join(', ')}
|
|
134
|
+
|
|
135
|
+
Templates (CLAUDE.md snippets):
|
|
136
|
+
${SNIPPETS.join(', ')}
|
|
137
|
+
|
|
138
|
+
After installing, open Claude Code and run /smartclaudo to finish setup.
|
|
139
|
+
`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function printList() {
|
|
143
|
+
console.log('\nSkills:');
|
|
144
|
+
SKILLS.forEach((s) => console.log(` skill:${s}`));
|
|
145
|
+
console.log('\nHooks:');
|
|
146
|
+
HOOKS.forEach((h) => console.log(` hook:${h}`));
|
|
147
|
+
console.log('\nTemplates (CLAUDE.md snippets):');
|
|
148
|
+
SNIPPETS.forEach((t) => console.log(` template:${t}`));
|
|
149
|
+
console.log('');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function addAll() {
|
|
153
|
+
console.log('\nInstalling all SmartClaudo resources...\n');
|
|
154
|
+
|
|
155
|
+
console.log('Skills:');
|
|
156
|
+
for (const skill of SKILLS) await installSkill(skill);
|
|
157
|
+
|
|
158
|
+
console.log('\nHooks:');
|
|
159
|
+
for (const hook of HOOKS) await installHook(hook);
|
|
160
|
+
|
|
161
|
+
console.log('\nCLAUDE.md snippets:');
|
|
162
|
+
for (const snippet of SNIPPETS) await installSnippet(snippet);
|
|
163
|
+
|
|
164
|
+
console.log(`
|
|
165
|
+
Done!
|
|
166
|
+
|
|
167
|
+
Next step: open Claude Code in this project and run:
|
|
168
|
+
/smartclaudo
|
|
169
|
+
|
|
170
|
+
This completes the setup (issue tracker, labels, doc paths) and you're ready to go.
|
|
171
|
+
`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function addOne(target) {
|
|
175
|
+
if (target.startsWith('skill:')) {
|
|
176
|
+
const name = target.slice(6);
|
|
177
|
+
if (!SKILLS.includes(name)) {
|
|
178
|
+
console.error(`Unknown skill: ${name}\nAvailable: ${SKILLS.join(', ')}`);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
await installSkill(name);
|
|
182
|
+
} else if (target.startsWith('hook:')) {
|
|
183
|
+
const name = target.slice(5);
|
|
184
|
+
if (!HOOKS.includes(name)) {
|
|
185
|
+
console.error(`Unknown hook: ${name}\nAvailable: ${HOOKS.join(', ')}`);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
await installHook(name);
|
|
189
|
+
} else if (target.startsWith('template:')) {
|
|
190
|
+
const name = target.slice(9);
|
|
191
|
+
if (!SNIPPETS.includes(name)) {
|
|
192
|
+
console.error(`Unknown template: ${name}\nAvailable: ${SNIPPETS.join(', ')}`);
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
await installSnippet(name);
|
|
196
|
+
} else {
|
|
197
|
+
console.error(`Unknown target format: "${target}"`);
|
|
198
|
+
console.error('Expected: all | skill:<name> | hook:<name> | template:<name>');
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
console.log('');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ─── main ─────────────────────────────────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
async function main() {
|
|
207
|
+
const [,, command, target] = process.argv;
|
|
208
|
+
|
|
209
|
+
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
210
|
+
printHelp();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (command === 'list') {
|
|
215
|
+
printList();
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (command === 'add') {
|
|
220
|
+
if (!target) {
|
|
221
|
+
console.error('Missing argument. Usage: npx smartclaudo add <all | skill:name | hook:name | template:name>');
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
if (target === 'all') {
|
|
225
|
+
await addAll();
|
|
226
|
+
} else {
|
|
227
|
+
console.log('');
|
|
228
|
+
await addOne(target);
|
|
229
|
+
}
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
console.error(`Unknown command: "${command}". Run "npx smartclaudo help" for usage.`);
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
main().catch((err) => {
|
|
238
|
+
console.error(`\nError: ${err.message}`);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "smartclaudo",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI to install SmartClaudo skills, hooks, and CLAUDE.md snippets into your project",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"claude",
|
|
7
|
+
"claude-code",
|
|
8
|
+
"ai",
|
|
9
|
+
"skills",
|
|
10
|
+
"hooks",
|
|
11
|
+
"llm"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://github.com/sharklandy/SmartClaudo",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/sharklandy/SmartClaudo.git",
|
|
17
|
+
"directory": "cli"
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"author": "sharklandy",
|
|
21
|
+
"bin": {
|
|
22
|
+
"smartclaudo": "bin/smartclaudo.js"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18.0.0"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"bin/"
|
|
29
|
+
]
|
|
30
|
+
}
|