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
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ agent host delete beta # remove a host and its stored token
agent session start --config <id> # start a session from a saved config
agent session start --config-file f.json # ...or from an inline config
agent session start --template ellipsis-helper # ...or from a maintained template
agent session start --config <id> --config-override "budget:\n session: 5" # override config fields for this session
agent session start --config <id> --override "budget:\n session: 5" # override config fields for this session
agent session start --config <id> --watch # start and immediately stream it
agent session list --limit 20 # list recent sessions (filter by --source, --author, --since, …)
agent session search "webhook retries" # search session history: transcripts, recaps, created PRs, similarity
Expand All @@ -81,9 +81,6 @@ agent config edit <config-id> --file agents/foo.yaml # replace its definition
agent config delete <config-id> # delete it; the agent stops and its name is freed
agent config link <config-id> --repo api # move it into a repository, via a pull request
agent config unlink <config-id> # take it over from its file, so the API changes it
agent config default # the effective default agent for the repo you are standing in
agent config default set <config-id> # set the account default agent (--repo [owner/name] for one repo)
agent config default clear # clear the account default (--repo [owner/name] for one repo)

agent model list # list selectable agent models (the account default is marked)

Expand Down
4 changes: 2 additions & 2 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"test:watch": "vitest"
},
"dependencies": {
"@ellipsis-dev/sdk": "^0.17.0",
"@ellipsis-dev/sdk": "^0.18.0",
"chalk": "^5.6.2",
"cli-table3": "^0.6.5",
"commander": "^12.1.0",
Expand Down
2 changes: 1 addition & 1 deletion skills/cli-conventions/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Singular nouns, one verb per action.

```
agent file list agent file delete <file-id>
agent session start agent config default set <config-id>
agent session start agent config edit <config-id>
```

- **The noun is singular, always.** `file`, not `files`. `hook`, not
Expand Down
12 changes: 5 additions & 7 deletions skills/ellipsis/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -381,24 +381,23 @@ wins: the environment variable, then the token stored in `~/.ellipsis/config.jso
Start and follow work:

```sh
agent session start "triage the failing CI on api" # runs the resolved default agent
agent session start "triage the failing CI on api" # a bare ad-hoc session
agent session start --config <config-id> --watch # stream until terminal
agent session start --config-file agents/my_agent.yaml --watch
agent session start --template ellipsis-helper --watch
agent session get <session-id> --watch # follow a running session
agent session connect <session-id> # live view plus send messages
agent session stop <session-id>
agent session replay <session-id> # re-run against its frozen snapshot
agent session ide <session-id> # browser IDE into the live sandbox
agent session port <session-id> 3000 # preview a port the sandbox serves
```

With no config source, a bare `start` resolves the repository default, then the
account default, then a bare ad-hoc config on `claude-opus-5`, so the prompt is
the sole instruction. The CLI also sends the repository you are standing in, and
With no config source, a bare `start` runs the bare ad-hoc config — an empty
system prompt on the account's default model in the basic sandbox — so the
prompt is the sole instruction. The CLI also sends the repository you are standing in, and
the server clones it. Per-session overrides need no config edit: `--model`,
`--system`, `--repo`, `--cpu`, `--memory`, `--timeout`, `--budget`, and
`--config-override` for a full partial config. `--rebuild` skips the image
`--override` for a full partial config patch. `--rebuild` skips the image
cache. `--detach` returns immediately. `--watch --quiet` prints only status
transitions and the result, and either watch form exits `0` only when the session
completes.
Expand Down Expand Up @@ -447,7 +446,6 @@ agent config edit <id> --file agents/my_agent.yaml # replace its definition, l
agent config delete <id> # delete it; it stops and frees its name
agent config link <id> --repo api # move it into a repo, via a pull request
agent config unlink <id> # take it over from its file
agent config default set <config-id> # the account default (--repo for one repo)
agent template list # built-in templates and their slugs
agent model list # the model ids valid under claude.model
```
Expand Down
167 changes: 1 addition & 166 deletions src/commands/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { configUrl } from '../lib/urls'
import { readConfigFile } from './session'
import type {
AgentConfig,
AgentDefaults,
CreateAgentConfigRequest,
CreatedAgentConfig,
SavedAgentConfig,
Expand All @@ -23,7 +22,7 @@ export function registerConfig(program: Command): void {
const config = alsoKnownAs(
program
.command('config')
.description('Inspect your agent configs and set which one runs by default'),
.description('Inspect and manage your saved agent configs'),
'configs',
)

Expand Down Expand Up @@ -255,145 +254,6 @@ export function registerConfig(program: Command): void {
})
})

