@blockrun/franklin 3.8.35 → 3.8.36

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/README.md CHANGED
@@ -398,7 +398,7 @@ That economic loop is the product.
398
398
 
399
399
  ```text
400
400
  src/
401
- ├── index.ts CLI entry (franklin + runcode alias)
401
+ ├── index.ts CLI entry (franklin)
402
402
  ├── banner.ts Ben Franklin portrait + FRANKLIN gradient text
403
403
  ├── agent/ Agent loop, LLM client, compaction, commands
404
404
  ├── tools/ 20+ built-in tools (Read/Write/Edit/Bash/Glob/Grep/
@@ -289,7 +289,7 @@ const DIRECT_COMMANDS = {
289
289
  }
290
290
  const hasWallet = fs.existsSync(path.join(BLOCKRUN_DIR, 'wallet.json'))
291
291
  || fs.existsSync(path.join(BLOCKRUN_DIR, 'solana-wallet.json'));
292
- checks.push(hasWallet ? '✓ wallet configured' : '⚠ no wallet — run: runcode setup');
292
+ checks.push(hasWallet ? '✓ wallet configured' : '⚠ no wallet — run: franklin setup');
293
293
  checks.push(fs.existsSync(path.join(BLOCKRUN_DIR, 'franklin-config.json')) || fs.existsSync(path.join(BLOCKRUN_DIR, 'runcode-config.json')) ? '✓ config file exists' : '⚠ no config — using defaults');
294
294
  // Check MCP
295
295
  const { listMcpServers } = await import('../mcp/client.js');
@@ -434,7 +434,7 @@ function pickCompactionModel(primaryModel) {
434
434
  if (primaryModel.includes('opus') || primaryModel.includes('pro')) {
435
435
  return 'anthropic/claude-sonnet-4.6';
436
436
  }
437
- if (primaryModel.includes('sonnet') || primaryModel.includes('gpt-5.4') || primaryModel.includes('gemini-2.5-pro')) {
437
+ if (primaryModel.includes('sonnet') || primaryModel.includes('gpt-5.4') || primaryModel.includes('gpt-5.5') || primaryModel.includes('gemini-2.5-pro')) {
438
438
  return 'anthropic/claude-haiku-4.5-20251001';
439
439
  }
440
440
  if (primaryModel.includes('haiku') || primaryModel.includes('mini') || primaryModel.includes('nano')) {
@@ -1208,6 +1208,25 @@ export async function interactiveSession(config, getUserInput, onEvent, onAbortR
1208
1208
  };
1209
1209
  }
1210
1210
  }
1211
+ // Vision attachments: if a tool returned image bytes (e.g. Read on a
1212
+ // .png), wrap them into Anthropic-native tool_result.content so
1213
+ // vision-capable models can actually see the image. The gateway
1214
+ // preserves these blocks end-to-end via the tool_result side channel.
1215
+ if (result.images && result.images.length > 0) {
1216
+ const content = [{ type: 'text', text: result.output }];
1217
+ for (const img of result.images) {
1218
+ content.push({
1219
+ type: 'image',
1220
+ source: { type: 'base64', media_type: img.mediaType, data: img.base64 },
1221
+ });
1222
+ }
1223
+ return {
1224
+ type: 'tool_result',
1225
+ tool_use_id: inv.id,
1226
+ content,
1227
+ is_error: result.isError,
1228
+ };
1229
+ }
1211
1230
  return {
1212
1231
  type: 'tool_result',
1213
1232
  tool_use_id: inv.id,
@@ -29,6 +29,7 @@ const MODEL_MAX_OUTPUT = {
29
29
  'anthropic/claude-opus-4.6': 32_000,
30
30
  'anthropic/claude-sonnet-4.6': 64_000,
31
31
  'anthropic/claude-haiku-4.5-20251001': 16_384,
32
+ 'openai/gpt-5.5': 32_768,
32
33
  'openai/gpt-5.4': 32_768,
33
34
  'openai/gpt-5-mini': 16_384,
34
35
  'google/gemini-2.5-pro': 65_536,
@@ -151,7 +151,16 @@ export class PermissionManager {
151
151
  }
152
152
  // ─── Internal ──────────────────────────────────────────────────────────
153
153
  loadRules() {
154
- const configPath = path.join(BLOCKRUN_DIR, 'runcode-permissions.json');
154
+ const configPath = path.join(BLOCKRUN_DIR, 'franklin-permissions.json');
155
+ const legacyPath = path.join(BLOCKRUN_DIR, 'runcode-permissions.json');
156
+ // One-shot migration from the old name. If the user only has the legacy
157
+ // file, rename it so future writes/reads land on the franklin path.
158
+ try {
159
+ if (!fs.existsSync(configPath) && fs.existsSync(legacyPath)) {
160
+ fs.renameSync(legacyPath, configPath);
161
+ }
162
+ }
163
+ catch { /* best effort */ }
155
164
  try {
156
165
  if (fs.existsSync(configPath)) {
157
166
  const raw = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
@@ -166,6 +166,10 @@ const MODEL_CONTEXT_WINDOWS = {
166
166
  'anthropic/claude-haiku-4.5': 200_000,
167
167
  'anthropic/claude-haiku-4.5-20251001': 200_000,
168
168
  // OpenAI
169
+ // gpt-5.5 advertises 1.05M context at the gateway, but Franklin keeps the
170
+ // conservative 128k baseline matching every other gpt-5.x line — bump in
171
+ // a separate change once a real >128k call has been verified end-to-end.
172
+ 'openai/gpt-5.5': 128_000,
169
173
  'openai/gpt-5.4': 128_000,
170
174
  'openai/gpt-5.4-pro': 128_000,
171
175
  'openai/gpt-5.3': 128_000,
@@ -18,10 +18,21 @@ export interface ThinkingSegment {
18
18
  thinking: string;
19
19
  signature?: string;
20
20
  }
21
+ export interface ImageSegment {
22
+ type: 'image';
23
+ source: {
24
+ type: 'base64';
25
+ media_type: string;
26
+ data: string;
27
+ } | {
28
+ type: 'url';
29
+ url: string;
30
+ };
31
+ }
21
32
  export interface CapabilityOutcome {
22
33
  type: 'tool_result';
23
34
  tool_use_id: string;
24
- content: string | ContentPart[];
35
+ content: string | Array<TextSegment | ImageSegment>;
25
36
  is_error?: boolean;
26
37
  }
27
38
  export type ContentPart = TextSegment | CapabilityInvocation | ThinkingSegment;
@@ -60,6 +71,16 @@ export interface CapabilityResult {
60
71
  };
61
72
  /** Full tool output for expandable display — separate from truncated preview. */
62
73
  fullOutput?: string;
74
+ /**
75
+ * Optional image attachments emitted by a tool (e.g. Read on a .png).
76
+ * The agent loop wraps these into an Anthropic-native tool_result.content
77
+ * array so vision-capable models can actually see the bytes instead of
78
+ * getting a "Binary file" stub.
79
+ */
80
+ images?: Array<{
81
+ mediaType: string;
82
+ base64: string;
83
+ }>;
63
84
  }
64
85
  export interface ExecutionScope {
65
86
  workingDir: string;
@@ -30,7 +30,7 @@ export async function balanceCommand() {
30
30
  catch (err) {
31
31
  const msg = err instanceof Error ? err.message : '';
32
32
  if (msg.includes('ENOENT') || msg.includes('wallet') || msg.includes('key')) {
33
- console.log(chalk.red('No wallet found. Run `runcode setup` first.'));
33
+ console.log(chalk.red('No wallet found. Run `franklin setup` first.'));
34
34
  }
35
35
  else {
36
36
  console.log(chalk.red(`Error checking balance: ${msg || 'unknown error'}`));
@@ -1,4 +1,4 @@
1
- import { spawn, execSync } from 'node:child_process';
1
+ import { spawn, execFileSync, execSync } from 'node:child_process';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import chalk from 'chalk';
@@ -42,6 +42,17 @@ function isRunning(pid) {
42
42
  return true;
43
43
  }
44
44
  }
45
+ function findDaemonBinary() {
46
+ for (const name of ['franklin', 'runcode']) {
47
+ try {
48
+ return execFileSync('which', [name], { encoding: 'utf-8' }).trim();
49
+ }
50
+ catch {
51
+ // Try the legacy alias next.
52
+ }
53
+ }
54
+ return null;
55
+ }
45
56
  export async function daemonCommand(action, options) {
46
57
  const port = parseInt(options.port || String(DEFAULT_PROXY_PORT));
47
58
  if (isNaN(port) || port < 1 || port > 65535) {
@@ -56,24 +67,20 @@ export async function daemonCommand(action, options) {
56
67
  console.log(chalk.dim(` Proxy: http://localhost:${port}/api`));
