@blockrun/franklin 3.10.0 → 3.10.2

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.
@@ -4,6 +4,10 @@ interface StartOptions {
4
4
  debug?: boolean;
5
5
  trust?: boolean;
6
6
  version?: string;
7
+ /** Start a new Franklin session seeded from another agent's saved context. */
8
+ from?: string;
9
+ /** Optional external agent session id/path for --from. If omitted, show a picker. */
10
+ fromSessionId?: string;
7
11
  /** Resume: explicit session ID, or true for "most recent in cwd", or 'picker' to prompt */
8
12
  resume?: string | boolean | 'picker';
9
13
  /** Continue: resume most recent session matching the current working directory */
@@ -81,7 +81,48 @@ export async function startCommand(options) {
81
81
  // old nvidia-nemotron default, which stubbed tool use.
82
82
  model = 'blockrun/auto';
83
83
  }
84
- const workDir = process.cwd();
84
+ let workDir = process.cwd();
85
+ let importedKickoffPrompt;
86
+ if (options.from) {
87
+ const { importExternalSessionAsFranklin, parseExternalAgentSource } = await import('../session/from-import.js');
88
+ const source = parseExternalAgentSource(options.from);
89
+ if (!source) {
90
+ console.error(chalk.red(`Unknown --from source: ${options.from}`));
91
+ console.error(chalk.dim('Supported sources: claude, codex'));
92
+ process.exitCode = 1;
93
+ return;
94
+ }
95
+ try {
96
+ const imported = await importExternalSessionAsFranklin(source, options.fromSessionId, { model, workDir });
97
+ if (imported.imported.cwd) {
98
+ try {
99
+ process.chdir(imported.imported.cwd);
100
+ workDir = process.cwd();
101
+ }
102
+ catch {
103
+ // Keep the caller's cwd if the source session directory no longer exists.
104
+ }
105
+ }
106
+ options.resume = imported.sessionId;
107
+ options.continue = false;
108
+ importedKickoffPrompt = [
109
+ `Continue from the imported ${source} handoff context.`,
110
+ 'Briefly explain what you understand the previous session was working on, what state it appears to be in, and the most likely next step.',
111
+ 'Do not claim you resumed or modified the source agent session. This is a new Franklin session with imported context awareness.',
112
+ 'If the next action is clear, offer to proceed; if it is not clear, ask one concise question.',
113
+ ].join('\n');
114
+ console.log(chalk.green(` Imported ${source} context into Franklin session ${imported.sessionId.slice(0, 24)}…`));
115
+ console.log(chalk.dim(` Source session: ${imported.imported.id}`));
116
+ if (imported.imported.cwd)
117
+ console.log(chalk.dim(` Dir: ${workDir}`));
118
+ console.log('');
119
+ }
120
+ catch (err) {
121
+ console.error(chalk.red(err.message));
122
+ process.exitCode = 1;
123
+ return;
124
+ }
125
+ }
85
126
  // --prompt batch mode: skip all interactive startup UI/side effects so
86
127
  // stdout stays clean for scripts and one-shot callers. Keep the capability surface to the
87
128
  // built-ins only — no panel, no MCP autoconnect, no wallet/banner chatter.
@@ -314,10 +355,10 @@ export async function startCommand(options) {
314
355
  if (process.stdin.isTTY) {
315
356
  await runWithInkUI(agentConfig, model, workDir, version, walletInfo, (cb) => {
316
357
  onBalanceFetched = cb;
317
- }, fetchBalance);
358
+ }, fetchBalance, importedKickoffPrompt);
318
359
  }
319
360
  else {
320
- await runWithBasicUI(agentConfig, model, workDir);
361
+ await runWithBasicUI(agentConfig, model, workDir, importedKickoffPrompt);
321
362
  }
322
363
  }
323
364
  // ─── One-shot mode (franklin --prompt "...") ──────────────────────────────
@@ -348,7 +389,7 @@ async function runOneShot(agentConfig, prompt) {
348
389
  return exitCode;
349
390
  }
350
391
  // ─── Ink UI (interactive terminal) ─────────────────────────────────────────
