claudient 0.1.0 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/scripts/cli.js +378 -66
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudient",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "The definitive Claude Code knowledge system — skills, agents, hooks, rules, and workflows.",
5
5
  "keywords": [
6
6
  "claude-code",
package/scripts/cli.js CHANGED
@@ -3,12 +3,16 @@
3
3
  const fs = require('fs')
4
4
  const path = require('path')
5
5
  const os = require('os')
6
+ const { execSync } = require('child_process')
6
7
 
7
8
  const REPO_ROOT = path.resolve(__dirname, '..')
8
9
  const CLAUDE_DIR = path.join(os.homedir(), '.claude')
9
10
  const SKILLS_DEST = path.join(CLAUDE_DIR, 'skills')
11
+ const AGENTS_DEST = path.join(CLAUDE_DIR, 'agents')
12
+ const HOOKS_DEST = path.join(CLAUDE_DIR, 'hooks')
13
+ const RULES_DEST = path.join(CLAUDE_DIR, 'rules')
10
14
 
11
- const VALID_CATEGORIES = [
15
+ const SKILL_CATEGORIES = [
12
16
  'backend',
13
17
  'devops-infra',
14
18
  'data-ml',
@@ -17,23 +21,42 @@ const VALID_CATEGORIES = [
17
21
  'ai-engineering',
18
22
  ]
19
23
 
24
+ const SUPPORTED_LANGS = ['en', 'fr', 'de', 'nl', 'es']
25
+
20
26
  function usage() {
21
27
  console.log(`
22
- claudient — Claude Code knowledge system installer
28
+ claudient — Claude Code knowledge system
23
29
 
24
30
  Usage:
25
- npx claudient add <category> Add skills from a specific category
26
- npx claudient add all Add all skills
27
- npx claudient list List available categories and skills
28
- npx claudient help Show this help
31
+ npx claudient add skills [category] [--lang <lang>]
32
+ npx claudient add agents
33
+ npx claudient add rules
34
+ npx claudient add hooks
35
+ npx claudient add all [--lang <lang>]
36
+ npx claudient remove skills [category]
37
+ npx claudient remove agents
38
+ npx claudient remove rules
39
+ npx claudient update
40
+ npx claudient list [skills|agents|rules|hooks]
41
+ npx claudient help
42
+
43
+ Skill categories:
44
+ ${SKILL_CATEGORIES.join('\n ')}
29
45
 
30
- Categories:
31
- ${VALID_CATEGORIES.join('\n ')}
46
+ Languages (--lang):
47
+ en (default), fr, de, nl, es
32
48
 
33
49
  Examples:
34
- npx claudient add backend
35
- npx claudient add ai-engineering
36
- npx claudient add all
50
+ npx claudient add skills
51
+ npx claudient add skills backend
52
+ npx claudient add skills backend --lang fr
53
+ npx claudient add agents
54
+ npx claudient add rules
55
+ npx claudient add hooks
56
+ npx claudient add all --lang de
57
+ npx claudient remove skills backend
58
+ npx claudient update
59
+ npx claudient list agents
37
60
  `)
38
61
  }
39
62
 
@@ -42,111 +65,400 @@ function checkClaudeInstalled() {
42
65
  console.error(`
43
66
  Error: ~/.claude directory not found.
44
67
 
45
- Claude Code must be installed first. Install it from:
68
+ Claude Code must be installed first:
46
69
  https://claude.ai/code
47
-
48
- Or install the CLI:
49
- npm install -g @anthropic-ai/claude-code
50
70
  `)
51
71
  process.exit(1)
52
72
  }
53
73
  }
54
74
 
55
- function copyDir(src, dest, prefix = '') {
75
+ function parseArgs(args) {
76
+ const flags = {}
77
+ const positional = []
78
+ for (let i = 0; i < args.length; i++) {
79
+ if (args[i] === '--lang' && args[i + 1]) {
80
+ flags.lang = args[++i]
81
+ } else if (!args[i].startsWith('--')) {
82
+ positional.push(args[i])
83
+ }
84
+ }
85
+ return { flags, positional }
86
+ }
87
+
88
+ function copyDir(src, dest, label = '') {
56
89
  if (!fs.existsSync(src)) return 0
57
90
  fs.mkdirSync(dest, { recursive: true })
58
-
59
91
  let count = 0
60
- const entries = fs.readdirSync(src, { withFileTypes: true })
61
- for (const entry of entries) {
92
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
62
93
  const srcPath = path.join(src, entry.name)
63
94
  const destPath = path.join(dest, entry.name)
64
95
  if (entry.isDirectory()) {
65
- count += copyDir(srcPath, destPath, prefix + entry.name + '/')
96
+ count += copyDir(srcPath, destPath, label + entry.name + '/')
66
97
  } else if (entry.name.endsWith('.md')) {
67
98
  fs.copyFileSync(srcPath, destPath)
68
- console.log(` + ${prefix}${entry.name}`)
99
+ console.log(` + ${label}${entry.name}`)
69
100
  count++
70
101
  }
71
102
  }
72
103
  return count
73
104
  }
74
105
 
75
- function addCategory(category) {
106
+ function removeDir(dir, label) {
107
+ if (!fs.existsSync(dir)) {
108
+ console.log(` ~ ${label} (not installed)`)
109
+ return 0
110
+ }
111
+ fs.rmSync(dir, { recursive: true, force: true })
112
+ console.log(` - ${label} (removed)`)
113
+ return 1
114
+ }
115
+
116
+ function addSkills(category, lang) {
76
117
  checkClaudeInstalled()
118
+ fs.mkdirSync(SKILLS_DEST, { recursive: true })
119
+
120
+ const installCategory = (cat) => {
121
+ const src = path.join(REPO_ROOT, 'skills', cat)
122
+ if (!fs.existsSync(src)) return 0
77
123
 
78
- if (category === 'all') {
79
- let total = 0
80
- console.log(`Installing all Claudient skills to ${SKILLS_DEST}...\n`)
81
- for (const cat of VALID_CATEGORIES) {
82
- const src = path.join(REPO_ROOT, 'skills', cat)
124
+ if (!lang || lang === 'en') {
83
125
  const dest = path.join(SKILLS_DEST, cat)
84
126
  const count = copyDir(src, dest, cat + '/')
85
127
  if (count > 0) console.log(` ✓ ${cat} (${count} files)`)
128
+ return count
86
129
  }
87
- console.log(`\nDone. Skills installed to: ${SKILLS_DEST}`)
88
- console.log('Restart Claude Code to activate the new skills.')
89
- return
130
+
131
+ // Language-aware: copy only matching lang files, renaming to drop the lang subdir
132
+ let count = 0
133
+ count += copyLangFiles(src, path.join(SKILLS_DEST, cat), lang, cat + '/')
134
+ if (count > 0) console.log(` ✓ ${cat} [${lang}] (${count} files)`)
135
+ return count
90
136
  }
91
137
 
92
- if (!VALID_CATEGORIES.includes(category)) {
93
- console.error(`Unknown category: "${category}"`)
94
- console.error(`Valid categories: ${VALID_CATEGORIES.join(', ')}`)
95
- process.exit(1)
138
+ if (!category || category === 'all') {
139
+ console.log(`Installing all skills${lang && lang !== 'en' ? ` [${lang}]` : ''}...\n`)
140
+ for (const cat of SKILL_CATEGORIES) installCategory(cat)
141
+ } else {
142
+ if (!SKILL_CATEGORIES.includes(category)) {
143
+ console.error(`Unknown category: "${category}". Valid: ${SKILL_CATEGORIES.join(', ')}`)
144
+ process.exit(1)
145
+ }
146
+ console.log(`Installing ${category} skills${lang && lang !== 'en' ? ` [${lang}]` : ''}...\n`)
147
+ installCategory(category)
96
148
  }
97
149
 
98
- const src = path.join(REPO_ROOT, 'skills', category)
99
- if (!fs.existsSync(src)) {
100
- console.error(`Category directory not found: ${src}`)
101
- process.exit(1)
150
+ console.log(`\nDone. Skills installed to: ${SKILLS_DEST}`)
151
+ console.log('Restart Claude Code to activate.')
152
+ }
153
+
154
+ // Copies lang-specific files: for each file.md, checks if lang/file.md exists
155
+ // and copies it (as file.md) alongside the English version.
156
+ function copyLangFiles(src, dest, lang, prefix) {
157
+ if (!fs.existsSync(src)) return 0
158
+ fs.mkdirSync(dest, { recursive: true })
159
+ let count = 0
160
+
161
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
162
+ const srcPath = path.join(src, entry.name)
163
+ if (entry.isDirectory()) {
164
+ if (entry.name === lang) continue // skip the lang subdir itself
165
+ if (SUPPORTED_LANGS.includes(entry.name)) continue // skip other lang subdirs
166
+ count += copyLangFiles(srcPath, path.join(dest, entry.name), lang, prefix + entry.name + '/')
167
+ } else if (entry.name.endsWith('.md')) {
168
+ // Check for translated version
169
+ const translated = path.join(src, lang, entry.name)
170
+ const fileSrc = fs.existsSync(translated) ? translated : srcPath
171
+ fs.copyFileSync(fileSrc, path.join(dest, entry.name))
172
+ const tag = fs.existsSync(translated) ? `[${lang}]` : '[en]'
173
+ console.log(` + ${prefix}${entry.name} ${tag}`)
174
+ count++
175
+ }
176
+ }
177
+ return count
178
+ }
179
+
180
+ function addAgents() {
181
+ checkClaudeInstalled()
182
+ fs.mkdirSync(AGENTS_DEST, { recursive: true })
183
+ console.log(`Installing agents to ${AGENTS_DEST}...\n`)
184
+ const src = path.join(REPO_ROOT, 'agents')
185
+ let total = 0
186
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
187
+ if (!entry.isDirectory()) continue
188
+ if (SUPPORTED_LANGS.includes(entry.name)) continue
189
+ const count = copyDir(path.join(src, entry.name), path.join(AGENTS_DEST, entry.name), entry.name + '/')
190
+ if (count > 0) console.log(` ✓ ${entry.name} (${count} agents)`)
191
+ total += count
192
+ }
193
+ console.log(`\nInstalled ${total} agent file(s) to: ${AGENTS_DEST}`)
194
+ console.log('Restart Claude Code to activate.')
195
+ }
196
+
197
+ function addRules() {
198
+ checkClaudeInstalled()
199
+ const rulesDir = path.join(REPO_ROOT, 'rules')
200
+ console.log('\nAvailable rules:\n')
201
+
202
+ // Print all rules with their content paths
203
+ let i = 1
204
+ const rulePaths = []
205
+ for (const cat of fs.readdirSync(rulesDir, { withFileTypes: true })) {
206
+ if (!cat.isDirectory() || SUPPORTED_LANGS.includes(cat.name)) continue
207
+ const catDir = path.join(rulesDir, cat.name)
208
+ for (const f of fs.readdirSync(catDir, { withFileTypes: true })) {
209
+ if (!f.isFile() || !f.name.endsWith('.md')) continue
210
+ if (SUPPORTED_LANGS.some(l => f.name.startsWith(l + '/'))) continue
211
+ const filepath = path.join(catDir, f.name)
212
+ const relPath = `rules/${cat.name}/${f.name}`
213
+ rulePaths.push({ label: relPath, filepath })
214
+ console.log(` ${i++}. ${relPath}`)
215
+ }
216
+ }
217
+
218
+ console.log(`
219
+ Rules are added to your project's CLAUDE.md file.
220
+ Copy the rule content you need into your project's CLAUDE.md.
221
+
222
+ Rule files are located in:
223
+ ${rulesDir}
224
+
225
+ Quick copy all rules into a new CLAUDE.md:`)
226
+ console.log(` npx claudient add rules --write`)
227
+ }
228
+
229
+ function addRulesWrite() {
230
+ checkClaudeInstalled()
231
+ const rulesDir = path.join(REPO_ROOT, 'rules')
232
+ const claudeMd = path.join(process.cwd(), 'CLAUDE.md')
233
+ const exists = fs.existsSync(claudeMd)
234
+
235
+ let content = exists ? fs.readFileSync(claudeMd, 'utf-8') : '# Project Rules\n\n'
236
+ content += '\n\n---\n<!-- Claudient Rules -->\n\n'
237
+
238
+ for (const cat of fs.readdirSync(rulesDir, { withFileTypes: true })) {
239
+ if (!cat.isDirectory() || SUPPORTED_LANGS.includes(cat.name)) continue
240
+ const catDir = path.join(rulesDir, cat.name)
241
+ for (const f of fs.readdirSync(catDir).filter(n => n.endsWith('.md'))) {
242
+ const ruleContent = fs.readFileSync(path.join(catDir, f), 'utf-8')
243
+ content += ruleContent + '\n\n'
244
+ console.log(` + rules/${cat.name}/${f}`)
245
+ }
246
+ }
247
+
248
+ fs.writeFileSync(claudeMd, content)
249
+ console.log(`\n${exists ? 'Updated' : 'Created'} CLAUDE.md with all rules.`)
250
+ }
251
+
252
+ function addHooks() {
253
+ checkClaudeInstalled()
254
+ fs.mkdirSync(HOOKS_DEST, { recursive: true })
255
+ const hooksDir = path.join(REPO_ROOT, 'hooks')
256
+ console.log(`Installing hooks to ${HOOKS_DEST}...\n`)
257
+ let total = 0
258
+
259
+ for (const cat of fs.readdirSync(hooksDir, { withFileTypes: true })) {
260
+ if (!cat.isDirectory()) continue
261
+ const catDir = path.join(hooksDir, cat.name)
262
+ const destCatDir = path.join(HOOKS_DEST, cat.name)
263
+ fs.mkdirSync(destCatDir, { recursive: true })
264
+
265
+ for (const f of fs.readdirSync(catDir, { withFileTypes: true })) {
266
+ if (!f.isFile() || !f.name.endsWith('.sh')) continue
267
+ fs.copyFileSync(path.join(catDir, f.name), path.join(destCatDir, f.name))
268
+ fs.chmodSync(path.join(destCatDir, f.name), '755')
269
+ console.log(` + ${cat.name}/${f.name}`)
270
+ total++
271
+ }
102
272
  }
103
273
 
104
- const dest = path.join(SKILLS_DEST, category)
105
- console.log(`Installing ${category} skills to ${dest}...\n`)
106
- const count = copyDir(src, dest, category + '/')
107
- console.log(`\nInstalled ${count} skill file(s).`)
108
- console.log('Restart Claude Code to activate the new skills.')
274
+ console.log(`
275
+ Installed ${total} hook script(s) to: ${HOOKS_DEST}
276
+
277
+ Next step add hooks to your settings.json:
278
+ ~/.claude/settings.json (global)
279
+ .claude/settings.json (project)
280
+
281
+ See hook documentation at:
282
+ ${path.join(REPO_ROOT, 'hooks')}
283
+
284
+ Or browse online: https://github.com/Claudient/Claudient/tree/main/hooks
285
+ `)
286
+ }
287
+
288
+ function removeCommand(type, category) {
289
+ checkClaudeInstalled()
290
+ switch (type) {
291
+ case 'skills':
292
+ if (category) {
293
+ console.log(`Removing ${category} skills...\n`)
294
+ removeDir(path.join(SKILLS_DEST, category), category)
295
+ } else {
296
+ console.log('Removing all skills...\n')
297
+ for (const cat of SKILL_CATEGORIES) {
298
+ removeDir(path.join(SKILLS_DEST, cat), cat)
299
+ }
300
+ }
301
+ break
302
+ case 'agents':
303
+ console.log('Removing agents...\n')
304
+ removeDir(AGENTS_DEST, 'agents')
305
+ break
306
+ case 'rules':
307
+ console.log('Removing rules...\n')
308
+ removeDir(RULES_DEST, 'rules')
309
+ break
310
+ case 'hooks':
311
+ console.log('Removing hooks...\n')
312
+ removeDir(HOOKS_DEST, 'hooks')
313
+ break
314
+ default:
315
+ console.error(`Unknown type: "${type}". Use: skills, agents, rules, hooks`)
316
+ process.exit(1)
317
+ }
318
+ console.log('\nDone. Restart Claude Code to apply.')
109
319
  }
110
320
 
111
- function listSkills() {
112
- console.log('Available Claudient skill categories:\n')
113
- for (const cat of VALID_CATEGORIES) {
114
- const catDir = path.join(REPO_ROOT, 'skills', cat)
115
- if (!fs.existsSync(catDir)) continue
116
- const files = getSkillFiles(catDir, '')
117
- console.log(`${cat}/ (${files.length} skills)`)
118
- for (const f of files) {
119
- console.log(` ${f}`)
321
+ function updateCommand() {
322
+ console.log('Checking for updates...\n')
323
+ try {
324
+ const current = require(path.join(REPO_ROOT, 'package.json')).version
325
+ const latest = execSync('npm view claudient version', { encoding: 'utf-8' }).trim()
326
+ if (current === latest) {
327
+ console.log(`Already up to date (v${current}).`)
328
+ return
120
329
  }
330
+ console.log(`Update available: v${current} → v${latest}`)
331
+ console.log('Run: npm install -g claudient or npx claudient@latest add all')
332
+ } catch {
333
+ console.log('Could not check for updates. Visit: https://www.npmjs.com/package/claudient')
121
334
  }
122
335
  }
123
336
 
124
- function getSkillFiles(dir, prefix) {
337
+ function listCommand(type) {
338
+ const listSkills = () => {
339
+ console.log('Skills:\n')
340
+ for (const cat of SKILL_CATEGORIES) {
341
+ const catDir = path.join(REPO_ROOT, 'skills', cat)
342
+ if (!fs.existsSync(catDir)) continue
343
+ const files = getFiles(catDir).filter(f => !SUPPORTED_LANGS.some(l => f.includes(`/${l}/`)))
344
+ console.log(` ${cat}/ (${files.length})`)
345
+ for (const f of files) console.log(` ${f}`)
346
+ }
347
+ }
348
+
349
+ const listAgents = () => {
350
+ console.log('Agents:\n')
351
+ const agentsDir = path.join(REPO_ROOT, 'agents')
352
+ for (const cat of fs.readdirSync(agentsDir, { withFileTypes: true })) {
353
+ if (!cat.isDirectory() || SUPPORTED_LANGS.includes(cat.name)) continue
354
+ const files = getFiles(path.join(agentsDir, cat.name)).filter(f => !SUPPORTED_LANGS.some(l => f.includes(`/${l}/`)))
355
+ console.log(` ${cat.name}/ (${files.length})`)
356
+ for (const f of files) console.log(` ${f}`)
357
+ }
358
+ }
359
+
360
+ const listHooks = () => {
361
+ console.log('Hooks:\n')
362
+ const hooksDir = path.join(REPO_ROOT, 'hooks')
363
+ for (const cat of fs.readdirSync(hooksDir, { withFileTypes: true })) {
364
+ if (!cat.isDirectory()) continue
365
+ const files = fs.readdirSync(path.join(hooksDir, cat.name)).filter(f => f.endsWith('.sh'))
366
+ console.log(` ${cat.name}/ (${files.length})`)
367
+ for (const f of files) console.log(` ${f}`)
368
+ }
369
+ }
370
+
371
+ const listRules = () => {
372
+ console.log('Rules:\n')
373
+ const rulesDir = path.join(REPO_ROOT, 'rules')
374
+ for (const cat of fs.readdirSync(rulesDir, { withFileTypes: true })) {
375
+ if (!cat.isDirectory() || SUPPORTED_LANGS.includes(cat.name)) continue
376
+ const files = fs.readdirSync(path.join(rulesDir, cat.name)).filter(f => f.endsWith('.md'))
377
+ console.log(` ${cat.name}/ (${files.length})`)
378
+ for (const f of files) console.log(` ${f}`)
379
+ }
380
+ }
381
+
382
+ switch (type) {
383
+ case 'agents': listAgents(); break
384
+ case 'hooks': listHooks(); break
385
+ case 'rules': listRules(); break
386
+ case 'skills':
387
+ default:
388
+ if (!type) {
389
+ listSkills(); console.log(); listAgents(); console.log(); listRules(); console.log(); listHooks()
390
+ } else {
391
+ listSkills()
392
+ }
393
+ }
394
+ }
395
+
396
+ function getFiles(dir, prefix = '') {
125
397
  const results = []
126
398
  if (!fs.existsSync(dir)) return results
127
399
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
128
400
  const p = path.join(dir, entry.name)
129
- if (entry.isDirectory()) {
130
- results.push(...getSkillFiles(p, prefix + entry.name + '/'))
131
- } else if (entry.name.endsWith('.md')) {
132
- results.push(prefix + entry.name)
133
- }
401
+ if (entry.isDirectory()) results.push(...getFiles(p, prefix + entry.name + '/'))
402
+ else if (entry.name.endsWith('.md')) results.push(prefix + entry.name)
134
403
  }
135
404
  return results
136
405
  }
137
406
 
138
- const [, , command, ...args] = process.argv
407
+ // ── Main ──────────────────────────────────────────────────────────────────────
408
+
409
+ const [, , command, ...rawArgs] = process.argv
410
+ const { flags, positional } = parseArgs(rawArgs)
411
+ const lang = flags.lang && SUPPORTED_LANGS.includes(flags.lang) ? flags.lang : null
412
+
413
+ if (lang && !SUPPORTED_LANGS.includes(lang)) {
414
+ console.error(`Unknown language: "${flags.lang}". Supported: ${SUPPORTED_LANGS.join(', ')}`)
415
+ process.exit(1)
416
+ }
139
417
 
140
418
  switch (command) {
141
- case 'add':
142
- if (!args[0]) {
143
- console.error('Usage: claudient add <category|all>')
144
- process.exit(1)
419
+ case 'add': {
420
+ const type = positional[0]
421
+ const arg2 = positional[1]
422
+ switch (type) {
423
+ case 'skills': addSkills(arg2 || null, lang); break
424
+ case 'agents': addAgents(); break
425
+ case 'rules':
426
+ if (flags.write) addRulesWrite()
427
+ else addRules()
428
+ break
429
+ case 'hooks': addHooks(); break
430
+ case 'all':
431
+ addSkills(null, lang)
432
+ addAgents()
433
+ addHooks()
434
+ break
435
+ case undefined:
436
+ console.error('Usage: claudient add <skills|agents|rules|hooks|all>')
437
+ process.exit(1)
438
+ break
439
+ default:
440
+ // Backward compat: claudient add backend (old syntax)
441
+ if (SKILL_CATEGORIES.includes(type)) {
442
+ addSkills(type, lang)
443
+ } else {
444
+ console.error(`Unknown type: "${type}". Use: skills, agents, rules, hooks, all`)
445
+ process.exit(1)
446
+ }
145
447
  }
146
- addCategory(args[0])
448
+ break
449
+ }
450
+ case 'remove': {
451
+ const type = positional[0]
452
+ const arg2 = positional[1]
453
+ if (!type) { console.error('Usage: claudient remove <skills|agents|rules|hooks>'); process.exit(1) }
454
+ removeCommand(type, arg2)
455
+ break
456
+ }
457
+ case 'update':
458
+ updateCommand()
147
459
  break
148
460
  case 'list':
149
- listSkills()
461
+ listCommand(positional[0])
150
462
  break
151
463
  case 'help':
152
464
  case '--help':
@@ -155,7 +467,7 @@ switch (command) {
155
467
  usage()
156
468
  break
157
469
  default:
158
- console.error(`Unknown command: ${command}`)
470
+ console.error(`Unknown command: "${command}"`)
159
471
  usage()
160
472
  process.exit(1)
161
473
  }