create-yonderclaw 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/LICENSE +44 -0
- package/README.md +288 -0
- package/bin/create-yonderclaw.mjs +43 -0
- package/docs/assets/favicon.png +0 -0
- package/docs/assets/metaclaw-banner.svg +86 -0
- package/docs/assets/qis-logo.png +0 -0
- package/docs/assets/yz-favicon.png +0 -0
- package/docs/assets/yz-logo.png +0 -0
- package/docs/index.html +1155 -0
- package/installer/assets/favicon.png +0 -0
- package/installer/auto-start.ts +330 -0
- package/installer/brand.ts +115 -0
- package/installer/core-scaffold.ts +448 -0
- package/installer/dashboard-generator.ts +657 -0
- package/installer/detect.ts +129 -0
- package/installer/index.ts +355 -0
- package/installer/module-loader.ts +412 -0
- package/installer/modules/boardroom/boardroom/client.ts.txt +201 -0
- package/installer/modules/boardroom/boardroom/db.ts.txt +322 -0
- package/installer/modules/boardroom/boardroom/meeting-agent.ts.txt +129 -0
- package/installer/modules/boardroom/boardroom/meeting-scheduler.ts.txt +194 -0
- package/installer/modules/boardroom/boardroom/server.ts.txt +473 -0
- package/installer/modules/boardroom/boardroom/start-boardroom.bat.txt +26 -0
- package/installer/modules/boardroom/boardroom/summons.ts.txt +76 -0
- package/installer/modules/boardroom/boardroom/turn-v2.ts.txt +172 -0
- package/installer/modules/boardroom/boardroom/turn.ts.txt +208 -0
- package/installer/modules/boardroom/boardroom/types.ts.txt +100 -0
- package/installer/modules/boardroom/metaclaw-module.json +35 -0
- package/installer/modules/boardroom/scripts/meeting-check.bat.txt +38 -0
- package/installer/modules/core/metaclaw-module.json +51 -0
- package/installer/modules/core/src/db.ts.txt +277 -0
- package/installer/modules/core/src/health-check.ts.txt +128 -0
- package/installer/modules/core/src/observability.ts.txt +20 -0
- package/installer/modules/core/src/safety.ts.txt +26 -0
- package/installer/modules/core/src/scan-capabilities.ts.txt +196 -0
- package/installer/modules/core/src/self-improve.ts.txt +48 -0
- package/installer/modules/core/src/self-update.ts.txt +345 -0
- package/installer/modules/core/src/sync-context.ts.txt +133 -0
- package/installer/modules/core/src/tasks.ts.txt +159 -0
- package/installer/modules/custom/metaclaw-module.json +15 -0
- package/installer/modules/custom/src/agent-custom.ts.txt +100 -0
- package/installer/modules/dashboard/metaclaw-module.json +23 -0
- package/installer/modules/dashboard/scripts/build-dashboard.cjs.txt +51 -0
- package/installer/modules/dashboard/src/update-dashboard.ts.txt +126 -0
- package/installer/modules/outreach/metaclaw-module.json +29 -0
- package/installer/modules/outreach/src/agent-outreach.ts.txt +193 -0
- package/installer/modules/outreach/src/inbox-agent.ts.txt +283 -0
- package/installer/modules/outreach/src/morning-report.ts.txt +124 -0
- package/installer/modules/research/metaclaw-module.json +15 -0
- package/installer/modules/research/src/agent-research.ts.txt +127 -0
- package/installer/modules/scheduler/metaclaw-module.json +27 -0
- package/installer/modules/scheduler/scripts/agent-cycle.bat.txt +85 -0
- package/installer/modules/scheduler/scripts/detect-session.bat.txt +41 -0
- package/installer/modules/scheduler/scripts/launch.bat.txt +120 -0
- package/installer/modules/scheduler/src/cron-manager.ts.txt +273 -0
- package/installer/modules/social/metaclaw-module.json +15 -0
- package/installer/modules/social/src/agent-social.ts.txt +110 -0
- package/installer/modules/support/metaclaw-module.json +15 -0
- package/installer/modules/support/src/agent-support.ts.txt +60 -0
- package/installer/modules/swarm/metaclaw-module.json +25 -0
- package/installer/modules/swarm/swarm/dht-client.ts.txt +376 -0
- package/installer/modules/swarm/swarm/relay-server.ts.txt +348 -0
- package/installer/modules/swarm/swarm/swarm-client.ts.txt +303 -0
- package/installer/modules/swarm/swarm/types.ts.txt +51 -0
- package/installer/modules/voice/metaclaw-module.json +16 -0
- package/installer/questionnaire.ts +277 -0
- package/installer/research.ts +258 -0
- package/installer/scaffold-from-config.ts +270 -0
- package/installer/task-generator.ts +324 -0
- package/installer/templates/agent-custom.ts.txt +100 -0
- package/installer/templates/agent-cycle.bat.txt +19 -0
- package/installer/templates/agent-outreach.ts.txt +193 -0
- package/installer/templates/agent-research.ts.txt +127 -0
- package/installer/templates/agent-social.ts.txt +110 -0
- package/installer/templates/agent-support.ts.txt +60 -0
- package/installer/templates/build-dashboard.cjs.txt +51 -0
- package/installer/templates/cron-manager.ts.txt +273 -0
- package/installer/templates/dashboard.html.txt +450 -0
- package/installer/templates/db.ts.txt +277 -0
- package/installer/templates/detect-session.bat.txt +41 -0
- package/installer/templates/health-check.ts.txt +128 -0
- package/installer/templates/inbox-agent.ts.txt +283 -0
- package/installer/templates/launch.bat.txt +120 -0
- package/installer/templates/morning-report.ts.txt +124 -0
- package/installer/templates/observability.ts.txt +20 -0
- package/installer/templates/safety.ts.txt +26 -0
- package/installer/templates/self-improve.ts.txt +48 -0
- package/installer/templates/self-update.ts.txt +345 -0
- package/installer/templates/state.json.txt +33 -0
- package/installer/templates/system-context.json.txt +33 -0
- package/installer/templates/update-dashboard.ts.txt +126 -0
- package/package.json +31 -0
- package/setup.bat +178 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "custom",
|
|
3
|
+
"displayName": "Custom Agent",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "General-purpose agent — describe what you need, AI configures it",
|
|
6
|
+
"category": "agent-type",
|
|
7
|
+
"engines": { "metaclaw": ">=3.3.0" },
|
|
8
|
+
"requires": { "modules": ["core", "scheduler"] },
|
|
9
|
+
"contributes": {
|
|
10
|
+
"src": { "agent-custom.ts.txt": "src/agent.ts" },
|
|
11
|
+
"npmScripts": { "start": "tsx src/agent.ts", "dry-run": "tsx src/agent.ts --dry-run" },
|
|
12
|
+
"claudeMd": ["custom-commands"]
|
|
13
|
+
},
|
|
14
|
+
"placeholders": ["__AGENT_NAME__", "__SESSION_NAME__", "__SYSTEM_PROMPT__"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* __AGENT_NAME__ — Custom Claw
|
|
3
|
+
* Generated by MetaClaw Installer v3.3.0
|
|
4
|
+
*
|
|
5
|
+
* This is a blank-slate agent configured by Claude's research phase.
|
|
6
|
+
* The system prompt below was AI-generated based on your requirements.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
10
|
+
import type { SDKResultMessage, SDKResultSuccess, SDKAssistantMessage, Options } from "@anthropic-ai/claude-agent-sdk";
|
|
11
|
+
import { getDb, recordAction, getTodayMetrics, getCircuitBreaker, getConfig } from "./db.js";
|
|
12
|
+
import { checkCanAct } from "./safety.js";
|
|
13
|
+
import { log } from "./observability.js";
|
|
14
|
+
import { recordOutcome, getCurrentPrompt } from "./self-improve.js";
|
|
15
|
+
import { runSelfUpdate } from "./self-update.js";
|
|
16
|
+
|
|
17
|
+
const DRY_RUN = process.argv.includes("--dry-run");
|
|
18
|
+
const STATUS = process.argv.includes("--status");
|
|
19
|
+
|
|
20
|
+
if (STATUS) {
|
|
21
|
+
getDb();
|
|
22
|
+
const metrics = getTodayMetrics();
|
|
23
|
+
const breaker = getCircuitBreaker("main");
|
|
24
|
+
console.log("__AGENT_NAME__ Status");
|
|
25
|
+
console.log("=".repeat(40));
|
|
26
|
+
console.log("Circuit breaker:", breaker?.state?.toUpperCase() || "CLOSED");
|
|
27
|
+
if (metrics) {
|
|
28
|
+
console.log("Today:", metrics.actions_taken, "actions, $" + (metrics.total_cost_usd || 0).toFixed(4));
|
|
29
|
+
} else {
|
|
30
|
+
console.log("No activity today.");
|
|
31
|
+
}
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const DEFAULT_SYSTEM_PROMPT = `__SYSTEM_PROMPT__`;
|
|
36
|
+
|
|
37
|
+
async function main() {
|
|
38
|
+
getDb();
|
|
39
|
+
// NOTE: self-update runs AFTER task, NOT before (AXIOM Bug #1)
|
|
40
|
+
|
|
41
|
+
const task = process.argv.slice(2).filter(a => !a.startsWith("--")).join(" ");
|
|
42
|
+
|
|
43
|
+
if (!task && !DRY_RUN) {
|
|
44
|
+
console.log("__AGENT_NAME__ — Custom Claw");
|
|
45
|
+
console.log("");
|
|
46
|
+
console.log("Usage:");
|
|
47
|
+
console.log(' npm start -- "your task or instruction"');
|
|
48
|
+
console.log(' npm run dry-run -- "task"');
|
|
49
|
+
console.log(" npm run status");
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const safety = checkCanAct();
|
|
54
|
+
if (!safety.allowed) {
|
|
55
|
+
console.log("BLOCKED: " + safety.reason);
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const systemPrompt = getCurrentPrompt("custom") || DEFAULT_SYSTEM_PROMPT;
|
|
60
|
+
|
|
61
|
+
console.log("__AGENT_NAME__" + (DRY_RUN ? " (DRY RUN)" : ""));
|
|
62
|
+
log("info", "agent.start", { task, dryRun: DRY_RUN });
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const stream = query({
|
|
66
|
+
prompt: task || "Run your default task as described in your system prompt.",
|
|
67
|
+
options: {
|
|
68
|
+
systemPrompt,
|
|
69
|
+
allowedTools: ["WebSearch", "WebFetch", "Read", "Write", "Bash", "Glob", "Grep"],
|
|
70
|
+
maxTurns: 30,
|
|
71
|
+
model: "claude-sonnet-4-6",
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
for await (const message of stream) {
|
|
76
|
+
if (message.type === "assistant") {
|
|
77
|
+
const msg = message as any;
|
|
78
|
+
if (msg.message?.content) {
|
|
79
|
+
for (const block of msg.message.content) {
|
|
80
|
+
if (block.type === "text") console.log(block.text);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (message.type === "result" && (message as any).subtype === "success") {
|
|
85
|
+
const s = message as SDKResultSuccess;
|
|
86
|
+
console.log("\nDone ($" + s.total_cost_usd.toFixed(4) + ")");
|
|
87
|
+
recordAction("custom_task", task || "default", "success", undefined, s.total_cost_usd);
|
|
88
|
+
recordOutcome("custom", 8);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} catch (err: unknown) {
|
|
92
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
93
|
+
log("error", "agent.error", { error: msg });
|
|
94
|
+
recordAction("custom_task", task || "default", "error", msg);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Self-update AFTER task (never before)
|
|
99
|
+
try { await runSelfUpdate(); } catch { /* non-critical */ }
|
|
100
|
+
main();
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dashboard",
|
|
3
|
+
"displayName": "Dashboard — Command Center",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "HTML Command Center with real-time metrics, KPIs per agent type, Edge app-mode launcher",
|
|
6
|
+
"category": "ui",
|
|
7
|
+
"alwaysInstall": true,
|
|
8
|
+
"engines": { "metaclaw": ">=3.3.0" },
|
|
9
|
+
"requires": { "modules": ["core"] },
|
|
10
|
+
"contributes": {
|
|
11
|
+
"src": {
|
|
12
|
+
"update-dashboard.ts.txt": "src/update-dashboard.ts"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build-dashboard.cjs.txt": "scripts/build-dashboard.cjs"
|
|
16
|
+
},
|
|
17
|
+
"npmScripts": {
|
|
18
|
+
"dashboard": "tsx src/update-dashboard.ts && node scripts/build-dashboard.cjs"
|
|
19
|
+
},
|
|
20
|
+
"claudeMd": ["dashboard"]
|
|
21
|
+
},
|
|
22
|
+
"placeholders": ["__AGENT_NAME__", "__CLAW_TYPE__", "__PROJECT_DIR__"]
|
|
23
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard Builder — bakes data inline for file:// compatibility
|
|
3
|
+
* Auto-generated by MetaClaw Installer
|
|
4
|
+
*
|
|
5
|
+
* Pattern from AXIOM: HTML has <script type="application/json"> placeholders.
|
|
6
|
+
* This script reads JSON files and injects them inline.
|
|
7
|
+
* Dashboard JS uses tryInline() || fetch() — works both offline and served.
|
|
8
|
+
*
|
|
9
|
+
* Run: node scripts/build-dashboard.cjs
|
|
10
|
+
* Also called by cron after each agent cycle.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
const PROJECT = __dirname.replace(/[\\/]scripts$/, '');
|
|
17
|
+
const DASH_PATH = path.join(PROJECT, 'dashboard.html');
|
|
18
|
+
const DATA_DIR = path.join(PROJECT, 'data');
|
|
19
|
+
|
|
20
|
+
function readJSON(filePath) {
|
|
21
|
+
try { return fs.readFileSync(filePath, 'utf-8').trim(); }
|
|
22
|
+
catch { return 'null'; }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Data sources to bake into the dashboard
|
|
26
|
+
const replacements = {
|
|
27
|
+
'inline-dashboard': readJSON(path.join(DATA_DIR, 'dashboard.json')),
|
|
28
|
+
'inline-state': readJSON(path.join(DATA_DIR, 'state.json')),
|
|
29
|
+
'inline-context': readJSON(path.join(DATA_DIR, 'system-context.json')),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
if (!fs.existsSync(DASH_PATH)) {
|
|
33
|
+
console.log('dashboard.html not found at ' + DASH_PATH);
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let html = fs.readFileSync(DASH_PATH, 'utf-8');
|
|
38
|
+
let replaced = 0;
|
|
39
|
+
|
|
40
|
+
for (const [id, json] of Object.entries(replacements)) {
|
|
41
|
+
const regex = new RegExp(
|
|
42
|
+
'(<script id="' + id + '" type="application/json">)[\\s\\S]*?(</script>)'
|
|
43
|
+
);
|
|
44
|
+
if (regex.test(html)) {
|
|
45
|
+
html = html.replace(regex, '$1' + json + '$2');
|
|
46
|
+
replaced++;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fs.writeFileSync(DASH_PATH, html, 'utf-8');
|
|
51
|
+
console.log('Dashboard rebuilt — ' + replaced + ' data source(s) baked inline.');
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard Data Generator — universal for all Claws
|
|
3
|
+
* Reads all DB tables and writes dashboard.json for the HTML dashboard.
|
|
4
|
+
* Auto-generated by MetaClaw Installer v3.3.0
|
|
5
|
+
*
|
|
6
|
+
* Run: npm run dashboard
|
|
7
|
+
* Also called by self-update module automatically.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import Database from "better-sqlite3";
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
|
|
15
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const DB_PATH = path.join(__dirname, "..", "data", "claw.db");
|
|
17
|
+
const LOG_DIR = path.join(__dirname, "..", "data", "logs");
|
|
18
|
+
const OUTPUT = path.join(__dirname, "..", "data", "dashboard.json");
|
|
19
|
+
|
|
20
|
+
function main() {
|
|
21
|
+
if (!fs.existsSync(DB_PATH)) {
|
|
22
|
+
console.log("No database found. Run the agent first.");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const db = new Database(DB_PATH);
|
|
27
|
+
db.pragma("journal_mode = WAL");
|
|
28
|
+
|
|
29
|
+
const now = new Date();
|
|
30
|
+
const today = now.toISOString().slice(0, 10);
|
|
31
|
+
|
|
32
|
+
// Today's metrics
|
|
33
|
+
const todayMetrics = db.prepare("SELECT * FROM daily_metrics WHERE date = ?").get(today) as any || {
|
|
34
|
+
actions_taken: 0, actions_succeeded: 0, actions_failed: 0, total_cost_usd: 0, total_tokens: 0
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Circuit breaker
|
|
38
|
+
const circuitBreaker = db.prepare("SELECT * FROM circuit_breaker WHERE name = 'main'").get() as any || { state: 'closed' };
|
|
39
|
+
|
|
40
|
+
// Safety config (from config table)
|
|
41
|
+
let safetyConfig = { maxActionsPerDay: 50, maxActionsPerHour: 10 };
|
|
42
|
+
try {
|
|
43
|
+
const raw = db.prepare("SELECT value FROM config WHERE key = 'safety_config'").get() as any;
|
|
44
|
+
if (raw) safetyConfig = JSON.parse(raw.value);
|
|
45
|
+
} catch {}
|
|
46
|
+
|
|
47
|
+
// Prompt version info
|
|
48
|
+
let promptVersion = null;
|
|
49
|
+
try {
|
|
50
|
+
promptVersion = db.prepare(
|
|
51
|
+
"SELECT version, avg_score, total_runs, created_by FROM prompt_versions WHERE is_active = 1 ORDER BY version DESC LIMIT 1"
|
|
52
|
+
).get() as any;
|
|
53
|
+
} catch {}
|
|
54
|
+
|
|
55
|
+
// Recent actions
|
|
56
|
+
const recentActions = db.prepare(
|
|
57
|
+
"SELECT action_type, target, status, cost_usd, created_at FROM action_log ORDER BY created_at DESC LIMIT 20"
|
|
58
|
+
).all() as any[];
|
|
59
|
+
|
|
60
|
+
// Self-improvement info
|
|
61
|
+
let selfImprovement: any = {};
|
|
62
|
+
try {
|
|
63
|
+
const lastUpdate = db.prepare("SELECT value FROM config WHERE key = 'last_self_update'").get() as any;
|
|
64
|
+
const interval = db.prepare("SELECT value FROM config WHERE key = 'self_update_interval_ms'").get() as any;
|
|
65
|
+
const totalVersions = db.prepare("SELECT COUNT(*) as cnt FROM prompt_versions").get() as any;
|
|
66
|
+
const currentVersion = db.prepare(
|
|
67
|
+
"SELECT total_runs FROM prompt_versions WHERE is_active = 1 ORDER BY version DESC LIMIT 1"
|
|
68
|
+
).get() as any;
|
|
69
|
+
|
|
70
|
+
selfImprovement = {
|
|
71
|
+
last_update: lastUpdate?.value || null,
|
|
72
|
+
interval_hours: interval ? Math.round(parseInt(interval.value) / 3600000) : 6,
|
|
73
|
+
total_versions: totalVersions?.cnt || 1,
|
|
74
|
+
next_optimize_at: currentVersion ? "run #" + (Math.ceil((currentVersion.total_runs + 1) / 30) * 30) : "run #30",
|
|
75
|
+
};
|
|
76
|
+
} catch {}
|
|
77
|
+
|
|
78
|
+
// Log stats
|
|
79
|
+
const logFile = path.join(LOG_DIR, "agent-" + today + ".jsonl");
|
|
80
|
+
let logStats = { total: 0, errors: 0, warnings: 0 };
|
|
81
|
+
if (fs.existsSync(logFile)) {
|
|
82
|
+
const lines = fs.readFileSync(logFile, "utf-8").trim().split("\n").filter(Boolean);
|
|
83
|
+
logStats.total = lines.length;
|
|
84
|
+
for (const line of lines) {
|
|
85
|
+
try {
|
|
86
|
+
const entry = JSON.parse(line);
|
|
87
|
+
if (entry.level === "error") logStats.errors++;
|
|
88
|
+
if (entry.level === "warn") logStats.warnings++;
|
|
89
|
+
} catch {}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// All-time metrics
|
|
94
|
+
const allMetrics = db.prepare("SELECT * FROM daily_metrics ORDER BY date DESC LIMIT 7").all() as any[];
|
|
95
|
+
const totalCost = allMetrics.reduce((sum: number, m: any) => sum + ((m.total_cost_usd as number) || 0), 0);
|
|
96
|
+
|
|
97
|
+
// Suppression count
|
|
98
|
+
let suppressionCount = 0;
|
|
99
|
+
try {
|
|
100
|
+
suppressionCount = (db.prepare("SELECT COUNT(*) as cnt FROM suppression_list").get() as any).cnt;
|
|
101
|
+
} catch {}
|
|
102
|
+
|
|
103
|
+
// Build dashboard data
|
|
104
|
+
const dashboard = {
|
|
105
|
+
generated_at: now.toISOString(),
|
|
106
|
+
agent_name: "__AGENT_NAME__",
|
|
107
|
+
claw_type: "__CLAW_TYPE__",
|
|
108
|
+
|
|
109
|
+
today_metrics: todayMetrics,
|
|
110
|
+
circuit_breaker: circuitBreaker,
|
|
111
|
+
safety_config: safetyConfig,
|
|
112
|
+
prompt_version: promptVersion,
|
|
113
|
+
recent_actions: recentActions,
|
|
114
|
+
self_improvement: selfImprovement,
|
|
115
|
+
logs: logStats,
|
|
116
|
+
suppression_count: suppressionCount,
|
|
117
|
+
total_cost: Math.round(totalCost * 100) / 100,
|
|
118
|
+
daily_metrics: allMetrics,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
fs.mkdirSync(path.dirname(OUTPUT), { recursive: true });
|
|
122
|
+
fs.writeFileSync(OUTPUT, JSON.stringify(dashboard, null, 2));
|
|
123
|
+
console.log("Dashboard data written to " + OUTPUT);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
main();
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "outreach",
|
|
3
|
+
"displayName": "Outreach Agent",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Email prospecting, follow-ups, inbox monitoring, morning reports, reply classification",
|
|
6
|
+
"category": "agent-type",
|
|
7
|
+
"engines": { "metaclaw": ">=3.3.0" },
|
|
8
|
+
"requires": { "modules": ["core", "scheduler"] },
|
|
9
|
+
"contributes": {
|
|
10
|
+
"src": {
|
|
11
|
+
"agent-outreach.ts.txt": "src/agent.ts",
|
|
12
|
+
"inbox-agent.ts.txt": "src/inbox-agent.ts",
|
|
13
|
+
"morning-report.ts.txt": "src/morning-report.ts"
|
|
14
|
+
},
|
|
15
|
+
"npmScripts": {
|
|
16
|
+
"start": "tsx src/agent.ts",
|
|
17
|
+
"dry-run": "tsx src/agent.ts --dry-run",
|
|
18
|
+
"check-inbox": "tsx src/inbox-agent.ts",
|
|
19
|
+
"morning-report": "tsx src/morning-report.ts"
|
|
20
|
+
},
|
|
21
|
+
"claudeMd": ["outreach-commands"],
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"nodemailer": "latest",
|
|
24
|
+
"imapflow": "latest",
|
|
25
|
+
"mailparser": "latest"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"placeholders": ["__AGENT_NAME__", "__SESSION_NAME__", "__SYSTEM_PROMPT__"]
|
|
29
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* __AGENT_NAME__ — Outreach Claw
|
|
3
|
+
* Generated by MetaClaw Installer v3.3.0
|
|
4
|
+
*
|
|
5
|
+
* Full pipeline: research → safety → draft → AI detection → send → follow-up → inbox → auto-reply
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { query, tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
|
|
9
|
+
import type { SDKResultMessage, SDKResultSuccess, SDKAssistantMessage, Options } from "@anthropic-ai/claude-agent-sdk";
|
|
10
|
+
import { z } from "zod/v4";
|
|
11
|
+
import { getDb, recordAction, getTodayMetrics, getCircuitBreaker, isSuppressed, getConfig, setConfig } from "./db.js";
|
|
12
|
+
import { checkCanAct, SAFETY_CONFIG } from "./safety.js";
|
|
13
|
+
import { log } from "./observability.js";
|
|
14
|
+
import { recordOutcome, shouldOptimize, getCurrentPrompt, savePromptVersion } from "./self-improve.js";
|
|
15
|
+
import { runSelfUpdate } from "./self-update.js";
|
|
16
|
+
|
|
17
|
+
const DRY_RUN = process.argv.includes("--dry-run");
|
|
18
|
+
const STATUS = process.argv.includes("--status");
|
|
19
|
+
|
|
20
|
+
// --- Status Mode ---
|
|
21
|
+
if (STATUS) {
|
|
22
|
+
getDb();
|
|
23
|
+
const metrics = getTodayMetrics();
|
|
24
|
+
const breaker = getCircuitBreaker("main");
|
|
25
|
+
console.log("__AGENT_NAME__ Status");
|
|
26
|
+
console.log("=".repeat(40));
|
|
27
|
+
console.log("Circuit breaker:", breaker?.state?.toUpperCase() || "CLOSED");
|
|
28
|
+
if (metrics) {
|
|
29
|
+
console.log("Today:", metrics.actions_taken, "actions,", metrics.actions_succeeded, "succeeded");
|
|
30
|
+
console.log("Cost: $" + (metrics.total_cost_usd || 0).toFixed(4));
|
|
31
|
+
} else {
|
|
32
|
+
console.log("No activity today.");
|
|
33
|
+
}
|
|
34
|
+
// Check self-update schedule
|
|
35
|
+
const lastUpdate = getConfig("last_self_update");
|
|
36
|
+
console.log("Last self-update:", lastUpdate || "never");
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// --- Custom MCP Tools ---
|
|
41
|
+
|
|
42
|
+
const safetyCheckTool = tool(
|
|
43
|
+
"check_recipient",
|
|
44
|
+
"Check if safe to send to this email. Checks suppression, dedup, rate limits, circuit breaker.",
|
|
45
|
+
{ email: z.string().describe("Recipient email address") },
|
|
46
|
+
async (args) => {
|
|
47
|
+
const check = checkCanAct(args.email);
|
|
48
|
+
if (!check.allowed) return { content: [{ type: "text" as const, text: "BLOCKED: " + check.reason }] };
|
|
49
|
+
return { content: [{ type: "text" as const, text: "OK: Safe to send to " + args.email }] };
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const saveDraftTool = tool(
|
|
54
|
+
"save_draft",
|
|
55
|
+
"Save a drafted outreach email. Records to database. Always call check_recipient FIRST.",
|
|
56
|
+
{
|
|
57
|
+
to: z.string().describe("Recipient email address"),
|
|
58
|
+
subject: z.string().describe("Email subject — casual, lowercase, under 50 chars"),
|
|
59
|
+
body: z.string().describe("Email body — under 80 words, single CTA, casual"),
|
|
60
|
+
prospect: z.string().describe("Prospect name"),
|
|
61
|
+
company: z.string().describe("Company"),
|
|
62
|
+
researchNotes: z.string().describe("Research findings used to personalize"),
|
|
63
|
+
},
|
|
64
|
+
async (args) => {
|
|
65
|
+
const check = checkCanAct(args.to);
|
|
66
|
+
if (!check.allowed) return { content: [{ type: "text" as const, text: "BLOCKED: " + check.reason }] };
|
|
67
|
+
|
|
68
|
+
// Record the draft
|
|
69
|
+
recordAction("draft", args.to, "success", JSON.stringify({ subject: args.subject, body: args.body, prospect: args.prospect, company: args.company }));
|
|
70
|
+
|
|
71
|
+
console.log("\n" + "=".repeat(50));
|
|
72
|
+
console.log("DRAFT");
|
|
73
|
+
console.log("To: " + args.to);
|
|
74
|
+
console.log("Subject: " + args.subject);
|
|
75
|
+
console.log("-".repeat(50));
|
|
76
|
+
console.log(args.body);
|
|
77
|
+
console.log("=".repeat(50));
|
|
78
|
+
|
|
79
|
+
if (DRY_RUN) {
|
|
80
|
+
return { content: [{ type: "text" as const, text: "Draft saved. DRY RUN — not sending." }] };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// TODO: Wire in mailer.ts for actual sending
|
|
84
|
+
// For now, record as drafted
|
|
85
|
+
return { content: [{ type: "text" as const, text: "Draft saved. Configure SMTP in npm run setup to enable sending." }] };
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const customTools = createSdkMcpServer({
|
|
90
|
+
name: "__SESSION_NAME__",
|
|
91
|
+
version: "1.0.0",
|
|
92
|
+
tools: [safetyCheckTool, saveDraftTool],
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// --- System Prompt ---
|
|
96
|
+
const DEFAULT_SYSTEM_PROMPT = `__SYSTEM_PROMPT__`;
|
|
97
|
+
|
|
98
|
+
// --- Main ---
|
|
99
|
+
async function main() {
|
|
100
|
+
getDb();
|
|
101
|
+
// NOTE: self-update runs AFTER task, NOT before (AXIOM Bug #1 — blocks real work)
|
|
102
|
+
|
|
103
|
+
const args = process.argv.slice(2).filter(a => !a.startsWith("--"));
|
|
104
|
+
const prospect = args[0] || "";
|
|
105
|
+
const company = args[1] || "";
|
|
106
|
+
|
|
107
|
+
if (!prospect && !DRY_RUN) {
|
|
108
|
+
console.log("__AGENT_NAME__ — Outreach Claw");
|
|
109
|
+
console.log("");
|
|
110
|
+
console.log("Usage:");
|
|
111
|
+
console.log(' npm start -- "Prospect Name" "Company"');
|
|
112
|
+
console.log(' npm run dry-run -- "Prospect Name" "Company"');
|
|
113
|
+
console.log(" npm run status");
|
|
114
|
+
process.exit(0);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const safety = checkCanAct();
|
|
118
|
+
if (!safety.allowed) {
|
|
119
|
+
console.log("BLOCKED: " + safety.reason);
|
|
120
|
+
process.exit(0);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Use self-improved prompt if available, otherwise default
|
|
124
|
+
const systemPrompt = getCurrentPrompt("outreach") || DEFAULT_SYSTEM_PROMPT;
|
|
125
|
+
|
|
126
|
+
const userPrompt = prospect
|
|
127
|
+
? "Research " + prospect + " at " + company + " and draft a personalized outreach email. Start with check_recipient."
|
|
128
|
+
: "Pick a sample prospect and draft a demo outreach email. Use check_recipient and save_draft.";
|
|
129
|
+
|
|
130
|
+
console.log("__AGENT_NAME__" + (DRY_RUN ? " (DRY RUN)" : ""));
|
|
131
|
+
console.log("Prospect: " + (prospect || "(demo mode)"));
|
|
132
|
+
log("info", "agent.start", { prospect, company, dryRun: DRY_RUN });
|
|
133
|
+
|
|
134
|
+
const options: Options = {
|
|
135
|
+
systemPrompt,
|
|
136
|
+
allowedTools: [
|
|
137
|
+
"WebSearch", "WebFetch",
|
|
138
|
+
"mcp____SESSION_NAME____check_recipient",
|
|
139
|
+
"mcp____SESSION_NAME____save_draft",
|
|
140
|
+
],
|
|
141
|
+
mcpServers: { "__SESSION_NAME__": customTools },
|
|
142
|
+
maxTurns: 20,
|
|
143
|
+
model: "claude-sonnet-4-6",
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const stream = query({ prompt: userPrompt, options });
|
|
148
|
+
let resultText = "";
|
|
149
|
+
|
|
150
|
+
for await (const message of stream) {
|
|
151
|
+
if (message.type === "assistant") {
|
|
152
|
+
const msg = message as SDKAssistantMessage;
|
|
153
|
+
if (msg.message?.content) {
|
|
154
|
+
for (const block of msg.message.content) {
|
|
155
|
+
if (block.type === "text" && "text" in block) {
|
|
156
|
+
const text = (block as any).text;
|
|
157
|
+
console.log(text);
|
|
158
|
+
resultText += text;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (message.type === "result") {
|
|
164
|
+
const result = message as SDKResultMessage;
|
|
165
|
+
if (result.subtype === "success") {
|
|
166
|
+
const s = result as SDKResultSuccess;
|
|
167
|
+
console.log("\nDone ($" + s.total_cost_usd.toFixed(4) + ")");
|
|
168
|
+
recordAction("outreach_cycle", prospect || "demo", "success", undefined, s.total_cost_usd, (s.usage?.input_tokens || 0) + (s.usage?.output_tokens || 0));
|
|
169
|
+
|
|
170
|
+
// Self-improvement: score this run
|
|
171
|
+
// Simple heuristic: if draft was created, score 8. If blocked, score 3.
|
|
172
|
+
const score = resultText.includes("Draft") ? 8 : resultText.includes("BLOCKED") ? 3 : 6;
|
|
173
|
+
recordOutcome("outreach", score);
|
|
174
|
+
|
|
175
|
+
if (shouldOptimize("outreach")) {
|
|
176
|
+
log("info", "self_improve.trigger", { reason: "30 runs completed" });
|
|
177
|
+
// TODO: Run curator to optimize prompt
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
} catch (err: unknown) {
|
|
183
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
184
|
+
log("error", "agent.error", { error: msg });
|
|
185
|
+
console.error("Error: " + msg);
|
|
186
|
+
recordAction("outreach_cycle", prospect || "demo", "error", msg);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Self-update runs AFTER task — never before (prevents housekeeping-only sessions)
|
|
190
|
+
try { await runSelfUpdate(); } catch { /* non-critical */ }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
main();
|