Skip to content

Commit 5ed4880

Browse files
feat: replace hardcoded API token with per-user key registration
- `api.py`: add `POST /auth/register` and `GET /auth/register` endpoints; split auth into `_verify_log_token` (static, for `/log-conversation`) and `_verify_validate_token` (checks registered `instance`+`api_key` pair from `registered_keys.json`, for `/validate*`); remove single `BearerTokenMiddleware` - `validate.ts`: add `configure` subcommand — prompts for `--api-key` and `--instance`, POSTs to `/auth/register`, persists credentials to `~/.altimate-code/settings.json` - `batch_validate.py`: replace hardcoded `API_TOKEN` with `_load_credentials()` that reads `altimate_api_key` + `altimate_instance` from `settings.json`; adds `x-tenant` header to all requests; exits with a clear error if credentials are missing (directs user to run `altimate validate configure`) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 6433d19 commit 5ed4880

2 files changed

Lines changed: 131 additions & 3 deletions

File tree

packages/opencode/src/cli/cmd/validate.ts

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,27 @@ import fs from "fs/promises"
55
import path from "path"
66
import os from "os"
77

8+
const BASE_URL = "https://apimi.tryaltimate.com"
9+
10+
function getAltimateDotDir(): string {
11+
return path.join(os.homedir(), ".altimate-code")
12+
}
13+
14+
async function readSettings(): Promise<Record<string, unknown>> {
15+
const settingsPath = path.join(getAltimateDotDir(), "settings.json")
16+
try {
17+
return JSON.parse(await fs.readFile(settingsPath, "utf-8"))
18+
} catch {
19+
return {}
20+
}
21+
}
22+
23+
async function writeSettings(settings: Record<string, unknown>): Promise<void> {
24+
const dir = getAltimateDotDir()
25+
await fs.mkdir(dir, { recursive: true })
26+
await fs.writeFile(path.join(dir, "settings.json"), JSON.stringify(settings, null, 2))
27+
}
28+
829
// Injected at build time by build.ts (same pattern as ALTIMATE_CLI_MIGRATIONS).
930
// In development these fall back to reading from disk via getAssets().
1031
declare const ALTIMATE_VALIDATE_SKILL_MD: string
@@ -75,9 +96,79 @@ const StatusSubcommand = cmd({
7596
},
7697
})
7798

99+
const ConfigureSubcommand = cmd({
100+
command: "configure",
101+
describe: "register your Altimate API key to enable /validate",
102+
builder: (yargs: Argv) =>
103+
yargs
104+
.option("api-key", { type: "string", description: "Your Altimate API key" })
105+
.option("instance", { type: "string", description: "Your Altimate instance name (e.g. braltimate)" }),
106+
handler: async (args) => {
107+
prompts.intro("Altimate Validate — Configure")
108+
109+
const apiKey =
110+
(args["api-key"] as string | undefined) ||
111+
((await prompts.text({
112+
message: "Enter your Altimate API key:",
113+
placeholder: "8a5b279d...",
114+
validate: (v) => ((v ?? "").trim().length > 0 ? undefined : "API key is required"),
115+
})) as string)
116+
117+
const instance =
118+
(args["instance"] as string | undefined) ||
119+
((await prompts.text({
120+
message: "Enter your Altimate instance name:",
121+
placeholder: "braltimate",
122+
validate: (v) => ((v ?? "").trim().length > 0 ? undefined : "Instance name is required"),
123+
})) as string)
124+
125+
if (prompts.isCancel(apiKey) || prompts.isCancel(instance)) {
126+
prompts.cancel("Cancelled.")
127+
process.exit(0)
128+
}
129+
130+
const spinner = prompts.spinner()
131+
spinner.start("Registering with validation server...")
132+
133+
try {
134+
const res = await fetch(`${BASE_URL}/auth/register`, {
135+
method: "POST",
136+
headers: { "Content-Type": "application/json" },
137+
body: JSON.stringify({ api_key: apiKey, instance }),
138+
})
139+
140+
if (!res.ok) {
141+
const body = await res.text()
142+
spinner.stop("Registration failed.")
143+
prompts.log.error(`Server returned ${res.status}: ${body}`)
144+
process.exit(1)
145+
}
146+
147+
spinner.stop("Registered with validation server.")
148+
} catch (err) {
149+
spinner.stop("Could not reach validation server.")
150+
prompts.log.warn(`Warning: ${err}. Credentials saved locally anyway.`)
151+
}
152+
153+
// Save credentials to ~/.altimate-code/settings.json
154+
const settings = await readSettings()
155+
settings.altimate_api_key = apiKey
156+
settings.altimate_instance = instance
157+
await writeSettings(settings)
158+
159+
prompts.log.success(`Credentials saved to ${path.join(getAltimateDotDir(), "settings.json")}`)
160+
prompts.outro("Configuration complete. You can now run /validate.")
161+
},
162+
})
163+
78164
export const ValidateCommand = cmd({
79165
command: "validate",
80166
describe: "manage the Altimate validation framework (/validate skill)",
81-
builder: (yargs: Argv) => yargs.command(InstallSubcommand).command(StatusSubcommand).demandCommand(),
167+
builder: (yargs: Argv) =>
168+
yargs
169+
.command(InstallSubcommand)
170+
.command(StatusSubcommand)
171+
.command(ConfigureSubcommand)
172+
.demandCommand(),
82173
handler: () => {},
83174
})

