clamper-ai 1.5.1 → 1.5.3

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/README.md CHANGED
@@ -11,7 +11,10 @@
11
11
  AI Agent Toolkit for OpenClaw
12
12
  ```
13
13
 
14
- **Transform your OpenClaw agent into a production-ready AI assistant** with structured memory, curated skills, a real-time dashboard, and workspace scaffolding — in 3 commands.
14
+ **Transform your OpenClaw agent into a production-ready AI assistant** with structured memory, 50+ curated skills, a real-time dashboard, and workspace scaffolding — in 3 commands.
15
+
16
+ [![npm version](https://img.shields.io/npm/v/clamper-ai.svg)](https://www.npmjs.com/package/clamper-ai)
17
+ [![license](https://img.shields.io/npm/l/clamper-ai.svg)](https://github.com/nosselil/clamper)
15
18
 
16
19
  ---
17
20
 
@@ -29,31 +32,33 @@ That's it. Your workspace is ready.
29
32
 
30
33
  ## 📦 What You Get
31
34
 
32
- - **🧠 Structured Memory** — Daily notes, knowledge graph, long-term memory (MEMORY.md)
33
- - **📋 Soul File** — SOUL.md defines your agent's personality and behavior
34
- - **📚 Skill Templates** — Pre-built skills for common tasks
35
+ - **🧠 Structured Memory** — Three-layer system: daily notes, knowledge graph, and curated long-term memory (MEMORY.md)
36
+ - **📋 Soul File** — SOUL.md defines your agent's personality, tone, and behavior
37
+ - **📚 50+ Curated Skills** — Pre-built skills covering productivity, development, marketing, data analysis, social media, email, and more
35
38
  - **📊 Web Dashboard** — Real-time agent monitoring with dark Bloomberg-terminal UI
36
39
  - **💬 Live Chat** — Talk to your agent from the dashboard (Pro)
37
- - **✅ Task Manager** — Read/write tasks/QUEUE.md from the browser
40
+ - **✅ Task Manager** — Read/write tasks/QUEUE.md from the browser (Pro)
41
+ - **🔄 Sync** — Push agent state (memory, skills, metrics) to the cloud dashboard
38
42
  - **🔧 Doctor** — Diagnostics to verify your setup is healthy
39
- - **🔄 Auto-Updates** — Check for new versions and template updates
43
+ - **📝 Activity Logging** — Track what your agent does with `clamper log`
40
44
 
41
45
  ---
42
46
 
43
47
  ## 🆓 Free vs Pro
44
48
 
45
- | Feature | Free | Pro |
46
- |---------|------|-----|
49
+ | Feature | Free | Pro ($10/mo) |
50
+ |---------|------|------------|
47
51
  | Workspace scaffolding | ✅ | ✅ |
48
- | Memory structure | ✅ | ✅ |
49
- | Skill templates | Basic | Premium |
52
+ | Three-layer memory system | ✅ | ✅ |
53
+ | 40+ curated skills | | |
50
54
  | Dashboard (read-only) | ✅ | ✅ |
51
- | Dashboard chat | ❌ | ✅ |
52
- | Task editing | ❌ | ✅ |
55
+ | Dashboard live chat | ❌ | ✅ |
56
+ | Task editing from dashboard | ❌ | ✅ |
53
57
  | File management | ❌ | ✅ |
58
+ | Premium skills (10+) | ❌ | ✅ |
54
59
  | Priority support | ❌ | ✅ |
55
60
 
56
- **$10/month** → [Get Pro](https://clamper.tech/get-started)
61
+ → [Get Pro](https://clamper.tech/get-started)
57
62
 
58
63
  ---
59
64
 
@@ -75,6 +80,23 @@ Check workspace health — memory files, skills, config.
75
80
  clamper status
76
81
  ```
77
82
 
83
+ ### `clamper sync`
84
+ Sync agent state to the Clamper cloud dashboard. Requires `CLAMPER_API_KEY`.
85
+
86
+ ```bash
87
+ export CLAMPER_API_KEY="clk_..."
88
+ clamper sync # Push current state
89
+ clamper sync --quiet # Silent mode (for cron/heartbeat)
90
+ ```
91
+
92
+ ### `clamper log`
93
+ Log agent activity for tracking and analytics.
94
+
95
+ ```bash
96
+ clamper log "Deployed website update"
97
+ clamper log "Fixed auth bug" --tag=bugfix
98
+ ```
99
+
78
100
  ### `clamper doctor`
79
101
  Run full diagnostics — Node.js version, OpenClaw detection, file permissions.
