clawkit-ai 1.0.3 → 1.0.5

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/sync.mjs +141 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawkit-ai",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
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": {
package/src/sync.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from 'fs';
2
2
  import { join } from 'path';
3
+ import { execSync } from 'child_process';
3
4
  import { c, log, detectWorkspace } from './utils.mjs';
4
5
 
5
6
  const API_BASE = 'https://clawkit-api.vercel.app';
@@ -11,16 +12,22 @@ function getApiKey() {
11
12
  // --- Connected Accounts: parse from TOOLS.md ---
12
13
 
13
14
  const SERVICE_PATTERNS = [
14
- { key: 'google_sheets', names: ['Google Sheets'], patterns: [/Google Sheets.*Connection.*?[:`]\s*`?([^\s`]+)/i, /Sheets.*ID.*?[:`]\s*`?([^\s`]+)/i] },
15
- { key: 'google_docs', names: ['Google Docs'], patterns: [/Google Docs.*Connection.*?[:`]\s*`?([^\s`]+)/i, /Docs.*ID.*?[:`]\s*`?([^\s`]+)/i] },
16
- { key: 'twitter', names: ['Twitter', 'X', '@'], patterns: [/(?:Twitter|X).*(?:Consumer|API)\s*Key.*?[:`]\s*`?([^\s`]+)/i, /(?:Twitter|X).*Bearer.*?[:`]\s*`?([^\s`]+)/i] },
17
- { key: 'stripe', names: ['Stripe'], patterns: [/Stripe.*Secret.*?[:`]\s*`?(sk_[^\s`]+)/i, /Stripe.*Publishable.*?[:`]\s*`?(pk_[^\s`]+)/i] },
18
- { key: 'openai', names: ['OpenAI', 'Sora'], patterns: [/(?:OpenAI|Sora).*(?:Key|API).*?[:`]\s*`?(sk-[^\s`]+)/i] },
19
- { key: 'anthropic', names: ['Anthropic', 'Claude'], patterns: [/Anthropic.*(?:Key|API).*?[:`]\s*`?(sk-ant-[^\s`]+)/i] },
20
- { key: 'brave_search', names: ['Brave'], patterns: [/Brave.*(?:Key|API).*?[:`]\s*`?([^\s`]+)/i] },
15
+ { key: 'google_sheets', names: ['Google Sheets'], patterns: [/Google Sheets.*?(?:Connection|ID).*?[:`]\s*`?([0-9a-f-]{20,})/i] },
16
+ { key: 'google_docs', names: ['Google Docs'], patterns: [/Google Docs.*?(?:Connection|ID).*?[:`]\s*`?([0-9a-f-]{20,})/i] },
17
+ { key: 'twitter', names: ['Twitter', 'X API', '@Clawkit'], patterns: [/(?:Consumer|API)\s*Key.*?[:`]\s*`?([A-Za-z0-9]{20,})/i, /Bearer.*?[:`]\s*`?(AAAA[^\s`]+)/i] },
18
+ { key: 'stripe', names: ['Stripe'], patterns: [/(?:Secret|Publishable).*?[:`]\s*`?([sp]k_[^\s`]+)/i] },
19
+ { key: 'openai', names: ['OpenAI', 'Sora API'], patterns: [/(?:Key|API).*?[:`]\s*`?(sk-proj-[^\s`]+)/i, /(?:Key|API).*?[:`]\s*`?(sk-[^\s`]+)/i] },
20
+ { key: 'anthropic', names: ['Anthropic', 'Claude API'], patterns: [/(?:Key|API).*?[:`]\s*`?(sk-ant-[^\s`]+)/i] },
21
+ { key: 'brave_search', names: ['Brave Search', 'Brave API'], patterns: [/Brave.*(?:Key|API).*?[:`]\s*`?([^\s`]+)/i] },
21
22
  { key: 'telegram', names: ['Telegram'], patterns: [/Telegram.*(?:Token|Bot).*?[:`]\s*`?(\d+:[^\s`]+)/i] },
