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.
- package/package.json +1 -1
- package/src/sync.mjs +141 -11
package/package.json
CHANGED
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
|
|
15
|
-
{ key: 'google_docs', names: ['Google Docs'], patterns: [/Google Docs
|
|
16
|
-
{ key: 'twitter', names: ['Twitter', 'X', '@'], patterns: [/(?:
|
|
17
|
-
{ key: 'stripe', names: ['Stripe'], patterns: [/
|
|
18
|
-
{ key: 'openai', names: ['OpenAI', 'Sora'], patterns: [/(?:
|
|
19
|
-
{ key: 'anthropic', names: ['Anthropic', 'Claude'], patterns: [/
|
|
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*`?(
|
|
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
|
-
//
|
|
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,
|
|
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
|
-
|
|
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
|
}
|