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
11 changes: 11 additions & 0 deletions .changeset/default-claude-code-session-link.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@gemstack/framework': minor
---

Default the live-run session link to claude.ai/code

A live run now shows a session link to `https://claude.ai/code` by default (the page where a Claude Code session appears once Remote Control is enabled), so the dashboard points somewhere useful without needing `--session-link`. `--fake` gets no link (it has no real session), and an explicit `--session-link` still wins — including the `{sessionId}` template, which is filled in with the real Claude session id once known.

Why not a per-session deep link: we drive Claude Code headless, and Remote Control (which powers the claude.ai/code session view) is opt-in and subscription-gated, so there is no session-URL slug to construct. The session id is already surfaced on the dashboard and in the CLI narration; the README's new "Watching the live session" section documents the Remote Control path. New exports: `chooseSessionLink`, `CLAUDE_CODE_SESSION_LIST`.

Part of #209. Closes #212.
12 changes: 12 additions & 0 deletions packages/framework/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,18 @@ framework --fake Offline demo (no CLI, no model, deterministic).
The live path needs the Claude Code CLI installed (`claude` on `PATH`). The
`--fake` path needs neither a CLI nor a model, so it is what CI runs.

### Watching the live session

Every run surfaces the current Claude Code **session id** on the dashboard (and in
the CLI narration) as soon as the wrapped agent reports it. To open the live
session in a browser, enable [Remote Control](https://code.claude.com/docs/en/remote-control)
(`claude remote-control` / `--remote-control`; requires a claude.ai subscription
and Claude Code v2.1.51+) and find the session by id/name at
[claude.ai/code](https://claude.ai/code) — the dashboard's session link points
there by default. If you have a direct per-session URL scheme, pass it with
`--session-link "https://.../{sessionId}"`; `{sessionId}` is filled in with the
real Claude session id once it is known.

## Extensions (#190)

The Framework is modular: it composes **capability extensions** and **skills**
Expand Down
22 changes: 21 additions & 1 deletion packages/framework/src/cli.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { strict as assert } from 'node:assert'
import { test } from 'node:test'
import { buildDeployTarget, parseArgs, runCli, type CliIO } from './cli.js'
import {
buildDeployTarget,
chooseSessionLink,
CLAUDE_CODE_SESSION_LIST,
parseArgs,
runCli,
type CliIO,
} from './cli.js'

function capture(): { io: CliIO; out: string[]; err: string[] } {
const out: string[] = []
Expand Down Expand Up @@ -28,6 +35,19 @@ test('parseArgs reads permission-mode and skip-permissions', () => {
assert.equal(opts.skipPermissions, true)
})

test('chooseSessionLink defaults a live run to the claude.ai/code session list (#212)', () => {
assert.equal(chooseSessionLink({ sessionLink: undefined }, false), CLAUDE_CODE_SESSION_LIST)
assert.equal(CLAUDE_CODE_SESSION_LIST, 'https://claude.ai/code')
})

test('chooseSessionLink honors an explicit --session-link over the default', () => {
assert.equal(chooseSessionLink({ sessionLink: 'https://x/s/{sessionId}' }, false), 'https://x/s/{sessionId}')
})

test('chooseSessionLink gives no link for a fake run (no real session)', () => {
assert.equal(chooseSessionLink({ sessionLink: undefined }, true), undefined)
})

test('runCli --help prints usage and exits 0', async () => {
const { io, out } = capture()
const code = await runCli(['--help'], io)
Expand Down
32 changes: 29 additions & 3 deletions packages/framework/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,26 @@ import { FAKE_DEPLOY, FAKE_INTENT, FAKE_SIGNALS, fakeDriver } from './fake-scrip
import { discoverExtensions, readProjectSignals } from './extensions.js'
import { preflight } from './preflight.js'

/**
* The claude.ai/code session list — the default link shown for a live run. It is
* the page where a Claude Code session appears *when Remote Control is enabled*
* (`claude remote-control` / `--remote-control`, a claude.ai subscription, Claude
* Code v2.1.51+; https://code.claude.com/docs/en/remote-control). We drive Claude
* Code headless, so there is no per-session deep link to construct — pass
* `--session-link "...{sessionId}..."` if you have a real one.
*/
export const CLAUDE_CODE_SESSION_LIST = 'https://claude.ai/code'

/**
* The session link to show for a run: the user's `--session-link` if given, else
* the claude.ai/code list for a live run (nothing for `--fake`, which has no real
* session). Pure, so the default is unit-testable without a live run.
*/
export function chooseSessionLink(opts: Pick<CliOptions, 'sessionLink'>, fake: boolean): string | undefined {
if (opts.sessionLink) return opts.sessionLink
return fake ? undefined : CLAUDE_CODE_SESSION_LIST
}

/** Where the CLI writes. Injectable so tests capture output. */
export interface CliIO {
out: (line: string) => void
Expand Down Expand Up @@ -57,8 +77,11 @@ Options:
--no-dashboard Do not start the localhost dashboard.
--skip-preflight Skip the prerequisite checks before a live run.
--session-link <url> Link to the live agent session (shown on the dashboard).
Use {sessionId} as a placeholder to template in the real
id, e.g. "https://example.com/s/{sessionId}".
Defaults to https://claude.ai/code for a live run, where
the Claude Code session appears when Remote Control is on
(see code.claude.com/docs/en/remote-control). Pass your own
URL, using {sessionId} to template in the real Claude
session id, e.g. "https://example.com/s/{sessionId}".
-h, --help Show this help.
-v, --version Print the version.

Expand Down Expand Up @@ -345,7 +368,10 @@ export async function runCli(argv: string[], io: CliIO = defaultIO): Promise<num
...(serve ? { serve } : {}),
...(discovered ? { extensions: discovered } : {}),
...(opts.composeExtensions ? { composeExtensions: true } : {}),
...(opts.sessionLink ? { sessionLink: opts.sessionLink } : {}),
...((): { sessionLink?: string } => {
const link = chooseSessionLink(opts, fake)
return link ? { sessionLink: link } : {}
})(),
}

try {
Expand Down
Loading