Skip to content
Draft
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- Align Router help, manifest, root examples, and login/whoami guidance with
the current credential store: `tokens.json` is authoritative, `AISA_API_KEY`
overrides stored credentials, compatibility key files are mirrors, and
`aisa whoami` is not protected auth proof. Root help starts at `aisa login`
or `https://tools.aisa.one/mcp`; `aisa connect` is scoped to domain MCP.
The connect journal reports `AISA_API_KEY` or the CLI credential store,
not a key file. Manual file-read warnings apply to users and agents; the
bundled VS Code extension may still read the compatibility mirror.

## [0.6.1] — 2026-09-11

### Changed
Expand Down
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ aisa quote --input '{"calls":[{"call_id":"c1","tool":"get_financial_company_fact

`aisa login` opens a browser, signs you in, and stores OAuth tokens. You do not
need to create or paste a key from the console. For CI or scripts, set
`AISA_API_KEY` or run `aisa login --key <key>`. New accounts receive $5 in
`AISA_API_KEY` or run `aisa login --key <key>`. `AISA_API_KEY` overrides stored
credentials and never refreshes. Prove protected auth with `aisa balance`.
`aisa whoami` may refresh stored tokens and is not auth proof. Users and
agents must not manually read, copy, or refresh credential files; the CLI
and bundled integrations manage compatibility mirrors. New accounts receive $5 in
free credits.

This first block does not run `aisa chat` or `aisa call`. Quote is a price
Expand Down Expand Up @@ -326,13 +330,16 @@ Settings:
independent of `baseUrl`); overridden by `AISA_ROUTER_BASE_URL`
- `outputFormat` — `text` or `json`

`aisa login` stores OAuth credentials in `~/.aisa/tokens.json`.
`aisa login --key <key>` stores a static credential without refresh metadata.
Legacy mirrors contain the current access token; older CLIs cannot refresh it.
`aisa login` stores OAuth credentials in the CLI credential store
(`~/.aisa/tokens.json` is authoritative). `aisa login --key <key>` stores a
static credential without refresh metadata. Compatibility key files and conf
mirrors are not authority. Users and agents must not manually read, copy, or
refresh them; the CLI and bundled integrations (including the VS Code
extension shipped with `aisa connect`) manage those mirrors.
Third-party client configurations written by `aisa connect` also contain a
snapshot of the credential, not a refresh-capable OAuth session.
Environment variables:
`AISA_API_KEY` takes precedence over the stored key.
`AISA_API_KEY` overrides stored credentials and never refreshes.
`AISA_ROUTER_BASE_URL` is the Router origin/prefix before
`/v1/tool-router/...` and overrides the default `https://tools.aisa.one`.
`AISA_CACHE_DIR` relocates the cache. `GITHUB_TOKEN`
Expand Down
7 changes: 7 additions & 0 deletions src/commands/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ export async function logoutAction(): Promise<void> {
if (process.env[ENV_VAR_NAME]) info(`${ENV_VAR_NAME} is still set. Unset it to stop using that API key.`);
}

/** Commander after-help for `aisa whoami`. Mechanics only; runtime is unchanged. */
export function whoamiHelpAfter(): string {
return `
Shows the current credential source (AISA_API_KEY or the CLI credential store). May refresh stored OAuth tokens. This is not protected auth proof — use aisa balance. Users and agents must not manually read, copy, or refresh credential files; the CLI and bundled integrations manage compatibility mirrors.
`;
}

