clawkit-ai 1.0.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.
- package/PRD.md +416 -0
- package/README.md +156 -0
- package/bin/clawkit.mjs +57 -0
- package/dashboard/app.js +287 -0
- package/dashboard/index.html +137 -0
- package/dashboard/style.css +121 -0
- package/package.json +14 -0
- package/skills/code-mentor/SKILL.md +85 -0
- package/skills/csv-pipeline/SKILL.md +82 -0
- package/skills/developer/SKILL.md +57 -0
- package/skills/image/SKILL.md +71 -0
- package/skills/json/SKILL.md +59 -0
- package/skills/productivity/SKILL.md +68 -0
- package/skills/quick-reminders/SKILL.md +70 -0
- package/skills/svg-draw/SKILL.md +75 -0
- package/skills/weather/SKILL.md +72 -0
- package/skills/workspace-manager/SKILL.md +69 -0
- package/src/cron.mjs +30 -0
- package/src/dashboard.mjs +69 -0
- package/src/doctor.mjs +69 -0
- package/src/init.mjs +137 -0
- package/src/scaffold.mjs +57 -0
- package/src/skills.mjs +52 -0
- package/src/status.mjs +43 -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/templates/AGENTS.md +77 -0
- package/templates/HEARTBEAT.md +11 -0
- package/templates/IDENTITY.md +7 -0
- package/templates/LEARNINGS.md +14 -0
- package/templates/MEMORY.md +14 -0
- package/templates/PROTOCOL_COST_EFFICIENCY.md +30 -0
- package/templates/SKILLS-INDEX.md +21 -0
- package/templates/SOUL.md +22 -0
- package/templates/TOOLS.md +14 -0
- package/templates/USER.md +5 -0
- package/templates/memory/knowledge/about-me.md +13 -0
- package/templates/scripts/nightly-consolidation.sh +54 -0
- package/templates/tasks/QUEUE.md +10 -0
|
@@ -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 ClawKit.');
|
|
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, () => {
|
|
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
|
|
56
|
+
try {
|
|
57
|
+
const platform = process.platform;
|
|
58
|
+
if (platform === 'darwin') execSync(`open ${url}`);
|
|
59
|
+
else if (platform === 'win32') execSync(`start ${url}`);
|
|
60
|
+
else execSync(`xdg-open ${url} 2>/dev/null || true`);
|
|
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 CLAWKIT_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: clawkit init');
|
|
24
|
+
|
|
25
|
+
// 4. Config
|
|
26
|
+
const cfgPath = join(workspace, 'skills', '.clawkit', '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 clawkit init'); }
|
|
30
|
+
} else {
|
|
31
|
+
fail('Config valid', 'Run: clawkit 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: clawkit init');
|
|
39
|
+
} else {
|
|
40
|
+
fail('Skills installed', 'Run: clawkit 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: clawkit 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: clawkit init');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 5. API key
|
|
58
|
+
process.env.CLAWKIT_API_KEY ? pass('API key set') : fail('API key set', 'Set CLAWKIT_API_KEY env var');
|
|
59
|
+
|
|
60
|
+
console.log(`\n${c.bold}${c.cyan}🩺 ClawKit 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,137 @@
|
|
|
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}🧰 ClawKit 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 && !yes) {
|
|
59
|
+
apiKey = await ask(rl, 'Enter your ClawKit API key (or press enter to skip):', '');
|
|
60
|
+
}
|
|
61
|
+
const tier = parseTier(apiKey);
|
|
62
|
+
if (apiKey) {
|
|
63
|
+
log.ok(`API key found (tier: ${tier})`);
|
|
64
|
+
} else {
|
|
65
|
+
log.warn('No API key — using free tier');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Step 4: Validate
|
|
69
|
+
log.step(4, 'Validating...');
|
|
70
|
+
const validation = await validateKey(apiKey);
|
|
71
|
+
if (!validation.valid && !validation.offline) {
|
|
72
|
+
log.err(`Validation failed: ${validation.error}`);
|
|
73
|
+
log.info('Continuing with free tier...');
|
|
74
|
+
} else if (validation.offline) {
|
|
75
|
+
log.warn('Offline mode — continuing with key prefix tier');
|
|
76
|
+
} else {
|
|
77
|
+
log.ok('API key validated');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const effectiveTier = validation.tier || tier;
|
|
81
|
+
|
|
82
|
+
// Step 5: Prompts
|
|
83
|
+
log.step(5, 'Configuration...');
|
|
84
|
+
const agentName = yes ? 'Atlas' : await ask(rl, 'What should your agent be called?', 'Atlas');
|
|
85
|
+
const userName = yes ? 'Human' : await ask(rl, "What's your name?", 'Human');
|
|
86
|
+
const timezone = yes ? detectTimezone() : await ask(rl, 'Your timezone?', detectTimezone());
|
|
87
|
+
|
|
88
|
+
rl?.close();
|
|
89
|
+
|
|
90
|
+
const vars = { agentName, userName, timezone, tier: effectiveTier, date: today() };
|
|
91
|
+
|
|
92
|
+
// Step 6: Scaffold
|
|
93
|
+
log.step(6, 'Scaffolding workspace...');
|
|
94
|
+
scaffoldWorkspace(workspace, vars);
|
|
95
|
+
log.ok('Template files written');
|
|
96
|
+
|
|
97
|
+
// Step 7: Skills
|
|
98
|
+
log.step(7, 'Installing skills...');
|
|
99
|
+
const skillCount = installSkills(workspace, effectiveTier);
|
|
100
|
+
|
|
101
|
+
// Step 8: Config
|
|
102
|
+
log.step(8, 'Writing config...');
|
|
103
|
+
const configDir = join(workspace, 'skills', '.clawkit');
|
|
104
|
+
mkdirSync(configDir, { recursive: true });
|
|
105
|
+
const config = {
|
|
106
|
+
version: '1.0.0',
|
|
107
|
+
tier: effectiveTier,
|
|
108
|
+
apiKey: apiKey || '',
|
|
109
|
+
installedAt: new Date().toISOString(),
|
|
110
|
+
agentName,
|
|
111
|
+
userName,
|
|
112
|
+
timezone,
|
|
113
|
+
features: validation.features || {},
|
|
114
|
+
};
|
|
115
|
+
writeFileSync(join(configDir, 'config.json'), JSON.stringify(config, null, 2), 'utf8');
|
|
116
|
+
log.ok('Config saved');
|
|
117
|
+
|
|
118
|
+
// Step 9: Success
|
|
119
|
+
console.log(`
|
|
120
|
+
${c.green}╔══════════════════════════════════════════════╗
|
|
121
|
+
║ 🧰 ClawKit initialized successfully! ║
|
|
122
|
+
╠══════════════════════════════════════════════╣
|
|
123
|
+
║ ║
|
|
124
|
+
║ Agent: ${(agentName + ' ').slice(0, 36)}║
|
|
125
|
+
║ Tier: ${(effectiveTier + ' ').slice(0, 37)}║
|
|
126
|
+
║ Skills: ${(skillCount + ' installed ').slice(0, 35)}║
|
|
127
|
+
║ Memory: 3-layer system active ║
|
|
128
|
+
║ ║
|
|
129
|
+
║ Next steps: ║
|
|
130
|
+
║ 1. openclaw gateway restart ║
|
|
131
|
+
║ 2. Start chatting — your agent is ready! ║
|
|
132
|
+
║ ║
|
|
133
|
+
║ Run clawkit status to check health ║
|
|
134
|
+
║ Run clawkit upgrade to go Pro ║
|
|
135
|
+
╚══════════════════════════════════════════════╝${c.reset}
|
|
136
|
+
`);
|
|
137
|
+
}
|
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, '.clawkit-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
|
+
}
|
package/src/status.mjs
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { c, log, detectWorkspace } from './utils.mjs';
|
|
4
|
+
|
|
5
|
+
export function runStatus() {
|
|
6
|
+
const workspace = detectWorkspace();
|
|
7
|
+
if (!workspace) {
|
|
8
|
+
log.err('No workspace detected. Set CLAWKIT_WORKSPACE or run from ~/.openclaw/workspace');
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const configPath = join(workspace, 'skills', '.clawkit', 'config.json');
|
|
13
|
+
let config = {};
|
|
14
|
+
if (existsSync(configPath)) {
|
|
15
|
+
try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch { /* ignore */ }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const hasAgents = existsSync(join(workspace, 'AGENTS.md'));
|
|
19
|
+
const hasMemory = existsSync(join(workspace, 'MEMORY.md'));
|
|
20
|
+
const hasCron = existsSync(join(workspace, 'scripts/nightly-consolidation.sh'));
|
|
21
|
+
|
|
22
|
+
let skillCount = 0;
|
|
23
|
+
const skillsDir = join(workspace, 'skills');
|
|
24
|
+
if (existsSync(skillsDir)) {
|
|
25
|
+
skillCount = readdirSync(skillsDir, { withFileTypes: true })
|
|
26
|
+
.filter(d => d.isDirectory() && d.name !== '.clawkit' && d.name !== '.clawhub')
|
|
27
|
+
.length;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const check = (ok) => ok ? `${c.green}✔${c.reset}` : `${c.red}✖${c.reset}`;
|
|
31
|
+
|
|
32
|
+
console.log(`\n${c.bold}${c.cyan}🧰 ClawKit Status${c.reset}\n`);
|
|
33
|
+
console.log(` Workspace: ${workspace}`);
|
|
34
|
+
console.log(` Agent: ${config.agentName || 'Unknown'}`);
|
|
35
|
+
console.log(` Tier: ${config.tier || 'free'}`);
|
|
36
|
+
console.log(` Version: ${config.version || 'unknown'}`);
|
|
37
|
+
console.log(` Installed: ${config.installedAt || 'unknown'}\n`);
|
|
38
|
+
console.log(` ${check(hasAgents)} AGENTS.md`);
|
|
39
|
+
console.log(` ${check(hasMemory)} Memory system`);
|
|
40
|
+
console.log(` ${check(hasCron)} Nightly consolidation`);
|
|
41
|
+
console.log(` ${check(skillCount > 0)} Skills (${skillCount} installed)`);
|
|
42
|
+
console.log();
|
|
43
|
+
}
|
package/src/update.mjs
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { readFileSync, existsSync, readdirSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { createHash } from 'crypto';
|
|
4
|
+
import https from 'https';
|
|
5
|
+
import { c, log, detectWorkspace } from './utils.mjs';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
|
|
8
|
+
function httpGet(url) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
https.get(url, (res) => {
|
|
11
|
+
let body = '';
|
|
12
|
+
res.on('data', d => body += d);
|
|
13
|
+
res.on('end', () => resolve(body));
|
|
14
|
+
}).on('error', reject).setTimeout(8000, function() { this.destroy(); reject(new Error('timeout')); });
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function fileHash(path) {
|
|
19
|
+
try {
|
|
20
|
+
return createHash('md5').update(readFileSync(path)).digest('hex');
|
|
21
|
+
} catch { return null; }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function runUpdate(flags) {
|
|
25
|
+
const pkgDir = join(fileURLToPath(new URL('.', import.meta.url)), '..');
|
|
26
|
+
let localPkg;
|
|
27
|
+
try { localPkg = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf8')); } catch {
|
|
28
|
+
log.err('Cannot read package.json');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
const currentVersion = localPkg.version;
|
|
32
|
+
|
|
33
|
+
console.log(`\n ${c.cyan}⚡ ClawKit Update Check${c.reset}\n`);
|
|
34
|
+
console.log(` Current: v${currentVersion}`);
|
|
35
|
+
|
|
36
|
+
// Check latest from npm
|
|
37
|
+
let latestVersion = null;
|
|
38
|
+
try {
|
|
39
|
+
const data = JSON.parse(await httpGet('https://registry.npmjs.org/clawkit/latest'));
|
|
40
|
+
latestVersion = data.version;
|
|
41
|
+
} catch {
|
|
42
|
+
log.warn('Could not check npm registry (offline?)');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (latestVersion) {
|
|
46
|
+
console.log(` Latest: v${latestVersion}`);
|
|
47
|
+
if (latestVersion !== currentVersion) {
|
|
48
|
+
console.log(`\n ${c.yellow}⚠${c.reset} Update available! Run:`);
|
|
49
|
+
console.log(` ${c.cyan}npm update -g clawkit${c.reset}\n`);
|
|
50
|
+
} else {
|
|
51
|
+
console.log(` ${c.green}✔${c.reset} You're on the latest version!\n`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Check workspace template files
|
|
56
|
+
const ws = detectWorkspace();
|
|
57
|
+
if (!ws) {
|
|
58
|
+
log.warn('No workspace detected. Skipping template check.');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log(` ${c.dim}Checking workspace templates...${c.reset}`);
|
|
63
|
+
|
|
64
|
+
// Template files that ship with clawkit init
|
|
65
|
+
const templateDir = join(pkgDir, 'templates');
|
|
66
|
+
const templateFiles = [
|
|
67
|
+
{ template: 'SOUL.md', dest: 'SOUL.md' },
|
|
68
|
+
{ template: 'AGENTS.md', dest: 'AGENTS.md' },
|
|
69
|
+
{ template: 'LEARNINGS.md', dest: 'LEARNINGS.md' },
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
for (const { template, dest } of templateFiles) {
|
|
73
|
+
const templatePath = join(templateDir, template);
|
|
74
|
+
const destPath = join(ws, dest);
|
|
75
|
+
|
|
76
|
+
if (!existsSync(templatePath)) continue;
|
|
77
|
+
if (!existsSync(destPath)) {
|
|
78
|
+
log.info(`${dest} — missing, would scaffold`);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const origHash = fileHash(templatePath);
|
|
83
|
+
const currentHash = fileHash(destPath);
|
|
84
|
+
|
|
85
|
+
if (origHash === currentHash) {
|
|
86
|
+
log.ok(`${dest} — unchanged, can update`);
|
|
87
|
+
} else {
|
|
88
|
+
console.log(` ${c.yellow}⏭${c.reset} Skipping ${dest} (customized)`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Check skills
|
|
93
|
+
const skillsDir = join(ws, 'skills');
|
|
94
|
+
if (existsSync(skillsDir)) {
|
|
95
|
+
const skills = readdirSync(skillsDir).filter(f => !f.startsWith('.'));
|
|
96
|
+
console.log(`\n ${c.dim}Skills installed: ${skills.length}${c.reset}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.log('');
|
|
100
|
+
}
|
package/src/upgrade.mjs
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
import { c, log, detectWorkspace } from './utils.mjs';
|
|
5
|
+
|
|
6
|
+
export async function runUpgrade(flags) {
|
|
7
|
+
const ws = detectWorkspace();
|
|
8
|
+
const configDir = ws ? join(ws, 'skills', '.clawkit') : null;
|
|
9
|
+
const configPath = configDir ? join(configDir, 'config.json') : null;
|
|
10
|
+
|
|
11
|
+
let config = {};
|
|
12
|
+
if (configPath && existsSync(configPath)) {
|
|
13
|
+
try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch {}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const currentKey = process.env.CLAWKIT_API_KEY || config.apiKey || '';
|
|
17
|
+
const isPro = currentKey.includes('_pro_');
|
|
18
|
+
|
|
19
|
+
// If --key provided, activate pro
|
|
20
|
+
if (flags.key) {
|
|
21
|
+
const key = flags.key;
|
|
22
|
+
if (!key.startsWith('clk_pro_')) {
|
|
23
|
+
log.err('Invalid Pro key. Keys start with clk_pro_');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Validate key (basic format check + API call)
|
|
28
|
+
log.info('Validating Pro key...');
|
|
29
|
+
let valid = false;
|
|
30
|
+
try {
|
|
31
|
+
// Try API validation
|
|
32
|
+
const https = await import('https');
|
|
33
|
+
valid = await new Promise((resolve) => {
|
|
34
|
+
const req = https.request('https://clawkit-api.vercel.app/api/validate-key?key=' + encodeURIComponent(key), (res) => {
|
|
35
|
+
let body = '';
|
|
36
|
+
res.on('data', d => body += d);
|
|
37
|
+
res.on('end', () => {
|
|
38
|
+
try { resolve(JSON.parse(body).valid === true); } catch { resolve(false); }
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
req.on('error', () => resolve(false));
|
|
42
|
+
req.setTimeout(5000, () => { req.destroy(); resolve(false); });
|
|
43
|
+
req.end();
|
|
44
|
+
});
|
|
45
|
+
} catch {
|
|
46
|
+
// Offline — accept format-valid keys
|
|
47
|
+
valid = key.startsWith('clk_pro_');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!valid) {
|
|
51
|
+
log.err('Key validation failed. Check your key and try again.');
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Update config
|
|
56
|
+
if (configDir) {
|
|
57
|
+
mkdirSync(configDir, { recursive: true });
|
|
58
|
+
config.apiKey = key;
|
|
59
|
+
config.tier = 'pro';
|
|
60
|
+
config.upgradedAt = new Date().toISOString();
|
|
61
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(`\n ${c.green}🎉 Upgraded to Pro!${c.reset}\n`);
|
|
65
|
+
console.log(` ${c.cyan}Key:${c.reset} ${key.slice(0, 12)}...`);
|
|
66
|
+
console.log(` ${c.cyan}Features unlocked:${c.reset} Dashboard chat, file editing, premium skills\n`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Already pro?
|
|
71
|
+
if (isPro) {
|
|
72
|
+
console.log(`\n ${c.green}✔${c.reset} You're already on ${c.yellow}Pro${c.reset}!\n`);
|
|
73
|
+
console.log(` Key: ${currentKey.slice(0, 12)}...`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Show upgrade pitch
|
|
78
|
+
console.log(`
|
|
79
|
+
${c.yellow}⚡ Upgrade to ClawKit Pro${c.reset}
|
|
80
|
+
|
|
81
|
+
${c.green}What you get:${c.reset}
|
|
82
|
+
✔ Full dashboard with live chat
|
|
83
|
+
✔ Task management & file editing
|
|
84
|
+
✔ Premium skill templates
|
|
85
|
+
✔ Priority support
|
|
86
|
+
✔ Advanced memory management
|
|
87
|
+
|
|
88
|
+
${c.cyan}Pricing:${c.reset} $10/month
|
|
89
|
+
|
|
90
|
+
${c.dim}Get your Pro key at:${c.reset}
|
|
91
|
+
${c.cyan}https://clawkit.net/get-started${c.reset}
|
|
92
|
+
|
|
93
|
+
Then run: ${c.bold}clawkit upgrade --key clk_pro_...${c.reset}
|
|
94
|
+
`);
|
|
95
|
+
|
|
96
|
+
// Open browser
|
|
97
|
+
try {
|
|
98
|
+
const url = 'https://clawkit.net/get-started';
|
|
99
|
+
const platform = process.platform;
|
|
100
|
+
if (platform === 'darwin') execSync(`open ${url}`);
|
|
101
|
+
else if (platform === 'win32') execSync(`start ${url}`);
|
|
102
|
+
else execSync(`xdg-open ${url} 2>/dev/null || true`);
|
|
103
|
+
} catch {}
|
|
104
|
+
}
|