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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ out for all of them.
Every field is optional and the defaults above are what you get with no
`sessionBar` at all. Two caveats on `repo`: a shell outside a repository lists
every repository rather than nothing, and sessions that name their repository
only inside their agent config — dashboard starts, cron runs, handoffs — do not
only inside their agent config — dashboard starts, cron runs — do not
match a repo filter, so `"repo": "any"` is the way to see those alongside the
rest.

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.12.0",
"@ellipsis-dev/sdk": "^0.13.0",
"chalk": "^5.6.2",
"cli-table3": "^0.6.5",
"commander": "^12.1.0",
Expand Down
18 changes: 0 additions & 18 deletions skills/ellipsis/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,6 @@ fee. There are no seats.
The built-in responder needs no configuration and answers in the thread.
- **Catching bugs before merge**: turn code review on and every pull request is
reviewed, or commit a pipeline file to scope and customize it.
- **A task that should not block the laptop**: `agent session handoff` pushes a
snapshot of the working tree and continues the work in a cloud session.
- **Delegation from scripts or CI**: `agent session start` or
`POST /sessions`. With `--watch` it streams into the log and exits nonzero
unless the session completes, so it works as a gate.
Expand Down Expand Up @@ -474,22 +472,6 @@ agent github repos # also github members, slack channe
agent file upload shot.png # store a PNG, print an org-gated link
```

Sync local Claude Code sessions into the same searchable history, then hand work
off:

```sh
agent hook install # Stop and SessionEnd hooks
agent hook enroll # opt this repository in; sync is per-repo
agent hook status # what is installed and enrolled
agent session handoff "finish the retry backoff and add tests" --parent <session-id>
```

Sync is opt-in per repository: installing the hooks alone uploads nothing.
Transcripts are redacted locally before upload, so local work becomes auditable
without shipping credentials. `handoff` requires `--parent`, pushes the
working-tree snapshot to a hidden ref rather than a branch, and leaves your tree
undisturbed.

Most singular commands accept the plural spelling as a hidden alias, and
`review` also answers to `cr`. `agent --help` and `agent <command> --help` are
authoritative for flags.
Expand Down
2 changes: 0 additions & 2 deletions src/cli.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { registerReview } from './commands/review'
import { registerConfig } from './commands/config'
import { registerVariable } from './commands/variable'
import { registerFile } from './commands/file'
import { registerHook } from './commands/hooks'
import { registerTemplate } from './commands/template'
import { registerModel } from './commands/model'
import { registerIntegration } from './commands/integrations'
Expand Down Expand Up @@ -45,7 +44,6 @@ registerReview(program)
registerConfig(program)
registerVariable(program)
registerFile(program)
registerHook(program)
registerTemplate(program)
registerModel(program)
registerIntegration(program)
Expand Down
67 changes: 32 additions & 35 deletions src/commands/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { basename, dirname, extname } from 'node:path'
import { api } from '../lib/api'
import { resolveAppBase } from '../lib/config'
import { alsoKnownAs, apiRoutes } from '../lib/help'
import { repoFromCwd } from '../lib/laptop'
import { repoFromCwd } from '../lib/git'
import { formatTs, printJson, printTable, printYaml, runAction } from '../lib/output'
import { configUrl } from '../lib/urls'
import { readConfigFile } from './session'
import type {
AgentConfig,
AgentDefaultView,
AgentDefaults,
CreateAgentConfigRequest,
CreatedAgentConfig,
SavedAgentConfig,
Expand Down Expand Up @@ -262,15 +262,12 @@ export function registerConfig(program: Command): void {
// (the same ladder session start resolves server-side).
.action(async (opts: { json?: boolean }) => {
await runAction(async () => {
const { defaults: rungs } = await api().agents.defaults.list()
const ladder = await api().agents.defaults.list()
const repo = repoFromCwd(process.cwd())
const repoRung = repo
? rungs.find((d) => d.repository?.toLowerCase() === repo.toLowerCase())
: undefined
const accountRung = rungs.find((d) => d.repository === null)
const effective = repoRung ?? accountRung
const repoRung = repo ? repoDefault(ladder, repo) : undefined
const effective = repoRung ?? ladder.account ?? null
if (opts.json) {
printJson({ repository: repo ?? null, effective: effective ?? null })
printJson({ repository: repo ?? null, effective })
return
}
if (!effective) {
Expand All @@ -281,10 +278,8 @@ export function registerConfig(program: Command): void {
)
return
}
const rung = effective.repository
? `repo default for ${effective.repository}`
: 'account default'
console.log(`using config "${defaultName(effective)}" (${rung})${brokenSuffix(effective)}`)
const rung = repoRung ? `repo default for ${repo}` : 'account default'
console.log(`using config "${effective}" (${rung})`)
Comment on lines +281 to +282

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agent config default now prints the raw config id where it printed the config's name — using config "cfg_01K9..." (account default) instead of using config "code-reviewer". Same at line 352 for default set.

The ladder carries ids only, and effective is a bare id string; the old code printed defaultName(effective) = config_name ?? config_id. default list (line 316) went to the trouble of joining agents.configs.list() to keep names, so the two sibling commands now disagree on what they show for the same rung.

Suggested change
const rung = repoRung ? `repo default for ${repo}` : 'account default'
console.log(`using config "${effective}" (${rung})`)
const rung = repoRung ? `repo default for ${repo}` : 'account default'
const names = new Map(
(await api().agents.configs.list()).configs.map((c) => [c.id, configName(c)]),
)
console.log(`using config "${names.get(effective) ?? effective}" (${rung})`)

})
})

Expand All @@ -303,24 +298,28 @@ export function registerConfig(program: Command): void {
// merged view, not just this command's own opts.
.action(async (_opts: { json?: boolean }, cmd: Command) => {
await runAction(async () => {
const { defaults: rungs } = await api().agents.defaults.list()
const client = api()
const ladder = await client.agents.defaults.list()
if (cmd.optsWithGlobals().json) {
printJson(rungs)
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', 'STATUS', 'UPDATED'],
rungs.map((d) => [
d.repository ?? 'account',
d.config_name ?? '—',
d.config_id,
d.broken ? `broken: ${d.broken}` : 'ok',
formatTs(d.updated_at),
]),
['RUNG', 'CONFIG', 'CONFIG ID'],
rungs.map(([rung, id]) => [rung, names.get(id) ?? id, id]),
)
})
})
Expand All @@ -340,16 +339,17 @@ export function registerConfig(program: Command): void {
async (configId: string, opts: { repo?: string | boolean; json?: boolean }, cmd: Command) => {
await runAction(async () => {
const repository = resolveRepoFlag(opts.repo)
const { default: set } = await api().agents.defaults.set({
const ladder = await api().agents.defaults.set({
config_id: configId,
...(repository ? { repository } : {}),
})
if (cmd.optsWithGlobals().json) {
printJson(set)
printJson(ladder)
return
}
const rung = set.repository ? `default for ${set.repository}` : 'account default'
console.log(`✓ set ${rung} to "${defaultName(set)}" (${set.config_id})`)
const rung = repository ? `default for ${repository}` : 'account default'
const id = repository ? repoDefault(ladder, repository) : ladder.account
console.log(`✓ set ${rung} to ${id ?? configId}`)
})
},
)
Expand Down Expand Up @@ -478,14 +478,11 @@ export function resolveRepoFlag(repo: string | boolean | undefined): string | un
return repo
}

function defaultName(d: AgentDefaultView): string {
return d.config_name ?? d.config_id
}

// A set-but-broken rung fails session starts closed (never a silent
// fall-through), so surface it wherever the rung is shown.
function brokenSuffix(d: AgentDefaultView): string {
return d.broken ? ` (broken: ${d.broken})` : ''
// 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;
Expand Down
10 changes: 5 additions & 5 deletions src/commands/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function resolveConnectSessionId(

// Whether the composer can send to this session (and why not) — shared with
// the multi-session UI; re-exported so existing imports keep working.
import { connectability } from '../lib/sessions'
import { connectability, sessionConfigName } from '../lib/sessions'
export { connectability }

export function registerConnect(session: Command): void {
Expand Down Expand Up @@ -88,9 +88,9 @@ export async function runConnect(
// An extra opening notice from the caller — shown in the app instead of
// printed beforehand, which would land in scrollback behind the app.
startupNotice?: string,
// The agent config name from the caller (e.g. `start --connect`, whose
// start response carries resolved_config_name); when absent it is derived
// from the fetched session. Shown in the footer meta line.
// The agent config name from the caller (e.g. `start --connect`); when
// absent it is derived from the fetched session. Shown in the footer meta
// line.
configName?: string,
): Promise<void> {
const client = api()
Expand All @@ -112,7 +112,7 @@ export async function runConnect(
const url = sessionUrl(resolveAppBase(), me.customer_login, sessionId)
// The config identity for the footer meta line: the caller's resolved name
// first, then whatever the session itself carries.
const config = configName ?? session.config_id ?? null
const config = configName ?? sessionConfigName(session)

// No scrollback preamble: the app owns the whole surface, Claude Code-style.
// The footer carries the session identity/status; a watch-only reason
Expand Down
2 changes: 1 addition & 1 deletion src/commands/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Command } from 'commander'
import { api, APIError } from '../lib/api'
import { apiRoutes } from '../lib/help'
import { runAction } from '../lib/output'
import { repoFromCwd } from '../lib/laptop'
import { repoFromCwd } from '../lib/git'
import { startConnect } from './session'
import type { StartAgentSessionRequest } from '../lib/types'

Expand Down
Loading