// ------------------------------- defaults --------------------------------
// The default-config ladder a bare session start resolves: repo default ->
// account default -> the bare platform config. Rung-addressed, never row
// ids: writes target the ACCOUNT rung unless --repo names (or detects) a
// repository. Reading is context-aware (bare `agent config default` shows
// the effective default where you stand); writes never are — a mutation
// whose target depends on your cwd would be a footgun, so --repo is always
// explicit.
const defaults = apiRoutes(
alsoKnownAs(
config
.command('default')
.description('Show or set which agent config runs when a session names none'),
'defaults',
),
'GET /v1/agents/defaults',
)
.option('--json', 'output raw JSON')
// Bare `agent config default`: the effective default for the repo you're
// standing in, computed locally from GET /defaults + the origin remote
// (the same ladder session start resolves server-side).
.action(async (opts: { json?: boolean }) => {
await runAction(async () => {
const ladder = await api().agents.defaults.list()
const repo = repoFromCwd(process.cwd())
const repoRung = repo ? repoDefault(ladder, repo) : undefined
const effective = repoRung ?? ladder.account ?? null
if (opts.json) {
printJson({ repository: repo ?? null, effective })
return
}
if (!effective) {
console.log(
repo
? `no default set for ${repo} or the account (sessions start on the bare config)`
: 'no account default set (sessions start on the bare config)',
)
return
}
const rung = repoRung ? `repo default for ${repo}` : 'account default'
console.log(`using config "${effective}" (${rung})`)
})
})

apiRoutes(
alsoKnownAs(
defaults
.command('list')
.description('List every default that is set, account rung and per-repo rungs'),
'ls',
),
'GET /v1/agents/defaults',
)
.option('--json', 'output raw JSON')
// The group also defines --json (for the bare view), and commander parses
// parent options even when they follow the subcommand name — so read the
// merged view, not just this command's own opts.
.action(async (_opts: { json?: boolean }, cmd: Command) => {
await runAction(async () => {
const client = api()
const ladder = await client.agents.defaults.list()
if (cmd.optsWithGlobals().json) {
printJson(ladder)
return
}
const rungs: [string, string][] = [
...(ladder.account ? ([['account', ladder.account]] as [string, string][]) : []),
...Object.entries(ladder.repositories),
]
if (rungs.length === 0) {
console.log('No defaults set. Sessions start on the bare config.')
return
}
// The ladder carries ids only, but a human reads this table — so join
// the account's configs to show each rung's name.
const names = new Map(
(await client.agents.configs.list()).configs.map((c) => [c.id, configName(c)]),
)
printTable(
['RUNG', 'CONFIG', 'CONFIG ID'],
rungs.map(([rung, id]) => [rung, names.get(id) ?? id, id]),
)
})
})

apiRoutes(
defaults
.command('set <config-id>')
.description('Set the account default agent config, or a repo default with --repo'),
'PUT /v1/agents/defaults',
)
.option(
'-r, --repo [repository]',
'target a repo rung: "owner/name", or no value for the repo you are standing in',
)
.option('--json', 'output raw JSON')
.action(
async (configId: string, opts: { repo?: string | boolean; json?: boolean }, cmd: Command) => {
await runAction(async () => {
const repository = resolveRepoFlag(opts.repo)
const ladder = await api().agents.defaults.set({
config_id: configId,
...(repository ? { repository } : {}),
})
if (cmd.optsWithGlobals().json) {
printJson(ladder)
return
}
const rung = repository ? `default for ${repository}` : 'account default'
const id = repository ? repoDefault(ladder, repository) : ladder.account
console.log(`✓ set ${rung} to ${id ?? configId}`)
})
},
)

apiRoutes(
alsoKnownAs(
defaults
.command('clear')
.description('Clear the account default agent config, or a repo default with --repo'),
'rm',
'delete',
),
'DELETE /v1/agents/defaults',
)
.option(
'-r, --repo [repository]',
'target a repo rung: "owner/name", or no value for the repo you are standing in',
)
.action(async (opts: { repo?: string | boolean }) => {
await runAction(async () => {
const repository = resolveRepoFlag(opts.repo)
await api().agents.defaults.delete({ repository })
console.log(
`✓ cleared ${repository ? `default for ${repository}` : 'account default'}`,
)
})
})

apiRoutes(
config
.command('init [path]')
Expand Down Expand Up @@ -472,31 +332,6 @@ function configName(c: SavedAgentConfig): string {
return c.agent_config.ellipsis.name ?? c.id
}

// --repo semantics on defaults mutations: absent -> the account rung; bare
// --repo -> the repo you're standing in (from the origin remote, an error
// when there isn't one); --repo owner/name -> that repo. Shared with
// `agent review default`, whose rungs are addressed identically.
export function resolveRepoFlag(repo: string | boolean | undefined): string | undefined {
if (repo === undefined || repo === false) return undefined
if (repo === true) {
const detected = repoFromCwd(process.cwd())
if (!detected) {
throw new Error(
'no git repository detected here; pass --repo owner/name or run inside a clone',
)
}
return detected
}
return repo
}

// The repo rung's config id. Rungs are keyed "owner/name" as GitHub spells it,
// so match case-insensitively rather than indexing directly.
function repoDefault(ladder: AgentDefaults, repo: string): string | undefined {
const want = repo.toLowerCase()
return Object.entries(ladder.repositories).find(([r]) => r.toLowerCase() === want)?.[1]
}

// A minimal valid agent config. `claude.system` is the only required field;
// everything else has a server-side default. Roots Ellipsis syncs from:
// agents/, .agents/, ellipsis/, .ellipsis/ (any depth), as .yaml/.yml.
Expand Down
Loading