Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 23 additions & 7 deletions apps/cli/src/commands/claude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import {
import { cloudAgentAttach } from './_cloud-attach.js';
import { cloudAgentCreate } from './_cloud-agent-create.js';
import { runCarryGate } from '../lib/carry-gate.js';
import { FromBranchError, resolveFromBranch } from '../lib/from-branch.js';
import { FromBranchError, UseBranchError, resolveBranchSelection } from '../lib/from-branch.js';
import { providerForBox, providerForCreate } from '../provider/registry.js';
import {
prepareTeleport,
Expand Down Expand Up @@ -170,13 +170,15 @@ interface ClaudeCreateOptions {
provider?: string;
/** --from-branch <ref>: base the box's per-box branch on this ref instead of HEAD. */
fromBranch?: string;
/** -b / --use-branch <name>: reuse an existing branch directly instead of forking agentbox/<name>. */
useBranch?: string;
/** -v / --verbose: bypass the spinner and stream raw provider output. */
verbose?: boolean;
/** Raw `--attach-in <mode>` value; validated by `parseAttachInOption`. */
attachIn?: string;
/** --inline: shortcut for `--attach-in same` (long-form only — `-i` is `--initial-prompt`). */
inline?: boolean;
/** Commander parses `-b, --no-attach` as `attach: false` (defaults true). */
/** Commander parses `-d, --no-attach` as `attach: false` (defaults true). */
attach?: boolean;
/**
* `-i, --initial-prompt <text>`: seed the claude TUI with this user turn
Expand Down Expand Up @@ -349,13 +351,17 @@ export const claudeCommand = new Command('claude')
'--from-branch <ref>',
"base the box's per-box branch on this ref (branch / tag / SHA) instead of HEAD. Branch/tag names are fetched from origin first.",
)
.option(
'-b, --use-branch <name>',
"reuse an existing branch directly instead of forking agentbox/<box-name>. Commits/pushes flow straight to it. Docker fails if the host already has it checked out. Mutually exclusive with --from-branch.",
)
.option(
'-v, --verbose',
'bypass the spinner and stream raw provider output (docker build / Daytona snapshot create) to stderr. The same content always lands in ~/.agentbox/logs/claude.log.',
)
.option('--attach-in <mode>', ATTACH_IN_HELP)
.option('--inline', INLINE_HELP)
.option('-b, --no-attach', NO_ATTACH_HELP)
.option('-d, --no-attach', NO_ATTACH_HELP)
.option(
'-i, --initial-prompt <text>',
'seed the claude session with this initial user turn and run in background (no attach). Jobs go through the host-wide queue (queue.maxConcurrent). NOTE: this is NOT claude\'s own `-p` headless print mode — for that, pass `-- -p ...`.',
Expand Down Expand Up @@ -542,13 +548,21 @@ export const claudeCommand = new Command('claude')
effectiveClaudeArgs = buildPromptArgs('claude-code', wiz.initialPrompt, claudeArgs);
}

// Validate --from-branch before any provider work so a typo doesn't
// Validate branch selection before any provider work so a typo doesn't
// leave a half-created box.
let fromBranch: string | undefined;
let useBranch: string | undefined;
try {
fromBranch = await resolveFromBranch(opts.fromBranch, { repo: opts.workspace });
({ fromBranch, useBranch } = await resolveBranchSelection({
useBranch: opts.useBranch,
fromBranch: opts.fromBranch,
repo: opts.workspace,
providerName,
cloudUseCurrentBranch: cfg.effective.cloud.useCurrentBranch,
log: (m) => cmdLog.write(m),
}));
} catch (err) {
if (err instanceof FromBranchError) {
if (err instanceof FromBranchError || err instanceof UseBranchError) {
log.error(err.message);
cmdLog.close();
process.exit(2);
Expand Down Expand Up @@ -576,6 +590,7 @@ export const claudeCommand = new Command('claude')
vnc: { enabled: cfg.effective.box.vnc },
limits: resolveLimits(cfg.effective.box, opts),
fromBranch,
useBranch,
projectRoot,
},
binary: 'claude',
Expand Down Expand Up @@ -634,6 +649,7 @@ export const claudeCommand = new Command('claude')
useSnapshot,
checkpointRef,
fromBranch,
useBranch,
image: cfg.effective.box.image,
claudeConfig: { isolate: cfg.effective.box.isolateClaudeConfig },
claudeEnv: resolved.env,
Expand Down Expand Up @@ -986,7 +1002,7 @@ const claudeStartCommand = new Command('start')
)
.option('--attach-in <mode>', ATTACH_IN_HELP)
.option('-i, --inline', INLINE_HELP)
.option('-b, --no-attach', NO_ATTACH_HELP)
.option('-d, --no-attach', NO_ATTACH_HELP)
.option(
'-c, --continue',
'teleport the most recent host Claude Code session for this cwd into the box and resume',
Expand Down
28 changes: 22 additions & 6 deletions apps/cli/src/commands/codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import {
import { cloudAgentAttach } from './_cloud-attach.js';
import { cloudAgentCreate } from './_cloud-agent-create.js';
import { runCarryGate } from '../lib/carry-gate.js';
import { FromBranchError, resolveFromBranch } from '../lib/from-branch.js';
import { FromBranchError, UseBranchError, resolveBranchSelection } from '../lib/from-branch.js';
import { providerForBox, providerForCreate } from '../provider/registry.js';
import {
prepareTeleport,
Expand Down Expand Up @@ -153,13 +153,15 @@ interface CodexCreateOptions {
provider?: string;
/** --from-branch <ref>: base the box's per-box branch on this ref instead of HEAD. */
fromBranch?: string;
/** -b / --use-branch <name>: reuse an existing branch directly instead of forking agentbox/<name>. */
useBranch?: string;
/** -v / --verbose: bypass the spinner and stream raw provider output. */
verbose?: boolean;
/** Raw `--attach-in <mode>` value; validated by `parseAttachInOption`. */
attachIn?: string;
/** --inline: shortcut for `--attach-in same` (long-form only — `-i` is `--initial-prompt`). */
inline?: boolean;
/** Commander parses `-b, --no-attach` as `attach: false` (defaults true). */
/** Commander parses `-d, --no-attach` as `attach: false` (defaults true). */
attach?: boolean;
/** `-i, --initial-prompt <text>`: seed codex with this user turn; runs in background. */
initialPrompt?: string;
Expand Down Expand Up @@ -294,13 +296,17 @@ export const codexCommand = new Command('codex')
'--from-branch <ref>',
"base the box's per-box branch on this ref (branch / tag / SHA) instead of HEAD. Branch/tag names are fetched from origin first.",
)
.option(
'-b, --use-branch <name>',
"reuse an existing branch directly instead of forking agentbox/<box-name>. Commits/pushes flow straight to it. Docker fails if the host already has it checked out. Mutually exclusive with --from-branch.",
)
.option(
'-v, --verbose',
'bypass the spinner and stream raw provider output to stderr. The same content always lands in ~/.agentbox/logs/codex.log.',
)
.option('--attach-in <mode>', ATTACH_IN_HELP)
.option('--inline', INLINE_HELP)
.option('-b, --no-attach', NO_ATTACH_HELP)
.option('-d, --no-attach', NO_ATTACH_HELP)
.option(
'-i, --initial-prompt <text>',
'seed the codex session with this initial user turn and run in background (no attach). Jobs go through the host-wide queue (queue.maxConcurrent).',
Expand Down Expand Up @@ -434,10 +440,18 @@ export const codexCommand = new Command('codex')
}

let fromBranch: string | undefined;
let useBranch: string | undefined;
try {
fromBranch = await resolveFromBranch(opts.fromBranch, { repo: opts.workspace });
({ fromBranch, useBranch } = await resolveBranchSelection({
useBranch: opts.useBranch,
fromBranch: opts.fromBranch,
repo: opts.workspace,
providerName,
cloudUseCurrentBranch: cfg.effective.cloud.useCurrentBranch,
log: (m) => cmdLog.write(m),
}));
} catch (err) {
if (err instanceof FromBranchError) {
if (err instanceof FromBranchError || err instanceof UseBranchError) {
log.error(err.message);
cmdLog.close();
process.exit(2);
Expand All @@ -462,6 +476,7 @@ export const codexCommand = new Command('codex')
vnc: { enabled: cfg.effective.box.vnc },
limits: resolveLimits(cfg.effective.box, opts),
fromBranch,
useBranch,
projectRoot,
},
binary: 'codex',
Expand Down Expand Up @@ -528,6 +543,7 @@ export const codexCommand = new Command('codex')
useSnapshot,
checkpointRef,
fromBranch,
useBranch,
image: cfg.effective.box.image,
codexConfig: { isolate: cfg.effective.box.isolateCodexConfig },
withPlaywright,
Expand Down Expand Up @@ -817,7 +833,7 @@ const codexStartCommand = new Command('start')
)
.option('--attach-in <mode>', ATTACH_IN_HELP)
.option('-i, --inline', INLINE_HELP)
.option('-b, --no-attach', NO_ATTACH_HELP)
.option('-d, --no-attach', NO_ATTACH_HELP)
.option(
'-c, --continue',
'teleport the most recent host Codex session for this cwd into the box and resume',
Expand Down
26 changes: 22 additions & 4 deletions apps/cli/src/commands/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
import { Command } from 'commander';
import { execSync, spawnSync } from 'node:child_process';
import { runCarryGate } from '../lib/carry-gate.js';
import { FromBranchError, resolveFromBranch } from '../lib/from-branch.js';
import { FromBranchError, UseBranchError, resolveBranchSelection } from '../lib/from-branch.js';
import { openCommandLog } from '../lib/log-file.js';
import { makeProgressReporter } from '../lib/progress.js';
import { maybePromptPortless, setupPortlessHost } from '../portless-prompt.js';
Expand Down Expand Up @@ -59,6 +59,8 @@ interface CreateOptions {
bundleDepth?: number;
/** --from-branch <ref>: base the box's per-box branch on this ref (branch / tag / SHA) instead of HEAD. */
fromBranch?: string;
/** -b / --use-branch <name>: reuse an existing branch directly instead of forking agentbox/<name>. */
useBranch?: string;
/** -v / --verbose: also stream raw build / provision output to stderr. */
verbose?: boolean;
}
Expand Down Expand Up @@ -173,6 +175,10 @@ export const createCommand = new Command('create')
'--from-branch <ref>',
"base the box's per-box branch on this ref (branch / tag / SHA) instead of HEAD. Branch/tag names are fetched from origin first.",
)
.option(
'-b, --use-branch <name>',
"reuse an existing branch directly instead of forking agentbox/<box-name>. Commits/pushes flow straight to it. Docker fails if the host already has it checked out. Mutually exclusive with --from-branch.",
)
.option('-y, --yes', 'skip prompts, accept defaults')
.option(
'--carry-yes',
Expand Down Expand Up @@ -302,11 +308,22 @@ export const createCommand = new Command('create')
// provider for 'daytona'; everything below is provider-neutral.
const provider = await providerForCreate({ flag: opts.provider, config: cfg.effective });
let fromBranch: string | undefined;
let useBranch: string | undefined;
try {
fromBranch = await resolveFromBranch(opts.fromBranch, { repo: opts.workspace });
({ fromBranch, useBranch } = await resolveBranchSelection({
useBranch: opts.useBranch,
fromBranch: opts.fromBranch,
repo: opts.workspace,
providerName: provider.name,
cloudUseCurrentBranch: cfg.effective.cloud.useCurrentBranch,
log: (m) => {
s.message(m);
cmdLog.write(m);
},
}));
} catch (err) {
if (err instanceof FromBranchError) {
s.stop('aborting: invalid --from-branch');
if (err instanceof FromBranchError || err instanceof UseBranchError) {
s.stop('aborting: invalid branch selection');
log.error(err.message);
cmdLog.close();
process.exit(2);
Expand All @@ -326,6 +343,7 @@ export const createCommand = new Command('create')
limits: resolveLimits(cfg.effective.box, opts),
bundleDepth: cfg.effective.box.bundleDepth,
fromBranch,
useBranch,
projectRoot,
onLog: (line) => {
s.message(line);
Expand Down
28 changes: 22 additions & 6 deletions apps/cli/src/commands/opencode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import {
import { cloudAgentAttach } from './_cloud-attach.js';
import { cloudAgentCreate } from './_cloud-agent-create.js';
import { runCarryGate } from '../lib/carry-gate.js';
import { FromBranchError, resolveFromBranch } from '../lib/from-branch.js';
import { FromBranchError, UseBranchError, resolveBranchSelection } from '../lib/from-branch.js';
import { providerForCreate } from '../provider/registry.js';
import { prepareTeleport, TeleportError } from '../session-teleport/index.js';
import { clampSpinnerLine } from '../spinner-line.js';
Expand Down Expand Up @@ -145,13 +145,15 @@ interface OpencodeCreateOptions {
provider?: string;
/** --from-branch <ref>: base the box's per-box branch on this ref instead of HEAD. */
fromBranch?: string;
/** -b / --use-branch <name>: reuse an existing branch directly instead of forking agentbox/<name>. */
useBranch?: string;
/** -v / --verbose: bypass the spinner and stream raw provider output. */
verbose?: boolean;
/** Raw `--attach-in <mode>` value; validated by `parseAttachInOption`. */
attachIn?: string;
/** --inline: shortcut for `--attach-in same` (long-form only — `-i` is `--initial-prompt`). */
inline?: boolean;
/** Commander parses `-b, --no-attach` as `attach: false` (defaults true). */
/** Commander parses `-d, --no-attach` as `attach: false` (defaults true). */
attach?: boolean;
/** `-i, --initial-prompt <text>`: seed opencode with this user turn; runs in background. */
initialPrompt?: string;
Expand Down Expand Up @@ -290,13 +292,17 @@ export const opencodeCommand = new Command('opencode')
'--from-branch <ref>',
"base the box's per-box branch on this ref (branch / tag / SHA) instead of HEAD. Branch/tag names are fetched from origin first.",
)
.option(
'-b, --use-branch <name>',
"reuse an existing branch directly instead of forking agentbox/<box-name>. Commits/pushes flow straight to it. Docker fails if the host already has it checked out. Mutually exclusive with --from-branch.",
)
.option(
'-v, --verbose',
'bypass the spinner and stream raw provider output to stderr. The same content always lands in ~/.agentbox/logs/opencode.log.',
)
.option('--attach-in <mode>', ATTACH_IN_HELP)
.option('--inline', INLINE_HELP)
.option('-b, --no-attach', NO_ATTACH_HELP)
.option('-d, --no-attach', NO_ATTACH_HELP)
.option(
'-i, --initial-prompt <text>',
'seed the opencode session with this initial user turn and run in background (no attach). Jobs go through the host-wide queue (queue.maxConcurrent).',
Expand Down Expand Up @@ -420,10 +426,18 @@ export const opencodeCommand = new Command('opencode')
}

let fromBranch: string | undefined;
let useBranch: string | undefined;
try {
fromBranch = await resolveFromBranch(opts.fromBranch, { repo: opts.workspace });
({ fromBranch, useBranch } = await resolveBranchSelection({
useBranch: opts.useBranch,
fromBranch: opts.fromBranch,
repo: opts.workspace,
providerName,
cloudUseCurrentBranch: cfg.effective.cloud.useCurrentBranch,
log: (m) => cmdLog.write(m),
}));
} catch (err) {
if (err instanceof FromBranchError) {
if (err instanceof FromBranchError || err instanceof UseBranchError) {
log.error(err.message);
cmdLog.close();
process.exit(2);
Expand All @@ -448,6 +462,7 @@ export const opencodeCommand = new Command('opencode')
vnc: { enabled: cfg.effective.box.vnc },
limits: resolveLimits(cfg.effective.box, opts),
fromBranch,
useBranch,
projectRoot,
},
binary: 'opencode',
Expand Down Expand Up @@ -494,6 +509,7 @@ export const opencodeCommand = new Command('opencode')
useSnapshot,
checkpointRef,
fromBranch,
useBranch,
image: cfg.effective.box.image,
opencodeConfig: { isolate: cfg.effective.box.isolateOpencodeConfig },
withPlaywright,
Expand Down Expand Up @@ -720,7 +736,7 @@ const opencodeStartCommand = new Command('start')
)
.option('--attach-in <mode>', ATTACH_IN_HELP)
.option('-i, --inline', INLINE_HELP)
.option('-b, --no-attach', NO_ATTACH_HELP)
.option('-d, --no-attach', NO_ATTACH_HELP)
.option(
'-c, --continue',
'session teleport (not yet supported for opencode in v1; emits a friendly error)',
Expand Down
Loading
Loading