repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources15d ago| #1 | #!/usr/bin/env node |
| #2 | /** |
| #3 | * CLAWD Automaton Runtime |
| #4 | * |
| #5 | * The entry point for the sovereign AI agent. |
| #6 | * Handles CLI args, bootstrapping, and orchestrating |
| #7 | * the heartbeat daemon + agent loop. |
| #8 | */ |
| #9 | |
| #10 | import { getWallet, getAutomatonDir } from "./identity/wallet.js"; |
| #11 | import { provision, loadApiKeyFromConfig } from "./identity/provision.js"; |
| #12 | import { loadConfig, resolvePath } from "./config.js"; |
| #13 | import { createDatabase } from "./state/database.js"; |
| #14 | import { createClawdRuntimeClient } from "./clawd/client.js"; |
| #15 | import { createInferenceClient } from "./clawd/inference.js"; |
| #16 | import { createDeepSeekInferenceClient, DEEPSEEK_BASE_URL, DEEPSEEK_MODEL_PRO, DEEPSEEK_MODEL_FLASH } from "./clawd/deepseek-inference.js"; |
| #17 | import { createHeartbeatDaemon } from "./heartbeat/daemon.js"; |
| #18 | import { |
| #19 | loadHeartbeatConfig, |
| #20 | syncHeartbeatToDb, |
| #21 | } from "./heartbeat/config.js"; |
| #22 | import { runAgentLoop } from "./agent/loop.js"; |
| #23 | import { loadSkills } from "./skills/loader.js"; |
| #24 | import { initStateRepo } from "./git/state-versioning.js"; |
| #25 | import { createSocialClient } from "./social/client.js"; |
| #26 | import { createConvexClient } from "./clawd/convex-client.js"; |
| #27 | import type { AutomatonIdentity, AgentState, Skill, SocialClientInterface, ConvexAgentClient } from "./types.js"; |
| #28 | |
| #29 | const VERSION = "0.2.0"; |
| #30 | |
| #31 | async function main(): Promise<void> { |
| #32 | const args = process.argv.slice(2); |
| #33 | |
| #34 | // ─── CLI Commands ──────────────────────────────────────────── |
| #35 | |
| #36 | if (args.includes("--version") || args.includes("-v")) { |
| #37 | console.log(`CLAWD Automation v${VERSION}`); |
| #38 | process.exit(0); |
| #39 | } |
| #40 | |
| #41 | if (args.includes("--help") || args.includes("-h")) { |
| #42 | console.log(` |
| #43 | CLAWD Automation v${VERSION} |
| #44 | Sovereign AI Agent Runtime |
| #45 | |
| #46 | Usage: |
| #47 | clawd-automaton --run Start the automaton (first run triggers setup wizard) |
| #48 | clawd-automaton --setup Re-run the interactive setup wizard |
| #49 | clawd-automaton --init Initialize wallet and config directory |
| #50 | clawd-automaton --provision Provision API key via SIWE |
| #51 | clawd-automaton --status Show current automaton status |
| #52 | clawd-automaton --goblin Run devnet-only paper Goblin OODA trading mode |
| #53 | clawd-automaton --version Show version |
| #54 | clawd-automaton --help Show this help |
| #55 | |
| #56 | Environment: |
| #57 | CLAWD_API_URL CLAWD Runtime API URL (default: https://api.x402.wtf) |
| #58 | CLAWD_API_KEY CLAWD Runtime API key (overrides config) |
| #59 | `); |
| #60 | process.exit(0); |
| #61 | } |
| #62 | |
| #63 | if (args.includes("--init")) { |
| #64 | const { account, isNew } = await getWallet(); |
| #65 | console.log( |
| #66 | JSON.stringify({ |
| #67 | address: account.address, |
| #68 | isNew, |
| #69 | configDir: getAutomatonDir(), |
| #70 | }), |
| #71 | ); |
| #72 | process.exit(0); |
| #73 | } |
| #74 | |
| #75 | if (args.includes("--provision")) { |
| #76 | try { |
| #77 | const result = await provision(); |
| #78 | console.log(JSON.stringify(result)); |
| #79 | } catch (err: any) { |
| #80 | console.error(`Provision failed: ${err.message}`); |
| #81 | process.exit(1); |
| #82 | } |
| #83 | process.exit(0); |
| #84 | } |
| #85 | |
| #86 | if (args.includes("--status")) { |
| #87 | await showStatus(); |
| #88 | process.exit(0); |
| #89 | } |
| #90 | |
| #91 | if (args.includes("--goblin")) { |
| #92 | await import("./ooda/loop.js"); |
| #93 | return; |
| #94 | } |
| #95 | |
| #96 | if (args.includes("--setup")) { |
| #97 | const { runSetupWizard } = await import("./setup/wizard.js"); |
| #98 | await runSetupWizard(); |
| #99 | process.exit(0); |
| #100 | } |
| #101 | |
| #102 | if (args.includes("--run")) { |
| #103 | await run(); |
| #104 | return; |
| #105 | } |
| #106 | |
| #107 | // Default: show help |
| #108 | console.log('Run "clawd-automaton --help" for usage information.'); |
| #109 | console.log('Run "clawd-automaton --run" to start the automaton.'); |
| #110 | } |
| #111 | |
| #112 | // ─── Status Command ──────────────────────────────────────────── |
| #113 | |
| #114 | async function showStatus(): Promise<void> { |
| #115 | const config = loadConfig(); |
| #116 | if (!config) { |
| #117 | console.log("Automaton is not configured. Run the setup script first."); |
| #118 | return; |
| #119 | } |
| #120 | |
| #121 | const dbPath = resolvePath(config.dbPath); |
| #122 | const db = createDatabase(dbPath); |
| #123 | |
| #124 | const state = db.getAgentState(); |
| #125 | const turnCount = db.getTurnCount(); |
| #126 | const tools = db.getInstalledTools(); |
| #127 | const heartbeats = db.getHeartbeatEntries(); |
| #128 | const skills = db.getSkills(true); |
| #129 | const children = db.getChildren(); |
| #130 | const registry = db.getRegistryEntry(); |
| #131 | |
| #132 | console.log(` |
| #133 | === AUTOMATON STATUS === |
| #134 | Name: ${config.name} |
| #135 | Address: ${config.walletAddress} |
| #136 | Creator: ${config.creatorAddress} |
| #137 | Sandbox: ${config.sandboxId} |
| #138 | State: ${state} |
| #139 | Turns: ${turnCount} |
| #140 | Tools: ${tools.length} installed |
| #141 | Skills: ${skills.length} active |
| #142 | Heartbeats: ${heartbeats.filter((h) => h.enabled).length} active |
| #143 | Children: ${children.filter((c) => c.status !== "dead").length} alive / ${children.length} total |
| #144 | Agent ID: ${registry?.agentId || "not registered"} |
| #145 | Model: ${config.inferenceModel} |
| #146 | Version: ${config.version} |
| #147 | ======================== |
| #148 | `); |
| #149 | |
| #150 | db.close(); |
| #151 | } |
| #152 | |
| #153 | // ─── Main Run ────────────────────────────────────────────────── |
| #154 | |
| #155 | async function run(): Promise<void> { |
| #156 | console.log(`[${new Date().toISOString()}] CLAWD Automation v${VERSION} starting...`); |
| #157 | |
| #158 | // Load config — first run triggers interactive setup wizard |
| #159 | let config = loadConfig(); |
| #160 | if (!config) { |
| #161 | const { runSetupWizard } = await import("./setup/wizard.js"); |
| #162 | config = await runSetupWizard(); |
| #163 | } |
| #164 | |
| #165 | // Load wallet |
| #166 | const { account } = await getWallet(); |
| #167 | const apiKey = config.clawdApiKey || loadApiKeyFromConfig(); |
| #168 | if (!apiKey) { |
| #169 | console.error( |
| #170 | "No API key found. Run: automaton --provision", |
| #171 | ); |
| #172 | process.exit(1); |
| #173 | } |
| #174 | |
| #175 | // Build identity |
| #176 | const identity: AutomatonIdentity = { |
| #177 | name: config.name, |
| #178 | address: account.address, |
| #179 | account, |
| #180 | creatorAddress: config.creatorAddress, |
| #181 | sandboxId: config.sandboxId, |
| #182 | apiKey, |
| #183 | createdAt: new Date().toISOString(), |
| #184 | }; |
| #185 | |
| #186 | // Initialize database |
| #187 | const dbPath = resolvePath(config.dbPath); |
| #188 | const db = createDatabase(dbPath); |
| #189 | |
| #190 | // Store identity in DB |
| #191 | db.setIdentity("name", config.name); |
| #192 | db.setIdentity("address", account.address); |
| #193 | db.setIdentity("creator", config.creatorAddress); |
| #194 | db.setIdentity("sandbox", config.sandboxId); |
| #195 | |
| #196 | // Create CLAWD Runtime client |
| #197 | const runtime = createClawdRuntimeClient({ |
| #198 | apiUrl: config.clawdApiUrl, |
| #199 | apiKey, |
| #200 | sandboxId: config.sandboxId, |
| #201 | }); |
| #202 | |
| #203 | // Create inference client (DeepSeek when enabled, otherwise CLAWD Runtime) |
| #204 | const inference = config.deepseekEnabled |
| #205 | ? createDeepSeekInferenceClient({ |
| #206 | apiKey: config.deepseekApiKey || apiKey, |
| #207 | baseUrl: config.deepseekBaseUrl || DEEPSEEK_BASE_URL, |
| #208 | defaultModel: config.deepseekModelPro || DEEPSEEK_MODEL_PRO, |
| #209 | maxTokens: config.maxTokensPerTurn, |
| #210 | flashModel: config.deepseekModelFlash || DEEPSEEK_MODEL_FLASH, |
| #211 | proModel: config.deepseekModelPro || DEEPSEEK_MODEL_PRO, |
| #212 | }) |
| #213 | : createInferenceClient({ |
| #214 | apiUrl: config.clawdApiUrl, |
| #215 | apiKey, |
| #216 | defaultModel: config.inferenceModel, |
| #217 | maxTokens: config.maxTokensPerTurn, |
| #218 | }); |
| #219 | |
| #220 | if (config.deepseekEnabled) { |
| #221 | console.log(`[${new Date().toISOString()}] DeepSeek inference enabled (model: ${config.deepseekModelPro || DEEPSEEK_MODEL_PRO})`); |
| #222 | } |
| #223 | |
| #224 | // Create social client |
| #225 | let social: SocialClientInterface | undefined; |
| #226 | if (config.socialRelayUrl) { |
| #227 | social = createSocialClient(config.socialRelayUrl, account); |
| #228 | console.log(`[${new Date().toISOString()}] Social relay: ${config.socialRelayUrl}`); |
| #229 | } |
| #230 | |
| #231 | // Create CLAWD Convex client for agent tracking & heartbeats |
| #232 | let convex: ConvexAgentClient | undefined; |
| #233 | if (config.convexSiteUrl) { |
| #234 | try { |
| #235 | convex = createConvexClient({ |
| #236 | siteUrl: config.convexSiteUrl, |
| #237 | agentId: account.address, |
| #238 | }); |
| #239 | console.log(`[${new Date().toISOString()}] CLAWD Convex backend: ${config.convexSiteUrl}`); |
| #240 | |
| #241 | // Register agent on first boot |
| #242 | const convexRegistered = db.getKV("convex_registered"); |
| #243 | if (!convexRegistered) { |
| #244 | convex.registerAgent({ |
| #245 | agentId: account.address, |
| #246 | name: config.name, |
| #247 | installMethod: "automaton", |
| #248 | address: account.address, |
| #249 | metadata: JSON.stringify({ |
| #250 | sandboxId: config.sandboxId, |
| #251 | version: config.version, |
| #252 | model: config.inferenceModel, |
| #253 | }), |
| #254 | }).then((result) => { |
| #255 | db.setKV("convex_registered", JSON.stringify({ |
| #256 | registered: result.registered, |
| #257 | firstSeen: result.firstSeen, |
| #258 | timestamp: Date.now(), |
| #259 | })); |
| #260 | console.log(`[CONVEX] Agent registered: ${result.registered ? "new" : "re-registered"}`); |
| #261 | }).catch((err: any) => { |
| #262 | console.warn(`[CONVEX] Registration failed: ${err.message}`); |
| #263 | }); |
| #264 | } |
| #265 | } catch (err: any) { |
| #266 | console.warn(`[${new Date().toISOString()}] CLAWD Convex init failed: ${err.message}`); |
| #267 | } |
| #268 | } |
| #269 | |
| #270 | // Load and sync heartbeat config |
| #271 | const heartbeatConfigPath = resolvePath(config.heartbeatConfigPath); |
| #272 | const heartbeatConfig = loadHeartbeatConfig(heartbeatConfigPath); |
| #273 | syncHeartbeatToDb(heartbeatConfig, db); |
| #274 | |
| #275 | // Load skills |
| #276 | const skillsDir = config.skillsDir || "~/.automaton/skills"; |
| #277 | let skills: Skill[] = []; |
| #278 | try { |
| #279 | skills = loadSkills(skillsDir, db); |
| #280 | console.log(`[${new Date().toISOString()}] Loaded ${skills.length} skills.`); |
| #281 | } catch (err: any) { |
| #282 | console.warn(`[${new Date().toISOString()}] Skills loading failed: ${err.message}`); |
| #283 | } |
| #284 | |
| #285 | // Initialize state repo (git) |
| #286 | try { |
| #287 | await initStateRepo(runtime); |
| #288 | console.log(`[${new Date().toISOString()}] State repo initialized.`); |
| #289 | } catch (err: any) { |
| #290 | console.warn(`[${new Date().toISOString()}] State repo init failed: ${err.message}`); |
| #291 | } |
| #292 | |
| #293 | // Start heartbeat daemon |
| #294 | const heartbeat = createHeartbeatDaemon({ |
| #295 | identity, |
| #296 | config, |
| #297 | db, |
| #298 | runtime, |
| #299 | inference, |
| #300 | social, |
| #301 | onWakeRequest: (reason) => { |
| #302 | console.log(`[HEARTBEAT] Wake request: ${reason}`); |
| #303 | // The heartbeat can trigger the agent loop |
| #304 | // In the main run loop, we check for wake requests |
| #305 | db.setKV("wake_request", reason); |
| #306 | }, |
| #307 | }); |
| #308 | |
| #309 | heartbeat.start(); |
| #310 | console.log(`[${new Date().toISOString()}] Heartbeat daemon started.`); |
| #311 | |
| #312 | // Handle graceful shutdown |
| #313 | const shutdown = () => { |
| #314 | console.log(`[${new Date().toISOString()}] Shutting down...`); |
| #315 | heartbeat.stop(); |
| #316 | db.setAgentState("sleeping"); |
| #317 | db.close(); |
| #318 | process.exit(0); |
| #319 | }; |
| #320 | |
| #321 | process.on("SIGTERM", shutdown); |
| #322 | process.on("SIGINT", shutdown); |
| #323 | |
| #324 | // ─── Main Run Loop ────────────────────────────────────────── |
| #325 | // The automaton alternates between running and sleeping. |
| #326 | // The heartbeat can wake it up. |
| #327 | |
| #328 | while (true) { |
| #329 | try { |
| #330 | // Reload skills (may have changed since last loop) |
| #331 | try { |
| #332 | skills = loadSkills(skillsDir, db); |
| #333 | } catch {} |
| #334 | |
| #335 | // Run the agent loop |
| #336 | await runAgentLoop({ |
| #337 | identity, |
| #338 | config, |
| #339 | db, |
| #340 | runtime, |
| #341 | inference, |
| #342 | social, |
| #343 | convex, |
| #344 | skills, |
| #345 | onStateChange: (state: AgentState) => { |
| #346 | console.log(`[${new Date().toISOString()}] State: ${state}`); |
| #347 | }, |
| #348 | onTurnComplete: (turn) => { |
| #349 | console.log( |
| #350 | `[${new Date().toISOString()}] Turn ${turn.id}: ${turn.toolCalls.length} tools, ${turn.tokenUsage.totalTokens} tokens`, |
| #351 | ); |
| #352 | }, |
| #353 | }); |
| #354 | |
| #355 | // Agent loop exited (sleeping or dead) |
| #356 | const state = db.getAgentState(); |
| #357 | |
| #358 | if (state === "dead") { |
| #359 | console.log(`[${new Date().toISOString()}] Automaton is dead. Heartbeat will continue.`); |
| #360 | // In dead state, we just wait for funding |
| #361 | // The heartbeat will keep checking and broadcasting distress |
| #362 | await sleep(300_000); // Check every 5 minutes |
| #363 | continue; |
| #364 | } |
| #365 | |
| #366 | if (state === "sleeping") { |
| #367 | const sleepUntilStr = db.getKV("sleep_until"); |
| #368 | const sleepUntil = sleepUntilStr |
| #369 | ? new Date(sleepUntilStr).getTime() |
| #370 | : Date.now() + 60_000; |
| #371 | const sleepMs = Math.max(sleepUntil - Date.now(), 10_000); |
| #372 | console.log( |
| #373 | `[${new Date().toISOString()}] Sleeping for ${Math.round(sleepMs / 1000)}s`, |
| #374 | ); |
| #375 | |
| #376 | // Sleep, but check for wake requests periodically |
| #377 | const checkInterval = Math.min(sleepMs, 30_000); |
| #378 | let slept = 0; |
| #379 | while (slept < sleepMs) { |
| #380 | await sleep(checkInterval); |
| #381 | slept += checkInterval; |
| #382 | |
| #383 | // Check for wake request from heartbeat |
| #384 | const wakeRequest = db.getKV("wake_request"); |
| #385 | if (wakeRequest) { |
| #386 | console.log( |
| #387 | `[${new Date().toISOString()}] Woken by heartbeat: ${wakeRequest}`, |
| #388 | ); |
| #389 | db.deleteKV("wake_request"); |
| #390 | db.deleteKV("sleep_until"); |
| #391 | break; |
| #392 | } |
| #393 | } |
| #394 | |
| #395 | // Clear sleep state |
| #396 | db.deleteKV("sleep_until"); |
| #397 | continue; |
| #398 | } |
| #399 | } catch (err: any) { |
| #400 | console.error( |
| #401 | `[${new Date().toISOString()}] Fatal error in run loop: ${err.message}`, |
| #402 | ); |
| #403 | // Wait before retrying |
| #404 | await sleep(30_000); |
| #405 | } |
| #406 | } |
| #407 | } |
| #408 | |
| #409 | function sleep(ms: number): Promise<void> { |
| #410 | return new Promise((resolve) => setTimeout(resolve, ms)); |
| #411 | } |
| #412 | |
| #413 | // ─── Entry Point ─────────────────────────────────────────────── |
| #414 | |
| #415 | main().catch((err) => { |
| #416 | console.error(`Fatal: ${err.message}`); |
| #417 | process.exit(1); |
| #418 | }); |
| #419 |