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
6 changes: 3 additions & 3 deletions .github/workflows/pr-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ jobs:
shell: bash
run: |-
pip install --quiet "testcontainers[postgres]" "PyJWT>=2.8" cryptography pytest
pip install --quiet "highflame==0.3.17"
pip install --quiet "highflame==0.3.23"

- name: Run Python SDK smoke tests
run: pytest tests/sdk/test_sdk_smoke.py -v --tb=short -x
Expand All @@ -200,7 +200,7 @@ jobs:
run: |-
npm init -y
npm install --save-dev vitest typescript @testcontainers/postgresql
npm install @highflame/sdk@0.3.17
npm install @highflame/sdk@0.3.23

- name: Run TypeScript SDK smoke tests
working-directory: tests/sdk
Expand Down Expand Up @@ -259,7 +259,7 @@ jobs:

- name: Install notebook execution deps
shell: bash
run: pip install --quiet pytest nbmake ipykernel "highflame==0.3.17" cryptography "PyJWT>=2.8"
run: pip install --quiet pytest nbmake ipykernel "highflame==0.3.23" cryptography "PyJWT>=2.8"

- name: Execute the quickstart notebook against the live server
shell: bash
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/sdk-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ jobs:
shell: bash
run: |-
pip install --quiet "testcontainers[postgres]" "PyJWT>=2.8" cryptography pytest
pip install --quiet "highflame==0.3.17"
pip install --quiet "highflame==0.3.23"

- name: Run Python SDK smoke tests
run: pytest tests/sdk/test_sdk_smoke.py -v --tb=short -x
Expand Down Expand Up @@ -101,7 +101,7 @@ jobs:
run: |-
npm init -y
npm install --save-dev vitest typescript @testcontainers/postgresql
npm install @highflame/sdk@0.3.17
npm install @highflame/sdk@0.3.23

- name: Run TypeScript SDK smoke tests
working-directory: tests/sdk
Expand Down
32 changes: 26 additions & 6 deletions cli/package-lock.json

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

