clamper-ai 1.1.6

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 (44) hide show
  1. package/PRD.md +416 -0
  2. package/README.md +156 -0
  3. package/bin/clamper.mjs +66 -0
  4. package/dashboard/app.js +287 -0
  5. package/dashboard/index.html +137 -0
  6. package/dashboard/style.css +121 -0
  7. package/package.json +24 -0
  8. package/skills/clawkit-sync/SKILL.md +160 -0
  9. package/skills/code-mentor/SKILL.md +85 -0
  10. package/skills/csv-pipeline/SKILL.md +82 -0
  11. package/skills/developer/SKILL.md +57 -0
  12. package/skills/image/SKILL.md +71 -0
  13. package/skills/json/SKILL.md +59 -0
  14. package/skills/productivity/SKILL.md +68 -0
  15. package/skills/quick-reminders/SKILL.md +70 -0
  16. package/skills/svg-draw/SKILL.md +75 -0
  17. package/skills/weather/SKILL.md +72 -0
  18. package/skills/workspace-manager/SKILL.md +69 -0
  19. package/src/cron.mjs +30 -0
  20. package/src/dashboard.mjs +69 -0
  21. package/src/doctor.mjs +69 -0
  22. package/src/init.mjs +145 -0
  23. package/src/log-activity.mjs +26 -0
  24. package/src/scaffold.mjs +57 -0
  25. package/src/skills.mjs +52 -0
  26. package/src/status.mjs +43 -0
  27. package/src/sync.mjs +585 -0
  28. package/src/update.mjs +100 -0
  29. package/src/upgrade.mjs +104 -0
  30. package/src/utils.mjs +67 -0
  31. package/src/validate.mjs +66 -0
  32. package/templates/AGENTS.md +77 -0
  33. package/templates/HEARTBEAT.md +12 -0
  34. package/templates/IDENTITY.md +7 -0
  35. package/templates/LEARNINGS.md +14 -0
  36. package/templates/MEMORY.md +14 -0
  37. package/templates/PROTOCOL_COST_EFFICIENCY.md +30 -0
  38. package/templates/SKILLS-INDEX.md +21 -0
  39. package/templates/SOUL.md +22 -0
  40. package/templates/TOOLS.md +14 -0
  41. package/templates/USER.md +5 -0
  42. package/templates/memory/knowledge/about-me.md +13 -0
  43. package/templates/scripts/nightly-consolidation.sh +54 -0
  44. package/templates/tasks/QUEUE.md +10 -0
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: svg-draw
3
+ description: Create SVG images and convert them to PNG without external graphics libraries.
4
+ ---
5
+
6
+ # SVG Draw
7
+
8
+ Generate vector graphics using pure SVG code. No external libraries required.
9
+
10
+ ## Creating SVGs
11
+
12
+ ### Basic Structure
13
+ ```xml
14
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" width="400" height="400">
15
+ <rect width="400" height="400" fill="#1a1a2e"/> <!-- background -->
16
+ <!-- your content here -->
17
+ </svg>
18
+ ```
19
+
20
+ **Always include:** `xmlns`, `viewBox`, background `<rect>`, `width`/`height`.
21
+
22
+ ### Common Elements
23
+ ```xml
24
+ <!-- Shapes -->
25
+ <rect x="10" y="10" width="80" height="60" rx="10" fill="#3498db"/>
26
+ <circle cx="200" cy="200" r="50" fill="#e74c3c"/>
27
+ <ellipse cx="200" cy="200" rx="80" ry="50" fill="#2ecc71"/>
28
+ <polygon points="200,50 350,350 50,350" fill="#f39c12"/>
29
+
30
+ <!-- Paths (for complex shapes) -->
31
+ <path d="M10 80 C 40 10, 65 10, 95 80 S 150 150, 180 80" stroke="#333" fill="none" stroke-width="2"/>
32
+
33
+ <!-- Text -->
34
+ <text x="200" y="200" text-anchor="middle" font-family="Arial" font-size="24" fill="white">Hello</text>
35
+
36
+ <!-- Groups with transforms -->
37
+ <g transform="translate(100,100) rotate(45)">
38
+ <rect width="50" height="50" fill="blue"/>
39
+ </g>
40
+ ```
41
+
42
+ ### Gradients & Effects
43
+ ```xml
44
+ <defs>
45
+ <linearGradient id="sky" x1="0%" y1="0%" x2="0%" y2="100%">
46
+ <stop offset="0%" stop-color="#87CEEB"/>
47
+ <stop offset="100%" stop-color="#E0F7FA"/>
48
+ </linearGradient>
49
+ <filter id="shadow">
50
+ <feDropShadow dx="2" dy="2" stdDeviation="3" flood-opacity="0.3"/>
51
+ </filter>
52
+ </defs>
53
+ <rect width="400" height="400" fill="url(#sky)"/>
54
+ <circle cx="200" cy="200" r="50" fill="gold" filter="url(#shadow)"/>
55
+ ```
56
+
57
+ ## Converting to PNG
58
+
59
+ ```bash
60
+ # Using rsvg-convert (install: apt install librsvg2-bin)
61
+ rsvg-convert -w 800 -h 800 input.svg -o output.png
62
+
63
+ # Using ImageMagick
64
+ convert -density 150 input.svg output.png
65
+
66
+ # Using Inkscape CLI
67
+ inkscape input.svg --export-type=png --export-filename=output.png --export-width=800
68
+ ```
69
+
70
+ ## Tips
71
+ - Use `viewBox` for responsive scaling — set it once, resize via width/height
72
+ - Layer elements back to front (SVG paints in document order)
73
+ - Use `opacity` for transparency, `stroke-dasharray` for dashed lines
74
+ - Emoji work in `<text>` elements on most renderers
75
+ - Test complex SVGs in a browser before converting
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: weather
3
+ description: Get current weather and forecasts using wttr.in (no API key required).
4
+ ---
5
+
6
+ # Weather
7
+
8
+ Get weather information for any location using the free wttr.in service.
9
+
10
+ ## Quick Commands
11
+
12
+ ```bash
13
+ # Current weather for a city
14
+ curl -s "wttr.in/Toronto?format=3"
15
+ # Output: Toronto: ⛅️ +5°C
16
+
17
+ # Detailed forecast (3 days)
18
+ curl -s "wttr.in/Toronto"
19
+
20
+ # One-line format with custom fields
21
+ curl -s "wttr.in/Toronto?format=%l:+%c+%t+%h+%w"
22
+ # Output: Toronto: ⛅️ +5°C 65% →15km/h
23
+
24
+ # JSON format for parsing
25
+ curl -s "wttr.in/Toronto?format=j1"
26
+
27
+ # Specific day (0=today, 1=tomorrow, 2=day after)
28
+ curl -s "wttr.in/Toronto?1"
29
+ ```
30
+
31
+ ## Format Codes
32
+
33
+ | Code | Meaning |
34
+ |------|---------|
35
+ | `%c` | Weather condition icon |
36
+ | `%C` | Weather condition text |
37
+ | `%t` | Temperature |
38
+ | `%f` | Feels like |
39
+ | `%h` | Humidity |
40
+ | `%w` | Wind |
41
+ | `%p` | Precipitation (mm) |
42
+ | `%P` | Pressure |
43
+ | `%D` | Dawn |
44
+ | `%S` | Sunrise |
45
+ | `%s` | Sunset |
46
+ | `%l` | Location |
47
+
48
+ ## Usage Patterns
49
+
50
+ **Morning briefing:** `curl -s "wttr.in/{city}?format=%c+%t+feels+like+%f,+%C"`
51
+
52
+ **Should I bring an umbrella?** Check precipitation:
53
+ ```bash
54
+ curl -s "wttr.in/{city}?format=j1" | python3 -c "
55
+ import json,sys; d=json.load(sys.stdin)
56
+ rain=float(d['weather'][0]['hourly'][4].get('precipMM','0'))
57
+ print('☔ Yes, rain expected' if rain>0.5 else '☀️ No rain expected')
58
+ "
59
+ ```
60
+
61
+ **Multi-city comparison:**
62
+ ```bash
63
+ for city in Toronto NYC London; do
64
+ echo "$(curl -s "wttr.in/$city?format=3")"
65
+ done
66
+ ```
67
+
68
+ ## Notes
69
+ - No API key needed
70
+ - Rate limited — don't hit it more than a few times per minute
71
+ - Supports airports (ICAO codes), landmarks, IP-based geolocation
72
+ - Add `?lang=fr` for other languages
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: workspace-manager
3
+ description: Workspace setup and organization assistant.
4
+ ---
5
+
6
+ # Workspace Manager
7
+
8
+ Help users create and maintain well-organized workspaces.
9
+
10
+ ## Workspace Structure Templates
11
+
12
+ ### Engineering
13
+ ```
14
+ infrastructure/ # Cloud & infra docs
15
+ devops/ # CI/CD, pipelines
16
+ architecture/ # ADRs, system designs
17
+ security/ # Audits, compliance
18
+ team/ # Processes, hiring
19
+ daily-notes/ # Daily logs
20
+ ```
21
+
22
+ ### Business
23
+ ```
24
+ clients/ # Per-client folders
25
+ projects/ # Active projects
26
+ meetings/ # Notes & agendas
27
+ strategy/ # Plans, OKRs
28
+ templates/ # Reusable docs
29
+ archive/ # Completed work
30
+ ```
31
+
32
+ ### Personal Knowledge
33
+ ```
34
+ areas/ # Life areas (health, finance, career)
35
+ projects/ # Active projects
36
+ resources/ # Reference material
37
+ archive/ # Done projects
38
+ inbox/ # Unsorted notes
39
+ ```
40
+
41
+ ## File Naming Conventions
42
+ - Dates: `YYYY-MM-DD` prefix for time-sensitive files
43
+ - Projects: `project-name-description.md`
44
+ - No spaces: use hyphens (`my-document.md`)
45
+ - Lowercase everything
46
+
47
+ ## Organization Principles
48
+ 1. **Flat over deep** — Max 3 levels of nesting
49
+ 2. **Action-oriented** — Name folders by what you DO, not what things ARE
50
+ 3. **Archive aggressively** — Done projects → archive/
51
+ 4. **One inbox** — Single capture point, sort regularly
52
+ 5. **README in every folder** — What's this folder for? What goes here?
53
+
54
+ ## Maintenance Tasks
55
+ - Weekly: Process inbox, archive completed projects
56
+ - Monthly: Review folder structure, clean unused files
57
+ - Quarterly: Major reorganization if needed
58
+
59
+ ## Quick Commands
60
+ ```bash
61
+ # Find large files
62
+ find . -type f -size +10M | head -20
63
+
64
+ # Find recently modified
65
+ find . -type f -mtime -7 -name "*.md" | head -20
66
+
67
+ # Count files per directory
68
+ find . -maxdepth 2 -type f | cut -d/ -f2 | sort | uniq -c | sort -rn
69
+ ```
package/src/cron.mjs ADDED
@@ -0,0 +1,30 @@
1
+ import { execSync } from 'child_process';
2
+ import { log, cmdExists } from './utils.mjs';
3
+
4
+ export function registerCron(workspace) {
5
+ if (process.platform === 'win32') {
6
+ log.warn('Cron not available on Windows — set up nightly consolidation manually');
7
+ return false;
8
+ }
9
+
10
+ const scriptPath = `${workspace}/scripts/nightly-consolidation.sh`;
11
+ const cronLine = `0 2 * * * CLAMPER_WORKSPACE="${workspace}" bash "${scriptPath}" >> "${workspace}/scripts/consolidation.log" 2>&1`;
12
+
13
+ try {
14
+ let existing = '';
15
+ try { existing = execSync('crontab -l 2>/dev/null', { encoding: 'utf8' }); } catch { /* empty crontab */ }
16
+
17
+ if (existing.includes('nightly-consolidation.sh')) {
18
+ log.info('Nightly consolidation cron already registered');
19
+ return true;
20
+ }
21
+
22
+ const updated = existing.trimEnd() + '\n' + cronLine + '\n';
23
+ execSync(`echo "${updated.replace(/"/g, '\\"')}" | crontab -`, { encoding: 'utf8' });
24
+ log.ok('Registered nightly consolidation cron (2 AM daily)');
25
+ return true;
26
+ } catch (e) {
27
+ log.warn('Could not register cron job — set up nightly consolidation manually');
28
+ return false;
29
+ }
30
+ }
@@ -0,0 +1,69 @@
1
+ import { createServer } from 'http';
2
+ import { readFileSync, existsSync } from 'fs';
3
+ import { join, extname } from 'path';
4
+ import { execSync } from 'child_process';
5
+ import { fileURLToPath } from 'url';
6
+ import { c, log } from './utils.mjs';
7
+
8
+ const MIME = {
9
+ '.html': 'text/html',
10
+ '.css': 'text/css',
11
+ '.js': 'application/javascript',
12
+ '.json': 'application/json',
13
+ '.png': 'image/png',
14
+ '.svg': 'image/svg+xml',
15
+ };
16
+
17
+ export function runDashboard(flags) {
18
+ const port = parseInt(flags.port) || 8080;
19
+ const thisDir = fileURLToPath(new URL('.', import.meta.url));
20
+ const dashDir = join(thisDir, '..', 'dashboard');
21
+
22
+ if (!existsSync(join(dashDir, 'index.html'))) {
23
+ log.err('Dashboard files not found. Reinstall Clamper.');
24
+ process.exit(1);
25
+ }
26
+
27
+ const server = createServer((req, res) => {
28
+ let filePath = req.url === '/' ? '/index.html' : req.url;
29
+ filePath = filePath.split('?')[0]; // strip query
30
+ const full = join(dashDir, filePath);
31
+
32
+ if (!existsSync(full)) {
33
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
34
+ res.end('Not found');
35
+ return;
36
+ }
37
+
38
+ const ext = extname(full);
39
+ const mime = MIME[ext] || 'application/octet-stream';
40
+ try {
41
+ const data = readFileSync(full);
42
+ res.writeHead(200, { 'Content-Type': mime });
43
+ res.end(data);
44
+ } catch {
45
+ res.writeHead(500);
46
+ res.end('Error');
47
+ }
48
+ });
49
+
50
+ server.listen(port, '0.0.0.0', () => {
51
+ const url = `http://localhost:${port}`;
52
+ console.log(`\n ${c.green}⚡${c.reset} Dashboard running at ${c.cyan}${url}${c.reset}\n`);
53
+ console.log(` ${c.dim}Press Ctrl+C to stop${c.reset}\n`);
54
+
55
+ // Open browser (non-blocking — execSync blocks the event loop!)
56
+ try {
57
+ const { exec: execNB } = require !== undefined ? require('child_process') : {};
58
+ const platform = process.platform;
59
+ const cmd = platform === 'darwin' ? `open ${url}` : platform === 'win32' ? `start ${url}` : `xdg-open ${url} 2>/dev/null || true`;
60
+ import('child_process').then(cp => cp.exec(cmd));
61
+ } catch {}
62
+ });
63
+
64
+ process.on('SIGINT', () => {
65
+ console.log(`\n ${c.dim}Shutting down...${c.reset}`);
66
+ server.close();
67
+ process.exit(0);
68
+ });
69
+ }
package/src/doctor.mjs ADDED
@@ -0,0 +1,69 @@
1
+ import { existsSync, readFileSync, readdirSync, accessSync, constants } from 'fs';
2
+ import { join } from 'path';
3
+ import { c, log, detectWorkspace, cmdExists } from './utils.mjs';
4
+
5
+ export function runDoctor() {
6
+ const checks = [];
7
+ const pass = (name) => checks.push({ name, ok: true });
8
+ const fail = (name, hint) => checks.push({ name, ok: false, hint });
9
+
10
+ // 1. OpenClaw installed
11
+ cmdExists('openclaw') ? pass('OpenClaw CLI installed') : fail('OpenClaw CLI installed', 'Install: npm i -g openclaw');
12
+
13
+ // 2. Workspace
14
+ const workspace = detectWorkspace();
15
+ if (workspace && existsSync(workspace)) {
16
+ pass('Workspace directory exists');
17
+ } else {
18
+ fail('Workspace directory exists', 'Set CLAMPER_WORKSPACE env var');
19
+ }
20
+
21
+ if (workspace) {
22
+ // 3. AGENTS.md
23
+ existsSync(join(workspace, 'AGENTS.md')) ? pass('AGENTS.md present') : fail('AGENTS.md present', 'Run: clamper init');
24
+
25
+ // 4. Config
26
+ const cfgPath = join(workspace, 'skills', '.clamper', 'config.json');
27
+ if (existsSync(cfgPath)) {
28
+ try { JSON.parse(readFileSync(cfgPath, 'utf8')); pass('Config valid'); }
29
+ catch { fail('Config valid', 'config.json is corrupt — re-run clamper init'); }
30
+ } else {
31
+ fail('Config valid', 'Run: clamper init');
32
+ }
33
+
34
+ // 5. Skills
35
+ const sd = join(workspace, 'skills');
36
+ if (existsSync(sd)) {
37
+ const cnt = readdirSync(sd, { withFileTypes: true }).filter(d => d.isDirectory() && !d.name.startsWith('.')).length;
38
+ cnt > 0 ? pass(`Skills installed (${cnt})`) : fail('Skills installed', 'Run: clamper init');
39
+ } else {
40
+ fail('Skills installed', 'Run: clamper init');
41
+ }
42
+
43
+ // 6. Consolidation script
44
+ const cs = join(workspace, 'scripts/nightly-consolidation.sh');
45
+ if (existsSync(cs)) {
46
+ try { accessSync(cs, constants.X_OK); pass('Consolidation script executable'); }
47
+ catch { fail('Consolidation script executable', `Run: chmod +x ${cs}`); }
48
+ } else {
49
+ fail('Consolidation script present', 'Run: clamper init');
50
+ }
51
+
52
+ // 7. Memory dirs
53
+ existsSync(join(workspace, 'memory/daily')) && existsSync(join(workspace, 'memory/knowledge'))
54
+ ? pass('Memory directories') : fail('Memory directories', 'Run: clamper init');
55
+ }
56
+
57
+ // 5. API key
58
+ process.env.CLAMPER_API_KEY ? pass('API key set') : fail('API key set', 'Set CLAMPER_API_KEY env var');
59
+
60
+ console.log(`\n${c.bold}${c.cyan}🩺 Clamper Doctor${c.reset}\n`);
61
+ for (const ch of checks) {
62
+ const icon = ch.ok ? `${c.green}✔${c.reset}` : `${c.red}✖${c.reset}`;
63
+ console.log(` ${icon} ${ch.name}`);
64
+ if (!ch.ok && ch.hint) console.log(` ${c.dim}→ ${ch.hint}${c.reset}`);
65
+ }
66
+
67
+ const passed = checks.filter(c => c.ok).length;
68
+ console.log(`\n ${passed}/${checks.length} checks passed\n`);
69
+ }
package/src/init.mjs ADDED
@@ -0,0 +1,145 @@
1
+ import { createInterface } from 'readline';
2
+ import { existsSync, mkdirSync, writeFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { c, log, detectWorkspace, detectTimezone, today } from './utils.mjs';
5
+ import { readApiKey, parseTier, validateKey } from './validate.mjs';
6
+ import { scaffoldWorkspace, backupWorkspace } from './scaffold.mjs';
7
+ import { installSkills } from './skills.mjs';
8
+
9
+ function ask(rl, question, defaultVal) {
10
+ return new Promise((resolve) => {
11
+ rl.question(`${c.cyan}?${c.reset} ${question} ${c.dim}(${defaultVal})${c.reset} `, (answer) => {
12
+ resolve(answer.trim() || defaultVal);
13
+ });
14
+ });
15
+ }
16
+
17
+ export async function runInit(flags) {
18
+ const yes = flags.yes || flags.y;
19
+ const force = flags.force;
20
+
21
+ console.log(`\n${c.bold}${c.magenta}🧰 Clamper Init${c.reset}\n`);
22
+
23
+ // Step 1: Detect workspace
24
+ log.step(1, 'Detecting workspace...');
25
+ let workspace = detectWorkspace();
26
+
27
+ const rl = !yes ? createInterface({ input: process.stdin, output: process.stdout }) : null;
28
+
29
+ if (!workspace && !yes) {
30
+ workspace = await ask(rl, 'Workspace path:', join(process.env.HOME || '~', '.openclaw', 'workspace'));
31
+ }
32
+ if (!workspace) {
33
+ workspace = join(process.env.HOME || '/tmp', '.openclaw', 'workspace');
34
+ }
35
+ mkdirSync(workspace, { recursive: true });
36
+ log.ok(`Workspace: ${workspace}`);
37
+
38
+ // Step 2: Check existing
39
+ log.step(2, 'Checking existing files...');
40
+ if (existsSync(join(workspace, 'AGENTS.md'))) {
41
+ if (!yes && !force) {
42
+ const overwrite = await ask(rl, 'Existing workspace detected. Backup & overwrite? (y/n):', 'y');
43
+ if (overwrite.toLowerCase() !== 'y') {
44
+ log.info('Aborted.');
45
+ rl?.close();
46
+ return;
47
+ }
48
+ }
49
+ const bk = backupWorkspace(workspace);
50
+ log.ok(`Backed up to ${bk}`);
51
+ } else {
52
+ log.ok('Clean workspace');
53
+ }
54
+
55
+ // Step 3: Read API key
56
+ log.step(3, 'Reading API key...');
57
+ let apiKey = readApiKey();
58
+ if (!apiKey) {
59
+ console.log(`\n ${c.yellow}An API key is required to use Clamper.${c.reset}`);
60
+ console.log(` ${c.dim}Get your free key at: ${c.cyan}https://clamper.tech/get-started${c.reset}\n`);
61
+ if (!yes) {
62
+ apiKey = await ask(rl, 'Paste your Clamper API key:', '');
63
+ }
64
+ if (!apiKey) {
65
+ console.log(`\n ${c.yellow}No API key provided. Continuing with limited free tier.${c.reset}`);
66
+ console.log(` ${c.dim}Run ${c.cyan}clamper upgrade --key YOUR_KEY${c.reset}${c.dim} later to activate.${c.reset}\n`);
67
+ }
68
+ }
69
+ const tier = parseTier(apiKey);
70
+ if (apiKey) {
71
+ log.ok(`API key found (tier: ${tier})`);
72
+ } else {
73
+ log.warn('No API key — limited free tier');
74
+ }
75
+
76
+ // Step 4: Validate
77
+ log.step(4, 'Validating...');
78
+ const validation = await validateKey(apiKey);
79
+ if (!validation.valid && !validation.offline) {
80
+ log.err(`Validation failed: ${validation.error}`);
81
+ log.info('Continuing with free tier...');
82
+ } else if (validation.offline) {
83
+ log.warn('Offline mode — continuing with key prefix tier');
84
+ } else {
85
+ log.ok('API key validated');
86
+ }
87
+
88
+ const effectiveTier = validation.tier || tier;
89
+
90
+ // Step 5: Prompts
91
+ log.step(5, 'Configuration...');
92
+ const agentName = yes ? 'Atlas' : await ask(rl, 'What should your agent be called?', 'Atlas');
93
+ const userName = yes ? 'Human' : await ask(rl, "What's your name?", 'Human');
94
+ const timezone = yes ? detectTimezone() : await ask(rl, 'Your timezone?', detectTimezone());
95
+
96
+ rl?.close();
97
+
98
+ const vars = { agentName, userName, timezone, tier: effectiveTier, date: today() };
99
+
100
+ // Step 6: Scaffold
101
+ log.step(6, 'Scaffolding workspace...');
102
+ scaffoldWorkspace(workspace, vars);
103
+ log.ok('Template files written');
104
+
105
+ // Step 7: Skills
106
+ log.step(7, 'Installing skills...');
107
+ const skillCount = installSkills(workspace, effectiveTier);
108
+
109
+ // Step 8: Config
110
+ log.step(8, 'Writing config...');
111
+ const configDir = join(workspace, 'skills', '.clamper');
112
+ mkdirSync(configDir, { recursive: true });
113
+ const config = {
114
+ version: '1.0.0',
115
+ tier: effectiveTier,
116
+ apiKey: apiKey || '',
117
+ installedAt: new Date().toISOString(),
118
+ agentName,
119
+ userName,
120
+ timezone,
121
+ features: validation.features || {},
122
+ };
123
+ writeFileSync(join(configDir, 'config.json'), JSON.stringify(config, null, 2), 'utf8');
124
+ log.ok('Config saved');
125
+
126
+ // Step 9: Success
127
+ console.log(`
128
+ ${c.green}╔══════════════════════════════════════════════╗
129
+ ║ 🧰 Clamper initialized successfully! ║
130
+ ╠══════════════════════════════════════════════╣
131
+ ║ ║
132
+ ║ Agent: ${(agentName + ' ').slice(0, 36)}║
133
+ ║ Tier: ${(effectiveTier + ' ').slice(0, 37)}║
134
+ ║ Skills: ${(skillCount + ' installed ').slice(0, 35)}║
135
+ ║ Memory: 3-layer system active ║
136
+ ║ ║
137
+ ║ Next steps: ║
138
+ ║ 1. openclaw gateway restart ║
139
+ ║ 2. Start chatting — your agent is ready! ║
140
+ ║ ║
141
+ ║ Run clamper status to check health ║
142
+ ║ Run clamper upgrade to go Pro ║
143
+ ╚══════════════════════════════════════════════╝${c.reset}
144
+ `);
145
+ }
@@ -0,0 +1,26 @@
1
+ import { existsSync, appendFileSync, mkdirSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { detectWorkspace } from './utils.mjs';
4
+
5
+ export function logActivity(message) {
6
+ const workspace = detectWorkspace();
7
+ if (!workspace) return;
8
+
9
+ const logPath = join(workspace, 'logs', 'activity.jsonl');
10
+ const logDir = dirname(logPath);
11
+ if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });
12
+
13
+ const entry = JSON.stringify({ ts: new Date().toISOString(), msg: message }) + '\n';
14
+ appendFileSync(logPath, entry);
15
+ }
16
+
17
+ // CLI usage: clamper log "Did something"
18
+ export function runLog(flags) {
19
+ const msg = flags._ || process.argv.slice(3).join(' ');
20
+ if (!msg) {
21
+ console.log('Usage: clamper log "Your activity message"');
22
+ process.exit(1);
23
+ }
24
+ logActivity(msg);
25
+ console.log(` ✔ Logged: ${msg}`);
26
+ }
@@ -0,0 +1,57 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, cpSync, chmodSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ const PKG_ROOT = join(__dirname, '..');
7
+
8
+ export function replaceVars(text, vars) {
9
+ return text
10
+ .replace(/\{\{AGENT_NAME\}\}/g, vars.agentName || 'Atlas')
11
+ .replace(/\{\{USER_NAME\}\}/g, vars.userName || 'Human')
12
+ .replace(/\{\{TIMEZONE\}\}/g, vars.timezone || 'UTC')
13
+ .replace(/\{\{TIER\}\}/g, vars.tier || 'free')
14
+ .replace(/\{\{DATE\}\}/g, vars.date || new Date().toISOString().slice(0, 10));
15
+ }
16
+
17
+ const TEMPLATE_FILES = [
18
+ 'AGENTS.md', 'SOUL.md', 'IDENTITY.md', 'USER.md', 'TOOLS.md', 'HEARTBEAT.md',
19
+ 'MEMORY.md', 'LEARNINGS.md', 'PROTOCOL_COST_EFFICIENCY.md', 'SKILLS-INDEX.md',
20
+ 'memory/knowledge/about-me.md', 'tasks/QUEUE.md', 'scripts/nightly-consolidation.sh',
21
+ ];
22
+
23
+ export function scaffoldWorkspace(workspace, vars) {
24
+ for (const d of ['memory/daily', 'memory/knowledge', 'memory/para', 'tasks', 'scripts', 'skills']) {
25
+ mkdirSync(join(workspace, d), { recursive: true });
26
+ }
27
+
28
+ for (const f of TEMPLATE_FILES) {
29
+ const src = join(PKG_ROOT, 'templates', f);
30
+ const dest = join(workspace, f);
31
+ mkdirSync(dirname(dest), { recursive: true });
32
+ const content = readFileSync(src, 'utf8');
33
+ writeFileSync(dest, replaceVars(content, vars), 'utf8');
34
+ }
35
+
36
+ // Make consolidation script executable
37
+ const scriptPath = join(workspace, 'scripts/nightly-consolidation.sh');
38
+ if (existsSync(scriptPath)) {
39
+ chmodSync(scriptPath, 0o755);
40
+ }
41
+ }
42
+
43
+ export function backupWorkspace(workspace) {
44
+ const backupDir = join(workspace, '.clamper-backup');
45
+ const ts = new Date().toISOString().replace(/[:.]/g, '-');
46
+ const dest = join(backupDir, ts);
47
+ mkdirSync(dest, { recursive: true });
48
+ for (const f of TEMPLATE_FILES) {
49
+ const src = join(workspace, f);
50
+ if (existsSync(src)) {
51
+ const d = join(dest, f);
52
+ mkdirSync(dirname(d), { recursive: true });
53
+ cpSync(src, d);
54
+ }
55
+ }
56
+ return dest;
57
+ }
package/src/skills.mjs ADDED
@@ -0,0 +1,52 @@
1
+ import { readdirSync, readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { execSync } from 'child_process';
5
+ import { log, cmdExists } from './utils.mjs';
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const PKG_ROOT = join(__dirname, '..');
9
+ const BUNDLED_DIR = join(PKG_ROOT, 'skills');
10
+
11
+ const PRO_SKILLS = [
12
+ 'reminder', 'data-analyst', 'automation-workflows', 'sales',
13
+ 'security-monitor', 'nano-pdf', 'pdf-to-structured',
14
+ ];
15
+
16
+ export function installSkills(workspace, tier) {
17
+ const skillsDir = join(workspace, 'skills');
18
+ mkdirSync(skillsDir, { recursive: true });
19
+
20
+ // Install bundled free skills
21
+ let count = 0;
22
+ const entries = readdirSync(BUNDLED_DIR, { withFileTypes: true });
23
+ for (const entry of entries) {
24
+ if (!entry.isDirectory()) continue;
25
+ const srcSkill = join(BUNDLED_DIR, entry.name, 'SKILL.md');
26
+ if (!existsSync(srcSkill)) continue;
27
+ const destDir = join(skillsDir, entry.name);
28
+ mkdirSync(destDir, { recursive: true });
29
+ const content = readFileSync(srcSkill, 'utf8');
30
+ writeFileSync(join(destDir, 'SKILL.md'), content, 'utf8');
31
+ count++;
32
+ }
33
+ log.ok(`Installed ${count} free-tier skills`);
34
+
35
+ // Pro skills via clawhub
36
+ if (tier === 'pro') {
37
+ if (cmdExists('clawhub')) {
38
+ for (const slug of PRO_SKILLS) {
39
+ try {
40
+ execSync(`clawhub install ${slug} --force`, { cwd: workspace, stdio: 'ignore', timeout: 30000 });
41
+ log.ok(`Installed pro skill: ${slug}`);
42
+ } catch {
43
+ log.warn(`Could not install pro skill: ${slug}`);
44
+ }
45
+ }
46
+ } else {
47
+ log.warn('clawhub CLI not found — skipping pro skill installation. Run: npm i -g clawhub');
48
+ }
49
+ }
50
+
51
+ return count;
52
+ }