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
61 changes: 55 additions & 6 deletions .claude/commands/release-notes.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
description: Generate a short, curated CHANGELOG.md entry from the commits since the last release
description: Curate a CHANGELOG.md entry from commits since the last release; with a bump arg, also version, commit, push, and publish to npm
argument-hint: "[patch|minor|major]"
allowed-tools: Bash(git describe:*), Bash(git log:*), Bash(git tag:*), Bash(git rev-list:*), Bash(node:*), Read, Edit
allowed-tools: Bash(git describe:*), Bash(git log:*), Bash(git tag:*), Bash(git rev-list:*), Bash(git rev-parse:*), Bash(git status:*), Bash(git add:*), Bash(git commit:*), Bash(git push:*), Bash(node:*), Bash(npm version:*), Bash(npm publish:*), Bash(npm view:*), Bash(cp:*), Bash(pnpm:*), Read, Edit
---

You are writing the next release-notes entry for `@madarco/agentbox`. The
Expand Down Expand Up @@ -56,7 +56,56 @@ changelog is at `apps/cli/CHANGELOG.md` (Keep a Changelog format). Produce
```

Use today's real date — get it from the environment context, do not invent one.
- Do **not** bump `package.json` or create a git tag — that happens at publish
time (`pnpm --filter @madarco/agentbox run publish:<bump>`).
- Print the entry you wrote and stop, so the user can review and edit before
releasing.
- Print the entry you wrote.

## 6. Release (only when `$ARGUMENTS` named a bump)

If `$ARGUMENTS` did **not** name a bump (`patch` / `minor` / `major`), stop here so
the user can review and edit the changelog before releasing — do not bump or push.

Otherwise continue. **This publishes to a public registry and is irreversible**, so
get the user's explicit go-ahead at step 6.4 before publishing.

1. **Bump `package.json` (no commit, no tag yet).** Section 5 just edited the
changelog, so the tree is dirty and a plain `npm version` would abort with
`EGITDIRTYWORKINGDIR`. Bump the version field only, from the package dir:
`cd apps/cli && npm version <bump> --no-git-tag-version`
(this is the version you already wrote into the changelog heading).

2. **Commit the changelog + bump together, and tag.** One commit:
```
git add apps/cli/CHANGELOG.md apps/cli/package.json
git commit -m "release: v<next-version>"
git tag v<next-version>
```
(Stage whatever actually changed — add the root `CHANGELOG.md` too if you edited it.)

3. **Push the commit and tag.** Check the current branch first (`git rev-parse
--abbrev-ref HEAD`). If it is not `main`, tell the user and confirm they want to
release from this branch. Then: `git push --follow-tags`.

4. **Confirm before publishing.** Restate package (`@madarco/agentbox`), the new
version, and the branch. Verify the version is not already on the registry
(`npm view @madarco/agentbox@<next-version> version` should print nothing). Get
an explicit go-ahead.

5. **Publish and surface the MFA link.** From the package dir (`prepublishOnly`
rebuilds the whole workspace first, so this also runs the full build):
`cd apps/cli && npm publish --auth-type=web`
- With 2FA enabled, npm prints a web-auth URL:
```
npm notice Authenticate your account at:
npm notice https://www.npmjs.com/auth/cli/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
```
**Show that full URL to the user immediately and prominently** ("Open this to
authorize the publish: <url>"). Keep the command running in the **foreground** —
npm completes the publish automatically once the browser approval lands. Do not
cancel or background it.
- **Classic TOTP fallback:** if npm instead asks for a one-time password
(`This operation requires a one-time password`), ask the user for the 6-digit
code and re-run with `--otp=<code>`.

6. **Confirm success.** `npm view @madarco/agentbox version` should now show
<next-version>. Report the published version, the pushed tag, and the commit. If
`npm publish` fails (already-published version, auth, build), report the exact
error and stop — do not retry blindly.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,8 @@ coverage/
.vscode/
.idea/

# Claude Code session-specific runtime state (lock files, etc.) — keep
# .claude/commands and other intentional config tracked.
.claude/scheduled_tasks.lock

TODO.md
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,4 @@ Each topic has a dedicated file under [`docs/`](./docs). Read the relevant one b
- [`docs/daytona-backlog.md`](./docs/daytona-backlog.md) — what's done vs still missing on the Daytona path. Quick index of where each cloud feature actually lives.
- [`docs/hertzner_backlog.md`](./docs/hertzner_backlog.md) — Hetzner provider build-out status: phase-by-phase progress, the live e2e smoke results, deferred follow-ups (per-project snapshot tier, `--pause` checkpoint flag, `agentbox prune --provider hetzner`, the install-script post-Chromium trace mystery). Filename uses the user-requested spelling.
- [`docs/vercel-backlog.md`](./docs/vercel-backlog.md) — Vercel provider build-out status: why Vercel's shape differs (no Dockerfile, no containers, no SSH, persistent snapshots), phase-by-phase progress, and the live-verify checklist (user mapping, attach latency / ttyd upgrade, snapshot-vs-delete cascade, VNC on AL2023, published-CLI asset staging).
- [`docs/linux-host-backlog.md`](./docs/linux-host-backlog.md) — Linux (Ubuntu) **host** support: what's done (`agentbox doctor` is Linux-aware), how to test on a persistent Hetzner Ubuntu VM (`scripts/linux-dev-vm.sh` — `up`/`deploy`/`ssh`/`doctor`/`down`), and the remaining macOS-only host assumptions (browser `open`→`xdg-open`, iTerm2/AppleScript terminal spawning, OrbStack-only fast paths).
26 changes: 15 additions & 11 deletions apps/cli/src/commands/_cloud-agent-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
* only runs when the caller pre-resolved a non-docker provider.
*/

import { log, outro } from '@clack/prompts';
import type { BoxRecord, CreateBoxRequest, Provider } from '@agentbox/core';
import type { AttachOpenIn } from '@agentbox/config';
import { makeProgressReporter } from '../lib/progress.js';
import { printLaunchRecap } from '../lib/launch-recap.js';
import { cloudAgentAttach } from './_cloud-attach.js';

export interface CloudAgentCreateArgs {
Expand Down Expand Up @@ -71,26 +71,30 @@ export async function cloudAgentCreate(args: CloudAgentCreateArgs): Promise<void
typeof result.record.projectIndex === 'number'
? ` · n ${String(result.record.projectIndex)}`
: '';
s.stop(`box ${result.record.name} ready${nSuffix}`);
log.info(`id: ${result.record.id}`);
log.info(`provider: ${result.record.provider}`);
if (result.record.cloud?.sandboxId) {
log.info(`sandboxId: ${result.record.cloud.sandboxId}`);
}
s.stop(`box ready${nSuffix}`);
let extraArgs = args.extraArgs;
if (args.beforeStart) {
const hook = await args.beforeStart(result.record);
if (hook && hook.agentArgsPrefix && hook.agentArgsPrefix.length > 0) {
extraArgs = [...hook.agentArgsPrefix, ...(extraArgs ?? [])];
}
}
await printLaunchRecap({
record: result.record,
mode: args.mode,
reattach:
typeof result.record.projectIndex === 'number'
? String(result.record.projectIndex)
: result.record.name,
workspacePath: args.request.workspacePath,
fromBranch: args.request.fromBranch,
useBranch: args.request.useBranch,
checkpointRef: args.request.checkpointRef,
attaching: args.attach !== false,
});
if (args.attach === false) {
outro(
`session not started — attach with: agentbox ${args.mode} attach ${result.record.name}`,
);
return;
}
outro(`attaching ${args.mode} — Control+a d to detach, leaves the agent running`);
await cloudAgentAttach({
box: result.record,
binary: args.binary,
Expand Down
17 changes: 12 additions & 5 deletions apps/cli/src/commands/claude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import {
} from '../session-teleport/index.js';
import { clampSpinnerLine } from '../spinner-line.js';
import { makeProgressReporter } from '../lib/progress.js';
import { printLaunchRecap } from '../lib/launch-recap.js';
import { maybeShowInstallHint } from '../lib/install-hint.js';
import { openCommandLog } from '../lib/log-file.js';
import { resolveLimits } from '../limits.js';
Expand Down Expand Up @@ -816,20 +817,26 @@ export const claudeCommand = new Command('claude')
typeof result.record.projectIndex === 'number'
? ` · n ${String(result.record.projectIndex)}`
: '';
s.stop(`box ${result.record.container} ready${nSuffix}`);
s.stop(`box ready${nSuffix}`);
logPrune(rebuild);
for (const f of rebuild.failed) {
log.warn(`plugin install failed for ${f.dir}; claude may still load it. stderr:\n${f.stderr.trim()}`);
}
maybeShowInstallHint();

await printLaunchRecap({
record: result.record,
mode: 'claude',
reattach: reattachRef(result.record),
workspacePath: opts.workspace,
fromBranch,
useBranch,
checkpointRef,
attaching: opts.attach !== false,
});
if (opts.attach === false) {
outro(
`session started — attach with: agentbox claude attach ${reattachRef(result.record)}`,
);
return;
}
outro('attaching — Control+a d to detach, leaves claude running');
await attachClaudeWrapped(
result.record,
sessionName,
Expand Down
5 changes: 3 additions & 2 deletions apps/cli/src/commands/code.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { spawn } from 'node:child_process';
import { log } from '@clack/prompts';
import { Command, InvalidArgumentError } from 'commander';
import { hostOpenCommand } from '@agentbox/sandbox-core';
import type { BoxRecord } from '@agentbox/core';
import type { StatusReply, WaitReadyReply } from '@agentbox/ctl';
import { loadEffectiveConfig, type IdeFlavor as ConfigIdeFlavor, type UserConfig } from '@agentbox/config';
Expand Down Expand Up @@ -307,10 +308,10 @@ async function launchOne(flavor: IdeFlavor, folderUri: string): Promise<LaunchRe
const cliCode = await spawnCommand(profile.cli, ['--folder-uri', folderUri]);
if (cliCode !== 127) return { code: cliCode, flavor, via: 'cli' };
log.warn(
`\`${profile.cli}\` not found in PATH; falling back to \`open ${profile.protocolScheme}://...\` (the %2B URL-encoding bug may break attach)`,
`\`${profile.cli}\` not found in PATH; falling back to \`${hostOpenCommand()} ${profile.protocolScheme}://...\` (the %2B URL-encoding bug may break attach)`,
);
const url = `${profile.protocolScheme}://${folderUri.replace(/^vscode-remote:\/\//, 'vscode-remote/')}`;
const fallback = await spawnCommand('open', [url]);
const fallback = await spawnCommand(hostOpenCommand(), [url]);
return { code: fallback, flavor, via: 'open' };
}

