Skip to content
Open
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
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,24 @@ cosmic whoami # Show current user
cosmic logout # Clear credentials
```

### Agent Signup (no prior account)

For AI agents that need to provision a Cosmic project from scratch on behalf of a human (no prior login required):

```bash
# Provision a project + bucket tied to a human's email. Cosmic emails them a
# 6-digit claim code.
cosmic agent-signup --email tony@example.com --project "Recipe Blog" --agent-id my-agent

# When the human pastes the code back to the agent, run:
cosmic agent-verify 123456

# Check the current state and tier limits:
cosmic agent-status
```

The `agent-signup` command stores the returned `agent_key` and bucket keys in `~/.cosmic/credentials.json` under the `agent` slot, so `agent-verify` and `agent-status` don't need the key on the command line. Unclaimed agent buckets are auto-deleted after 14 days, so plan to verify within that window.

### Personal Access Token

Authenticate with a [Personal Access Token](https://app.cosmicjs.com/account/api-tokens) for scripts, CI/CD, and automation:
Expand Down Expand Up @@ -501,7 +519,7 @@ cosmic models # List all available models
Set your default model:

```bash
cosmic config set defaultModel claude-opus-4-5-20251101
cosmic config set defaultModel claude-opus-4-7
```

Or specify per-command:
Expand All @@ -511,7 +529,7 @@ cosmic ai generate --model=gpt-5 "Your prompt"
```

**Available models:**
- **Claude (Anthropic):** `claude-sonnet-4-6`, `claude-opus-4-5-20251101`, `claude-sonnet-4-5-20250929`, `claude-haiku-4-5-20251001`
- **Claude (Anthropic):** `claude-opus-4-7`, `claude-opus-4-6`, `claude-sonnet-4-6`, `claude-opus-4-5-20251101`, `claude-sonnet-4-5-20250929`, `claude-haiku-4-5-20251001`
- **GPT (OpenAI):** `gpt-5`, `gpt-5.2`, `gpt-5-mini`, `gpt-4o`
- **Gemini (Google):** `gemini-3-pro-preview`

Expand Down
141 changes: 141 additions & 0 deletions src/api/dashboard/agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/**
* Agent Signup API client.
*
* The existing `client.ts` axios wrapper auto-injects user/session auth
* headers via interceptor, which is wrong for agent signup (no auth) and for
* verify/status (wrong auth shape: agent_key, not user JWT). We use fetch
* directly here to keep the auth surface explicit.
*
* Endpoints (cosmic-backend public.routes.js):
* POST /v3/agents/sign-up
* POST /v3/agents/verify (Authorization: Bearer agk_...)
* GET /v3/agents/status (Authorization: Bearer agk_...)
*/

import { getApiUrl } from '../../config/store.js';
import { CLI_VERSION } from '../../version.js';

export interface AgentSignupRequest {
human_email: string;
project_name: string;
agent_id: string;
client?: string;
prompt_hint?: string;
}

export interface AgentSignupResponse {
message: string;
auth_type: 'unclaimed' | 'verified';
agent_key: string;
project: { id: string; name: string } | null;
bucket: {
slug: string;
read_key?: string;
write_key?: string;
} | null;
claim_url: string;
limits: {
ai_credits_remaining: number;
media_mb_total: number;
objects_max: number;
};
auto_delete_after_days: number;
}

export interface AgentVerifyResponse {
message: string;
auth_type: 'verified';
claim_status: string;
limits: null;
}

export interface AgentStatusResponse {
auth_type: 'unclaimed' | 'verified';
claim_status: string;
plan_id: string;
limits: AgentSignupResponse['limits'] | null;
auto_delete_after_days: number | null;
project: { id: string; name: string } | null;
bucket: { slug: string } | null;
agent_id: string | null;
client: string | null;
human_email: string;
}

async function dapiFetch<T>(
path: string,
init: { method: 'GET' | 'POST'; body?: unknown; agentKey?: string },
): Promise<T> {
const url = `${getApiUrl()}${path}`;
const headers: Record<string, string> = {
'Content-Type': 'application/json',
'Origin': 'https://app.cosmicjs.com',
'User-Agent': `CosmicCLI/${CLI_VERSION}`,
'X-Cosmic-Client': 'cli',
};
if (init.agentKey) {
headers['Authorization'] = `Bearer ${init.agentKey}`;
}
const res = await fetch(url, {
method: init.method,
headers,
body: init.body !== undefined ? JSON.stringify(init.body) : undefined,
});
const text = await res.text();
let parsed: unknown = undefined;
if (text) {
try {
parsed = JSON.parse(text);
} catch {
parsed = text;
}
}
if (!res.ok) {
const message =
typeof parsed === 'object' && parsed !== null && 'message' in parsed
? String((parsed as { message?: unknown }).message)
: `Agent API request failed (${res.status})`;
const err = new Error(message) as Error & { status: number; code?: string; body?: unknown };
err.status = res.status;
if (
typeof parsed === 'object' &&
parsed !== null &&
'code' in parsed &&
typeof (parsed as { code?: unknown }).code === 'string'
) {
err.code = (parsed as { code: string }).code;
}
err.body = parsed;
throw err;
}
return parsed as T;
}

export async function signupAgent(
body: AgentSignupRequest,
): Promise<AgentSignupResponse> {
return dapiFetch<AgentSignupResponse>('/agents/sign-up', {
method: 'POST',
body,
});
}

export async function verifyAgent(
agentKey: string,
otpCode: string,
): Promise<AgentVerifyResponse> {
return dapiFetch<AgentVerifyResponse>('/agents/verify', {
method: 'POST',
body: { code: otpCode },
agentKey,
});
}

export async function getAgentStatus(
agentKey: string,
): Promise<AgentStatusResponse> {
return dapiFetch<AgentStatusResponse>('/agents/status', {
method: 'GET',
agentKey,
});
}
4 changes: 2 additions & 2 deletions src/api/dashboard/ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export async function streamingChat(options: StreamingChatOptions): Promise<{ te
const {
messages,
bucketSlug,
model = 'claude-opus-4-5-20251101',
model = 'claude-opus-4-7',
maxTokens = 32000,
viewMode = 'build-app',
selectedObjectTypes = [],
Expand Down Expand Up @@ -357,7 +357,7 @@ export async function streamingRepositoryUpdate(options: RepositoryUpdateOptions
bucketSlug,
messages,
branch = 'main',
model = 'claude-opus-4-5-20251101',
model = 'claude-opus-4-7',
maxTokens = 32000,
chatMode = 'agent',
onChunk,
Expand Down
2 changes: 1 addition & 1 deletion src/chat/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ export async function executeAction(actionJson: string): Promise<string> {
agent_name: action.name,
agent_type: action.type || 'content',
prompt: action.prompt || 'You are a helpful content writing assistant.',
model: action.model || 'claude-opus-4-5-20251101',
model: action.model || 'claude-opus-4-7',
emoji: action.emoji || '🤖',
};

Expand Down
2 changes: 1 addition & 1 deletion src/chat/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ You can perform these actions by outputting JSON commands:
- agent_name: string (required, 1-100 chars)
- agent_type: "content" | "repository" | "computer_use"
- prompt: string (required, the system prompt/instructions)
- model: defaults to "claude-opus-4-5-20251101" (don't include unless user specifies different model)
- model: defaults to "claude-opus-4-7" (don't include unless user specifies different model)
- emoji: string (always include, e.g. "✍️", "📝", "🤖", "📰", "💡")
- object_types: array of object type slugs for context (e.g. ["posts", "authors"])

Expand Down
Loading