57
68
  return;
58
69
  }
59
- // Find runcode binary
60
- let runcodeBin;
61
- try {
62
- runcodeBin = execSync('which runcode', { encoding: 'utf-8' }).trim();
63
- }
64
- catch {
65
- console.log(chalk.red('runcode binary not found in PATH.'));
70
+ const daemonBin = findDaemonBinary();
71
+ if (!daemonBin) {
72
+ console.log(chalk.red('franklin binary not found in PATH.'));
66
73
  return;
67
74
  }
68
75
  fs.mkdirSync(BLOCKRUN_DIR, { recursive: true });
69
- const child = spawn(runcodeBin, ['proxy', '--port', String(port)], {
76
+ const child = spawn(daemonBin, ['proxy', '--port', String(port)], {
70
77
  detached: true,
71
78
  // stdout → /dev/null (banner + startup messages), stderr → log file (debug/errors only)
72
79
  stdio: ['ignore', 'ignore', fs.openSync(LOG_FILE, 'a')],
73
80
  });
74
81
  child.unref();
75
82
  fs.writeFileSync(PID_FILE, String(child.pid));
76
- console.log(chalk.green(`✓ runcode daemon started (PID ${child.pid})`));
83
+ console.log(chalk.green(`✓ franklin daemon started (PID ${child.pid})`));
77
84
  console.log(chalk.dim(` Proxy: http://localhost:${port}/api`));
78
85
  console.log(chalk.dim(` Logs: ${LOG_FILE}`));
79
86
  break;
@@ -81,7 +88,7 @@ export async function daemonCommand(action, options) {
81
88
  case 'stop': {
82
89
  const pid = readPid();
83
90
  if (!pid) {
84
- console.log(chalk.yellow('No runcode daemon found.'));
91
+ console.log(chalk.yellow('No franklin daemon found.'));
85
92
  return;
86
93
  }
87
94
  if (!isRunning(pid)) {
@@ -104,7 +111,7 @@ export async function daemonCommand(action, options) {
104
111
  fs.unlinkSync(PID_FILE);
105
112
  }
106
113
  catch { /* already gone */ }
107
- console.log(chalk.green(`✓ runcode daemon stopped (PID ${pid})`));
114
+ console.log(chalk.green(`✓ franklin daemon stopped (PID ${pid})`));
108
115
  }
109
116
  catch (e) {
110
117
  console.log(chalk.red(`Failed to stop daemon: ${e.message}`));
@@ -114,23 +121,23 @@ export async function daemonCommand(action, options) {
114
121
  case 'status': {
115
122
  const pid = readPid();
116
123
  if (!pid) {
117
- console.log(chalk.dim('runcode daemon: not running'));
124
+ console.log(chalk.dim('franklin daemon: not running'));
118
125
  return;
119
126
  }
120
127
  if (isRunning(pid)) {
121
- console.log(chalk.green(`✓ runcode daemon running`));
128
+ console.log(chalk.green(`✓ franklin daemon running`));
122
129
  console.log(` PID: ${chalk.bold(pid)}`);
123
130
  console.log(` Proxy: ${chalk.cyan(`http://localhost:${port}/api`)}`);
124
131
  console.log(chalk.dim(` Logs: ${LOG_FILE}`));
125
132
  }
126
133
  else {
127
134
  fs.unlinkSync(PID_FILE);
128
- console.log(chalk.yellow('runcode daemon: not running (stale PID cleaned up)'));
135
+ console.log(chalk.yellow('franklin daemon: not running (stale PID cleaned up)'));
129
136
  }
130
137
  break;
131
138
  }
132
139
  default:
133
140
  console.log(chalk.red(`Unknown daemon action: ${action}`));
134
- console.log('Usage: runcode daemon <start|stop|status>');
141
+ console.log('Usage: franklin daemon <start|stop|status>');
135
142
  }
136
143
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Generic plugin command dispatcher.
3
3
  *
4
- * `runcode <plugin-id> <action>` works for ANY plugin that registers a workflow.
4
+ * `franklin <plugin-id> <action>` works for ANY plugin that registers a workflow.
5
5
  * Core stays plugin-agnostic — adding a new plugin requires zero changes here.
6
6
  */
7
7
  export interface PluginCommandOptions {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Generic plugin command dispatcher.
3
3
  *
4
- * `runcode <plugin-id> <action>` works for ANY plugin that registers a workflow.
4
+ * `franklin <plugin-id> <action>` works for ANY plugin that registers a workflow.
5
5
  * Core stays plugin-agnostic — adding a new plugin requires zero changes here.
6
6
  */
7
7
  import chalk from 'chalk';
@@ -49,7 +49,7 @@ export async function pluginCommand(pluginId, action, options) {
49
49
  // No action and already configured: show stats + dry-run hint
50
50
  const stats = getStats(workflow.id);
51
51
  console.log(formatWorkflowStats(workflow, stats));
52
- console.log(chalk.dim(`Run "runcode ${pluginId} run --dry" to preview.\n`));
52
+ console.log(chalk.dim(`Run "franklin ${pluginId} run --dry" to preview.\n`));
53
53
  }
54
54
  break;
55
55
  }
@@ -73,7 +73,7 @@ export async function pluginCommand(pluginId, action, options) {
73
73
  case 'leads': {
74
74
  const leads = getByAction(workflow.id, 'lead');
75
75
  if (leads.length === 0) {
76
- console.log(chalk.dim(`\nNo leads found yet. Run "runcode ${pluginId} run" first.\n`));
76
+ console.log(chalk.dim(`\nNo leads found yet. Run "franklin ${pluginId} run" first.\n`));
77
77
  break;
78
78
  }
79
79
  console.log(chalk.bold(`\n LEADS (${leads.length})\n`));
@@ -94,12 +94,12 @@ export async function pluginCommand(pluginId, action, options) {
94
94
  console.log(chalk.red(`Unknown action: ${action}`));
95
95
  console.log(chalk.dim(`
96
96
  Usage:
97
- runcode ${pluginId} # show stats / first-run setup
98
- runcode ${pluginId} init # interactive setup
99
- runcode ${pluginId} run # execute workflow
100
- runcode ${pluginId} run --dry # dry run (no side effects)
101
- runcode ${pluginId} stats # show statistics
102
- runcode ${pluginId} leads # show tracked leads (if applicable)
97
+ franklin ${pluginId} # show stats / first-run setup
98
+ franklin ${pluginId} init # interactive setup
99
+ franklin ${pluginId} run # execute workflow
100
+ franklin ${pluginId} run --dry # dry run (no side effects)
101
+ franklin ${pluginId} stats # show statistics
102
+ franklin ${pluginId} leads # show tracked leads (if applicable)
103
103
  `));
104
104
  }
105
105
  }
@@ -166,7 +166,7 @@ async function runOnboarding(workflow, client) {
166
166
  const config = await workflow.buildConfigFromAnswers(answers, llm);
167
167
  console.log(chalk.green(' ✓ Configuration saved!\n'));
168
168
  console.log(chalk.dim(` Config: ~/.blockrun/workflows/${workflow.id}.config.json\n`));
169
- console.log(chalk.dim(` Run "runcode ${workflow.id} run --dry" to preview.\n`));
169
+ console.log(chalk.dim(` Run "franklin ${workflow.id} run --dry" to preview.\n`));
170
170
  return config;
171
171
  }
172
172
  catch (err) {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * runcode stats command
2
+ * franklin stats command
3
3
  * Display usage statistics and cost savings
4
4
  */
5
5
  interface StatsOptions {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * runcode stats command
2
+ * franklin stats command
3
3
  * Display usage statistics and cost savings
4
4
  */
5
5
  import chalk from 'chalk';
@@ -90,5 +90,5 @@ export function statsCommand(options) {
90
90
  }
91
91
  }
92
92
  console.log('\n' + '─'.repeat(55));
93
- console.log(chalk.gray(' Run `runcode stats --clear` to reset statistics\n'));
93
+ console.log(chalk.gray(' Run `franklin stats --clear` to reset statistics\n'));
94
94
  }
package/dist/index.js CHANGED
@@ -38,7 +38,7 @@ program
38
38
  program
39
39
  .command('start')
40
40
  .description('Start the franklin agent')
41
- .option('-m, --model <model>', 'Model to use (e.g. openai/gpt-5.4, anthropic/claude-sonnet-4.6). Default from config or claude-sonnet-4.6')
41
+ .option('-m, --model <model>', 'Model to use (e.g. openai/gpt-5.5, anthropic/claude-sonnet-4.6). Default from config or claude-sonnet-4.6')
42
42
  .option('--debug', 'Enable debug logging')
43
43
  .option('--trust', 'Trust mode — skip permission prompts for all tools')
44
44
  .option('-r, --resume [sessionId]', 'Resume a session by ID (or show picker if omitted)')
@@ -252,7 +252,7 @@ function parseStartFlags(argv, startIdx = 0) {
252
252
  }
253
253
  return opts;
254
254
  }
255
- // Handle chain shortcuts: `runcode solana` or `runcode base`
255
+ // Handle chain shortcuts: `franklin solana` or `franklin base`
256
256
  if (firstArg === 'solana' || firstArg === 'base') {
257
257
  if (hasAnyFlag(args, HELP_FLAGS)) {
258
258
  program.parse(['node', 'franklin', 'start', '--help']);
@@ -6,8 +6,8 @@
6
6
  import http from 'node:http';
7
7
  import fs from 'node:fs';
8
8
  import path from 'node:path';
9
- import { BLOCKRUN_DIR, loadChain, saveChain } from '../config.js';
10
- import { getStatsSummary } from '../stats/tracker.js';
9
+ import { loadChain, saveChain } from '../config.js';
10
+ import { getStatsSummary, getStatsFilePath } from '../stats/tracker.js';
11
11
  import { generateInsights } from '../stats/insights.js';
12
12
  import { listSessions, loadSessionHistory } from '../session/storage.js';
13
13
  import { searchSessions } from '../session/search.js';
@@ -413,10 +413,11 @@ export function createPanelServer(port) {
413
413
  console.error('[panel] client error:', err.message);
414
414
  }
415
415
  });
416
- // Watch stats file for changes → push to SSE clients
417
- const statsFile = fs.existsSync(path.join(BLOCKRUN_DIR, 'franklin-stats.json'))
418
- ? path.join(BLOCKRUN_DIR, 'franklin-stats.json')
419
- : path.join(BLOCKRUN_DIR, 'runcode-stats.json');
416
+ // Watch stats file for changes → push to SSE clients.
417
+ // getStatsFilePath() also handles the runcode-stats.json franklin-stats.json
418
+ // migration on first call, so users coming from the old binary keep their
419
+ // history without an extra cleanup step.
420
+ const statsFile = getStatsFilePath();
420
421
  if (fs.existsSync(statsFile)) {
421
422
  fs.watchFile(statsFile, { interval: 2000 }, () => {
422
423
  try {
@@ -1,7 +1,7 @@
1
1
  /**
2
- * RunCode Plugin SDK — public surface for plugins.
2
+ * Franklin Plugin SDK — public surface for plugins.
3
3
  *
4
- * Plugins import ONLY from '@blockrun/runcode/plugin-sdk' (or this barrel).
4
+ * Plugins import ONLY from '@blockrun/franklin/plugin-sdk' (or this barrel).
5
5
  * They MUST NOT import from src/** of core or other plugins.
6
6
  *
7
7
  * Core stays plugin-agnostic: adding a plugin should never require editing core.
@@ -1,7 +1,7 @@
1
1
  /**
2
- * RunCode Plugin SDK — public surface for plugins.
2
+ * Franklin Plugin SDK — public surface for plugins.
3
3
  *
4
- * Plugins import ONLY from '@blockrun/runcode/plugin-sdk' (or this barrel).
4
+ * Plugins import ONLY from '@blockrun/franklin/plugin-sdk' (or this barrel).
5
5
  * They MUST NOT import from src/** of core or other plugins.
6
6
  *
7
7
  * Core stays plugin-agnostic: adding a plugin should never require editing core.
@@ -26,8 +26,8 @@ export interface PluginManifest {
26
26
  homepage?: string;
27
27
  /** License */
28
28
  license?: string;
29
- /** Required runcode version (semver range) */
30
- runcodeVersion?: string;
29
+ /** Required Franklin version (semver range) */
30
+ franklinVersion?: string;
31
31
  }
32
32
  export interface PluginProvides {
33
33
  /** This plugin contributes one or more workflows (e.g. "social", "trading") */
@@ -54,8 +54,8 @@ export interface Plugin {
54
54
  }
55
55
  /** Context passed to plugin lifecycle hooks */
56
56
  export interface PluginContext {
57
- /** RunCode version */
58
- runcodeVersion: string;
57
+ /** Franklin version */
58
+ franklinVersion: string;
59
59
  /** Plugin's own data directory (~/.blockrun/plugins/<id>/) */
60
60
  dataDir: string;
61
61
  /** Path to plugin's installation directory */
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * Core stays plugin-agnostic: it knows about the *interface*, not specific plugins.
5
5
  * Plugins are discovered from:
6
- * 1. Bundled: <runcode>/plugins-bundled/* (ships with runcode)
7
- * 2. User: ~/.blockrun/plugins/* (installed via `runcode plugin install`)
8
- * 3. Local dev: $RUNCODE_PLUGINS_DIR/* (env var for development)
6
+ * 1. Bundled: <franklin>/plugins-bundled/* (ships with Franklin)
7
+ * 2. User: ~/.blockrun/plugins/*
8
+ * 3. Local dev: $FRANKLIN_PLUGINS_DIR/* (or legacy $RUNCODE_PLUGINS_DIR/*)
9
9
  */
10
10
  import type { Plugin, PluginManifest } from '../plugin-sdk/plugin.js';
11
11
  export declare function getBundledPluginsDir(): string;
@@ -3,9 +3,9 @@
3
3
  *
4
4
  * Core stays plugin-agnostic: it knows about the *interface*, not specific plugins.
5
5
  * Plugins are discovered from:
6
- * 1. Bundled: <runcode>/plugins-bundled/* (ships with runcode)
7
- * 2. User: ~/.blockrun/plugins/* (installed via `runcode plugin install`)
8
- * 3. Local dev: $RUNCODE_PLUGINS_DIR/* (env var for development)
6
+ * 1. Bundled: <franklin>/plugins-bundled/* (ships with Franklin)
7
+ * 2. User: ~/.blockrun/plugins/*
8
+ * 3. Local dev: $FRANKLIN_PLUGINS_DIR/* (or legacy $RUNCODE_PLUGINS_DIR/*)
9
9
  */
10
10
  import fs from 'node:fs';
11
11
  import path from 'node:path';
@@ -22,7 +22,7 @@ export function getUserPluginsDir() {
22
22
  return path.join(os.homedir(), '.blockrun', 'plugins');
23
23
  }
24
24
  function getDevPluginsDir() {
25
- return process.env.RUNCODE_PLUGINS_DIR || null;
25
+ return process.env.FRANKLIN_PLUGINS_DIR || process.env.RUNCODE_PLUGINS_DIR || null;
26
26
  }
27
27
  const loaded = new Map();
28
28
  // ─── Discovery ────────────────────────────────────────────────────────────
@@ -119,7 +119,7 @@ export async function loadAllPlugins() {
119
119
  if (plugin.onLoad) {
120
120
  try {
121
121
  await plugin.onLoad({
122
- runcodeVersion: getRuncodeVersion(),
122
+ franklinVersion: getFranklinVersion(),
123
123
  dataDir: path.join(os.homedir(), '.blockrun', 'plugins', manifest.id),
124
124
  pluginDir: dir,
125
125
  log: (msg) => process.stderr.write(`[${manifest.id}] ${msg}\n`),
@@ -149,7 +149,7 @@ export function listChannelPlugins() {
149
149
  return listPlugins().filter(p => p.plugin.channels && Object.keys(p.plugin.channels).length > 0);
150
150
  }
151
151
  // ─── Helpers ──────────────────────────────────────────────────────────────
152
- function getRuncodeVersion() {
152
+ function getFranklinVersion() {
153
153
  try {
154
154
  const pkgPath = path.resolve(__dirname, '..', '..', 'package.json');
155
155
  return JSON.parse(fs.readFileSync(pkgPath, 'utf-8')).version || '0.0.0';
package/dist/pricing.js CHANGED
@@ -46,6 +46,7 @@ export const MODEL_PRICING = {
46
46
  'openai/o3-mini': { input: 1.1, output: 4.4 },
47
47
  'openai/o4-mini': { input: 1.1, output: 4.4 },
48
48
  'openai/o1': { input: 15.0, output: 60.0 },
49
+ 'openai/gpt-5.5': { input: 5.0, output: 30.0 },
49
50
  'openai/gpt-5.2-pro': { input: 21.0, output: 168.0 },
50
51
  'openai/gpt-5.4-pro': { input: 30.0, output: 180.0 },
51
52
  // Google
@@ -67,9 +67,11 @@ const MODEL_SHORTCUTS = {
67
67
  'opus-4.6': 'anthropic/claude-opus-4.6',
68
68
  haiku: 'anthropic/claude-haiku-4.5',
69
69
  // OpenAI
70
- gpt: 'openai/gpt-5.4',
71
- gpt5: 'openai/gpt-5.4',
72
- 'gpt-5': 'openai/gpt-5.4',
70
+ // `gpt` / `gpt5` / `gpt-5` follow the gateway's flagship — currently 5.5.
71
+ gpt: 'openai/gpt-5.5',
72
+ gpt5: 'openai/gpt-5.5',
73
+ 'gpt-5': 'openai/gpt-5.5',
74
+ 'gpt-5.5': 'openai/gpt-5.5',
73
75
  'gpt-5.4': 'openai/gpt-5.4',
74
76
  'gpt-5.4-pro': 'openai/gpt-5.4-pro',
75
77
  'gpt-5.3': 'openai/gpt-5.3',
@@ -44,11 +44,11 @@ const AUTO_TIERS = {
44
44
  },
45
45
  MEDIUM: {
46
46
  primary: 'anthropic/claude-sonnet-4.6',
47
- fallback: ['openai/gpt-5.4', 'google/gemini-3.1-pro', 'moonshot/kimi-k2.6'],
47
+ fallback: ['openai/gpt-5.5', 'google/gemini-3.1-pro', 'moonshot/kimi-k2.6'],
48
48
  },
49
49
  COMPLEX: {
50
50
  primary: 'anthropic/claude-sonnet-4.6',
51
- fallback: ['openai/gpt-5.4', 'anthropic/claude-opus-4.7', 'moonshot/kimi-k2.6'],
51
+ fallback: ['openai/gpt-5.5', 'anthropic/claude-opus-4.7', 'moonshot/kimi-k2.6'],
52
52
  },
53
53
  REASONING: {
54
54
  // Opus 4.7: step-change improvement in agentic coding over 4.6 per
@@ -93,7 +93,7 @@ const PREMIUM_TIERS = {
93
93
  },
94
94
  COMPLEX: {
95
95
  primary: 'anthropic/claude-opus-4.7',
96
- fallback: ['anthropic/claude-opus-4.6', 'openai/gpt-5.4', 'anthropic/claude-sonnet-4.6'],
96
+ fallback: ['anthropic/claude-opus-4.6', 'openai/gpt-5.5', 'anthropic/claude-sonnet-4.6'],
97
97
  },
98
98
  REASONING: {
99
99
  primary: 'anthropic/claude-opus-4.7',
@@ -13,7 +13,7 @@ function getSessionsDir() {
13
13
  if (resolvedSessionsDir)
14
14
  return resolvedSessionsDir;
15
15
  const preferred = path.join(BLOCKRUN_DIR, 'sessions');
16
- const fallback = path.join(os.tmpdir(), 'runcode', 'sessions');
16
+ const fallback = path.join(os.tmpdir(), 'franklin', 'sessions');
17
17
  for (const dir of [preferred, fallback]) {
18
18
  try {
19
19
  fs.mkdirSync(dir, { recursive: true });
@@ -41,7 +41,7 @@ function metaPath(id) {
41
41
  }
42
42
  function withWritableSessionDir(action) {
43
43
  const preferred = path.join(BLOCKRUN_DIR, 'sessions');
44
- const fallback = path.join(os.tmpdir(), 'runcode', 'sessions');
44
+ const fallback = path.join(os.tmpdir(), 'franklin', 'sessions');
45
45
  try {
46
46
  action();
47
47
  }
@@ -4,6 +4,20 @@
4
4
  */
5
5
  import type { CapabilityHandler } from '../agent/types.js';
6
6
  import type { ContentLibrary } from '../content/library.js';
7
+ /**
8
+ * Models that accept a reference image via /v1/images/image2image. Currently
9
+ * limited to OpenAI's edit endpoint — Gemini Nano Banana Pro and Grok Imagine
10
+ * Image Pro need gateway-side support before they can be wired in here.
11
+ */
12
+ export declare const EDIT_SUPPORTED_MODELS: Set<string>;
13
+ export declare const REFERENCE_IMAGE_MAX_BYTES = 4000000;
14
+ /**
15
+ * Normalize a reference image into a base64 data URI for the gateway. The
16
+ * /v1/images/image2image endpoint validates `image` against /^data:image\//,
17
+ * so http(s) URLs and local paths both have to be inlined client-side before
18
+ * posting. Already-formed data URIs pass through.
19
+ */
20
+ export declare function resolveReferenceImage(input: string, workingDir: string): Promise<string>;
7
21
  export interface ImageGenDeps {
8
22
  /** Optional Content library for auto-recording generations into a piece. */
9
23
  library?: ContentLibrary;