351
- async function runWithInkUI(agentConfig, model, workDir, version, walletInfo, onBalanceReady, fetchBalance) {
392
+ async function runWithInkUI(agentConfig, model, workDir, version, walletInfo, onBalanceReady, fetchBalance, initialInput) {
352
393
  const startSnapshot = snapshotStats();
353
394
  const ui = launchInkUI({
354
395
  model,
@@ -381,8 +422,13 @@ async function runWithInkUI(agentConfig, model, workDir, version, walletInfo, on
381
422
  });
382
423
  }
383
424
  let sessionHistory;
425
+ let deliveredInitialInput = false;
384
426
  try {
385
427
  sessionHistory = await interactiveSession(agentConfig, async () => {
428
+ if (initialInput && !deliveredInitialInput) {
429
+ deliveredInitialInput = true;
430
+ return initialInput;
431
+ }
386
432
  const input = await ui.waitForInput();
387
433
  if (input === null)
388
434
  return null;
@@ -441,14 +487,20 @@ async function runWithInkUI(agentConfig, model, workDir, version, walletInfo, on
441
487
  console.log(chalk.dim('\nGoodbye.\n'));
442
488
  }
443
489
  // ─── Basic readline UI (piped input) ───────────────────────────────────────
444
- async function runWithBasicUI(agentConfig, model, workDir) {
490
+ async function runWithBasicUI(agentConfig, model, workDir, initialInput) {
445
491
  const { TerminalUI } = await import('../ui/terminal.js');
446
492
  const ui = new TerminalUI();
447
493
  ui.printWelcome(model, workDir);
448
494
  const startSnapshot = snapshotStats();
449
495
  let lastTerminalPrompt = '';
496
+ let deliveredInitialInput = false;
450
497
  try {
451
498
  await interactiveSession(agentConfig, async () => {
499
+ if (initialInput && !deliveredInitialInput) {
500
+ deliveredInitialInput = true;
501
+ lastTerminalPrompt = initialInput;
502
+ return initialInput;
503
+ }
452
504
  while (true) {
453
505
  const input = await ui.promptUser();
454
506
  if (input === null)
package/dist/index.js CHANGED
@@ -38,15 +38,17 @@ program
38
38
  .action((chain) => setupCommand(chain));
39
39
  program
40
40
  .command('start')
41
+ .argument('[fromSessionId]', 'External agent session id/path for --from')
41
42
  .description('Start the franklin agent')
42
43
  .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')
43
44
  .option('--debug', 'Enable debug logging')
44
45
  .option('--trust', 'Trust mode — skip permission prompts for all tools')
46
+ .option('--from <agent>', 'Start a new Franklin session from another agent context (claude or codex)')
45
47
  .option('-r, --resume [sessionId]', 'Resume a session by ID (or show picker if omitted)')
46
48
  .option('-c, --continue', 'Continue the most recent session in this directory')
47
49
  .option('--max-spend <usd>', 'Hard USD cap on total session API spend — session stops when exceeded')
48
50
  .option('-p, --prompt <text>', 'Run a single prompt non-interactively (for batch/scripted use)')
49
- .action((options) => startCommand({ ...options, version }));
51
+ .action((fromSessionId, options) => startCommand({ ...options, fromSessionId, version }));
50
52
  program
51
53
  .command('resume [sessionId]')
52
54
  .description('Resume a saved Franklin session (alias for: franklin --resume)')
@@ -236,7 +238,7 @@ const args = process.argv.slice(2);
236
238
  const firstArg = args[0];
237
239
  const HELP_FLAGS = new Set(['-h', '--help']);
238
240
  const VERSION_FLAGS = new Set(['-V', '--version']);
239
- const START_ONLY_FLAGS = new Set(['--trust', '--debug', '-m', '--model', '-r', '--resume', '-c', '--continue', '-p', '--prompt', '--max-spend']);
241
+ const START_ONLY_FLAGS = new Set(['--trust', '--debug', '-m', '--model', '--from', '-r', '--resume', '-c', '--continue', '-p', '--prompt', '--max-spend']);
240
242
  function hasAnyFlag(argv, flags) {
241
243
  return argv.some(arg => flags.has(arg));
242
244
  }
@@ -260,6 +262,14 @@ function parseStartFlags(argv, startIdx = 0) {
260
262
  else if (arg === '--max-spend' && argv[i + 1]) {
261
263
  opts.maxSpend = argv[++i];
262
264
  }
265
+ else if (arg === '--from') {
266
+ opts.from = argv[i + 1] && !argv[i + 1].startsWith('-') ? argv[++i] : '';
267
+ const next = argv[i + 1];
268
+ if (next && !next.startsWith('-')) {
269
+ opts.fromSessionId = next;
270
+ i++;
271
+ }
272
+ }
263
273
  else if (arg === '-c' || arg === '--continue') {
264
274
  opts.continue = true;
265
275
  }