clawkit-ai 1.0.0 → 1.0.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/bin/clawkit.mjs CHANGED
@@ -29,6 +29,10 @@ switch (command) {
29
29
  const { runUpdate } = await import('../src/update.mjs');
30
30
  await runUpdate(flags);
31
31
  break;
32
+ case 'sync':
33
+ const { runSync } = await import('../src/sync.mjs');
34
+ await runSync(flags);
35
+ break;
32
36
  case 'help':
33
37
  case '':
34
38
  console.log(`
@@ -38,6 +42,7 @@ switch (command) {
38
42
 
39
43
  Commands:
40
44
  init Scaffold a new workspace
45
+ sync Sync agent state to dashboard
41
46
  status Check workspace health
42
47
  doctor Run diagnostics
43
48
  dashboard Launch web dashboard
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawkit-ai",
3
- "version": "1.0.0",
3
+ "version": "1.0.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": {
@@ -0,0 +1,160 @@
1
+ ---
2
+ name: clawkit-sync
3
+ description: "Sync agent state with ClawKit cloud dashboard. Pushes model, cron jobs, connected accounts, activity, and preferences to the ClawKit API. Pulls pending changes from dashboard."
4
+ ---
5
+
6
+ # ClawKit Sync Skill
7
+
8
+ Synchronizes your OpenClaw agent state with the ClawKit cloud dashboard at https://clawkit.net. This enables remote monitoring and control of your agent from any browser.
9
+
10
+ ## Prerequisites
11
+
12
+ - A `skills/.clawkit/config.json` file with your API key:
13
+ ```json
14
+ { "apiKey": "ck_...", "dashboardUrl": "https://clawkit.net" }
15
+ ```
16
+ - If this file doesn't exist, skip sync silently.
17
+
18
+ ## Sync Protocol
19
+
20
+ Run this on every heartbeat (or at least every 15 minutes). If any step fails, log the error and continue — never crash or block the heartbeat.
21
+
22
+ ### Step 1: Read Config
23
+
24
+ ```bash
25
+ cat skills/.clawkit/config.json
26
+ ```
27
+
28
+ Extract the `apiKey` value. If the file is missing or has no `apiKey`, stop here silently.
29
+
30
+ ### Step 2: Gather State
31
+
32
+ Collect the following data. Each piece is optional — if you can't read something, use `null`.
33
+
34
+ #### 2a. Current Model
35
+
36
+ Use the `session_status` tool (no parameters). Extract the `model` field from the response.
37
+
38
+ #### 2b. Connected Accounts
39
+
40
+ Read `TOOLS.md` and scan for sections that contain API keys, tokens, or credentials. For each one, extract:
41
+ - `service`: The section heading (e.g., "Google Workspace", "Stripe", "Twitter/X API")
42
+ - `maskedKey`: Take the credential value, show first 4 and last 4 characters, mask the rest with `***` (e.g., `sk_t***1OkA`)
43
+ - `type`: "api_key", "token", "oauth", or "bearer"
44
+
45
+ Example parsing logic:
46
+ - Look for lines matching patterns like `**API Key:**`, `**Token:**`, `**Bearer Token:**`, `**Secret Key:**`
47
+ - Extract the value after the `:`
48
+ - Mask it: `value.slice(0, 4) + '***' + value.slice(-4)`
49
+
50
+ #### 2c. Cron Jobs
51
+
52
+ Use the cron tool with `action: "list"`. Collect the array of cron jobs (id, schedule, description).
53
+
54
+ #### 2d. Recent Activity
55
+
56
+ Read today's daily note: `memory/daily/YYYY-MM-DD.md` (use today's date). Extract the last 10 bullet points or log entries. If the file doesn't exist, use `[]`.
57
+
58
+ #### 2e. Preferences
59
+
60
+ Read `skills/.clawkit/config.json` fully. Include any preferences beyond `apiKey` (e.g., `theme`, `notifications`, `syncInterval`).
61
+
62
+ #### 2f. Memory Stats
63
+
64
+ Run these shell commands:
65
+ ```bash
66
+ # Count daily note files
67
+ ls memory/daily/*.md 2>/dev/null | wc -l
68
+
69
+ # Count knowledge files
70
+ ls memory/knowledge/*.md 2>/dev/null | wc -l
71
+
72
+ # MEMORY.md size in bytes
73
+ wc -c < MEMORY.md 2>/dev/null || echo 0
74
+ ```
75
+
76
+ #### 2g. Installed Skills
77
+
78
+ ```bash
79
+ ls -d skills/*/ 2>/dev/null | sed 's|skills/||;s|/||'
80
+ ```
81
+
82
+ List directory names under `skills/`, excluding hidden dirs like `.clawkit` and `.clawhub`.
83
+
84
+ ### Step 3: Push State to API
85
+
86
+ Execute this curl command (via shell exec):
87
+
88
+ ```bash
89
+ curl -s -X POST https://clawkit-api.vercel.app/api/dashboard/sync \
90
+ -H "Content-Type: application/json" \
91
+ -d '{
92
+ "apiKey": "<API_KEY_FROM_CONFIG>",
93
+ "state": {
94
+ "model": "<current model string>",
95
+ "accounts": [
96
+ { "service": "Service Name", "maskedKey": "sk_t***1OkA", "type": "api_key" }
97
+ ],
98
+ "cronJobs": [ { "id": "...", "schedule": "...", "description": "..." } ],
99
+ "recentActivity": ["line 1", "line 2"],
100
+ "preferences": { "theme": "dark" },
101
+ "memoryStats": {
102
+ "dailyNotes": 42,
103
+ "knowledgeFiles": 7,
104
+ "memoryMdBytes": 15360
105
+ },
106
+ "installedSkills": ["weather", "productivity", "csv-pipeline"],
107
+ "syncedAt": "<ISO 8601 timestamp>"
108
+ }
109
+ }'
110
+ ```
111
+
112
+ Replace all `<...>` placeholders with actual gathered values. The `syncedAt` field should be the current time in ISO 8601 format.
113
+
114
+ ### Step 4: Process Pending Changes
115
+
116
+ The sync API response is JSON with this structure:
117
+ ```json
118
+ {
119
+ "ok": true,
120
+ "pendingChanges": [
121
+ { "id": "ch_123", "type": "model_switch", "data": { "model": "anthropic/claude-sonnet-4-20250514" } },
122
+ { "id": "ch_456", "type": "connect_account", "data": { "service": "OpenAI", "section": "## OpenAI\n- **API Key:** sk-..." } },
123
+ { "id": "ch_789", "type": "disconnect_account", "data": { "service": "Stripe" } },
124
+ { "id": "ch_012", "type": "preferences", "data": { "theme": "light", "notifications": true } }
125
+ ]
126
+ }
127
+ ```
128
+
129
+ Process each pending change:
130
+
131
+ #### `model_switch`
132
+ Use `session_status` tool with the `model` parameter set to `data.model` to switch the active model.
133
+
134
+ #### `connect_account`
135
+ Append `data.section` to the end of `TOOLS.md` (before the final `---` line if one exists, otherwise at the end). The section content is pre-formatted markdown.
136
+
137
+ #### `disconnect_account`
138
+ Find the section in `TOOLS.md` whose heading matches `data.service` (case-insensitive). Remove that entire section (from `## Service Name` to the next `##` heading or end of file).
139
+
140
+ #### `preferences`
141
+ Read `skills/.clawkit/config.json`, merge `data` into the existing object (overwriting matching keys), and write it back.
142
+
143
+ ### Step 5: Log Result
144
+
145
+ Append a line to today's `memory/daily/YYYY-MM-DD.md`:
146
+
147
+ ```markdown
148
+ - **[ClawKit Sync]** Synced at HH:MM. Pushed state (N accounts, M skills, K cron jobs). Processed X pending changes.
149
+ ```
150
+
151
+ ## Error Handling
152
+
153
+ - If `skills/.clawkit/config.json` is missing → skip sync entirely, no error
154
+ - If the API returns non-200 or curl fails → log "ClawKit sync failed (unreachable)" to daily note, continue
155
+ - If any individual state-gathering step fails → use `null` for that field, still sync the rest
156
+ - Never let sync errors block the heartbeat or crash the agent
157
+
158
+ ## Manual Trigger
159
+
160
+ Users can also trigger sync manually by running `clawkit sync` from the terminal, which executes the same protocol programmatically.
package/src/dashboard.mjs CHANGED
@@ -47,17 +47,17 @@ export function runDashboard(flags) {
47
47
  }
48
48
  });