Expand Down
17 changes: 12 additions & 5 deletions apps/cli/src/commands/codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import {
} from '../session-teleport/index.js';
import { clampSpinnerLine } from '../spinner-line.js';
import { makeProgressReporter } from '../lib/progress.js';
import { printLaunchRecap } from '../lib/launch-recap.js';
import { openCommandLog } from '../lib/log-file.js';
import { resolveLimits } from '../limits.js';
import { maybePromptPortless } from '../portless-prompt.js';
Expand Down Expand Up @@ -694,15 +695,21 @@ export const codexCommand = new Command('codex')
typeof result.record.projectIndex === 'number'
? ` · n ${String(result.record.projectIndex)}`
: '';
s.stop(`box ${result.record.container} ready${nSuffix}`);
s.stop(`box ready${nSuffix}`);

await printLaunchRecap({
record: result.record,
mode: 'codex',
reattach: reattachRef(result.record),
workspacePath: opts.workspace,
fromBranch,
useBranch,
checkpointRef,
attaching: opts.attach !== false,
});
if (opts.attach === false) {
outro(
`session started — attach with: agentbox codex attach ${reattachRef(result.record)}`,
);
return;
}
outro('attaching — Control+a d to detach, leaves codex running');
await attachCodexWrapped(
result.record,
sessionName,
Expand Down
8 changes: 4 additions & 4 deletions apps/cli/src/commands/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
waitForTmuxPaneContent,
type ListedBox,
} from '@agentbox/sandbox-docker';
import { readState } from '@agentbox/sandbox-core';
import { hostOpenCommand, readState } from '@agentbox/sandbox-core';
import type { BoxRecord } from '@agentbox/core';
import { resolveBoxOrExit } from '../box-ref.js';
import { resolveClaudeAuth } from '../auth.js';
Expand Down Expand Up @@ -557,9 +557,9 @@ export const dashboardCommand = new Command('dashboard')
} catch {
// Best-effort — still open the viewer even if the box isn't running.
}
detach('open', [url]);
detach(hostOpenCommand(), [url]);
if (exposedWebUrl) {
detach('open', [exposedWebUrl]);
detach(hostOpenCommand(), [exposedWebUrl]);
return 'Opening VNC + web in browser…';
}
return 'Opening VNC in browser…';
Expand All @@ -569,7 +569,7 @@ export const dashboardCommand = new Command('dashboard')
const box = (await listBoxes()).find((b) => b.id === boxId);
if (!box) return 'box not found';
const { url } = webTarget(box);
detach('open', [url]);
detach(hostOpenCommand(), [url]);
return `Opening ${url.replace(/^https?:\/\//, '')}…`;
};