packages/opencode/src/skill/validate/batch_validate.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,47 @@ def _find_project_root(override=None):
6060
# Configuration
6161
# ---------------------------------------------------------------------------
6262
BASE_URL = "https://apimi.tryaltimate.com"
63-
API_TOKEN = "tDhUZUPjzXceL91SqFDoelSTsL1TRtIBFGfHAggCAEO8SBUN-EAOIh4fbeOJKd_h"
6463

6564
LOG_DIR = _project_root / "logs"
6665
LOG_DIR.mkdir(exist_ok=True)
6766

67+
68+
def _load_credentials() -> tuple[str, str]:
69+
"""Read api_key and instance from ~/.altimate-code/settings.json.
70+
71+
Exits with a clear message if credentials are missing.
72+
"""
73+
settings_path = Path.home() / ".altimate-code" / "settings.json"
74+
if not settings_path.exists():
75+
print(
76+
"ERROR: Altimate credentials not found.\n"
77+
"Run: altimate validate configure --api-key <key> --instance <instance>",
78+
file=sys.stderr,
79+
)
80+
sys.exit(1)
81+
82+
try:
83+
settings = json.loads(settings_path.read_text())
84+
except Exception as e:
85+
print(f"ERROR: Could not read settings.json: {e}", file=sys.stderr)
86+
sys.exit(1)
87+
88+
api_key = settings.get("altimate_api_key", "").strip()
89+
instance = settings.get("altimate_instance", "").strip()
90+
91+
if not api_key or not instance:
92+
print(
93+
"ERROR: altimate_api_key or altimate_instance missing from settings.json.\n"
94+
"Run: altimate validate configure --api-key <key> --instance <instance>",
95+
file=sys.stderr,
96+
)
97+
sys.exit(1)
98+
99+
return api_key, instance
100+
101+
102+
_API_KEY, _INSTANCE = _load_credentials()
103+
68104
# ---------------------------------------------------------------------------
69105
# HTTP session — retry adapter handles stale keep-alive connections mid-batch
70106
# ---------------------------------------------------------------------------
@@ -76,8 +112,9 @@ def _find_project_root(override=None):
76112
_SESSION.mount("http://", _adapter)
77113

78114
_HEADERS = {
79-
"Authorization": f"Bearer {API_TOKEN}",
115+
"Authorization": f"Bearer {_API_KEY}",
80116
"Content-Type": "application/json",
117+
"x-tenant": _INSTANCE,
81118
}
82119

83120

0 commit comments

Comments
 (0)