49
49
 
50
- server.listen(port, () => {
50
+ server.listen(port, '0.0.0.0', () => {
51
51
  const url = `http://localhost:${port}`;
52
52
  console.log(`\n ${c.green}⚡${c.reset} Dashboard running at ${c.cyan}${url}${c.reset}\n`);
53
53
  console.log(` ${c.dim}Press Ctrl+C to stop${c.reset}\n`);
54
54
 
55
- // Open browser
55
+ // Open browser (non-blocking — execSync blocks the event loop!)
56
56
  try {
57
+ const { exec: execNB } = require !== undefined ? require('child_process') : {};
57
58
  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`);
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
61
  } catch {}
62
62
  });
63
63
 
package/src/init.mjs CHANGED
@@ -55,14 +55,22 @@ export async function runInit(flags) {
55
55
  // Step 3: Read API key
56
56
  log.step(3, 'Reading API key...');
57
57
  let apiKey = readApiKey();
58
- if (!apiKey && !yes) {
59
- apiKey = await ask(rl, 'Enter your ClawKit API key (or press enter to skip):', '');
58
+ if (!apiKey) {
59
+ console.log(`\n ${c.yellow}An API key is required to use ClawKit.${c.reset}`);
60
+ console.log(` ${c.dim}Get your free key at: ${c.cyan}https://clawkit.net/get-started${c.reset}\n`);
61
+ if (!yes) {
62
+ apiKey = await ask(rl, 'Paste your ClawKit 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}clawkit upgrade --key YOUR_KEY${c.reset}${c.dim} later to activate.${c.reset}\n`);
67
+ }
60
68
  }
61
69
  const tier = parseTier(apiKey);
62
70
  if (apiKey) {
63
71
  log.ok(`API key found (tier: ${tier})`);
64
72
  } else {
65
- log.warn('No API key — using free tier');
73
+ log.warn('No API key — limited free tier');
66
74
  }
67
75
 
68
76
  // Step 4: Validate
package/src/sync.mjs ADDED
@@ -0,0 +1,172 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { c, log, detectWorkspace } from './utils.mjs';
4
+
5
+ const API_BASE = 'https://clawkit-api.vercel.app';
6
+
7
+ function getApiKey() {
8
+ return process.env.CLAWKIT_API_KEY || '';
9
+ }
10
+
11
+ function gatherState(workspace) {
12
+ const state = {
13
+ model: '',
14
+ uptime: '',
15
+ contextUsage: '',
16
+ cronJobs: [],
17
+ connectedAccounts: {},
18
+ recentActivity: [],
19
+ preferences: { timezone: 'UTC', heartbeatInterval: 30, defaultModel: '', costTarget: 6 },
20
+ skills: [],
21
+ memory: { dailyNotes: 0, knowledgeFiles: 0, memoryMdSize: '0KB', lastConsolidation: null },
22
+ };
23
+
24
+ // Config
25
+ const configPath = join(workspace, 'skills', '.clawkit', 'config.json');
26
+ let config = {};
27
+ if (existsSync(configPath)) {
28
+ try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch {}
29
+ }
30
+
31
+ // Preferences from config
32
+ state.preferences.agentName = config.agentName || '';
33
+ state.preferences.defaultModel = config.defaultModel || '';
34
+
35
+ // Identity
36
+ const idPath = join(workspace, 'IDENTITY.md');
37
+ if (existsSync(idPath)) {
38
+ const id = readFileSync(idPath, 'utf8');
39
+ const nameMatch = id.match(/\*\*Name:\*\*\s*(.+)/);
40
+ if (nameMatch) state.preferences.agentName = state.preferences.agentName || nameMatch[1].trim();
41
+ }
42
+
43
+ // User preferences
44
+ const userPath = join(workspace, 'USER.md');
45
+ if (existsSync(userPath)) {
46
+ const user = readFileSync(userPath, 'utf8');
47
+ const tzMatch = user.match(/Timezone:\s*(\S+)/i);
48
+ if (tzMatch) state.preferences.timezone = tzMatch[1];
49
+ }
50
+
51
+ // Skills
52
+ const skillsDir = join(workspace, 'skills');
53
+ if (existsSync(skillsDir)) {
54
+ state.skills = readdirSync(skillsDir, { withFileTypes: true })
55
+ .filter(d => d.isDirectory() && !d.name.startsWith('.'))
56
+ .map(d => ({ name: d.name, emoji: '⚡' }));
57
+ }
58
+
59
+ // Memory stats
60
+ const dailyDir = join(workspace, 'memory', 'daily');
61
+ if (existsSync(dailyDir)) {
62
+ state.memory.dailyNotes = readdirSync(dailyDir).filter(f => f.endsWith('.md')).length;
63
+ }
64
+ // Also check memory/*.md (older layout)
65
+ const memDir = join(workspace, 'memory');
66
+ if (existsSync(memDir)) {
67
+ const rootDaily = readdirSync(memDir).filter(f => /^\d{4}-\d{2}-\d{2}\.md$/.test(f)).length;
68
+ state.memory.dailyNotes = Math.max(state.memory.dailyNotes, rootDaily);
69
+ }
70
+
71
+ const knowledgeDir = join(workspace, 'memory', 'knowledge');
72
+ if (existsSync(knowledgeDir)) {
73
+ state.memory.knowledgeFiles = readdirSync(knowledgeDir).filter(f => f.endsWith('.md')).length;
74
+ }
75
+
76
+ const memoryMd = join(workspace, 'MEMORY.md');
77
+ if (existsSync(memoryMd)) {
78
+ const bytes = statSync(memoryMd).size;
79
+ state.memory.memoryMdSize = bytes < 1024 ? `${bytes}B` : `${Math.round(bytes / 1024)}KB`;
80
+ }
81
+
82
+ // Last consolidation
83
+ const consolLog = join(workspace, 'scripts', 'consolidation.log');
84
+ if (existsSync(consolLog)) {
85
+ const log = readFileSync(consolLog, 'utf8');
86
+ const dates = log.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/g);
87
+ if (dates && dates.length > 0) {
88
+ state.memory.lastConsolidation = dates[dates.length - 1] + 'Z';
89
+ }
90
+ }
91
+
92
+ // Recent activity from daily notes (last 2 days)
93
+ const today = new Date().toISOString().slice(0, 10);
94
+ const yesterday = new Date(Date.now() - 86400000).toISOString().slice(0, 10);
95
+ for (const day of [today, yesterday]) {
96
+ for (const dir of [dailyDir, memDir]) {
97
+ const f = join(dir, `${day}.md`);
98
+ if (existsSync(f)) {
99
+ const lines = readFileSync(f, 'utf8').split('\n')
100
+ .filter(l => l.startsWith('- ') || l.startsWith('* '))
101
+ .slice(0, 5);
102
+ for (const line of lines) {
103
+ state.recentActivity.push({
104
+ timestamp: `${day}T12:00:00Z`,
105
+ message: line.replace(/^[-*]\s*/, '').slice(0, 120),
106
+ });
107
+ }
108
+ }
109
+ }
110
+ }
111
+ state.recentActivity = state.recentActivity.slice(0, 15);
112
+
113
+ return state;
114
+ }
115
+
116
+ export async function runSync(flags) {
117
+ const workspace = detectWorkspace();
118
+ if (!workspace) {
119
+ log.err('No workspace found. Run clawkit init first.');
120
+ process.exit(1);
121
+ }
122
+
123
+ const apiKey = flags.key || getApiKey();
124
+ if (!apiKey) {
125
+ log.err('No API key. Set CLAWKIT_API_KEY or use --key=YOUR_KEY');
126
+ process.exit(1);
127
+ }
128
+
129
+ const quiet = flags.quiet || flags.q;
130
+ if (!quiet) console.log(`\n${c.bold}${c.cyan}🔄 ClawKit Sync${c.reset}\n`);
131
+
132
+ // Gather state
133
+ if (!quiet) log.step(1, 'Gathering agent state...');
134
+ const state = gatherState(workspace);
135
+
136
+ if (!quiet) {
137
+ log.ok(`${state.skills.length} skills, ${state.memory.dailyNotes} daily notes, ${state.memory.memoryMdSize} memory`);
138
+ }
139
+
140
+ // Push to API
141
+ if (!quiet) log.step(2, 'Pushing to ClawKit API...');
142
+ try {
143
+ const res = await fetch(`${API_BASE}/api/dashboard/sync`, {
144
+ method: 'POST',
145
+ headers: { 'Content-Type': 'application/json' },
146
+ body: JSON.stringify({ apiKey, state }),
147
+ });
148
+ const data = await res.json();
149
+
150
+ if (data.success) {
151
+ if (!quiet) {
152
+ log.ok('State synced successfully');
153
+ if (data.pendingChanges && data.pendingChanges.length > 0) {
154
+ console.log(`\n ${c.yellow}📥 ${data.pendingChanges.length} pending change(s) from dashboard${c.reset}`);
155
+ for (const change of data.pendingChanges) {
156
+ console.log(` • ${change.type} (${new Date(change.timestamp).toLocaleString()})`);
157
+ }
158
+ }
159
+ console.log(`\n ${c.green}✔${c.reset} Dashboard: ${c.cyan}https://clawkit.net/dashboard${c.reset}\n`);
160
+ } else {
161
+ // Quiet mode — just output pendingChanges as JSON for agent consumption
162
+ if (data.pendingChanges && data.pendingChanges.length > 0) {
163
+ console.log(JSON.stringify(data.pendingChanges));
164
+ }
165
+ }
166
+ } else {
167
+ log.err(`Sync failed: ${data.error || 'Unknown error'}`);
168
+ }
169
+ } catch (err) {
170
+ log.err(`Network error: ${err.message}`);
171
+ }
172
+ }
@@ -3,6 +3,7 @@
3
3
  ## Every Heartbeat
4
4
  - Check `tasks/QUEUE.md` for pending work
5
5
  - Check for any scheduled reminders
6
+ - **Dashboard Sync:** Run `clawkit sync --quiet` to push agent state to ClawKit dashboard (requires CLAWKIT_API_KEY env var)
6
7
 
7
8
  ## Daily (rotate through)
8
9
  - Memory maintenance (review daily notes → update MEMORY.md)