22
23
  { key: 'discord', names: ['Discord'], patterns: [/Discord.*(?:Token|Bot).*?[:`]\s*`?([^\s`]+)/i] },
23
- { key: 'github', names: ['GitHub'], patterns: [/GitHub.*(?:Token|PAT).*?[:`]\s*`?(gh[pso]_[^\s`]+)/i] },
24
+ { key: 'github', names: ['GitHub'], patterns: [/GitHub.*(?:Token|PAT|User).*?[:`]\s*`?([^\s`]+)/i] },
25
+ { key: 'email', names: ['Email', 'IMAP', 'SMTP'], patterns: [/(?:Email|IMAP|SMTP).*?(?:address|account)?.*?[:`]\s*`?([^\s`]*@[^\s`]+)/i, /App Password.*?[:`]\s*`?([a-z ]{16,})/i] },
26
+ { key: 'sogni', names: ['Sogni'], patterns: [/SOGNI_USERNAME.*?[="`]\s*`?([^\s`"]+)/i] },
27
+ { key: 'upload_post', names: ['Upload-Post', 'upload-post'], patterns: [/upload-post.*?(?:Key|API).*?[:`]\s*`?(ey[^\s`]+)/i, /Authorization.*?Apikey\s+([^\s`]+)/i] },
28
+ { key: 'vercel', names: ['Vercel'], patterns: [/Vercel.*?(?:Token).*?[:`]\s*`?(vcp_[^\s`]+)/i] },
29
+ { key: 'netlify', names: ['Netlify'], patterns: [/Netlify.*?(?:Site|ID).*?[:`]\s*`?([0-9a-f-]{20,})/i] },
30
+ { key: 'maton', names: ['Maton', 'Google Workspace'], patterns: [/MATON_API_KEY.*?[:`]\s*`?([A-Za-z0-9_-]{30,})/i] },
24
31
  ];
25
32
 
26
33
  function parseAccountsFromTools(workspace) {
@@ -232,7 +239,75 @@ function gatherState(workspace) {
232
239
  }
233
240
  }
234
241
 
235
- // Recent activity from daily notes
242
+ // --- Cron Jobs (from OpenClaw gateway API) ---
243
+ try {
244
+ // Try the local gateway WebSocket API via HTTP
245
+ const cronOut = execSync('curl -s -m 3 http://127.0.0.1:18789/api/cron/list 2>/dev/null || true', { timeout: 5000, encoding: 'utf8' });
246
+ const cronMatch = cronOut.match(/\{[\s\S]*\}/);
247
+ if (cronMatch) {
248
+ const cronData = JSON.parse(cronMatch[0]);
249
+ const jobs = cronData.jobs || [];
250
+ state.cronJobs = jobs.map(j => ({
251
+ name: j.name || (j.payload?.text || '').slice(0, 40) || 'Unnamed',
252
+ schedule: j.schedule?.kind === 'cron' ? j.schedule.expr :
253
+ j.schedule?.kind === 'every' ? `every ${Math.round((j.schedule.everyMs || 0) / 60000)}m` :
254
+ j.schedule?.kind === 'at' ? `at ${j.schedule.at}` : 'unknown',
255
+ lastRun: j.state?.lastRunAtMs ? new Date(j.state.lastRunAtMs).toISOString() : undefined,
256
+ status: j.state?.lastStatus || (j.enabled ? 'ok' : 'disabled'),
257
+ }));
258
+ }
259
+ } catch {}
260
+
261
+ // Fallback: parse cron info from HEARTBEAT.md
262
+ if (state.cronJobs.length === 0) {
263
+ const hbPath = join(workspace, 'HEARTBEAT.md');
264
+ if (existsSync(hbPath)) {
265
+ const hb = readFileSync(hbPath, 'utf8');
266
+ const tasks = hb.match(/\*\*(?:Task|Check):\*\*\s*(.+)/g) || [];
267
+ for (const t of tasks.slice(0, 8)) {
268
+ state.cronJobs.push({
269
+ name: t.replace(/\*\*(?:Task|Check):\*\*\s*/, '').slice(0, 50),
270
+ schedule: 'heartbeat',
271
+ status: 'ok',
272
+ });
273
+ }
274
+ }
275
+ }
276
+
277
+ // --- Recent Activity (real sources: git log, file changes, daily notes) ---
278
+
279
+ // Git log (last 10 commits from any repo in Desktop)
280
+ try {
281
+ const gitDirs = ['clawkit', 'clawkit-api', 'clawkit-package', 'dashboard'].map(d => join('/home', 'semo', 'Desktop', d));
282
+ for (const dir of gitDirs) {
283
+ if (!existsSync(join(dir, '.git'))) continue;
284
+ try {
285
+ const gitLog = execSync(`cd "${dir}" && git log --oneline --format="%ai|%s" -5 2>/dev/null`, { timeout: 3000, encoding: 'utf8' });
286
+ const repoName = dir.split('/').pop();
287
+ for (const line of gitLog.trim().split('\n').filter(Boolean)) {
288
+ const [date, ...msgParts] = line.split('|');
289
+ state.recentActivity.push({
290
+ timestamp: new Date(date.trim()).toISOString(),
291
+ message: `[${repoName}] ${msgParts.join('|').slice(0, 100)}`,
292
+ });
293
+ }
294
+ } catch {}
295
+ }
296
+ } catch {}
297
+
298
+ // Recent workspace file changes (last 24h)
299
+ try {
300
+ const recentFiles = execSync(`find "${workspace}" -name "*.md" -mmin -1440 -not -path "*/node_modules/*" -not -path "*/.git/*" -printf "%T@|%f\n" 2>/dev/null | sort -rn | head -5`, { timeout: 3000, encoding: 'utf8' });
301
+ for (const line of recentFiles.trim().split('\n').filter(Boolean)) {
302
+ const [ts, file] = line.split('|');
303
+ state.recentActivity.push({
304
+ timestamp: new Date(parseFloat(ts) * 1000).toISOString(),
305
+ message: `Updated ${file}`,
306
+ });
307
+ }
308
+ } catch {}
309
+
310
+ // Daily notes entries
236
311
  const today = new Date().toISOString().slice(0, 10);
237
312
  const yesterday = new Date(Date.now() - 86400000).toISOString().slice(0, 10);
238
313
  for (const day of [today, yesterday]) {
@@ -242,7 +317,7 @@ function gatherState(workspace) {
242
317
  if (existsSync(f)) {
243
318
  const lines = readFileSync(f, 'utf8').split('\n')
244
319
  .filter(l => l.startsWith('- ') || l.startsWith('* '))
245
- .slice(0, 5);
320
+ .slice(0, 3);
246
321
  for (const line of lines) {
247
322
  state.recentActivity.push({
248
323
  timestamp: `${day}T12:00:00Z`,
@@ -252,7 +327,62 @@ function gatherState(workspace) {
252
327
  }
253
328
  }
254
329
  }
255
- state.recentActivity = state.recentActivity.slice(0, 15);
330
+
331
+ // Sort by timestamp descending, dedupe, limit
332
+ state.recentActivity.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
333
+ state.recentActivity = state.recentActivity.slice(0, 20);
334
+
335
+ // --- Token Usage & Cost ---
336
+ state.usage = { inputTokens: 0, outputTokens: 0, contextUsed: 0, contextMax: 0, costEstimate: '', model: '', compactions: 0 };
337
+
338
+ // Try to read from OpenClaw gateway config for model
339
+ try {
340
+ const gwConfig = join(process.env.HOME || '', '.openclaw', 'config.yaml');
341
+ if (existsSync(gwConfig)) {
342
+ const cfg = readFileSync(gwConfig, 'utf8');
343
+ const modelMatch = cfg.match(/model:\s*["']?([^\s"']+)/);
344
+ if (modelMatch) state.usage.model = modelMatch[1];
345
+ }
346
+ } catch {}
347
+
348
+ // Try to get session stats from gateway HTTP API
349
+ try {
350
+ const statusOut = execSync('curl -s -m 3 http://127.0.0.1:18789/api/session/status 2>/dev/null || true', { timeout: 5000, encoding: 'utf8' });
351
+ const statusMatch = statusOut.match(/\{[\s\S]*\}/);
352
+ if (statusMatch) {
353
+ const sd = JSON.parse(statusMatch[0]);
354
+ if (sd.inputTokens || sd.tokensIn) state.usage.inputTokens = sd.inputTokens || sd.tokensIn || 0;
355
+ if (sd.outputTokens || sd.tokensOut) state.usage.outputTokens = sd.outputTokens || sd.tokensOut || 0;
356
+ if (sd.contextUsed) state.usage.contextUsed = sd.contextUsed;
357
+ if (sd.contextMax) state.usage.contextMax = sd.contextMax;
358
+ if (sd.compactions) state.usage.compactions = sd.compactions;
359
+ if (sd.model) state.usage.model = sd.model;
360
+ }
361
+ } catch {}
362
+
363
+ // If we couldn't get from CLI, estimate from daily note sizes
364
+ if (!state.usage.model) {
365
+ state.usage.model = state.preferences.defaultModel || 'unknown';
366
+ }
367
+
368
+ // Cost estimation based on model
369
+ const MODEL_COSTS = {
370
+ 'claude-opus-4-6': { input: 15, output: 75 }, // per 1M tokens
371
+ 'claude-opus-4': { input: 15, output: 75 },
372
+ 'claude-sonnet-4-5': { input: 3, output: 15 },
373
+ 'claude-sonnet-4': { input: 3, output: 15 },
374
+ 'claude-haiku-3-5': { input: 0.8, output: 4 },
375
+ 'gpt-4o': { input: 2.5, output: 10 },
376
+ 'gpt-4o-mini': { input: 0.15, output: 0.6 },
377
+ 'gemini-2.0-flash': { input: 0.1, output: 0.4 },
378
+ };
379
+ const modelKey = Object.keys(MODEL_COSTS).find(k => state.usage.model?.includes(k));
380
+ const costs = MODEL_COSTS[modelKey] || { input: 3, output: 15 };
381
+ const inCost = (state.usage.inputTokens / 1_000_000) * costs.input;
382
+ const outCost = (state.usage.outputTokens / 1_000_000) * costs.output;
383
+ state.usage.costEstimate = `$${(inCost + outCost).toFixed(4)}`;
384
+ state.usage.costPerMInput = costs.input;
385
+ state.usage.costPerMOutput = costs.output;
256
386
 
257
387
  return state;
258
388
  }