export async function whoamiAction(): Promise<void> {
const key = await getAccessToken();
const source = getKeySource();
Expand Down
14 changes: 10 additions & 4 deletions src/commands/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import chalk from "chalk";
import { success, error, info, hint } from "../utils/display.js";
import { expandHome } from "../utils/file.js";
import { MCP_CONFIGS, MCP_DEFAULT_SLUGS, AISA_PROVIDER_ID } from "../constants.js";
import { getAccessToken, getConfig, setConfig } from "../config.js";
import { getAccessToken, getConfig, getKeySource, setConfig } from "../config.js";
import { fetchLiveServers, writeClientConfig, buildEntry, stripped, type LiveServer } from "./mcp.js";
import { INSTALLERS, installAgent, isInstalled, supported } from "./install.js";
import {
Expand Down Expand Up @@ -2265,8 +2265,7 @@ async function offerLaunch(
* The closing block of a run: what changed on this machine, which commands
* the user now has, and what to run next. It is the part someone reads
* after the browser tab is gone — and the part that teaches them to do this
* by hand next time, so it names files and commands rather than describing
* them.
* by hand next time. Credential source is semantic only: no file path or value.
*/
function summarise(
log: Journal,
Expand Down Expand Up @@ -2303,7 +2302,14 @@ function summarise(
log.line("write", "AIsa added beside your setup", r.steps.find((s) => s.id === "llm-backup")?.detail);
}
if (done("install:aisa-cli")) log.line("write", "AIsa CLI available as `aisa`");
log.record(`credential: ~/.aisa/key (0600)`);
const credentialSource = getKeySource();
log.record(
credentialSource === "env"
? "credential: AISA_API_KEY"
: credentialSource === "config"
? "credential: CLI credential store"
: "credential: none"
);

log.section("Commands you now have");
for (const c of launchChoices(r.clientId, r.llmMode)) log.command(c.cmd, c.desc);
Expand Down
4 changes: 2 additions & 2 deletions src/commands/flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -527,8 +527,8 @@ export const RUNTIME_COPY = {
zh: "想用 AIsa 的模型时就跑 <code>{bin}</code>;删掉那一个文件就等于移除。",
},
noKeyStored: {
en: "No key stored here — run <code>aisa login</code>, then copy it from <code>~/.aisa/key</code>.",
zh: "本机没有存 key —— 先跑 <code>aisa login</code>,再从 <code>~/.aisa/key</code> 复制。",
en: "No credential stored here — run <code>aisa login</code>, then <code>aisa balance</code>. Do not copy token files. For a static key use <code>AISA_API_KEY</code> or <code>aisa login --key</code>.",
zh: "本机没有存凭证 —— 先跑 <code>aisa login</code>,再用 <code>aisa balance</code> 验证。不要复制 token 文件。静态密钥用 <code>AISA_API_KEY</code> 或 <code>aisa login --key</code>。",
},
cannotStartApp: {
en: "Could not start {name} — open it from your Applications folder.",
Expand Down
2 changes: 1 addition & 1 deletion src/commands/skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Describe how an AI agent should use this skill.
export AISA_API_KEY=sk-your-key
\`\`\`

Same key as \`aisa login\`: \`AISA_API_KEY\`, then \`~/.aisa/key\`, then legacy login.
Prefer \`aisa login\`. \`AISA_API_KEY\` overrides stored credentials. Users and agents must not manually read, copy, or refresh credential files; the CLI and bundled integrations manage compatibility mirrors. Prove auth with \`aisa balance\`.

## Published tools

Expand Down
15 changes: 9 additions & 6 deletions src/commands/tool-help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ const JSON_CONTRACT =
"--json writes the unmodified application body, including MCP identifiers and numeric tokens. Human output maps only AISA_SEARCH_TOOL, AISA_BATCH_GET_SCHEMA, AISA_BATCH_QUOTE, and AISA_BATCH_USE to aisa search / schema / quote / call.";

const KEY_RESOLUTION =
"Prefer aisa login (stores a CLI key). Resolution: AISA_API_KEY, then ~/.aisa/key, then legacy login. CI: AISA_API_KEY or aisa login --key.";
"Prefer aisa login (stores OAuth or a static key in the CLI credential store). AISA_API_KEY overrides stored credentials and never refreshes. Users and agents must not manually read, copy, or refresh credential files; the CLI and bundled integrations manage compatibility mirrors. Prove protected auth with aisa balance; aisa whoami may refresh stored tokens and is not proof. CI: AISA_API_KEY or aisa login --key.";

const ENFORCED_OPTIONAL = `Enforced: invalid local input exits 2 and is not sent. A configured AIsa API key is optional (${KEY_RESOLUTION}).`;
const ENFORCED_OPTIONAL = `Enforced: invalid local input exits 2 and is not sent. A configured OAuth session or static AIsa API key is optional (${KEY_RESOLUTION}).`;

const ENFORCED_REQUIRED = `Enforced: quote and call refuse to run without a configured AIsa API key (${KEY_RESOLUTION}). Invalid local input exits 2 and is not sent. search and schema may be anonymous.`;
const ENFORCED_REQUIRED = `Enforced: quote and call refuse to run without an OAuth session or static AIsa API key (${KEY_RESOLUTION}). Invalid local input exits 2 and is not sent. search and schema may be anonymous.`;

const NOT_ENFORCED =
"Not enforced: the CLI does not record quotes, approvals, or budget caps and does not reject an unquoted aisa call.";
Expand Down Expand Up @@ -137,7 +137,7 @@ export function routerContract(kind: RouterOperation): RouterCommandContract {
"Instruction: A missing or failed quote is never free.",
"A data request or credentials alone is not spending approval.",
"Instruction: Call only an independently approved successful subset. Do not silently retry or expand the set.",
"Instruction: Without a configured AIsa API key, do not invent a business result.",
"Instruction: Without an OAuth session or static AIsa API key, do not invent a business result.",
],
examples: [example("call", EXAMPLE_BATCH)],
};
Expand Down Expand Up @@ -223,18 +223,21 @@ export function callHelpAfter(): string {
export function rootHelpAfter(): string {
return `
Examples (POSIX sh; single-quoted --input):
$ aisa connect wire your coding agent to AIsa (start here)
$ aisa login sign in (prove with aisa balance)
$ aisa search "company facts" --json
$ aisa schema ${EXAMPLE_PUBLISHED_TOOL} --json
$ aisa quote ${shellInputFlag(EXAMPLE_BATCH_JSON)} --json
$ aisa call ${shellInputFlag(EXAMPLE_BATCH_JSON)} --json
$ aisa api list browse the provider catalog
$ aisa api show coingecko browse one provider's endpoints
$ aisa connect optional: domain MCP for local coding agents (not the four-tool Router)

General setup: aisa login for CLI credentials, or native MCP at https://tools.aisa.one/mcp.
aisa connect installs domain MCP servers into local agents; its default web-search server is not the four-tool Router.
Router: ${MCP_CLI_MAP.search.identifier}→search, ${MCP_CLI_MAP.schema.identifier}→schema, ${MCP_CLI_MAP.quote.identifier}→quote, ${MCP_CLI_MAP.call.identifier}→call.
--json keeps MCP identifiers. Human output maps those four names to CLI commands.
${FLOW}
Use aisa <command> --help or aisa manifest <command> for complete JSON/file/stdin examples, shared API-key sources, exit codes, and cost constraints.
Use aisa <command> --help or aisa manifest <command> for complete JSON/file/stdin examples, credential sources, exit codes, and cost constraints.
Catalog list/show are browsing metadata, not a substitute for schema or quote.
`;
}
10 changes: 9 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { CliError } from "./cli-error.js";
import type { RouterIoOptions } from "./commands/tool-input.js";

// Auth
import { loginAction, logoutAction, whoamiAction } from "./commands/auth.js";
import { loginAction, logoutAction, whoamiAction, whoamiHelpAfter } from "./commands/auth.js";
import { loginHelpAfter } from "./commands/oauth-login.js";
// Account
import { balanceAction, topupAction, usageAction } from "./commands/account.js";
Expand Down Expand Up @@ -105,6 +105,7 @@ program
program
.command("whoami")
.description("Show authentication status")
.addHelpText("after", whoamiHelpAfter())
.action(wrap(whoamiAction));

// ── Account ──
Expand Down Expand Up @@ -276,6 +277,13 @@ skills
program
.command("connect")
.description("Connect AIsa MCP servers to your local coding agents via a one-shot local page")
.addHelpText(
"after",
`
Optional local-agent domain MCP setup. Not general CLI sign-in and not the four-tool Router at https://tools.aisa.one/mcp.
General setup: aisa login (prove with aisa balance) or native MCP at that Router URL. The default connect server is domain web-search.
`
)
.option("--no-open", "Print the URL instead of opening the browser")
.option("--port <port>", "Bind a specific port (default: random)")
.option("--dry-run", "Show what would be configured without writing anything")
Expand Down
Loading
Loading