clamper-ai 1.5.0 → 1.5.2
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 +81 -42
- package/package.json +3 -2
- package/src/cron.mjs +30 -0
- package/src/dashboard.mjs +69 -0
- package/src/doctor.mjs +69 -0
- package/src/init.mjs +145 -0
- package/src/log-activity.mjs +26 -0
- package/src/scaffold.mjs +57 -0
- package/src/skills.mjs +52 -0
- package/src/status.mjs +43 -0
- package/src/sync.mjs +585 -0
- package/src/update.mjs +100 -0
- package/src/upgrade.mjs +104 -0
- package/src/utils.mjs +67 -0
- package/src/validate.mjs +66 -0
package/README.md
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
# ⚡ Clamper
|
|
2
2
|
|
|
3
3
|
```
|
|
4
|
-
_____ _
|
|
5
|
-
/ ____| |
|
|
6
|
-
| | | | __
|
|
7
|
-
| | | |/ _`
|
|
8
|
-
| |____| | (_|
|
|
9
|
-
\_____|_|\__,_|
|
|
10
|
-
|
|
4
|
+
_____ _
|
|
5
|
+
/ ____| |
|
|
6
|
+
| | | | __ _ _ __ ___ _ __ ___ _ __
|
|
7
|
+
| | | |/ _` | '_ ` _ \| '_ \ / _ \ '__|
|
|
8
|
+
| |____| | (_| | | | | | | |_) | __/ |
|
|
9
|
+
\_____|_|\__,_|_| |_| |_| .__/ \___|_|
|
|
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
|
+
[](https://www.npmjs.com/package/clamper-ai)
|
|
17
|
+
[](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** —
|
|
33
|
-
- **📋 Soul File** — SOUL.md defines your agent's personality and behavior
|
|
34
|
-
- **📚
|
|
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
|
-
-
|
|
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
|
-
|
|
|
49
|
-
|
|
|
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
|
-
|
|
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
|
|
121
|
-
├── SOUL.md
|
|
122
|
-
├── LEARNINGS.md
|
|
123
|
-
├── MEMORY.md
|
|
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/
|
|
126
|
-
│ └── knowledge/
|
|
142
|
+
│ ├── daily/ # Daily conversation logs
|
|
143
|
+
│ └── knowledge/ # Extracted facts & patterns
|
|
127
144
|
├── skills/
|
|
128
|
-
│
|
|
129
|
-
│ └── [skills...] # Installed skills
|
|
145
|
+
│ └── [50+ skills...] # Installed skill modules
|
|
130
146
|
└── tasks/
|
|
131
|
-
└── QUEUE.md
|
|
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
|
-
##
|
|
184
|
+
## 🔗 Links
|
|
145
185
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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.
|
|
3
|
+
"version": "1.5.2",
|
|
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,7 +22,8 @@
|
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"bin/",
|
|
25
|
+
"src/",
|
|
25
26
|
"skills/"
|
|
26
27
|
],
|
|
27
28
|
"dependencies": {}
|
|
28
|
-
}
|
|
29
|
+
}
|
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
|
+
}
|
package/src/scaffold.mjs
ADDED
|
@@ -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
|
+
}
|