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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 0.6.1

- Negotiate development-session capabilities with the control plane instead
of requiring one hard-coded capability vector.
- Accept boolean development capability values so a newly enabled platform
capability cannot turn a successful response into a client-side schema
failure.

## 0.6.0

- Add `doctor` with redacted session, endpoint, CLI, and deployed-platform diagnostics.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ offline source bundle, but cannot connect to or deploy through OpenCloud.

## Install a pinned release

OpenCloud application skills pin an exact CLI release. To install `v0.6.0` in
OpenCloud application skills pin an exact CLI release. To install `v0.6.1` in
an isolated task directory:

```bash
OPENCLOUD_CLI_VERSION="v0.6.0"
OPENCLOUD_CLI_PACKAGE="opencloud-cli-0.6.0.tgz"
OPENCLOUD_CLI_VERSION="v0.6.1"
OPENCLOUD_CLI_PACKAGE="opencloud-cli-0.6.1.tgz"
OPENCLOUD_CLI_DIR="$(mktemp -d)"

curl -fsSLo "$OPENCLOUD_CLI_DIR/$OPENCLOUD_CLI_PACKAGE" \
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

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

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@opencloud/cli",
"version": "0.6.0",
"version": "0.6.1",
"description": "Versioned command-line client for building, deploying, and verifying OpenCloud applications",
"type": "module",
"bin": {
Expand All @@ -18,7 +18,7 @@
"scripts": {
"build": "node scripts/build.mjs",
"typecheck": "tsc -p tsconfig.json --noEmit",
"test": "vitest run src vendor/contracts/src",
"test": "vitest run src vendor/contracts/src vendor/control-plane-client/src",
"lint": "npm run typecheck",
"prepack": "npm run build"
},
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
type OpenCloudSession,
} from "./session-store.js";

const CLI_VERSION = "0.6.0";
const CLI_VERSION = "0.6.1";

const program = new Command()
.name("opencloud")
Expand Down
37 changes: 37 additions & 0 deletions vendor/contracts/src/control-plane.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,41 @@ describe("controlPlaneOperations", () => {
expect(controlPlaneOperations.deployDraft.idempotency).toBe("required");
expect(controlPlaneOperations.verifyApp.idempotency).toBe("required");
});

it("treats development capabilities as negotiated booleans", () => {
const session = {
id: "11111111-1111-4111-8111-111111111111",
appId: "22222222-2222-4222-8222-222222222222",
draftId: "33333333-3333-4333-8333-333333333333",
status: "active",
previewUrl: "https://dev-example.opencloud.ai",
baseDeploymentId: null,
activeRevision: null,
verification: null,
capabilities: {
frontend: true,
database: true,
functions: true,
productionSecrets: false,
cron: false,
storageSandbox: false,
syntheticAuth: false,
},
createdAt: "2026-08-05T00:00:00.000Z",
updatedAt: "2026-08-05T00:00:00.000Z",
lastActivityAt: "2026-08-05T00:00:00.000Z",
expiresAt: "2026-08-06T00:00:00.000Z",
};

for (const [name, enabled] of Object.entries(session.capabilities)) {
const parsed = controlPlaneOperations.getDevSession.output.parse({
...session,
capabilities: {
...session.capabilities,
[name]: !enabled,
},
});
expect(parsed.capabilities).toMatchObject({ [name]: !enabled });
}
});
});
14 changes: 7 additions & 7 deletions vendor/contracts/src/control-plane.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,13 +198,13 @@ export const devSessionOutput = z
})
.nullable(),
capabilities: z.object({
frontend: z.literal(true),
database: z.literal(true),
functions: z.literal(true),
productionSecrets: z.literal(false),
cron: z.literal(false),
storageSandbox: z.literal(false),
syntheticAuth: z.literal(false),
frontend: z.boolean(),
database: z.boolean(),
functions: z.boolean(),
productionSecrets: z.boolean(),
cron: z.boolean(),
storageSandbox: z.boolean(),
syntheticAuth: z.boolean(),
}),
createdAt: z.string(),
updatedAt: z.string(),
Expand Down
33 changes: 33 additions & 0 deletions vendor/control-plane-client/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { describe, expect, it } from "vitest";
import {
OPEN_CLOUD_CLIENT_CAPABILITIES_HEADER,
OPEN_CLOUD_DEV_SESSION_CAPABILITIES_V2,
OpenCloudClient,
} from "./index.js";

describe("OpenCloudClient", () => {
it("advertises support for negotiated development capabilities", async () => {
const requests: RequestInit[] = [];
const fetcher = async (
_input: string | URL | Request,
init?: RequestInit,
) => {
requests.push(init ?? {});
return new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { "content-type": "application/json" },
});
};
const client = new OpenCloudClient({
apiUrl: "https://api.opencloud.ai",
token: "test-token",
fetch: fetcher,
});

await client.get("/v1/apps");

const init = requests[0];
expect(new Headers(init?.headers).get(OPEN_CLOUD_CLIENT_CAPABILITIES_HEADER))
.toBe(OPEN_CLOUD_DEV_SESSION_CAPABILITIES_V2);
});
});
12 changes: 12 additions & 0 deletions vendor/control-plane-client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ import {
type ControlPlaneOperationOutput,
} from "@opencloud/contracts";

export const OPEN_CLOUD_CLIENT_CAPABILITIES_HEADER =
"x-opencloud-client-capabilities";
export const OPEN_CLOUD_DEV_SESSION_CAPABILITIES_V2 =
"dev-session-capabilities-v2";

const OPEN_CLOUD_CLIENT_CAPABILITIES =
OPEN_CLOUD_DEV_SESSION_CAPABILITIES_V2;

export interface ClientOptions {
apiUrl: string;
token?: string | undefined;
Expand Down Expand Up @@ -142,6 +150,8 @@ export class OpenCloudClient {
headers: {
authorization: `Bearer ${this.options.token}`,
"idempotency-key": idempotencyKey,
[OPEN_CLOUD_CLIENT_CAPABILITIES_HEADER]:
OPEN_CLOUD_CLIENT_CAPABILITIES,
},
body: form,
signal: AbortSignal.timeout(120_000),
Expand Down Expand Up @@ -172,6 +182,8 @@ export class OpenCloudClient {
accept: "application/json",
...(body === undefined ? {} : { "content-type": "application/json" }),
...(idempotencyKey ? { "idempotency-key": idempotencyKey } : {}),
[OPEN_CLOUD_CLIENT_CAPABILITIES_HEADER]:
OPEN_CLOUD_CLIENT_CAPABILITIES,
},
...(body === undefined ? {} : { body: JSON.stringify(body) }),
signal: AbortSignal.timeout(timeoutMs),
Expand Down
Loading