2 changes: 1 addition & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"lint:fix": "eslint src tests --fix"
},
"dependencies": {
"@highflame/sdk": "^0.3.9",
"@highflame/sdk": "^0.3.23",
"chalk": "^5.4.1",
"cli-table3": "^0.6.5",
"commander": "^13.1.0"
Expand Down
2 changes: 1 addition & 1 deletion cli/src/commands/ciba/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export function resolveCibaAdminRequest(

return {
baseUrl: nonEmpty(opts.adminBaseUrl) ?? readEnv("ZID_ADMIN_BASE_URL") ?? context.base_url,
prefix: readOptionOrEnvAllowEmpty(opts.adminPrefix, "ZID_ADMIN_PREFIX") ?? "/api/v1",
prefix: readOptionOrEnvAllowEmpty(opts.adminPrefix, "ZID_ADMIN_PREFIX") ?? "",
headers: cleanHeaders({
"X-Internal-Service": internalService,
"X-Internal-Service-Secret": internalServiceSecret,
Expand Down
2 changes: 1 addition & 1 deletion cli/src/commands/ciba/approve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export function registerCibaApprove(cibaCmd: Command): void {
.option("--admin-base-url <url>", "Admin API base URL (defaults to profile base URL)")
.option(
"--admin-prefix <path>",
'Admin route prefix before /oauth2/bc-authorize (default: /api/v1; use "" for AuthN)',
'Admin route prefix before /oauth2/bc-authorize (default: none — admin routes at the server root; set /api/v1 for pre-flip deployments)',
)
.option("--internal-service <name>", "Internal service name header for protected admin routes")
.option("--internal-service-secret <secret>", "Internal service secret header")
Expand Down
2 changes: 1 addition & 1 deletion cli/src/commands/ciba/deny.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function registerCibaDeny(cibaCmd: Command): void {
.option("--admin-base-url <url>", "Admin API base URL (defaults to profile base URL)")
.option(
"--admin-prefix <path>",
'Admin route prefix before /oauth2/bc-authorize (default: /api/v1; use "" for AuthN)',
'Admin route prefix before /oauth2/bc-authorize (default: none — admin routes at the server root; set /api/v1 for pre-flip deployments)',
)
.option("--internal-service <name>", "Internal service name header for protected admin routes")
.option("--internal-service-secret <secret>", "Internal service secret header")
Expand Down
4 changes: 1 addition & 3 deletions cli/src/commands/token/issue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ export function registerIssue(tokenCmd: Command): void {
try {
const profile = requireProfile(opts.profile as string | undefined);
const client = makeClientFromProfile(profile);
const token = await client.tokens.issue({
grant_type: "api_key",
api_key: profile.api_key,
const token = await client.tokens.issueApiKey(profile.api_key, {
scope: (opts.scope as string).trim() || undefined,
});

Expand Down
21 changes: 14 additions & 7 deletions cli/src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,22 @@ interface ConfigFile {
profiles: Record<string, Profile>;
}

const CONFIG_DIR = join(homedir(), ".config", "zeroid");
const CONFIG_PATH = join(CONFIG_DIR, "config.json");
// ZID_CONFIG_DIR overrides where profiles live (tests isolate through it;
// when set, the legacy path is not consulted). Resolved per call so a
// process can re-point it.
function configDir(): string {
return process.env.ZID_CONFIG_DIR || join(homedir(), ".config", "zeroid");
}
function configPath(): string {
return join(configDir(), "config.json");
}
const LEGACY_CONFIG_PATH = join(homedir(), ".config", "zid", "config.json");

function _read(): ConfigFile {
if (existsSync(CONFIG_PATH)) {
return readConfigFile(CONFIG_PATH);
if (existsSync(configPath())) {
return readConfigFile(configPath());
}
if (existsSync(LEGACY_CONFIG_PATH)) {
if (!process.env.ZID_CONFIG_DIR && existsSync(LEGACY_CONFIG_PATH)) {
return readConfigFile(LEGACY_CONFIG_PATH);
}
return { active_profile: "", profiles: {} };
Expand All @@ -56,8 +63,8 @@ function readConfigFile(path: string): ConfigFile {
}

function _write(cfg: ConfigFile): void {
mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2) + "\n", { encoding: "utf8", mode: 0o600 });
mkdirSync(configDir(), { recursive: true, mode: 0o700 });
writeFileSync(configPath(), JSON.stringify(cfg, null, 2) + "\n", { encoding: "utf8", mode: 0o600 });
}

export function getProfile(name?: string): Profile | undefined {
Expand Down
48 changes: 24 additions & 24 deletions cli/tests/commands/agents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest";
import { http, HttpResponse } from "msw";
import { setupServer } from "msw/node";
import { runCLI, BASE_URL } from "../helpers.js";
import { runCLI, BASE_URL, tokenMintHandler } from "../helpers.js";
import type { AgentResponse, AgentRegistered, AgentListResponse } from "@highflame/sdk";

const server = setupServer();
const server = setupServer(tokenMintHandler());
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
Expand Down Expand Up @@ -58,8 +58,8 @@ const ROTATED: AgentRegistered = {
// ---------------------------------------------------------------------------

describe("zeroid agents list", () => {
it("GET /api/v1/agents/registry and renders a table", async () => {
server.use(http.get(`${BASE_URL}/api/v1/agents/registry`, () => HttpResponse.json(AGENT_LIST)));
it("GET /agents/registry and renders a table", async () => {
server.use(http.get(`${BASE_URL}/agents/registry`, () => HttpResponse.json(AGENT_LIST)));
const { stdout, exitCode } = await runCLI(["agents", "list"]);
expect(exitCode).toBeUndefined();
const out = stdout.join("\n");
Expand All @@ -72,7 +72,7 @@ describe("zeroid agents list", () => {
it("forwards --type as query param", async () => {
let qs = "";
server.use(
http.get(`${BASE_URL}/api/v1/agents/registry`, ({ request }) => {
http.get(`${BASE_URL}/agents/registry`, ({ request }) => {
qs = new URL(request.url).search;
return HttpResponse.json(AGENT_LIST);
}),
Expand All @@ -82,7 +82,7 @@ describe("zeroid agents list", () => {
});

it("outputs a JSON array with --json", async () => {
server.use(http.get(`${BASE_URL}/api/v1/agents/registry`, () => HttpResponse.json(AGENT_LIST)));
server.use(http.get(`${BASE_URL}/agents/registry`, () => HttpResponse.json(AGENT_LIST)));
const { stdout } = await runCLI(["agents", "list", "--json"]);
const parsed = JSON.parse(stdout.join("")) as AgentResponse[];
expect(Array.isArray(parsed)).toBe(true);
Expand All @@ -91,7 +91,7 @@ describe("zeroid agents list", () => {

it("prints 'No agents found' when list is empty", async () => {
server.use(
http.get(`${BASE_URL}/api/v1/agents/registry`, () =>
http.get(`${BASE_URL}/agents/registry`, () =>
HttpResponse.json({ agents: [], total: 0, limit: 50, offset: 0 }),
),
);
Expand All @@ -110,7 +110,7 @@ describe("zeroid agents list", () => {

it("exits 1 on API error", async () => {
server.use(
http.get(`${BASE_URL}/api/v1/agents/registry`, () =>
http.get(`${BASE_URL}/agents/registry`, () =>
HttpResponse.json({ title: "Unauthorized" }, { status: 401 }),
),
);
Expand All @@ -124,9 +124,9 @@ describe("zeroid agents list", () => {
// ---------------------------------------------------------------------------

describe("zeroid agents get", () => {
it("GET /api/v1/agents/registry/:id and prints agent details", async () => {
it("GET /agents/registry/:id and prints agent details", async () => {
server.use(
http.get(`${BASE_URL}/api/v1/agents/registry/agt_abc123`, () => HttpResponse.json(AGENT)),
http.get(`${BASE_URL}/agents/registry/agt_abc123`, () => HttpResponse.json(AGENT)),
);
const { stdout, exitCode } = await runCLI(["agents", "get", "agt_abc123"]);
expect(exitCode).toBeUndefined();
Expand All @@ -138,7 +138,7 @@ describe("zeroid agents get", () => {

it("outputs raw JSON with --json", async () => {
server.use(
http.get(`${BASE_URL}/api/v1/agents/registry/agt_abc123`, () => HttpResponse.json(AGENT)),
http.get(`${BASE_URL}/agents/registry/agt_abc123`, () => HttpResponse.json(AGENT)),
);
const { stdout } = await runCLI(["agents", "get", "--json", "agt_abc123"]);
const parsed = JSON.parse(stdout.join("")) as AgentResponse;
Expand All @@ -148,7 +148,7 @@ describe("zeroid agents get", () => {

it("exits 1 when agent not found", async () => {
server.use(
http.get(`${BASE_URL}/api/v1/agents/registry/ghost`, () =>
http.get(`${BASE_URL}/agents/registry/ghost`, () =>
HttpResponse.json({ title: "Not Found", detail: "identity not found" }, { status: 404 }),
),
);
Expand All @@ -162,9 +162,9 @@ describe("zeroid agents get", () => {
// ---------------------------------------------------------------------------

describe("zeroid agents rotate-key", () => {
it("POST /api/v1/agents/registry/:id/rotate-key and prints new key", async () => {
it("POST /agents/registry/:id/rotate-key and prints new key", async () => {
server.use(
http.post(`${BASE_URL}/api/v1/agents/registry/agt_abc123/rotate-key`, () =>
http.post(`${BASE_URL}/agents/registry/agt_abc123/rotate-key`, () =>
HttpResponse.json(ROTATED),
),
);
Expand All @@ -177,7 +177,7 @@ describe("zeroid agents rotate-key", () => {

it("outputs raw JSON with --json", async () => {
server.use(
http.post(`${BASE_URL}/api/v1/agents/registry/agt_abc123/rotate-key`, () =>
http.post(`${BASE_URL}/agents/registry/agt_abc123/rotate-key`, () =>
HttpResponse.json(ROTATED),
),
);
Expand All @@ -190,7 +190,7 @@ describe("zeroid agents rotate-key", () => {

it("exits 1 when agent not found", async () => {
server.use(
http.post(`${BASE_URL}/api/v1/agents/registry/ghost/rotate-key`, () =>
http.post(`${BASE_URL}/agents/registry/ghost/rotate-key`, () =>
HttpResponse.json({ title: "Not Found", detail: "identity not found" }, { status: 404 }),
),
);
Expand All @@ -204,10 +204,10 @@ describe("zeroid agents rotate-key", () => {
// ---------------------------------------------------------------------------

describe("zeroid agents deactivate", () => {
it("POST /api/v1/agents/registry/:id/deactivate", async () => {
it("POST /agents/registry/:id/deactivate", async () => {
const deactivated = { ...AGENT, status: "deactivated" as const };
server.use(
http.post(`${BASE_URL}/api/v1/agents/registry/agt_abc123/deactivate`, () =>
http.post(`${BASE_URL}/agents/registry/agt_abc123/deactivate`, () =>
HttpResponse.json(deactivated),
),
);
Expand All @@ -219,7 +219,7 @@ describe("zeroid agents deactivate", () => {
it("outputs raw JSON with --json", async () => {
const deactivated = { ...AGENT, status: "deactivated" as const };
server.use(
http.post(`${BASE_URL}/api/v1/agents/registry/agt_abc123/deactivate`, () =>
http.post(`${BASE_URL}/agents/registry/agt_abc123/deactivate`, () =>
HttpResponse.json(deactivated),
),
);
Expand All @@ -232,7 +232,7 @@ describe("zeroid agents deactivate", () => {

it("exits 1 on API error", async () => {
server.use(
http.post(`${BASE_URL}/api/v1/agents/registry/ghost/deactivate`, () =>
http.post(`${BASE_URL}/agents/registry/ghost/deactivate`, () =>
HttpResponse.json({ title: "Not Found" }, { status: 404 }),
),
);
Expand All @@ -242,9 +242,9 @@ describe("zeroid agents deactivate", () => {
});

describe("zeroid agents activate", () => {
it("POST /api/v1/agents/registry/:id/activate", async () => {
it("POST /agents/registry/:id/activate", async () => {
server.use(
http.post(`${BASE_URL}/api/v1/agents/registry/agt_abc123/activate`, () =>
http.post(`${BASE_URL}/agents/registry/agt_abc123/activate`, () =>
HttpResponse.json(AGENT),
),
);
Expand All @@ -255,7 +255,7 @@ describe("zeroid agents activate", () => {

it("outputs raw JSON with --json", async () => {
server.use(
http.post(`${BASE_URL}/api/v1/agents/registry/agt_abc123/activate`, () =>
http.post(`${BASE_URL}/agents/registry/agt_abc123/activate`, () =>
HttpResponse.json(AGENT),
),
);
Expand All @@ -268,7 +268,7 @@ describe("zeroid agents activate", () => {

it("exits 1 on API error", async () => {
server.use(
http.post(`${BASE_URL}/api/v1/agents/registry/ghost/activate`, () =>
http.post(`${BASE_URL}/agents/registry/ghost/activate`, () =>
HttpResponse.json({ title: "Not Found" }, { status: 404 }),
),
);
Expand Down
4 changes: 2 additions & 2 deletions cli/tests/commands/ciba.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ describe("zeroid ciba approve", () => {
let projectHeader = "";

server.use(
http.post(`${BASE_URL}/api/v1/oauth2/bc-authorize/ari_test_123/approve`, async ({ request }) => {
http.post(`${BASE_URL}/oauth2/bc-authorize/ari_test_123/approve`, async ({ request }) => {
captured = (await request.json()) as Record<string, unknown>;
accountHeader = request.headers.get("x-account-id") ?? "";
projectHeader = request.headers.get("x-project-id") ?? "";
Expand Down Expand Up @@ -172,7 +172,7 @@ describe("zeroid ciba deny", () => {
let accountHeader = "";

server.use(
http.post(`${BASE_URL}/api/v1/oauth2/bc-authorize/ari_test_123/deny`, async ({ request }) => {
http.post(`${BASE_URL}/oauth2/bc-authorize/ari_test_123/deny`, async ({ request }) => {
captured = (await request.json()) as Record<string, unknown>;
accountHeader = request.headers.get("x-account-id") ?? "";
return HttpResponse.json({ auth_req_id: "ari_test_123", status: "denied" });
Expand Down
Loading