80
102
 
@@ -83,7 +105,7 @@ clamper doctor
83
105
  ```
84
106
 
85
107
  ### `clamper dashboard`
86
- Launch the web dashboard in your browser.
108
+ Launch the web dashboard locally in your browser.
87
109
 
88
110
  ```bash
89
111
  clamper dashboard # Default port 8080
@@ -107,32 +129,50 @@ clamper update
107
129
 
108
130
  ---
109
131
 
110
- ## 📸 Screenshots
111
-
112
- > _Dashboard screenshots coming soon_
113
-
114
- ---
115
-
116
132
  ## 🏗 Architecture
117
133
 
118
134
  ```
119
135
  your-workspace/
120
- ├── AGENTS.md # Agent behavior config
121
- ├── SOUL.md # Personality definition
122
- ├── LEARNINGS.md # Rules & patterns
123
- ├── MEMORY.md # Long-term curated memory
136
+ ├── AGENTS.md # Agent behavior & boot protocol
137
+ ├── SOUL.md # Personality & tone definition
138
+ ├── LEARNINGS.md # Rules, patterns & mistake log
139
+ ├── MEMORY.md # Long-term curated memory
140
+ ├── HEARTBEAT.md # Proactive background task config
124
141
  ├── memory/
125
- │ ├── daily/ # Daily conversation logs
126
- │ └── knowledge/ # Extracted facts & patterns
142
+ │ ├── daily/ # Daily conversation logs
143
+ │ └── knowledge/ # Extracted facts & patterns
127
144
  ├── skills/
128
- ├── .clamper/ # Clamper config
129
- │ └── [skills...] # Installed skills
145
+ └── [50+ skills...] # Installed skill modules
130
146
  └── tasks/
131
- └── QUEUE.md # Task queue
147
+ └── QUEUE.md # Task queue (tracks in-progress & done)
132
148
  ```
133
149
 
134
150
  ---
135
151
 
152
+ ## 🔑 Environment Variables
153
+
154
+ | Variable | Required | Description |
155
+ |----------|----------|-------------|
156
+ | `CLAMPER_API_KEY` | For sync/dashboard | Your API key from [clamper.tech/dashboard](https://clamper.tech/dashboard) |
157
+
158
+ ---
159
+
160
+ ## 🧩 Included Skills (50+)
161
+
162
+ Skills cover a wide range of use cases out of the box:
163
+
164
+ **Productivity** — CEO Advisor, ADHD Assistant, Daily Briefing, Ask a Human
165
+ **Development** — Developer, Code Mentor, CSV Pipeline, Comfy AI
166
+ **Marketing** — Social media posting, Bluesky, SEO, content creation
167
+ **Data** — Data analysis, PDF extraction, Google Sheets integration
168
+ **Creative** — Image generation, video toolkit, SVG drawing, assets from video
169
+ **Utilities** — Catbox upload, weather, system monitoring, email (IMAP/SMTP)
170
+ **Fun** — Tarot readings, D&D 5e, Digimon, 4chan reader
171
+
172
+ Browse all skills after install with `clamper status`.
173
+
174
+ ---
175
+
136
176
  ## 🔧 Requirements
137
177
 
138
178
  - **Node.js** ≥ 18
@@ -141,13 +181,12 @@ your-workspace/
141
181
 
142
182
  ---
143
183
 
144
- ## 🤝 Contributing
184
+ ## 🔗 Links
145
185
 
146
- 1. Fork the repo
147
- 2. Create a feature branch
148
- 3. Make your changes (ESM only, no dependencies)
149
- 4. Test with `clamper doctor`
150
- 5. Submit a PR
186
+ - **Website:** [clamper.tech](https://clamper.tech)
187
+ - **Dashboard:** [clamper.tech/dashboard](https://clamper.tech/dashboard)
188
+ - **OpenClaw:** [openclaw.ai](https://docs.openclaw.ai)
189
+ - **Discord:** [OpenClaw Community](https://discord.com/invite/clawd)
151
190
 
152
191
  ---
153
192
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clamper-ai",
3
- "version": "1.5.1",
3
+ "version": "1.5.3",
4
4
  "description": "Transform your OpenClaw agent into a production-ready AI assistant with memory, skills, and a dashboard",
5
5
  "type": "module",
6
6
  "bin": {
@@ -22,6 +22,7 @@
22
22
  },
23
23
  "files": [
24
24
  "bin/",
25
+ "src/",
25
26
  "skills/"
26
27
  ],
27
28
  "dependencies": {}
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
+ }