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 ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Global config lives in `~/.config/openscience/openscience.json`; project config

## Atlas integration

Atlas is a separate, closed platform. Only its client lives here. The CLI talks to it over a documented wire contract: the `synsci` model provider id, `thk_` wallet keys, and the `/api/cli/*` endpoints, with `api.syntheticsciences.ai` as the default managed base URL (`src/endpoints.ts`). Billing classification (`byok`, `managed`, `oauth-free`) is decided client-side in `src/session/billing-gate.ts`; the server is the billing authority. None of the Atlas server, its secrets, or its internal endpoints are part of this repository.
Atlas is a separate, closed platform. Only its client lives here. The CLI talks to it over a documented wire contract: the `synsci` model provider id, `thk_` wallet keys, and the `/api/cli/*` endpoints, with `app.syntheticsciences.ai` as the default managed base URL (`src/endpoints.ts`). Billing classification (`byok`, `managed`, `oauth-free`) is decided client-side in `src/session/billing-gate.ts`; the server is the billing authority. None of the Atlas server, its secrets, or its internal endpoints are part of this repository.

## Build and release

Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ npm install -g @synsci/openscience
openscience
```

The command is `openscience`, and it opens the workspace in your browser. If you would rather not install it globally, `npx synsci` does the same thing in a single step:
The command is `openscience`, and it opens the workspace in your browser. The first time you run it, a short setup walks you through how to power the models — Atlas managed models, your own provider keys, or skip and start on the free demo models. If you would rather not install it globally, `npx synsci` does the same thing in a single step:

```bash
npx synsci
Expand All @@ -58,7 +58,7 @@ export ANTHROPIC_API_KEY=sk-ant-...
openscience
```

`openscience` opens the workspace in your browser. Your keys stay on your machine and requests go straight to the provider. You can also add keys from the Credentials panel and pick a model from the model selector. To open the workspace in a specific project:
`openscience` opens the workspace in your browser. Your keys stay on your machine and requests go straight to the provider. You can also run `openscience keys add` to store a key from the terminal, add keys from the Credentials panel, and pick a model from the model selector. To open the workspace in a specific project:

```bash
openscience ~/code/my-project
Expand All @@ -69,10 +69,11 @@ openscience ~/code/my-project
[Atlas](https://app.syntheticsciences.ai) is Synthetic Sciences' managed platform. It gives you a curated set of frontier models billed from a prepaid wallet, so you do not need per-provider keys, plus a persistent research graph and cloud compute. OpenScience works with Atlas but never requires it.

```bash
openscience connect login
openscience login # connect your Atlas account
openscience wallet # check your balance and top up
```

Bring-your-own-key usage is always free and is never gated. Atlas only meters the models it serves.
Bring-your-own-key usage is always free and is never gatedAtlas only meters the models it serves. Use `openscience status` to see what you are connected to, and `openscience logout` to disconnect.

## How it works

Expand Down
2 changes: 1 addition & 1 deletion backend/cli/src/acp/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ export namespace ACP {
log.info("initialize", { protocolVersion: params.protocolVersion })

const authMethod: AuthMethod = {
description: "Run `openscience connect login` in the terminal",
description: "Run `openscience login` in the terminal",
name: "Login with OpenScience",
id: "openscience-login",
}
Expand Down
59 changes: 16 additions & 43 deletions backend/cli/src/cli/cmd/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,10 @@ async function handlePluginAuth(
return false
}

export const AuthCommand = cmd({
command: "login",
export const KeysCommand = cmd({
command: "keys",
aliases: ["auth"],
describe: "manage credentials",
describe: "manage your own provider API keys (BYOK)",
builder: (yargs) =>
yargs
.command(AuthLoginCommand)
Expand Down Expand Up @@ -236,8 +236,8 @@ export const AuthListCommand = cmd({
})

export const AuthLoginCommand = cmd({
command: "login [url]",
describe: "log in to a provider",
command: ["add [url]", "login [url]"],
describe: "add a provider API key (BYOK)",
builder: (yargs) =>
yargs.positional("url", {
describe: "openscience auth provider",
Expand Down Expand Up @@ -421,8 +421,8 @@ async function backendHasCodex(): Promise<boolean | null> {
}

export const AuthCodexCommand = cmd({
command: "codex",
describe: "log in to Codex (Sign in with ChatGPT Plus/Pro/Business)",
command: ["signin", "codex"],
describe: "sign in with ChatGPT / Codex (Plus/Pro/Business subscription)",
async handler() {
await Instance.provide({
directory: process.cwd(),
Expand Down Expand Up @@ -474,8 +474,8 @@ export const AuthCodexCommand = cmd({
})

export const AuthLogoutCommand = cmd({
command: "logout",
describe: "log out from a configured provider",
command: ["remove", "rm", "logout"],
describe: "remove a saved provider key",
async handler() {
UI.empty()
const credentials = await Auth.all().then((x) => Object.entries(x))
Expand All @@ -494,6 +494,13 @@ export const AuthLogoutCommand = cmd({
})
if (prompts.isCancel(providerID)) throw new UI.CancelledError()
await Auth.remove(providerID)
// Removing Codex must also revoke it on the Atlas backend and re-sync so
// the provider list drops openai-codex/* immediately — otherwise the CLI
// and backend drift (local removed, backend still connected).
if (providerID === "openai-codex") {
await revokeCodexOnBackend()
await OpenScience.syncServices?.().catch(() => {})
}
prompts.outro("Logout successful")
},
})
Expand All @@ -518,37 +525,3 @@ async function revokeCodexOnBackend(): Promise<void> {
log.warn("backend codex revoke errored", { error: String(e) })
}
}

export const LogoutCodexCommand = cmd({
command: "codex",
describe: "log out of Codex (clears local OAuth + revokes on Atlas backend)",
async handler() {
UI.empty()
prompts.intro("Codex logout")
const existing = await Auth.get("openai-codex")
if (!existing) {
prompts.log.info("Not signed in to Codex.")
prompts.outro("Done")
return
}
await Auth.remove("openai-codex")
await revokeCodexOnBackend()
// Re-sync so the local provider list drops openai-codex/* immediately.
await OpenScience.syncServices?.().catch((e: unknown) => {
log.warn("post-logout sync failed", { error: String(e) })
})
prompts.outro("Logout successful")
},
})

export const LogoutCommand = cmd({
command: "logout",
describe: "log out of a connected provider (use `logout codex` for Codex)",
builder: (yargs) => yargs.command(LogoutCodexCommand),
// Default (no subcommand) falls through to the same interactive flow as
// `openscience login logout`. Keeps the existing UX for users who don't know
// the provider id.
async handler() {
await AuthLogoutCommand.handler({} as never)
},
})
8 changes: 4 additions & 4 deletions backend/cli/src/cli/cmd/billing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { OpenScience } from "../../openscience"

const PLAN_URL = process.env.SYNSC_AUTH_URL?.replace(/\/+$/, "") || "https://app.syntheticsciences.ai/cli"

export const BillingCommand = cmd({
command: "billing",
describe: "show CLI wallet balance and how key routing works",
export const WalletCommand = cmd({
command: ["wallet", "billing"],
describe: "Atlas wallet balance, top up, and key routing",
builder: (yargs) => yargs.command(BillingShowCommand).command(BillingTopupCommand).demandCommand(),
async handler() {},
})
Expand All @@ -21,7 +21,7 @@ const BillingShowCommand = cmd({

const session = await OpenScience.getSession()
if (!session) {
prompts.log.warn("Not authenticated. Run `openscience connect login` first.")
prompts.log.warn("Not authenticated. Run `openscience login` first.")
prompts.outro("Done")
return
}
Expand Down
Loading
Loading