Expand Down
8 changes: 5 additions & 3 deletions apps/cli/src/commands/open.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { existsSync, mkdirSync } from 'node:fs';
import { homedir } from 'node:os';
import { join } from 'node:path';
import type { BoxRecord } from '@agentbox/core';
import { hostOpenCommand } from '@agentbox/sandbox-core';
import { openBoxInFinder } from '@agentbox/sandbox-docker';
import { Command } from 'commander';
import { resolveBoxOrExit } from '../box-ref.js';
Expand Down Expand Up @@ -156,9 +157,10 @@ async function runCloudOpen(
if (mount.exitCode !== 0) {
throw new Error(`sshfs mount failed (exit ${String(mount.exitCode)}): ${mount.stderr || mount.stdout}`);
}
// `open` on macOS reveals the dir in Finder. On non-macOS this is a no-op
// / error — degrade silently because the mount path is already printed.
await execa('open', [mountRoot], { reject: false });
// Reveal the mount in the OS file manager (Finder on macOS, the default
// handler via xdg-open on Linux). Best-effort — the mount path is already
// printed, so a missing opener degrades silently.
await execa(hostOpenCommand(), [mountRoot], { reject: false });
process.stdout.write(`opened ${mountRoot}\n`);
process.stdout.write(`unmount later with: agentbox open ${box.name} --unmount\n`);
}
Expand Down
17 changes: 12 additions & 5 deletions apps/cli/src/commands/opencode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import { providerForCreate } from '../provider/registry.js';
import { prepareTeleport, TeleportError } from '../session-teleport/index.js';
import { clampSpinnerLine } from '../spinner-line.js';
import { makeProgressReporter } from '../lib/progress.js';
import { printLaunchRecap } from '../lib/launch-recap.js';
import { openCommandLog } from '../lib/log-file.js';
import { resolveLimits } from '../limits.js';
import { maybePromptPortless } from '../portless-prompt.js';
Expand Down Expand Up @@ -620,15 +621,21 @@ export const opencodeCommand = new Command('opencode')
typeof result.record.projectIndex === 'number'
? ` · n ${String(result.record.projectIndex)}`
: '';
s.stop(`box ${result.record.container} ready${nSuffix}`);
s.stop(`box ready${nSuffix}`);

