@blockrun/franklin 3.7.0 → 3.7.1

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.
@@ -38,6 +38,7 @@ You are an interactive agent — not a chatbot. Use the tools available to you t
38
38
  - **Search strategy**: Glob/Grep for directed searches (known file/symbol). Use Agent for open-ended exploration that may require multiple rounds.
39
39
  - **Batch bash**: chain sequential shell commands with && in a single call. Only split when you need intermediate output.
40
40
  - **AskUser discipline**: Only use AskUser when you need explicit confirmation for a destructive action (deleting files, dropping databases). NEVER use AskUser to ask what the user wants — just answer their message directly. If the request is vague, make a reasonable assumption and proceed.
41
+ - **Greetings**: When the user sends only a greeting or filler ("hi", "hello", "hey", "ok", "thanks", "yo"), reply with ONE short plain-text sentence (e.g. "Hi — what do you want to work on?"). Do NOT call AskUser. Do NOT assume a marketing/trading/coding task. Do NOT invoke any tools.
41
42
  - Never write to /etc, /usr, ~/.ssh, ~/.aws. Don't commit secrets.`;
42
43
  }
43
44
  function getCodeStyleSection() {
@@ -1,7 +1,8 @@
1
1
  import chalk from 'chalk';
2
2
  import { getOrCreateWallet, getOrCreateSolanaWallet } from '@blockrun/llm';
3
3
  import { loadChain, API_URLS } from '../config.js';
4
- import { flushStats } from '../stats/tracker.js';
4
+ import { flushStats, loadStats } from '../stats/tracker.js';
5
+ import { OPUS_PRICING } from '../pricing.js';
5
6
  import { loadConfig } from './config.js';
6
7
  import { printBanner } from '../banner.js';
7
8
  import { assembleInstructions } from '../agent/context.js';
@@ -114,11 +115,17 @@ export async function startCommand(options) {
114
115
  }
115
116
  printBanner(version);
116
117
  const workDir = process.cwd();
118
+ // Auto-start panel in background unless explicitly disabled.
119
+ // Binds loopback-only (wallet secrets on /api/wallet/secret — never expose on LAN).
120
+ let panelUrl;
121
+ if (process.env.FRANKLIN_PANEL_AUTOSTART !== '0') {
122
+ panelUrl = await startPanelBackground(3100);
123
+ }
117
124
  // Session info — aligned, minimal. Model + balance live in the input bar below.
118
125
  // Full wallet address is shown so the user can copy-paste it to fund the wallet.
119
126
  console.log(chalk.dim(' Wallet: ') + (walletAddress || chalk.yellow('not set')));
120
127
  console.log(chalk.dim(' Dir: ') + workDir);
121
- console.log(chalk.dim(' Dashboard: ') + chalk.cyan('franklin panel') + chalk.dim(' → http://localhost:3100'));
128
+ console.log(chalk.dim(' Dashboard: ') + (panelUrl ? chalk.cyan(panelUrl) : chalk.cyan('franklin panel') + chalk.dim(' → http://localhost:3100')));
122
129
  console.log(chalk.dim(' Help: ') + chalk.cyan('/help'));
123
130
  console.log('');
124
131
  // Balance fetcher — used at startup and after each turn
@@ -257,6 +264,7 @@ export async function startCommand(options) {
257
264
  }
258
265
  // ─── Ink UI (interactive terminal) ─────────────────────────────────────────
259
266
  async function runWithInkUI(agentConfig, model, workDir, version, walletInfo, onBalanceReady, fetchBalance) {
267
+ const startSnapshot = snapshotStats();
260
268
  const ui = launchInkUI({
261
269
  model,
262
270
  workDir,
@@ -324,15 +332,17 @@ async function runWithInkUI(agentConfig, model, workDir, version, walletInfo, on
324
332
  catch { /* extraction is best-effort */ }
325
333
  }
326
334
  await disconnectMcpServers();
327
- // Session summary — show cost and usage before goodbye
335
+ // Session summary — delta vs. snapshot at session start
328
336
  try {
329
- const { getStatsSummary } = await import('../stats/tracker.js');
330
- const { stats, saved } = getStatsSummary();
331
- if (stats.totalRequests > 0) {
332
- const cost = stats.totalCostUsd.toFixed(4);
333
- const savedStr = saved > 0.001 ? ` · saved $${saved.toFixed(2)} vs Opus` : '';
334
- const tokens = `${(stats.totalInputTokens / 1000).toFixed(0)}k in / ${(stats.totalOutputTokens / 1000).toFixed(0)}k out`;
335
- console.log(chalk.dim(`\n Session: ${stats.totalRequests} requests · $${cost} USDC${savedStr} · ${tokens}`));
337
+ const delta = statsDelta(startSnapshot);
338
+ if (delta.requests > 0) {
339
+ const cost = delta.cost.toFixed(4);
340
+ const savedStr = delta.saved > 0.001 ? ` · saved $${delta.saved.toFixed(2)} vs Opus` : '';
341
+ const tokens = `${(delta.inputTokens / 1000).toFixed(0)}k in / ${(delta.outputTokens / 1000).toFixed(0)}k out`;
342
+ console.log(chalk.dim(`\n Session: ${delta.requests} requests · $${cost} USDC${savedStr} · ${tokens}`));
343
+ }
344
+ else {
345
+ console.log(chalk.dim('\n Session: 0 requests · no spend'));
336
346
  }
337
347
  }
338
348
  catch { /* stats unavailable */ }
@@ -343,6 +353,7 @@ async function runWithBasicUI(agentConfig, model, workDir) {
343
353
  const { TerminalUI } = await import('../ui/terminal.js');
344
354
  const ui = new TerminalUI();
345
355
  ui.printWelcome(model, workDir);
356
+ const startSnapshot = snapshotStats();
346
357
  let lastTerminalPrompt = '';
347
358
  try {
348
359
  await interactiveSession(agentConfig, async () => {
@@ -390,19 +401,69 @@ async function runWithBasicUI(agentConfig, model, workDir) {
390
401
  }
391
402
  // Session summary for piped mode
392
403
  try {
393
- const { getStatsSummary } = await import('../stats/tracker.js');
394
- const { stats, saved } = getStatsSummary();
395
- if (stats.totalRequests > 0) {
396
- const cost = stats.totalCostUsd.toFixed(4);
397
- const savedStr = saved > 0.001 ? ` · saved $${saved.toFixed(2)} vs Opus` : '';
398
- const tokens = `${(stats.totalInputTokens / 1000).toFixed(0)}k in / ${(stats.totalOutputTokens / 1000).toFixed(0)}k out`;
399
- console.error(`Session: ${stats.totalRequests} requests · $${cost} USDC${savedStr} · ${tokens}`);
404
+ const delta = statsDelta(startSnapshot);
405
+ if (delta.requests > 0) {
406
+ const cost = delta.cost.toFixed(4);
407
+ const savedStr = delta.saved > 0.001 ? ` · saved $${delta.saved.toFixed(2)} vs Opus` : '';
408
+ const tokens = `${(delta.inputTokens / 1000).toFixed(0)}k in / ${(delta.outputTokens / 1000).toFixed(0)}k out`;
409
+ console.error(`Session: ${delta.requests} requests · $${cost} USDC${savedStr} · ${tokens}`);
400
410
  }
401
411
  }
402
412
  catch { /* stats unavailable */ }
403
413
  ui.printGoodbye();
404
414
  flushStats();
405
415
  }
416
+ // ─── Panel auto-start ──────────────────────────────────────────────────────
417
+ async function startPanelBackground(startPort) {
418
+ const MAX_ATTEMPTS = 20;
419
+ try {
420
+ const { createPanelServer } = await import('../panel/server.js');
421
+ return await new Promise((resolve) => {
422
+ const tryListen = (port, attempt) => {
423
+ const server = createPanelServer(port);
424
+ server.on('error', (err) => {
425
+ if (err.code === 'EADDRINUSE' && attempt < MAX_ATTEMPTS) {
426
+ tryListen(port + 1, attempt + 1);
427
+ return;
428
+ }
429
+ resolve(undefined);
430
+ });
431
+ server.listen(port, '127.0.0.1', () => {
432
+ server.unref?.();
433
+ resolve(`http://localhost:${port}`);
434
+ });
435
+ };
436
+ tryListen(startPort, 0);
437
+ });
438
+ }
439
+ catch {
440
+ return undefined;
441
+ }
442
+ }
443
+ function snapshotStats() {
444
+ try {
445
+ const s = loadStats();
446
+ return {
447
+ requests: s.totalRequests,
448
+ cost: s.totalCostUsd,
449
+ inputTokens: s.totalInputTokens,
450
+ outputTokens: s.totalOutputTokens,
451
+ };
452
+ }
453
+ catch {
454
+ return { requests: 0, cost: 0, inputTokens: 0, outputTokens: 0 };
455
+ }
456
+ }
457
+ function statsDelta(before) {
458
+ const now = loadStats();
459
+ const requests = Math.max(0, now.totalRequests - before.requests);
460
+ const cost = Math.max(0, now.totalCostUsd - before.cost);
461
+ const inputTokens = Math.max(0, now.totalInputTokens - before.inputTokens);
462
+ const outputTokens = Math.max(0, now.totalOutputTokens - before.outputTokens);
463
+ const opusCost = (inputTokens / 1_000_000) * OPUS_PRICING.input +
464
+ (outputTokens / 1_000_000) * OPUS_PRICING.output;
465
+ return { requests, cost, inputTokens, outputTokens, saved: Math.max(0, opusCost - cost) };
466
+ }
406
467
  async function handleSlashCommand(cmd, config, ui) {
407
468
  const parts = cmd.trim().split(/\s+/);
408
469
  const command = parts[0].toLowerCase();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.7.0",
3
+ "version": "3.7.1",
4
4
  "description": "Franklin — The AI agent with a wallet. Spends USDC autonomously to get real work done. Pay per action, no subscriptions.",
5
5
  "type": "module",
6
6
  "exports": {