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.
Files changed (2) hide show
  1. package/bin/smartclaudo.js +240 -0
  2. 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
+ }