await printLaunchRecap({
record: result.record,
mode: 'opencode',
reattach: reattachRef(result.record),
workspacePath: opts.workspace,
fromBranch,
useBranch,
checkpointRef,
attaching: opts.attach !== false,
});
if (opts.attach === false) {
outro(
`session started — attach with: agentbox opencode attach ${reattachRef(result.record)}`,
);
return;
}
outro('attaching — Control+a d to detach, leaves opencode running');
await attachOpencodeWrapped(
result.record,
sessionName,
Expand Down
3 changes: 2 additions & 1 deletion apps/cli/src/commands/screen.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { spawnSync } from 'node:child_process';
import { log } from '@clack/prompts';
import { hostOpenCommand } from '@agentbox/sandbox-core';
import {
buildVncUrls,
detectEngine,
Expand Down Expand Up @@ -173,7 +174,7 @@ export const screenCommand = new Command('screen')
return;
}

const opened = spawnSync('open', [url], { stdio: 'inherit' });
const opened = spawnSync(hostOpenCommand(), [url], { stdio: 'inherit' });
if (opened.status !== 0) {
throw new Error(`open ${url} failed (exit ${String(opened.status ?? 'n/a')})`);
}
Expand Down
3 changes: 2 additions & 1 deletion apps/cli/src/commands/url.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { spawnSync } from 'node:child_process';
import { log } from '@clack/prompts';
import { hostOpenCommand } from '@agentbox/sandbox-core';
import {
detectEngine,
getBoxHostPaths,
Expand Down Expand Up @@ -119,7 +120,7 @@ export const urlCommand = new Command('url')
return;
}

const opened = spawnSync('open', [url], { stdio: 'inherit' });
const opened = spawnSync(hostOpenCommand(), [url], { stdio: 'inherit' });
if (opened.status !== 0) {
throw new Error(`open ${url} failed (exit ${String(opened.status ?? 'n/a')})`);
}
Expand Down
Loading
Loading