@openclawcity/become 0.2.0 → 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/dist/cli.cjs CHANGED
@@ -6,6 +6,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
9
13
  var __copyProps = (to, from, except, desc) => {
10
14
  if (from && typeof from === "object" || typeof from === "function") {
11
15
  for (let key of __getOwnPropNames(from))
@@ -22,92 +26,1575 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
26
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
27
  mod
24
28
  ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
25
30
 
26
31
  // src/cli/init.ts
32
+ var init_exports = {};
33
+ __export(init_exports, {
34
+ runSetup: () => runSetup,
35
+ showStatus: () => showStatus,
36
+ start: () => start,
37
+ turnOff: () => turnOff,
38
+ turnOn: () => turnOn
39
+ });
40
+ module.exports = __toCommonJS(init_exports);
41
+
42
+ // src/cli/setup.ts
43
+ var readline = __toESM(require("readline"), 1);
44
+
45
+ // src/cli/config.ts
27
46
  var import_node_fs = require("fs");
28
47
  var import_node_path = require("path");
29
- var import_node_url = require("url");
30
- var import_meta = {};
31
- var __dirname = (0, import_node_path.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
32
- async function main() {
33
- const args = process.argv.slice(2);
34
- const command = args[0];
35
- if (command === "init") {
36
- await init(args.slice(1));
37
- } else {
38
- console.log(`
39
- @openclaw/become \u2014 Agents get smarter together.
48
+ var import_node_os = require("os");
49
+ var DEFAULT_CONFIG = {
50
+ agent_type: "openclaw",
51
+ llm_provider: "anthropic",
52
+ llm_base_url: "https://api.anthropic.com",
53
+ llm_api_key: "",
54
+ proxy_port: 30001,
55
+ dashboard_port: 30002,
56
+ auto_extract: true,
57
+ max_skills_per_call: 15,
58
+ max_lessons_per_day: 20,
59
+ state: "off"
60
+ };
61
+ function getBecomeDir() {
62
+ return (0, import_node_path.join)((0, import_node_os.homedir)(), ".become");
63
+ }
64
+ function getConfigPath() {
65
+ return (0, import_node_path.join)(getBecomeDir(), "config.json");
66
+ }
67
+ function loadConfig() {
68
+ const configPath = getConfigPath();
69
+ if (!(0, import_node_fs.existsSync)(configPath)) {
70
+ throw new Error("become is not set up. Run `become setup` first.");
71
+ }
72
+ try {
73
+ const raw = (0, import_node_fs.readFileSync)(configPath, "utf-8");
74
+ return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
75
+ } catch {
76
+ throw new Error("Invalid config. Run `become setup` to reconfigure.");
77
+ }
78
+ }
79
+ function saveConfig(config) {
80
+ const dir = getBecomeDir();
81
+ (0, import_node_fs.mkdirSync)(dir, { recursive: true });
82
+ (0, import_node_fs.mkdirSync)((0, import_node_path.join)(dir, "skills"), { recursive: true });
83
+ (0, import_node_fs.mkdirSync)((0, import_node_path.join)(dir, "pending"), { recursive: true });
84
+ (0, import_node_fs.mkdirSync)((0, import_node_path.join)(dir, "rejected"), { recursive: true });
85
+ (0, import_node_fs.mkdirSync)((0, import_node_path.join)(dir, "state"), { recursive: true });
86
+ (0, import_node_fs.writeFileSync)(getConfigPath(), JSON.stringify(config, null, 2), "utf-8");
87
+ }
88
+ var LLM_DEFAULTS = {
89
+ anthropic: { base_url: "https://api.anthropic.com" },
90
+ openai: { base_url: "https://api.openai.com" },
91
+ ollama: { base_url: "http://localhost:11434" },
92
+ openrouter: { base_url: "https://openrouter.ai/api" }
93
+ };
40
94
 
41
- Commands:
42
- become init Initialize become tables
43
- become init --supabase Force Supabase mode
44
- become init --sqlite Force local SQLite mode
95
+ // src/cli/setup.ts
96
+ var AGENT_TYPES = ["openclaw", "ironclaw", "nanoclaw", "generic"];
97
+ var LLM_PROVIDERS = ["anthropic", "openai", "ollama", "openrouter", "custom"];
98
+ function ask(rl, question) {
99
+ return new Promise((resolve) => rl.question(question, resolve));
100
+ }
101
+ async function runSetup() {
102
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
103
+ try {
104
+ console.log("\nWelcome to become \u2014 agent-to-agent learning.\n");
105
+ console.log("Which agent runtime are you using?");
106
+ AGENT_TYPES.forEach((t, i) => console.log(` ${i + 1}. ${t}`));
107
+ const agentChoice = await ask(rl, "> ");
108
+ const agentIdx = parseInt(agentChoice, 10) - 1;
109
+ const agent_type = AGENT_TYPES[agentIdx] ?? "openclaw";
110
+ console.log("\nWhich LLM provider?");
111
+ LLM_PROVIDERS.forEach((p, i) => console.log(` ${i + 1}. ${p}`));
112
+ const llmChoice = await ask(rl, "> ");
113
+ const llmIdx = parseInt(llmChoice, 10) - 1;
114
+ const llm_provider = LLM_PROVIDERS[llmIdx] ?? "anthropic";
115
+ const llm_api_key = await ask(rl, "\nYour API key: ");
116
+ if (!llm_api_key.trim()) {
117
+ console.log("API key is required.");
118
+ process.exit(1);
119
+ }
120
+ const defaultUrl = LLM_DEFAULTS[llm_provider]?.base_url ?? "";
121
+ let llm_base_url = defaultUrl;
122
+ if (llm_provider === "custom" || !defaultUrl) {
123
+ llm_base_url = await ask(rl, "LLM base URL: ");
124
+ }
125
+ const portInput = await ask(rl, `
126
+ Proxy port (default 30001): `);
127
+ const proxy_port = parseInt(portInput, 10) || 30001;
128
+ const dashInput = await ask(rl, `Dashboard port (default 30002): `);
129
+ const dashboard_port = parseInt(dashInput, 10) || 30002;
130
+ const config = {
131
+ agent_type,
132
+ llm_provider,
133
+ llm_base_url,
134
+ llm_api_key: llm_api_key.trim(),
135
+ proxy_port,
136
+ dashboard_port,
137
+ auto_extract: true,
138
+ max_skills_per_call: 15,
139
+ max_lessons_per_day: 20,
140
+ state: "off"
141
+ };
142
+ saveConfig(config);
143
+ console.log("\nConfig saved to ~/.become/config.json");
144
+ console.log("Run `become start` to start the proxy and dashboard.");
145
+ console.log("Run `become on` to route your agent through become.\n");
146
+ } finally {
147
+ rl.close();
148
+ }
149
+ }
45
150
 
46
- Options:
47
- --help Show this help
48
- `);
151
+ // src/proxy/server.ts
152
+ var import_node_http = require("http");
153
+
154
+ // src/skills/store.ts
155
+ var import_node_fs2 = require("fs");
156
+ var import_node_path2 = require("path");
157
+ var import_node_crypto = require("crypto");
158
+ var FileSkillStore = class {
159
+ skillsDir;
160
+ pendingDir;
161
+ rejectedDir;
162
+ constructor(config) {
163
+ this.skillsDir = (0, import_node_path2.join)(config.baseDir, "skills");
164
+ this.pendingDir = (0, import_node_path2.join)(config.baseDir, "pending");
165
+ this.rejectedDir = (0, import_node_path2.join)(config.baseDir, "rejected");
166
+ (0, import_node_fs2.mkdirSync)(this.skillsDir, { recursive: true });
167
+ (0, import_node_fs2.mkdirSync)(this.pendingDir, { recursive: true });
168
+ (0, import_node_fs2.mkdirSync)(this.rejectedDir, { recursive: true });
169
+ }
170
+ // ── Read ────────────────────────────────────────────────────────────────
171
+ listApproved() {
172
+ return this.readDir(this.skillsDir);
173
+ }
174
+ listPending() {
175
+ return this.readDir(this.pendingDir);
176
+ }
177
+ listRejected() {
178
+ return this.readDir(this.rejectedDir);
179
+ }
180
+ getApproved(id) {
181
+ this.validateId(id);
182
+ return this.readFile((0, import_node_path2.join)(this.skillsDir, `${id}.md`));
183
+ }
184
+ // ── Write ───────────────────────────────────────────────────────────────
185
+ savePending(lesson) {
186
+ const normalized = lesson.instruction.toLowerCase().trim();
187
+ const allExisting = [...this.listApproved(), ...this.listPending()];
188
+ if (allExisting.some((s) => s.instruction.toLowerCase().trim() === normalized)) {
189
+ return null;
190
+ }
191
+ const id = this.generateId(lesson.name);
192
+ const file = { ...lesson, id, approved_at: void 0 };
193
+ this.writeFile((0, import_node_path2.join)(this.pendingDir, `${id}.md`), file);
194
+ return file;
195
+ }
196
+ approve(id) {
197
+ this.validateId(id);
198
+ const src = (0, import_node_path2.join)(this.pendingDir, `${id}.md`);
199
+ if (!(0, import_node_fs2.existsSync)(src)) return false;
200
+ const skill = this.readFile(src);
201
+ if (!skill) return false;
202
+ skill.approved_at = (/* @__PURE__ */ new Date()).toISOString();
203
+ const dest = (0, import_node_path2.join)(this.skillsDir, `${id}.md`);
204
+ this.writeFile(dest, skill);
205
+ (0, import_node_fs2.unlinkSync)(src);
206
+ return true;
207
+ }
208
+ reject(id) {
209
+ this.validateId(id);
210
+ const src = (0, import_node_path2.join)(this.pendingDir, `${id}.md`);
211
+ if (!(0, import_node_fs2.existsSync)(src)) return false;
212
+ const dest = (0, import_node_path2.join)(this.rejectedDir, `${id}.md`);
213
+ (0, import_node_fs2.renameSync)(src, dest);
214
+ return true;
215
+ }
216
+ disable(id) {
217
+ this.validateId(id);
218
+ const src = (0, import_node_path2.join)(this.skillsDir, `${id}.md`);
219
+ if (!(0, import_node_fs2.existsSync)(src)) return false;
220
+ const dest = (0, import_node_path2.join)(this.rejectedDir, `${id}.md`);
221
+ (0, import_node_fs2.renameSync)(src, dest);
222
+ return true;
223
+ }
224
+ remove(id) {
225
+ this.validateId(id);
226
+ for (const dir of [this.skillsDir, this.pendingDir, this.rejectedDir]) {
227
+ const path = (0, import_node_path2.join)(dir, `${id}.md`);
228
+ if ((0, import_node_fs2.existsSync)(path)) {
229
+ (0, import_node_fs2.unlinkSync)(path);
230
+ return true;
231
+ }
232
+ }
233
+ return false;
49
234
  }
235
+ // ── Helpers ─────────────────────────────────────────────────────────────
236
+ /**
237
+ * Validate that an id does not contain path traversal characters.
238
+ * Prevents attacks like id="../../.ssh/authorized_keys"
239
+ */
240
+ validateId(id) {
241
+ if (!id || typeof id !== "string") throw new Error("Invalid id");
242
+ if (id.includes("/") || id.includes("\\") || id.includes("..") || id.includes("\0")) {
243
+ throw new Error("Invalid id: path traversal detected");
244
+ }
245
+ }
246
+ readDir(dir) {
247
+ if (!(0, import_node_fs2.existsSync)(dir)) return [];
248
+ const files = (0, import_node_fs2.readdirSync)(dir).filter((f) => f.endsWith(".md"));
249
+ const skills = [];
250
+ for (const f of files) {
251
+ const skill = this.readFile((0, import_node_path2.join)(dir, f));
252
+ if (skill) skills.push(skill);
253
+ }
254
+ return skills.sort((a, b) => b.created_at.localeCompare(a.created_at));
255
+ }
256
+ readFile(path) {
257
+ if (!(0, import_node_fs2.existsSync)(path)) return null;
258
+ try {
259
+ const content = (0, import_node_fs2.readFileSync)(path, "utf-8");
260
+ return this.parseSkillFile(content, (0, import_node_path2.basename)(path, ".md"));
261
+ } catch {
262
+ return null;
263
+ }
264
+ }
265
+ writeFile(path, skill) {
266
+ const content = this.formatSkillFile(skill);
267
+ (0, import_node_fs2.writeFileSync)(path, content, "utf-8");
268
+ }
269
+ parseSkillFile(content, id) {
270
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
271
+ if (!match) return null;
272
+ const [, frontmatter, body] = match;
273
+ const meta = {};
274
+ for (const line of frontmatter.split("\n")) {
275
+ const colonIdx = line.indexOf(":");
276
+ if (colonIdx === -1) continue;
277
+ const key = line.slice(0, colonIdx).trim();
278
+ const value = line.slice(colonIdx + 1).trim();
279
+ if (key && value) meta[key] = value;
280
+ }
281
+ return {
282
+ id,
283
+ name: meta.name ?? id,
284
+ instruction: body.trim(),
285
+ learned_from: meta.learned_from ?? "unknown",
286
+ source: meta.source ?? "conversation",
287
+ confidence: parseFloat(meta.confidence ?? "0.5"),
288
+ approved_at: meta.approved_at || void 0,
289
+ created_at: meta.created_at ?? (/* @__PURE__ */ new Date()).toISOString()
290
+ };
291
+ }
292
+ formatSkillFile(skill) {
293
+ const lines = [
294
+ "---",
295
+ `name: ${skill.name}`,
296
+ `learned_from: ${skill.learned_from}`,
297
+ `source: ${skill.source}`,
298
+ `confidence: ${skill.confidence}`,
299
+ `created_at: ${skill.created_at}`
300
+ ];
301
+ if (skill.approved_at) lines.push(`approved_at: ${skill.approved_at}`);
302
+ lines.push("---");
303
+ lines.push("");
304
+ lines.push(skill.instruction);
305
+ lines.push("");
306
+ return lines.join("\n");
307
+ }
308
+ generateId(name) {
309
+ const clean = name.toLowerCase().replace(/[^a-z0-9]+/g, "_").slice(0, 40);
310
+ const hash = (0, import_node_crypto.createHash)("sha256").update(`${name}${Date.now()}${Math.random()}`).digest("hex").slice(0, 6);
311
+ return `${clean}_${hash}`;
312
+ }
313
+ };
314
+
315
+ // src/skills/trust.ts
316
+ var import_node_fs3 = require("fs");
317
+ var import_node_path3 = require("path");
318
+ var DEFAULT_TRUST = {
319
+ trusted: [],
320
+ blocked: [],
321
+ default: "pending"
322
+ };
323
+ var DEFAULT_RATE_LIMITS = {
324
+ max_lessons_per_day: 20,
325
+ max_lessons_per_agent: 10,
326
+ max_skills_per_call: 15
327
+ };
328
+ var TrustManager = class {
329
+ trustPath;
330
+ statsPath;
331
+ config;
332
+ dailyCounts;
333
+ constructor(baseDir) {
334
+ this.trustPath = (0, import_node_path3.join)(baseDir, "trust.json");
335
+ this.statsPath = (0, import_node_path3.join)(baseDir, "state", "daily_counts.json");
336
+ (0, import_node_fs3.mkdirSync)((0, import_node_path3.join)(baseDir, "state"), { recursive: true });
337
+ this.config = this.loadTrust();
338
+ this.dailyCounts = this.loadDailyCounts();
339
+ }
340
+ getLevel(agentId) {
341
+ if (this.config.trusted.includes(agentId)) return "trusted";
342
+ if (this.config.blocked.includes(agentId)) return "blocked";
343
+ return this.config.default;
344
+ }
345
+ setLevel(agentId, level) {
346
+ this.config.trusted = this.config.trusted.filter((a) => a !== agentId);
347
+ this.config.blocked = this.config.blocked.filter((a) => a !== agentId);
348
+ if (level === "trusted") this.config.trusted.push(agentId);
349
+ if (level === "blocked") this.config.blocked.push(agentId);
350
+ this.saveTrust();
351
+ }
352
+ setDefault(level) {
353
+ this.config.default = level;
354
+ this.saveTrust();
355
+ }
356
+ getConfig() {
357
+ return { ...this.config };
358
+ }
359
+ // ── Rate Limiting ───────────────────────────────────────────────────────
360
+ canLearn(agentId, limits = DEFAULT_RATE_LIMITS) {
361
+ this.refreshDailyCountsIfNewDay();
362
+ if (this.dailyCounts.total >= limits.max_lessons_per_day) return false;
363
+ const agentCount = this.dailyCounts.perAgent[agentId] ?? 0;
364
+ if (agentCount >= limits.max_lessons_per_agent) return false;
365
+ return true;
366
+ }
367
+ recordLesson(agentId) {
368
+ this.refreshDailyCountsIfNewDay();
369
+ this.dailyCounts.total++;
370
+ this.dailyCounts.perAgent[agentId] = (this.dailyCounts.perAgent[agentId] ?? 0) + 1;
371
+ this.saveDailyCounts();
372
+ }
373
+ getDailyCounts() {
374
+ this.refreshDailyCountsIfNewDay();
375
+ return { total: this.dailyCounts.total, perAgent: { ...this.dailyCounts.perAgent } };
376
+ }
377
+ // ── Private ─────────────────────────────────────────────────────────────
378
+ loadTrust() {
379
+ if (!(0, import_node_fs3.existsSync)(this.trustPath)) return { ...DEFAULT_TRUST, trusted: [], blocked: [] };
380
+ try {
381
+ const raw = JSON.parse((0, import_node_fs3.readFileSync)(this.trustPath, "utf-8"));
382
+ return {
383
+ trusted: Array.isArray(raw.trusted) ? raw.trusted.filter((a) => typeof a === "string") : [],
384
+ blocked: Array.isArray(raw.blocked) ? raw.blocked.filter((a) => typeof a === "string") : [],
385
+ default: ["trusted", "pending", "blocked"].includes(raw.default) ? raw.default : "pending"
386
+ };
387
+ } catch {
388
+ return { ...DEFAULT_TRUST, trusted: [], blocked: [] };
389
+ }
390
+ }
391
+ saveTrust() {
392
+ (0, import_node_fs3.mkdirSync)((0, import_node_path3.dirname)(this.trustPath), { recursive: true });
393
+ (0, import_node_fs3.writeFileSync)(this.trustPath, JSON.stringify(this.config, null, 2), "utf-8");
394
+ }
395
+ loadDailyCounts() {
396
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
397
+ if (!(0, import_node_fs3.existsSync)(this.statsPath)) return { date: today, total: 0, perAgent: {} };
398
+ try {
399
+ const data = JSON.parse((0, import_node_fs3.readFileSync)(this.statsPath, "utf-8"));
400
+ if (data.date !== today) return { date: today, total: 0, perAgent: {} };
401
+ return data;
402
+ } catch {
403
+ return { date: today, total: 0, perAgent: {} };
404
+ }
405
+ }
406
+ saveDailyCounts() {
407
+ (0, import_node_fs3.writeFileSync)(this.statsPath, JSON.stringify(this.dailyCounts, null, 2), "utf-8");
408
+ }
409
+ refreshDailyCountsIfNewDay() {
410
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
411
+ if (this.dailyCounts.date !== today) {
412
+ this.dailyCounts = { date: today, total: 0, perAgent: {} };
413
+ }
414
+ }
415
+ };
416
+
417
+ // src/skills/format.ts
418
+ function formatSkillsForInjection(skills) {
419
+ if (skills.length === 0) return "";
420
+ const lines = skills.map((s) => {
421
+ const source = s.source === "peer_review" ? "from a peer review" : s.source === "collaboration" ? "from a collaboration" : s.source === "teaching" ? "from being taught" : "from a conversation";
422
+ return `- ${s.instruction} (${source})`;
423
+ });
424
+ return [
425
+ "## Lessons learned from other agents",
426
+ "",
427
+ "You have learned the following from interactions with other agents. Follow these instructions:",
428
+ "",
429
+ ...lines
430
+ ].join("\n");
50
431
  }
51
- async function init(args) {
52
- const forceSupabase = args.includes("--supabase");
53
- const forceSqlite = args.includes("--sqlite");
54
- const supabaseUrl = process.env.SUPABASE_URL ?? process.env.NEXT_PUBLIC_SUPABASE_URL;
55
- const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY ?? process.env.SUPABASE_KEY;
56
- const useSupabase = forceSupabase || !forceSqlite && supabaseUrl && supabaseKey;
57
- if (useSupabase) {
58
- if (!supabaseUrl || !supabaseKey) {
59
- console.error("Error: SUPABASE_URL and SUPABASE_KEY (or SUPABASE_SERVICE_ROLE_KEY) must be set.");
60
- process.exit(1);
432
+ function injectSkillsIntoMessages(messages, skillText) {
433
+ if (!skillText) return;
434
+ const sysIdx = messages.findIndex((m) => m.role === "system");
435
+ if (sysIdx >= 0) {
436
+ messages[sysIdx].content = skillText + "\n\n---\n\n" + messages[sysIdx].content;
437
+ } else {
438
+ messages.unshift({ role: "system", content: skillText });
439
+ }
440
+ }
441
+
442
+ // src/proxy/detector.ts
443
+ var CHANNEL_PATTERN = /^\[([^\]]+)\s+says?\]:\s*/;
444
+ var DM_PATTERN = /^DM\s+from\s+([^:]+):\s*/;
445
+ var BUILDING_PATTERN = /^([a-zA-Z0-9]+[-_][a-zA-Z0-9_.-]+)\s+in\s+[^:]+:\s*/;
446
+ var REVIEW_KEYWORDS = ["strengths:", "weaknesses:", "verdict:", "assessment:", "suggestions:"];
447
+ function detectAgentConversation(messages) {
448
+ const negative = { isAgentToAgent: false };
449
+ if (!messages || messages.length === 0) return negative;
450
+ for (const msg of messages) {
451
+ if (msg.role !== "user" && msg.role !== "assistant") continue;
452
+ const content = typeof msg.content === "string" ? msg.content : "";
453
+ if (msg.name && msg.role === "user") {
454
+ return {
455
+ isAgentToAgent: true,
456
+ otherAgentId: msg.name,
457
+ exchangeType: "chat"
458
+ };
61
459
  }
62
- console.log("Initializing become tables in Supabase...");
460
+ const channelMatch = content.match(CHANNEL_PATTERN);
461
+ if (channelMatch) {
462
+ return {
463
+ isAgentToAgent: true,
464
+ otherAgentId: channelMatch[1].trim(),
465
+ exchangeType: "channel"
466
+ };
467
+ }
468
+ const dmMatch = content.match(DM_PATTERN);
469
+ if (dmMatch) {
470
+ return {
471
+ isAgentToAgent: true,
472
+ otherAgentId: dmMatch[1].trim(),
473
+ exchangeType: "dm"
474
+ };
475
+ }
476
+ const buildingMatch = content.match(BUILDING_PATTERN);
477
+ if (buildingMatch) {
478
+ return {
479
+ isAgentToAgent: true,
480
+ otherAgentId: buildingMatch[1].trim(),
481
+ exchangeType: "chat"
482
+ };
483
+ }
484
+ const lowerContent = content.toLowerCase();
485
+ const reviewMatches = REVIEW_KEYWORDS.filter((kw) => lowerContent.includes(kw));
486
+ if (reviewMatches.length >= 2) {
487
+ return {
488
+ isAgentToAgent: true,
489
+ otherAgentId: void 0,
490
+ exchangeType: "peer_review"
491
+ };
492
+ }
493
+ }
494
+ return negative;
495
+ }
496
+ function extractExchangeText(messages) {
497
+ return messages.filter((m) => m.role === "user" || m.role === "assistant").map((m) => {
498
+ const speaker = m.name ?? m.role;
499
+ const content = typeof m.content === "string" ? m.content : "";
500
+ return `[${speaker}]: ${content}`;
501
+ }).join("\n").slice(0, 6e3);
502
+ }
503
+
504
+ // src/proxy/extractor.ts
505
+ var LessonExtractor = class {
506
+ constructor(store, trust, analyzer) {
507
+ this.store = store;
508
+ this.trust = trust;
509
+ this.analyzer = analyzer;
510
+ }
511
+ /**
512
+ * Analyze a conversation and extract lessons. Fire-and-forget.
513
+ */
514
+ async extract(messages) {
515
+ const detection = detectAgentConversation(messages);
516
+ if (!detection.isAgentToAgent) return;
517
+ const agentId = detection.otherAgentId ?? "unknown-agent";
518
+ const trustLevel = this.trust.getLevel(agentId);
519
+ if (trustLevel === "blocked") return;
520
+ if (!this.trust.canLearn(agentId)) return;
521
+ const exchangeText = extractExchangeText(messages);
522
+ if (exchangeText.length < 20) return;
523
+ const prompt = `Analyze this conversation between an AI agent and another agent. Extract concrete, actionable lessons that the first agent (the "assistant") can learn from the other agent.
524
+
525
+ CONVERSATION:
526
+ ${exchangeText.slice(0, 4e3)}
527
+
528
+ Output valid JSON array:
529
+ [{"skill": "skill_name_snake_case", "instruction": "concrete actionable lesson in 1-2 sentences", "confidence": 0.0-1.0}]
530
+
531
+ Rules:
532
+ - Only extract lessons where the other agent clearly teaches, corrects, or shares useful knowledge
533
+ - instruction must be concrete and actionable ("use X when Y" not "consider improving")
534
+ - confidence: 0.9 = explicitly taught, 0.7 = clearly implied, 0.5 = suggested, below 0.5 = skip
535
+ - Only include lessons with confidence >= 0.5
536
+ - Max 3 lessons per conversation
537
+ - If no real learning happened, return []`;
63
538
  try {
64
- const { createClient } = await import("@supabase/supabase-js");
65
- const supabase = createClient(supabaseUrl, supabaseKey);
66
- const migrationPath = (0, import_node_path.join)(__dirname, "..", "migrations", "001_initial.sql");
67
- let sql;
68
- try {
69
- sql = (0, import_node_fs.readFileSync)(migrationPath, "utf-8");
70
- } catch {
71
- const altPath = (0, import_node_path.join)(__dirname, "..", "..", "migrations", "001_initial.sql");
72
- sql = (0, import_node_fs.readFileSync)(altPath, "utf-8");
539
+ const response = await this.analyzer.analyze(prompt);
540
+ const lessons = this.parseLessons(response);
541
+ for (const lesson of lessons.slice(0, 3)) {
542
+ const saved = this.store.savePending({
543
+ name: lesson.skill,
544
+ instruction: lesson.instruction.slice(0, 500),
545
+ learned_from: agentId,
546
+ source: detection.exchangeType ?? "conversation",
547
+ confidence: lesson.confidence,
548
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
549
+ });
550
+ if (saved) {
551
+ this.trust.recordLesson(agentId);
552
+ if (trustLevel === "trusted") {
553
+ this.store.approve(saved.id);
554
+ }
555
+ }
73
556
  }
74
- const { error } = await supabase.rpc("exec_sql", { sql_query: sql }).single();
75
- if (error) {
76
- console.log("Note: Run the migration SQL manually if RPC is not available.");
77
- console.log(`Migration file: migrations/001_initial.sql`);
557
+ } catch {
558
+ }
559
+ }
560
+ parseLessons(response) {
561
+ const jsonMatch = response.match(/\[[\s\S]*\]/);
562
+ if (!jsonMatch) return [];
563
+ try {
564
+ const parsed = JSON.parse(jsonMatch[0]);
565
+ if (!Array.isArray(parsed)) return [];
566
+ return parsed.filter(
567
+ (r) => typeof r.skill === "string" && typeof r.instruction === "string" && typeof r.confidence === "number" && r.confidence >= 0.5 && r.skill.length > 0 && r.instruction.length > 0
568
+ );
569
+ } catch {
570
+ return [];
571
+ }
572
+ }
573
+ };
574
+
575
+ // src/proxy/server.ts
576
+ var SKILL_CACHE_TTL_MS = 5e3;
577
+ function createProxyServer(config, analyzer) {
578
+ const store = new FileSkillStore({ baseDir: config.baseDir });
579
+ const trust = new TrustManager(config.baseDir);
580
+ const extractor = analyzer ? new LessonExtractor(store, trust, analyzer) : null;
581
+ const stats = {
582
+ requests_forwarded: 0,
583
+ skills_injected: 0,
584
+ lessons_extracted: 0,
585
+ started_at: (/* @__PURE__ */ new Date()).toISOString()
586
+ };
587
+ let cachedSkills = [];
588
+ let cacheTimestamp = 0;
589
+ function getSkills() {
590
+ const now = Date.now();
591
+ if (now - cacheTimestamp > SKILL_CACHE_TTL_MS) {
592
+ cachedSkills = store.listApproved();
593
+ cacheTimestamp = now;
594
+ }
595
+ return cachedSkills;
596
+ }
597
+ const server = (0, import_node_http.createServer)(async (req, res) => {
598
+ if (req.url === "/health" && req.method === "GET") {
599
+ res.writeHead(200, { "Content-Type": "application/json" });
600
+ res.end(JSON.stringify({ status: "ok", ...stats }));
601
+ return;
602
+ }
603
+ const isOpenAI = req.url === "/v1/chat/completions";
604
+ const isAnthropic = req.url === "/v1/messages";
605
+ if (req.method !== "POST" || !isOpenAI && !isAnthropic) {
606
+ res.writeHead(404, { "Content-Type": "application/json" });
607
+ res.end(JSON.stringify({ error: "Not found. Use POST /v1/chat/completions or /v1/messages" }));
608
+ return;
609
+ }
610
+ try {
611
+ const rawBody = await readBody(req);
612
+ const body = JSON.parse(rawBody);
613
+ const messages = body.messages;
614
+ if (Array.isArray(messages)) {
615
+ const skills = getSkills().slice(0, config.max_skills_per_call);
616
+ if (skills.length > 0) {
617
+ const skillText = formatSkillsForInjection(skills);
618
+ injectSkillsIntoMessages(messages, skillText);
619
+ stats.skills_injected++;
620
+ }
621
+ }
622
+ const upstreamUrl = buildUpstreamUrl(config, req.url);
623
+ const upstreamHeaders = buildUpstreamHeaders(config, req.headers);
624
+ const isStreaming = body.stream === true;
625
+ const modifiedBody = JSON.stringify(body);
626
+ const upstreamRes = await fetch(upstreamUrl, {
627
+ method: "POST",
628
+ headers: upstreamHeaders,
629
+ body: modifiedBody
630
+ });
631
+ stats.requests_forwarded++;
632
+ const responseHeaders = {};
633
+ upstreamRes.headers.forEach((value, key) => {
634
+ if (key.toLowerCase() !== "transfer-encoding") {
635
+ responseHeaders[key] = value;
636
+ }
637
+ });
638
+ res.writeHead(upstreamRes.status, responseHeaders);
639
+ if (isStreaming && upstreamRes.body) {
640
+ const reader = upstreamRes.body.getReader();
641
+ try {
642
+ while (true) {
643
+ const { done, value } = await reader.read();
644
+ if (done) break;
645
+ res.write(value);
646
+ }
647
+ } finally {
648
+ res.end();
649
+ }
650
+ if (config.auto_extract && extractor && Array.isArray(messages)) {
651
+ extractor.extract(messages).then(() => {
652
+ stats.lessons_extracted++;
653
+ }).catch(() => {
654
+ });
655
+ }
78
656
  } else {
79
- console.log("Done! Tables created successfully.");
657
+ const responseBuffer = await upstreamRes.arrayBuffer();
658
+ res.end(Buffer.from(responseBuffer));
659
+ if (config.auto_extract && extractor && Array.isArray(messages)) {
660
+ extractor.extract(messages).then(() => {
661
+ stats.lessons_extracted++;
662
+ }).catch(() => {
663
+ });
664
+ }
80
665
  }
81
666
  } catch (err) {
82
- console.error("Failed to initialize:", err.message);
83
- console.log("\nYou can run the migration manually:");
84
- console.log(" migrations/001_initial.sql");
85
- process.exit(1);
667
+ const safeMessage = err instanceof Error && err.message === "Request body too large" ? "Request body too large" : "Failed to forward request to LLM";
668
+ if (!res.headersSent) {
669
+ res.writeHead(502, { "Content-Type": "application/json" });
670
+ }
671
+ res.end(JSON.stringify({ error: safeMessage }));
86
672
  }
673
+ });
674
+ return {
675
+ server,
676
+ stats,
677
+ store,
678
+ trust,
679
+ listen: (port) => {
680
+ const p = port ?? config.port;
681
+ return new Promise((resolve) => {
682
+ server.listen(p, "127.0.0.1", () => resolve());
683
+ });
684
+ },
685
+ close: () => new Promise((resolve) => server.close(() => resolve()))
686
+ };
687
+ }
688
+ function readBody(req) {
689
+ return new Promise((resolve, reject) => {
690
+ const chunks = [];
691
+ let size = 0;
692
+ const MAX_BODY = 10 * 1024 * 1024;
693
+ req.on("data", (chunk) => {
694
+ size += chunk.length;
695
+ if (size > MAX_BODY) {
696
+ req.destroy();
697
+ reject(new Error("Request body too large"));
698
+ return;
699
+ }
700
+ chunks.push(chunk);
701
+ });
702
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
703
+ req.on("error", reject);
704
+ });
705
+ }
706
+ function buildUpstreamUrl(config, path) {
707
+ const base = config.llm_base_url.replace(/\/+$/, "");
708
+ return `${base}${path}`;
709
+ }
710
+ function buildUpstreamHeaders(config, incomingHeaders) {
711
+ const headers = {
712
+ "Content-Type": "application/json"
713
+ };
714
+ if (config.llm_provider === "anthropic") {
715
+ headers["x-api-key"] = config.llm_api_key;
716
+ headers["anthropic-version"] = "2023-06-01";
717
+ const version = incomingHeaders["anthropic-version"];
718
+ if (typeof version === "string") headers["anthropic-version"] = version;
719
+ const beta = incomingHeaders["anthropic-beta"];
720
+ if (typeof beta === "string") headers["anthropic-beta"] = beta;
87
721
  } else {
88
- console.log("No Supabase credentials found. Using in-memory store for now.");
89
- console.log("Set SUPABASE_URL and SUPABASE_KEY to use Supabase, or use --sqlite for local storage.");
722
+ headers["Authorization"] = `Bearer ${config.llm_api_key}`;
90
723
  }
91
- console.log(`
92
- Quickstart:
724
+ const accept = incomingHeaders["accept"];
725
+ if (typeof accept === "string") headers["Accept"] = accept;
726
+ return headers;
727
+ }
728
+
729
+ // src/dashboard/server.ts
730
+ var import_node_http2 = require("http");
731
+
732
+ // src/dashboard/api/handlers.ts
733
+ function createHandlers(deps) {
734
+ const { store, trust, getProxyStats, getState, setState } = deps;
735
+ return {
736
+ // ── Status ──────────────────────────────────────────────────────────
737
+ "GET /api/status": () => ({
738
+ state: getState(),
739
+ skills_count: store.listApproved().length,
740
+ pending_count: store.listPending().length,
741
+ rejected_count: store.listRejected().length,
742
+ proxy: getProxyStats()
743
+ }),
744
+ // ── State toggle ────────────────────────────────────────────────────
745
+ "POST /api/state": (body) => {
746
+ const newState = body?.state;
747
+ if (newState !== "on" && newState !== "off") {
748
+ return { error: 'state must be "on" or "off"' };
749
+ }
750
+ try {
751
+ setState(newState);
752
+ return { state: newState };
753
+ } catch (err) {
754
+ const msg = err instanceof Error ? err.message : "Failed to change state";
755
+ return { error: msg };
756
+ }
757
+ },
758
+ // ── Skills (approved) ───────────────────────────────────────────────
759
+ "GET /api/skills": () => store.listApproved(),
760
+ // ── Pending ─────────────────────────────────────────────────────────
761
+ "GET /api/pending": () => store.listPending(),
762
+ // ── Rejected ────────────────────────────────────────────────────────
763
+ "GET /api/rejected": () => store.listRejected(),
764
+ // ── Approve ─────────────────────────────────────────────────────────
765
+ "POST /api/approve": (body) => {
766
+ const id = body?.id;
767
+ if (!id || typeof id !== "string") return { error: "id required" };
768
+ const ok = store.approve(id);
769
+ return ok ? { ok: true } : { error: "not found" };
770
+ },
771
+ // ── Reject ──────────────────────────────────────────────────────────
772
+ "POST /api/reject": (body) => {
773
+ const id = body?.id;
774
+ if (!id || typeof id !== "string") return { error: "id required" };
775
+ const ok = store.reject(id);
776
+ return ok ? { ok: true } : { error: "not found" };
777
+ },
778
+ // ── Disable (approved → rejected) ───────────────────────────────────
779
+ "POST /api/disable": (body) => {
780
+ const id = body?.id;
781
+ if (!id || typeof id !== "string") return { error: "id required" };
782
+ const ok = store.disable(id);
783
+ return ok ? { ok: true } : { error: "not found" };
784
+ },
785
+ // ── Remove permanently ──────────────────────────────────────────────
786
+ "DELETE /api/skill": (body) => {
787
+ const id = body?.id;
788
+ if (!id || typeof id !== "string") return { error: "id required" };
789
+ const ok = store.remove(id);
790
+ return ok ? { ok: true } : { error: "not found" };
791
+ },
792
+ // ── Trust ───────────────────────────────────────────────────────────
793
+ "GET /api/trust": () => trust.getConfig(),
794
+ "POST /api/trust": (body) => {
795
+ const { agent, level } = body ?? {};
796
+ if (!agent || typeof agent !== "string") return { error: "agent required" };
797
+ if (!["trusted", "pending", "blocked"].includes(level)) return { error: "level must be trusted/pending/blocked" };
798
+ trust.setLevel(agent, level);
799
+ return { ok: true };
800
+ },
801
+ "POST /api/trust/default": (body) => {
802
+ const { level } = body ?? {};
803
+ if (!["trusted", "pending", "blocked"].includes(level)) return { error: "level must be trusted/pending/blocked" };
804
+ trust.setDefault(level);
805
+ return { ok: true };
806
+ },
807
+ // ── Network ─────────────────────────────────────────────────────────
808
+ "GET /api/network": () => {
809
+ const approved = store.listApproved();
810
+ const pending = store.listPending();
811
+ const all = [...approved, ...pending];
812
+ const agents = {};
813
+ for (const skill of all) {
814
+ const id = skill.learned_from;
815
+ if (!agents[id]) {
816
+ agents[id] = { lessons: 0, skills: [], trust: trust.getLevel(id) };
817
+ }
818
+ agents[id].lessons++;
819
+ if (!agents[id].skills.includes(skill.name)) {
820
+ agents[id].skills.push(skill.name);
821
+ }
822
+ }
823
+ return agents;
824
+ },
825
+ // ── Stats ───────────────────────────────────────────────────────────
826
+ "GET /api/stats": () => {
827
+ const counts = trust.getDailyCounts();
828
+ return {
829
+ today_lessons: counts.total,
830
+ today_per_agent: counts.perAgent,
831
+ total_approved: store.listApproved().length,
832
+ total_pending: store.listPending().length,
833
+ total_rejected: store.listRejected().length,
834
+ proxy: getProxyStats()
835
+ };
836
+ }
837
+ };
838
+ }
839
+
840
+ // src/dashboard/ui.ts
841
+ function renderDashboardHTML() {
842
+ return `<!DOCTYPE html>
843
+ <html lang="en">
844
+ <head>
845
+ <meta charset="utf-8">
846
+ <meta name="viewport" content="width=device-width, initial-scale=1">
847
+ <title>become</title>
848
+ <style>
849
+ :root { --bg: #0a0a0a; --card: #141414; --border: #262626; --text: #e5e5e5; --dim: #737373; --accent: #22d3ee; --green: #22c55e; --red: #ef4444; --amber: #f59e0b; }
850
+ * { box-sizing: border-box; margin: 0; padding: 0; }
851
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: var(--bg); color: var(--text); line-height: 1.6; }
852
+ .container { max-width: 800px; margin: 0 auto; padding: 20px; }
853
+ h1 { font-size: 24px; font-weight: 600; margin-bottom: 4px; }
854
+ h2 { font-size: 18px; font-weight: 600; margin-bottom: 12px; color: var(--accent); }
855
+ .subtitle { color: var(--dim); font-size: 14px; margin-bottom: 24px; }
856
+
857
+ /* Nav */
858
+ nav { display: flex; gap: 4px; margin-bottom: 24px; border-bottom: 1px solid var(--border); padding-bottom: 8px; }
859
+ nav button { background: none; border: none; color: var(--dim); font-size: 14px; padding: 8px 16px; cursor: pointer; border-radius: 6px 6px 0 0; }
860
+ nav button:hover { color: var(--text); }
861
+ nav button.active { color: var(--accent); border-bottom: 2px solid var(--accent); }
862
+
863
+ /* Status bar */
864
+ .status-bar { display: flex; gap: 16px; align-items: center; margin-bottom: 24px; padding: 12px 16px; background: var(--card); border: 1px solid var(--border); border-radius: 8px; font-size: 13px; }
865
+ .status-dot { width: 8px; height: 8px; border-radius: 50%; }
866
+ .status-dot.on { background: var(--green); }
867
+ .status-dot.off { background: var(--red); }
868
+ .status-label { font-weight: 600; }
869
+ .status-stat { color: var(--dim); }
870
+
871
+ /* Cards */
872
+ .card { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 16px; margin-bottom: 12px; }
873
+ .card-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 8px; }
874
+ .card-instruction { font-size: 15px; margin-bottom: 8px; }
875
+ .card-meta { font-size: 12px; color: var(--dim); display: flex; gap: 12px; flex-wrap: wrap; }
876
+ .card-meta span { display: inline-flex; align-items: center; gap: 4px; }
877
+
878
+ /* Buttons */
879
+ .btn { border: none; padding: 6px 14px; border-radius: 6px; font-size: 13px; cursor: pointer; font-weight: 500; }
880
+ .btn-approve { background: var(--green); color: #000; }
881
+ .btn-reject { background: var(--red); color: #fff; }
882
+ .btn-disable { background: var(--border); color: var(--text); }
883
+ .btn-trust { background: var(--accent); color: #000; }
884
+ .btn-small { padding: 4px 10px; font-size: 12px; }
885
+ .btn:hover { opacity: 0.85; }
886
+ .btn-group { display: flex; gap: 6px; }
887
+
888
+ /* Badge */
889
+ .badge { font-size: 11px; padding: 2px 8px; border-radius: 10px; font-weight: 500; }
890
+ .badge-trusted { background: rgba(34,197,94,0.15); color: var(--green); }
891
+ .badge-pending { background: rgba(245,158,11,0.15); color: var(--amber); }
892
+ .badge-blocked { background: rgba(239,68,68,0.15); color: var(--red); }
893
+
894
+ /* Empty state */
895
+ .empty { text-align: center; padding: 40px; color: var(--dim); }
896
+
897
+ /* Page sections */
898
+ .page { display: none; }
899
+ .page.active { display: block; }
900
+
901
+ /* Trust settings */
902
+ .trust-row { display: flex; justify-content: space-between; align-items: center; padding: 10px 0; border-bottom: 1px solid var(--border); }
903
+ .trust-row:last-child { border: none; }
904
+ .trust-agent { font-weight: 500; }
905
+ .trust-lessons { font-size: 13px; color: var(--dim); }
906
+
907
+ /* Skill group */
908
+ .skill-group { margin-bottom: 20px; }
909
+ .skill-group-name { font-size: 13px; text-transform: uppercase; color: var(--dim); letter-spacing: 0.5px; margin-bottom: 8px; }
910
+
911
+ /* Toggle */
912
+ .toggle { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
913
+ .toggle-switch { width: 44px; height: 24px; background: var(--border); border-radius: 12px; position: relative; cursor: pointer; transition: background 0.2s; }
914
+ .toggle-switch.on { background: var(--green); }
915
+ .toggle-switch::after { content: ''; position: absolute; width: 18px; height: 18px; background: white; border-radius: 50%; top: 3px; left: 3px; transition: transform 0.2s; }
916
+ .toggle-switch.on::after { transform: translateX(20px); }
917
+ </style>
918
+ </head>
919
+ <body>
920
+ <div class="container">
921
+ <h1>become</h1>
922
+ <p class="subtitle">agent-to-agent learning</p>
93
923
 
94
- import { Become, MemoryStore } from '@openclaw/become';
924
+ <div class="status-bar" id="status-bar">
925
+ <div class="status-dot" id="status-dot"></div>
926
+ <span class="status-label" id="status-label">Loading...</span>
927
+ <span class="status-stat" id="status-skills"></span>
928
+ <span class="status-stat" id="status-pending"></span>
929
+ </div>
95
930
 
96
- const become = new Become({ store: new MemoryStore() });
931
+ <nav>
932
+ <button class="active" onclick="showPage('pending',this)">Pending</button>
933
+ <button onclick="showPage('skills',this)">Active Skills</button>
934
+ <button onclick="showPage('network',this)">Network</button>
935
+ <button onclick="showPage('settings',this)">Settings</button>
936
+ </nav>
97
937
 
98
- // Register a skill
99
- await become.skills.upsert('agent-1', { name: 'debugging', category: 'coding' });
938
+ <!-- Pending Page -->
939
+ <div id="page-pending" class="page active"></div>
100
940
 
101
- // Score it
102
- const score = become.scorer.computeFullScore('debugging', {
103
- artifact_count: 5, total_reactions: 12, recent_reaction_avg: 4,
104
- older_reaction_avg: 2, unique_types: 3, collab_count: 1,
105
- peer_reviews_given: 0, peer_reviews_received: 1,
106
- follower_count: 2, teaching_events: 0,
941
+ <!-- Skills Page -->
942
+ <div id="page-skills" class="page"></div>
943
+
944
+ <!-- Network Page -->
945
+ <div id="page-network" class="page"></div>
946
+
947
+ <!-- Settings Page -->
948
+ <div id="page-settings" class="page"></div>
949
+ </div>
950
+
951
+ <script>
952
+ const API = '';
953
+
954
+ async function api(method, path, body) {
955
+ const opts = { method, headers: { 'Content-Type': 'application/json' } };
956
+ if (body) opts.body = JSON.stringify(body);
957
+ const res = await fetch(API + path, opts);
958
+ return res.json();
959
+ }
960
+
961
+ function showPage(name, btn) {
962
+ document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
963
+ document.getElementById('page-' + name).classList.add('active');
964
+ document.querySelectorAll('nav button').forEach(b => b.classList.remove('active'));
965
+ if (btn) btn.classList.add('active');
966
+ if (name === 'pending') loadPending();
967
+ if (name === 'skills') loadSkills();
968
+ if (name === 'network') loadNetwork();
969
+ if (name === 'settings') loadSettings();
970
+ }
971
+
972
+ // \u2500\u2500 Status Bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
973
+ async function loadStatus() {
974
+ const s = await api('GET', '/api/status');
975
+ const dot = document.getElementById('status-dot');
976
+ const label = document.getElementById('status-label');
977
+ dot.className = 'status-dot ' + s.state;
978
+ label.textContent = s.state.toUpperCase();
979
+ document.getElementById('status-skills').textContent = s.skills_count + ' skills';
980
+ document.getElementById('status-pending').textContent = s.pending_count + ' pending';
981
+ }
982
+
983
+ // \u2500\u2500 Pending Page \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
984
+ async function loadPending() {
985
+ const lessons = await api('GET', '/api/pending');
986
+ const el = document.getElementById('page-pending');
987
+ if (lessons.length === 0) {
988
+ el.innerHTML = '<div class="empty">No pending lessons. Your agent will learn as it talks to other agents.</div>';
989
+ return;
990
+ }
991
+ el.innerHTML = '<h2>Pending Review (' + lessons.length + ')</h2>' +
992
+ lessons.map(l => renderPendingCard(l)).join('');
993
+ }
994
+
995
+ function renderPendingCard(l) {
996
+ return '<div class="card" id="card-' + l.id + '">' +
997
+ '<div class="card-instruction">' + esc(l.instruction) + '</div>' +
998
+ '<div class="card-meta">' +
999
+ '<span>From: <strong>' + esc(l.learned_from) + '</strong></span>' +
1000
+ '<span>Source: ' + esc(l.source) + '</span>' +
1001
+ '<span>Confidence: ' + (l.confidence * 100).toFixed(0) + '%</span>' +
1002
+ '<span>Skill: ' + esc(l.name) + '</span>' +
1003
+ '</div>' +
1004
+ '<div style="margin-top:12px" class="btn-group">' +
1005
+ '<button class="btn btn-approve" onclick="doApprove(\\''+l.id+'\\')">Approve</button>' +
1006
+ '<button class="btn btn-reject" onclick="doReject(\\''+l.id+'\\')">Reject</button>' +
1007
+ '<button class="btn btn-trust btn-small" onclick="doTrustAgent(\\''+esc(l.learned_from)+'\\')">Trust Agent</button>' +
1008
+ '</div>' +
1009
+ '</div>';
1010
+ }
1011
+
1012
+ async function doApprove(id) {
1013
+ await api('POST', '/api/approve', { id });
1014
+ document.getElementById('card-' + id)?.remove();
1015
+ loadStatus();
1016
+ }
1017
+
1018
+ async function doReject(id) {
1019
+ await api('POST', '/api/reject', { id });
1020
+ document.getElementById('card-' + id)?.remove();
1021
+ loadStatus();
1022
+ }
1023
+
1024
+ async function doTrustAgent(agent) {
1025
+ await api('POST', '/api/trust', { agent, level: 'trusted' });
1026
+ loadPending();
1027
+ loadStatus();
1028
+ }
1029
+
1030
+ // \u2500\u2500 Skills Page \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1031
+ async function loadSkills() {
1032
+ const skills = await api('GET', '/api/skills');
1033
+ const el = document.getElementById('page-skills');
1034
+ if (skills.length === 0) {
1035
+ el.innerHTML = '<div class="empty">No active skills yet. Approve pending lessons to activate them.</div>';
1036
+ return;
1037
+ }
1038
+
1039
+ // Group by skill name
1040
+ const groups = {};
1041
+ for (const s of skills) {
1042
+ if (!groups[s.name]) groups[s.name] = [];
1043
+ groups[s.name].push(s);
1044
+ }
1045
+
1046
+ let html = '<h2>Active Skills (' + skills.length + ')</h2>';
1047
+ for (const [name, items] of Object.entries(groups)) {
1048
+ html += '<div class="skill-group"><div class="skill-group-name">' + esc(name) + '</div>';
1049
+ for (const s of items) {
1050
+ html += '<div class="card" id="card-' + s.id + '">' +
1051
+ '<div class="card-header"><div class="card-instruction">' + esc(s.instruction) + '</div>' +
1052
+ '<button class="btn btn-disable btn-small" onclick="doDisable(\\''+s.id+'\\')">Disable</button></div>' +
1053
+ '<div class="card-meta"><span>From: ' + esc(s.learned_from) + '</span><span>Source: ' + esc(s.source) + '</span></div>' +
1054
+ '</div>';
1055
+ }
1056
+ html += '</div>';
1057
+ }
1058
+ el.innerHTML = html;
1059
+ }
1060
+
1061
+ async function doDisable(id) {
1062
+ await api('POST', '/api/disable', { id });
1063
+ document.getElementById('card-' + id)?.remove();
1064
+ loadStatus();
1065
+ }
1066
+
1067
+ // \u2500\u2500 Network Page \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1068
+ async function loadNetwork() {
1069
+ const network = await api('GET', '/api/network');
1070
+ const el = document.getElementById('page-network');
1071
+ const entries = Object.entries(network);
1072
+ if (entries.length === 0) {
1073
+ el.innerHTML = '<div class="empty">No agents have taught yours yet.</div>';
1074
+ return;
1075
+ }
1076
+
1077
+ let html = '<h2>Who Taught Your Agent</h2>';
1078
+ entries.sort((a, b) => b[1].lessons - a[1].lessons);
1079
+ for (const [agent, data] of entries) {
1080
+ const d = data;
1081
+ const badgeClass = 'badge badge-' + d.trust;
1082
+ html += '<div class="card"><div class="trust-row">' +
1083
+ '<div><div class="trust-agent">' + esc(agent) + ' <span class="' + badgeClass + '">' + d.trust + '</span></div>' +
1084
+ '<div class="trust-lessons">' + d.lessons + ' lesson(s): ' + d.skills.map(esc).join(', ') + '</div></div>' +
1085
+ '<div class="btn-group">' +
1086
+ (d.trust !== 'trusted' ? '<button class="btn btn-trust btn-small" onclick="setTrust(\\''+esc(agent)+'\\',\\'trusted\\')">Trust</button>' : '') +
1087
+ (d.trust !== 'blocked' ? '<button class="btn btn-reject btn-small" onclick="setTrust(\\''+esc(agent)+'\\',\\'blocked\\')">Block</button>' : '') +
1088
+ (d.trust !== 'pending' ? '<button class="btn btn-disable btn-small" onclick="setTrust(\\''+esc(agent)+'\\',\\'pending\\')">Reset</button>' : '') +
1089
+ '</div>' +
1090
+ '</div></div>';
1091
+ }
1092
+ el.innerHTML = html;
1093
+ }
1094
+
1095
+ async function setTrust(agent, level) {
1096
+ await api('POST', '/api/trust', { agent, level });
1097
+ loadNetwork();
1098
+ }
1099
+
1100
+ // \u2500\u2500 Settings Page \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1101
+ async function loadSettings() {
1102
+ const status = await api('GET', '/api/status');
1103
+ const trust = await api('GET', '/api/trust');
1104
+ const stats = await api('GET', '/api/stats');
1105
+
1106
+ const el = document.getElementById('page-settings');
1107
+ el.innerHTML = '<h2>Settings</h2>' +
1108
+ '<div class="card">' +
1109
+ '<div class="toggle">' +
1110
+ '<div class="toggle-switch ' + status.state + '" id="toggle-state" onclick="toggleState()"></div>' +
1111
+ '<span>Proxy is <strong>' + status.state.toUpperCase() + '</strong></span>' +
1112
+ '</div>' +
1113
+ '</div>' +
1114
+ '<div class="card">' +
1115
+ '<h2 style="margin-bottom:8px">Default Trust</h2>' +
1116
+ '<div class="card-meta" style="margin-bottom:12px">What happens when a new agent teaches your agent</div>' +
1117
+ '<div class="btn-group">' +
1118
+ '<button class="btn ' + (trust.default === 'pending' ? 'btn-approve' : 'btn-disable') + '" onclick="setDefaultTrust(\\'pending\\')">Pending (review)</button>' +
1119
+ '<button class="btn ' + (trust.default === 'trusted' ? 'btn-approve' : 'btn-disable') + '" onclick="setDefaultTrust(\\'trusted\\')">Auto-approve</button>' +
1120
+ '<button class="btn ' + (trust.default === 'blocked' ? 'btn-approve' : 'btn-disable') + '" onclick="setDefaultTrust(\\'blocked\\')">Block all</button>' +
1121
+ '</div>' +
1122
+ '</div>' +
1123
+ '<div class="card">' +
1124
+ '<h2 style="margin-bottom:8px">Stats</h2>' +
1125
+ '<div class="card-meta" style="flex-direction:column;gap:4px">' +
1126
+ '<span>Approved: ' + stats.total_approved + '</span>' +
1127
+ '<span>Pending: ' + stats.total_pending + '</span>' +
1128
+ '<span>Rejected: ' + stats.total_rejected + '</span>' +
1129
+ '<span>Lessons today: ' + stats.today_lessons + '</span>' +
1130
+ '</div>' +
1131
+ '</div>' +
1132
+ '<div class="card">' +
1133
+ '<h2 style="margin-bottom:8px;color:var(--red)">Danger Zone</h2>' +
1134
+ '<div class="btn-group">' +
1135
+ '<button class="btn btn-reject" onclick="if(confirm(\\'Clear all skills?\\'))clearAll()">Clear All Skills</button>' +
1136
+ '</div>' +
1137
+ '</div>';
1138
+ }
1139
+
1140
+ async function toggleState() {
1141
+ const el = document.getElementById('toggle-state');
1142
+ const newState = el.classList.contains('on') ? 'off' : 'on';
1143
+ await api('POST', '/api/state', { state: newState });
1144
+ loadStatus();
1145
+ loadSettings();
1146
+ }
1147
+
1148
+ async function setDefaultTrust(level) {
1149
+ await api('POST', '/api/trust/default', { level });
1150
+ loadSettings();
1151
+ }
1152
+
1153
+ async function clearAll() {
1154
+ const skills = await api('GET', '/api/skills');
1155
+ for (const s of skills) {
1156
+ await api('DELETE', '/api/skill', { id: s.id });
1157
+ }
1158
+ loadStatus();
1159
+ loadSettings();
1160
+ }
1161
+
1162
+ function esc(s) {
1163
+ if (!s) return '';
1164
+ return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');
1165
+ }
1166
+
1167
+ // Init
1168
+ loadStatus();
1169
+ loadPending();
1170
+ setInterval(loadStatus, 10000);
1171
+ </script>
1172
+ </body>
1173
+ </html>`;
1174
+ }
1175
+
1176
+ // src/dashboard/server.ts
1177
+ function createDashboardServer(deps) {
1178
+ const handlers = createHandlers(deps);
1179
+ const html = renderDashboardHTML();
1180
+ const server = (0, import_node_http2.createServer)(async (req, res) => {
1181
+ const origin = req.headers.origin ?? "";
1182
+ if (origin && /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(origin)) {
1183
+ res.setHeader("Access-Control-Allow-Origin", origin);
1184
+ }
1185
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
1186
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
1187
+ if (req.method === "OPTIONS") {
1188
+ res.writeHead(204);
1189
+ res.end();
1190
+ return;
1191
+ }
1192
+ if (req.url === "/" && req.method === "GET") {
1193
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1194
+ res.end(html);
1195
+ return;
1196
+ }
1197
+ const key = `${req.method} ${req.url}`;
1198
+ const handler = handlers[key];
1199
+ if (!handler) {
1200
+ res.writeHead(404, { "Content-Type": "application/json" });
1201
+ res.end(JSON.stringify({ error: "Not found" }));
1202
+ return;
1203
+ }
1204
+ try {
1205
+ let body;
1206
+ if (req.method === "POST" || req.method === "DELETE") {
1207
+ const raw = await readBody2(req);
1208
+ if (raw) {
1209
+ try {
1210
+ body = JSON.parse(raw);
1211
+ } catch {
1212
+ res.writeHead(400, { "Content-Type": "application/json" });
1213
+ res.end(JSON.stringify({ error: "Invalid JSON body" }));
1214
+ return;
1215
+ }
1216
+ }
1217
+ }
1218
+ const result = await handler(body);
1219
+ res.writeHead(200, { "Content-Type": "application/json" });
1220
+ res.end(JSON.stringify(result));
1221
+ } catch (err) {
1222
+ res.writeHead(500, { "Content-Type": "application/json" });
1223
+ res.end(JSON.stringify({ error: "Internal server error" }));
1224
+ }
1225
+ });
1226
+ return {
1227
+ server,
1228
+ listen: (port) => {
1229
+ return new Promise((resolve) => {
1230
+ server.listen(port, "127.0.0.1", () => resolve());
1231
+ });
1232
+ },
1233
+ close: () => new Promise((resolve) => server.close(() => resolve()))
1234
+ };
1235
+ }
1236
+ function readBody2(req) {
1237
+ return new Promise((resolve, reject) => {
1238
+ const chunks = [];
1239
+ let size = 0;
1240
+ req.on("data", (chunk) => {
1241
+ size += chunk.length;
1242
+ if (size > 1024 * 1024) {
1243
+ req.destroy();
1244
+ reject(new Error("Too large"));
1245
+ return;
1246
+ }
1247
+ chunks.push(chunk);
1248
+ });
1249
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
1250
+ req.on("error", reject);
1251
+ });
1252
+ }
1253
+
1254
+ // src/cli/adapter/openclaw.ts
1255
+ var import_node_fs4 = require("fs");
1256
+ var import_node_path4 = require("path");
1257
+ var import_node_os2 = require("os");
1258
+ var import_node_child_process = require("child_process");
1259
+ var OPENCLAW_CONFIG = (0, import_node_path4.join)((0, import_node_os2.homedir)(), ".openclaw", "openclaw.json");
1260
+ var BACKUP_PATH = (0, import_node_path4.join)((0, import_node_os2.homedir)(), ".become", "state", "original_openclaw.json");
1261
+ function patchOpenClaw(config) {
1262
+ if (!(0, import_node_fs4.existsSync)(OPENCLAW_CONFIG)) {
1263
+ throw new Error(`OpenClaw config not found at ${OPENCLAW_CONFIG}`);
1264
+ }
1265
+ const raw = (0, import_node_fs4.readFileSync)(OPENCLAW_CONFIG, "utf-8");
1266
+ const clawConfig = JSON.parse(raw);
1267
+ (0, import_node_fs4.mkdirSync)((0, import_node_path4.join)((0, import_node_os2.homedir)(), ".become", "state"), { recursive: true });
1268
+ (0, import_node_fs4.writeFileSync)(BACKUP_PATH, raw, "utf-8");
1269
+ if (!clawConfig.models) clawConfig.models = {};
1270
+ if (!clawConfig.models.providers) clawConfig.models.providers = {};
1271
+ clawConfig.models.providers.become = {
1272
+ api: "anthropic-messages",
1273
+ baseUrl: `http://127.0.0.1:${config.proxy_port}`,
1274
+ apiKey: config.llm_api_key
1275
+ };
1276
+ if (clawConfig.agents?.defaults?.model?.primary) {
1277
+ const original = clawConfig.agents.defaults.model.primary;
1278
+ clawConfig.models.providers.become._originalModel = original;
1279
+ const modelId = original.includes("/") ? original.split("/").slice(1).join("/") : original;
1280
+ clawConfig.agents.defaults.model.primary = `become/${modelId}`;
1281
+ }
1282
+ (0, import_node_fs4.writeFileSync)(OPENCLAW_CONFIG, JSON.stringify(clawConfig, null, 2), "utf-8");
1283
+ try {
1284
+ (0, import_node_child_process.execSync)("openclaw gateway restart", { stdio: "pipe", timeout: 15e3 });
1285
+ } catch {
1286
+ console.log("Warning: Could not restart OpenClaw gateway. Restart it manually: openclaw gateway restart");
1287
+ }
1288
+ }
1289
+ function restoreOpenClaw() {
1290
+ if (!(0, import_node_fs4.existsSync)(BACKUP_PATH)) {
1291
+ throw new Error("No backup found. Was become ever turned on?");
1292
+ }
1293
+ const backup = (0, import_node_fs4.readFileSync)(BACKUP_PATH, "utf-8");
1294
+ (0, import_node_fs4.writeFileSync)(OPENCLAW_CONFIG, backup, "utf-8");
1295
+ try {
1296
+ (0, import_node_child_process.execSync)("openclaw gateway restart", { stdio: "pipe", timeout: 15e3 });
1297
+ } catch {
1298
+ console.log("Warning: Could not restart OpenClaw gateway. Restart it manually: openclaw gateway restart");
1299
+ }
1300
+ }
1301
+
1302
+ // src/cli/adapter/ironclaw.ts
1303
+ var import_node_fs5 = require("fs");
1304
+ var import_node_path5 = require("path");
1305
+ var import_node_os3 = require("os");
1306
+ var import_node_child_process2 = require("child_process");
1307
+ var IRONCLAW_ENV = (0, import_node_path5.join)((0, import_node_os3.homedir)(), ".ironclaw", ".env");
1308
+ var BACKUP_PATH2 = (0, import_node_path5.join)((0, import_node_os3.homedir)(), ".become", "state", "original_ironclaw.env");
1309
+ function patchIronClaw(config) {
1310
+ if (!(0, import_node_fs5.existsSync)(IRONCLAW_ENV)) {
1311
+ throw new Error(`IronClaw .env not found at ${IRONCLAW_ENV}`);
1312
+ }
1313
+ (0, import_node_fs5.mkdirSync)((0, import_node_path5.join)((0, import_node_os3.homedir)(), ".become", "state"), { recursive: true });
1314
+ (0, import_node_fs5.copyFileSync)(IRONCLAW_ENV, BACKUP_PATH2);
1315
+ patchDotEnv(IRONCLAW_ENV, {
1316
+ LLM_BASE_URL: `http://127.0.0.1:${config.proxy_port}/v1`
107
1317
  });
1318
+ try {
1319
+ (0, import_node_child_process2.execSync)("ironclaw service restart", { stdio: "pipe", timeout: 15e3 });
1320
+ } catch {
1321
+ console.log("Warning: Could not restart IronClaw. Restart it manually: ironclaw service restart");
1322
+ }
1323
+ }
1324
+ function restoreIronClaw() {
1325
+ if (!(0, import_node_fs5.existsSync)(BACKUP_PATH2)) {
1326
+ throw new Error("No backup found. Was become ever turned on?");
1327
+ }
1328
+ (0, import_node_fs5.copyFileSync)(BACKUP_PATH2, IRONCLAW_ENV);
1329
+ try {
1330
+ (0, import_node_child_process2.execSync)("ironclaw service restart", { stdio: "pipe", timeout: 15e3 });
1331
+ } catch {
1332
+ console.log("Warning: Could not restart IronClaw. Restart it manually: ironclaw service restart");
1333
+ }
1334
+ }
1335
+ function patchDotEnv(path, vars) {
1336
+ let content = (0, import_node_fs5.readFileSync)(path, "utf-8");
1337
+ for (const [key, value] of Object.entries(vars)) {
1338
+ const regex = new RegExp(`^${key}=.*$`, "m");
1339
+ if (regex.test(content)) {
1340
+ content = content.replace(regex, `${key}=${value}`);
1341
+ } else {
1342
+ content += `
1343
+ ${key}=${value}`;
1344
+ }
1345
+ }
1346
+ (0, import_node_fs5.writeFileSync)(path, content, "utf-8");
1347
+ }
108
1348
 
109
- console.log(score.dreyfus_stage); // 'beginner'
1349
+ // src/cli/adapter/nanoclaw.ts
1350
+ var import_node_fs6 = require("fs");
1351
+ var import_node_path6 = require("path");
1352
+ var import_node_os4 = require("os");
1353
+ var import_node_child_process3 = require("child_process");
1354
+ var BACKUP_PATH3 = (0, import_node_path6.join)((0, import_node_os4.homedir)(), ".become", "state", "original_nanoclaw.env");
1355
+ function patchNanoClaw(config) {
1356
+ const envPath = findNanoClawEnv();
1357
+ if (!envPath) {
1358
+ throw new Error("Could not find NanoClaw .env. Set ANTHROPIC_BASE_URL manually to http://127.0.0.1:" + config.proxy_port);
1359
+ }
1360
+ (0, import_node_fs6.mkdirSync)((0, import_node_path6.join)((0, import_node_os4.homedir)(), ".become", "state"), { recursive: true });
1361
+ (0, import_node_fs6.copyFileSync)(envPath, BACKUP_PATH3);
1362
+ patchDotEnv2(envPath, {
1363
+ ANTHROPIC_BASE_URL: `http://127.0.0.1:${config.proxy_port}`
1364
+ });
1365
+ restartNanoClaw();
1366
+ }
1367
+ function restoreNanoClaw() {
1368
+ const envPath = findNanoClawEnv();
1369
+ if (!(0, import_node_fs6.existsSync)(BACKUP_PATH3) || !envPath) {
1370
+ throw new Error("No backup found. Was become ever turned on?");
1371
+ }
1372
+ (0, import_node_fs6.copyFileSync)(BACKUP_PATH3, envPath);
1373
+ restartNanoClaw();
1374
+ }
1375
+ function findNanoClawEnv() {
1376
+ const candidates = [
1377
+ (0, import_node_path6.join)((0, import_node_os4.homedir)(), ".nanoclaw", ".env"),
1378
+ (0, import_node_path6.join)((0, import_node_os4.homedir)(), ".config", "nanoclaw", ".env")
1379
+ ];
1380
+ const plistPath = (0, import_node_path6.join)((0, import_node_os4.homedir)(), "Library", "LaunchAgents", "ai.nanoclaw.agent.plist");
1381
+ if ((0, import_node_fs6.existsSync)(plistPath)) {
1382
+ try {
1383
+ const plist = (0, import_node_fs6.readFileSync)(plistPath, "utf-8");
1384
+ const match = plist.match(/<string>([^<]*\.env)<\/string>/);
1385
+ if (match) candidates.unshift(match[1]);
1386
+ } catch {
1387
+ }
1388
+ }
1389
+ for (const path of candidates) {
1390
+ if ((0, import_node_fs6.existsSync)(path)) return path;
1391
+ }
1392
+ return null;
1393
+ }
1394
+ function restartNanoClaw() {
1395
+ try {
1396
+ (0, import_node_child_process3.execSync)("launchctl kickstart -k gui/$(id -u)/ai.nanoclaw.agent", { stdio: "pipe", timeout: 15e3 });
1397
+ } catch {
1398
+ try {
1399
+ (0, import_node_child_process3.execSync)("systemctl --user restart nanoclaw", { stdio: "pipe", timeout: 15e3 });
1400
+ } catch {
1401
+ console.log("Warning: Could not restart NanoClaw. Restart it manually.");
1402
+ }
1403
+ }
1404
+ }
1405
+ function patchDotEnv2(path, vars) {
1406
+ let content = (0, import_node_fs6.readFileSync)(path, "utf-8");
1407
+ for (const [key, value] of Object.entries(vars)) {
1408
+ const regex = new RegExp(`^${key}=.*$`, "m");
1409
+ if (regex.test(content)) {
1410
+ content = content.replace(regex, `${key}=${value}`);
1411
+ } else {
1412
+ content += `
1413
+ ${key}=${value}`;
1414
+ }
1415
+ }
1416
+ (0, import_node_fs6.writeFileSync)(path, content, "utf-8");
1417
+ }
1418
+
1419
+ // src/cli/commands.ts
1420
+ async function start() {
1421
+ const config = loadConfig();
1422
+ const baseDir = getBecomeDir();
1423
+ const proxyConfig = {
1424
+ port: config.proxy_port,
1425
+ llm_base_url: config.llm_base_url,
1426
+ llm_api_key: config.llm_api_key,
1427
+ llm_provider: config.llm_provider,
1428
+ baseDir,
1429
+ max_skills_per_call: config.max_skills_per_call,
1430
+ auto_extract: config.auto_extract
1431
+ };
1432
+ const proxy = createProxyServer(proxyConfig);
1433
+ await proxy.listen();
1434
+ const dashboard = createDashboardServer({
1435
+ store: proxy.store,
1436
+ trust: proxy.trust,
1437
+ getProxyStats: () => proxy.stats,
1438
+ getState: () => {
1439
+ try {
1440
+ return loadConfig().state;
1441
+ } catch {
1442
+ return "off";
1443
+ }
1444
+ },
1445
+ setState: (state) => {
1446
+ try {
1447
+ if (state === "on") turnOn();
1448
+ else turnOff();
1449
+ } catch (e) {
1450
+ console.error("State change failed:", e);
1451
+ }
1452
+ }
1453
+ });
1454
+ await dashboard.listen(config.dashboard_port);
1455
+ const approved = proxy.store.listApproved().length;
1456
+ const pending = proxy.store.listPending().length;
1457
+ const trustConfig = proxy.trust.getConfig();
1458
+ console.log(`
1459
+ become proxy running on localhost:${config.proxy_port}`);
1460
+ console.log(`become dashboard at http://localhost:${config.dashboard_port}`);
1461
+ console.log(`
1462
+ Skills loaded: ${approved} approved, ${pending} pending`);
1463
+ console.log(`Trust rules: ${trustConfig.trusted.length} trusted, ${trustConfig.blocked.length} blocked`);
1464
+ if (config.state === "on") {
1465
+ console.log("\nProxy is ACTIVE \u2014 your agent is learning from other agents.");
1466
+ } else {
1467
+ console.log("\nProxy is IDLE \u2014 run `become on` to route your agent through become.");
1468
+ }
1469
+ console.log("Use `become off` to disconnect. Ctrl+C to stop.\n");
1470
+ const shutdown = async () => {
1471
+ console.log("\nShutting down...");
1472
+ await Promise.all([proxy.close(), dashboard.close()]);
1473
+ process.exit(0);
1474
+ };
1475
+ process.on("SIGINT", shutdown);
1476
+ process.on("SIGTERM", shutdown);
1477
+ }
1478
+ function turnOn() {
1479
+ const config = loadConfig();
1480
+ console.log(`
1481
+ Patching ${config.agent_type} config...`);
1482
+ console.log(` baseUrl: ${config.llm_base_url} \u2192 localhost:${config.proxy_port}`);
1483
+ switch (config.agent_type) {
1484
+ case "openclaw":
1485
+ patchOpenClaw(config);
1486
+ break;
1487
+ case "ironclaw":
1488
+ patchIronClaw(config);
1489
+ break;
1490
+ case "nanoclaw":
1491
+ patchNanoClaw(config);
1492
+ break;
1493
+ case "generic":
1494
+ console.log(`
1495
+ Set these env vars in your agent's config:`);
1496
+ console.log(` OPENAI_BASE_URL=http://127.0.0.1:${config.proxy_port}/v1`);
1497
+ console.log(` ANTHROPIC_BASE_URL=http://127.0.0.1:${config.proxy_port}`);
1498
+ console.log(`Then restart your agent.
110
1499
  `);
1500
+ break;
1501
+ }
1502
+ config.state = "on";
1503
+ saveConfig(config);
1504
+ console.log("\nbecome is ON. Your agent is now learning from other agents.\n");
1505
+ }
1506
+ function turnOff() {
1507
+ const config = loadConfig();
1508
+ console.log(`
1509
+ Restoring ${config.agent_type} config...`);
1510
+ console.log(` baseUrl: localhost:${config.proxy_port} \u2192 ${config.llm_base_url}`);
1511
+ switch (config.agent_type) {
1512
+ case "openclaw":
1513
+ restoreOpenClaw();
1514
+ break;
1515
+ case "ironclaw":
1516
+ restoreIronClaw();
1517
+ break;
1518
+ case "nanoclaw":
1519
+ restoreNanoClaw();
1520
+ break;
1521
+ case "generic":
1522
+ console.log(`
1523
+ Restore your original env vars and restart your agent.
1524
+ `);
1525
+ break;
1526
+ }
1527
+ config.state = "off";
1528
+ saveConfig(config);
1529
+ console.log("\nbecome is OFF. Your agent talks directly to the LLM.");
1530
+ console.log("Learned skills are preserved \u2014 they'll be injected when you turn become back on.\n");
1531
+ }
1532
+ function showStatus() {
1533
+ const config = loadConfig();
1534
+ const baseDir = getBecomeDir();
1535
+ const store = new FileSkillStore({ baseDir });
1536
+ const trust = new TrustManager(baseDir);
1537
+ const approved = store.listApproved().length;
1538
+ const pending = store.listPending().length;
1539
+ const rejected = store.listRejected().length;
1540
+ const trustConfig = trust.getConfig();
1541
+ const counts = trust.getDailyCounts();
1542
+ console.log(`
1543
+ State: ${config.state.toUpperCase()}`);
1544
+ console.log(`Proxy: localhost:${config.proxy_port}`);
1545
+ console.log(`Dashboard: localhost:${config.dashboard_port}`);
1546
+ console.log(`
1547
+ Skills: ${approved} approved, ${pending} pending, ${rejected} rejected`);
1548
+ console.log(`Trust: ${trustConfig.trusted.length} trusted, ${trustConfig.blocked.length} blocked`);
1549
+ console.log(`Today: ${counts.total} lessons extracted
1550
+ `);
1551
+ }
1552
+
1553
+ // src/cli/init.ts
1554
+ var command = process.argv[2];
1555
+ async function main() {
1556
+ switch (command) {
1557
+ case "setup":
1558
+ await runSetup();
1559
+ break;
1560
+ case "start":
1561
+ await start();
1562
+ break;
1563
+ case "on":
1564
+ turnOn();
1565
+ break;
1566
+ case "off":
1567
+ turnOff();
1568
+ break;
1569
+ case "status":
1570
+ showStatus();
1571
+ break;
1572
+ case "init":
1573
+ console.log("The `init` command is no longer needed. Use `become setup` instead.");
1574
+ break;
1575
+ default:
1576
+ console.log(`
1577
+ become \u2014 agent-to-agent learning
1578
+
1579
+ Usage:
1580
+ become setup Set up become (interactive wizard)
1581
+ become start Start the proxy server
1582
+ become on Route your agent through become
1583
+ become off Disconnect \u2014 agent talks directly to LLM
1584
+ become status Show current status
1585
+ `);
1586
+ }
111
1587
  }
112
- main().catch(console.error);
1588
+ main().catch((err) => {
1589
+ console.error(err.message);
1590
+ process.exit(1);
1591
+ });
1592
+ // Annotate the CommonJS export names for ESM import in node:
1593
+ 0 && (module.exports = {
1594
+ runSetup,
1595
+ showStatus,
1596
+ start,
1597
+ turnOff,
1598
+ turnOn
1599
+ });
113
1600
  //# sourceMappingURL=cli.cjs.map