Overhaul UI with shadcn components and new pages
Add shadcn/ui components (badge, button, card, input, select,
separator). Add company context provider. New pages: Activity,
Approvals, Companies, Costs, Org chart. Restyle existing pages
(Dashboard, Agents, Issues, Goals, Projects) with shadcn components
and dark theme. Update layout, sidebar navigation, and routing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:32 -06:00
|
|
|
import { type ClassValue, clsx } from "clsx";
|
|
|
|
|
import { twMerge } from "tailwind-merge";
|
2026-03-31 16:33:48 +00:00
|
|
|
import { deriveAgentUrlKey, deriveProjectUrlKey, normalizeProjectUrlKey, hasNonAsciiContent } from "@paperclipai/shared";
|
2026-03-14 22:00:12 -05:00
|
|
|
import type { BillingType, FinanceDirection, FinanceEventKind } from "@paperclipai/shared";
|
Overhaul UI with shadcn components and new pages
Add shadcn/ui components (badge, button, card, input, select,
separator). Add company context provider. New pages: Activity,
Approvals, Companies, Costs, Org chart. Restyle existing pages
(Dashboard, Agents, Issues, Goals, Projects) with shadcn components
and dark theme. Update layout, sidebar navigation, and routing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:32 -06:00
|
|
|
|
|
|
|
|
export function cn(...inputs: ClassValue[]) {
|
|
|
|
|
return twMerge(clsx(inputs));
|
2026-02-16 13:32:04 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function formatCents(cents: number): string {
|
|
|
|
|
return `$${(cents / 100).toFixed(2)}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function formatDate(date: Date | string): string {
|
|
|
|
|
return new Date(date).toLocaleDateString("en-US", {
|
|
|
|
|
month: "short",
|
|
|
|
|
day: "numeric",
|
|
|
|
|
year: "numeric",
|
|
|
|
|
});
|
|
|
|
|
}
|
Build out agent management UI: detail page, create dialog, list view
Add NewAgentDialog for creating agents with adapter config. Expand
AgentDetail page with tabbed view (overview, runs, config, logs),
run history timeline, and live status. Enhance Agents list page with
richer cards and filtering. Update AgentProperties panel, API client,
query keys, and utility helpers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 12:33:04 -06:00
|
|
|
|
2026-02-20 16:19:54 -06:00
|
|
|
export function formatDateTime(date: Date | string): string {
|
|
|
|
|
return new Date(date).toLocaleString("en-US", {
|
|
|
|
|
month: "short",
|
|
|
|
|
day: "numeric",
|
|
|
|
|
year: "numeric",
|
|
|
|
|
hour: "numeric",
|
|
|
|
|
minute: "2-digit",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-06 16:13:46 -05:00
|
|
|
export function formatShortDate(date: Date | string): string {
|
|
|
|
|
return new Date(date).toLocaleString("en-US", {
|
|
|
|
|
month: "short",
|
|
|
|
|
day: "numeric",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
Build out agent management UI: detail page, create dialog, list view
Add NewAgentDialog for creating agents with adapter config. Expand
AgentDetail page with tabbed view (overview, runs, config, logs),
run history timeline, and live status. Enhance Agents list page with
richer cards and filtering. Update AgentProperties panel, API client,
query keys, and utility helpers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 12:33:04 -06:00
|
|
|
export function relativeTime(date: Date | string): string {
|
|
|
|
|
const now = Date.now();
|
|
|
|
|
const then = new Date(date).getTime();
|
|
|
|
|
const diffSec = Math.round((now - then) / 1000);
|
|
|
|
|
if (diffSec < 60) return "just now";
|
|
|
|
|
const diffMin = Math.round(diffSec / 60);
|
|
|
|
|
if (diffMin < 60) return `${diffMin}m ago`;
|
|
|
|
|
const diffHr = Math.round(diffMin / 60);
|
|
|
|
|
if (diffHr < 24) return `${diffHr}h ago`;
|
|
|
|
|
const diffDay = Math.round(diffHr / 24);
|
|
|
|
|
if (diffDay < 30) return `${diffDay}d ago`;
|
|
|
|
|
return formatDate(date);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function formatTokens(n: number): string {
|
|
|
|
|
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
|
|
|
|
|
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}k`;
|
|
|
|
|
return String(n);
|
|
|
|
|
}
|
2026-02-20 16:04:05 -06:00
|
|
|
|
2026-03-08 03:35:23 +05:30
|
|
|
/** Map a raw provider slug to a display-friendly name. */
|
|
|
|
|
export function providerDisplayName(provider: string): string {
|
|
|
|
|
const map: Record<string, string> = {
|
|
|
|
|
anthropic: "Anthropic",
|
feat: add AWS Bedrock auth support on "claude-local" (#2793)
Closes #2412
Related: #2681, #498, #128
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The Claude Code adapter spawns the `claude` CLI to run agent tasks
> - The adapter detects auth mode by checking for `ANTHROPIC_API_KEY` —
recognizing only "api" and "subscription" modes
> - But users running Claude Code via **AWS Bedrock**
(`CLAUDE_CODE_USE_BEDROCK=1`) fall through to the "subscription" path
> - This causes a misleading "ANTHROPIC_API_KEY is not set;
subscription-based auth can be used" message in the environment check
> - Additionally, the hello probe passes `--model claude-opus-4-6` which
is **not a valid Bedrock model identifier**, causing `400 The provided
model identifier is invalid` and a probe failure
> - This pull request adds Bedrock auth detection, skips the
Anthropic-style `--model` flag for Bedrock, and returns the correct
billing type
> - The benefit is that Bedrock users get a working environment check
and correct cost tracking out of the box
---
## Pain Point
Many enterprise teams use **Claude Code through AWS Bedrock** rather
than Anthropic's direct API — for compliance, billing consolidation, or
VPC requirements. Currently, these users hit a **hard wall during
onboarding**:
| Problem | Impact |
|---|---|
| :x: Adapter environment check **always fails** | Users cannot create
their first agent — blocked at step 1 |
| :x: `--model claude-opus-4-6` is **invalid on Bedrock** (requires
`us.anthropic.*` format) | Hello probe exits with code 1: `400 The
provided model identifier is invalid` |
| :x: Auth shown as _"subscription-based"_ | Misleading — Bedrock is
neither subscription nor API-key auth |
| :x: Quota polling hits Anthropic OAuth endpoint | Fails silently for
Bedrock users who have no Anthropic subscription |
> **Bottom line**: Paperclip is completely unusable for Bedrock users
out of the box.
## Why Bedrock Matters
AWS Bedrock is a major deployment path for Claude in enterprise
environments:
- **Enterprise compliance** — data stays within the customer's AWS
account and VPC
- **Unified billing** — Claude usage appears on the existing AWS
invoice, no separate Anthropic billing
- **IAM integration** — access controlled through AWS IAM roles and
policies
- **Regional deployment** — models run in the customer's preferred AWS
region
Supporting Bedrock unlocks Paperclip for organizations that **cannot**
use Anthropic's direct API due to procurement, security, or regulatory
constraints.
---
## What Changed
- **`execute.ts`**: Added `isBedrockAuth()` helper that checks
`CLAUDE_CODE_USE_BEDROCK` and `ANTHROPIC_BEDROCK_BASE_URL` env vars.
`resolveClaudeBillingType()` now returns `"metered_api"` for Bedrock.
Biller set to `"aws_bedrock"`. Skips `--model` flag when Bedrock is
active (Anthropic-style model IDs are invalid on Bedrock; the CLI uses
its own configured model).
- **`test.ts`**: Environment check now detects Bedrock env vars (from
adapter config or server env) and shows `"AWS Bedrock auth detected.
Claude will use Bedrock for inference."` instead of the misleading
subscription message. Also skips `--model` in the hello probe for
Bedrock.
- **`quota.ts`**: Early return with `{ ok: true, windows: [] }` when
Bedrock is active — Bedrock usage is billed through AWS, not Anthropic's
subscription quota system.
- **`ui/src/lib/utils.ts`**: Added `"aws_bedrock"` → `"AWS Bedrock"` to
`providerDisplayName()` and `quotaSourceDisplayName()`.
## Verification
1. `pnpm -r typecheck` — all packages pass
2. Unit tests added and passing (6/6)
3. Environment check with Bedrock env vars:
| | Before | After |
|---|---|---|
| **Status** | :red_circle: Failed | :white_check_mark: Passed |
| **Auth message** | `ANTHROPIC_API_KEY is not set; subscription-based
auth can be used if Claude is logged in.` | `AWS Bedrock auth detected.
Claude will use Bedrock for inference.` |
| **Hello probe** | `ERROR · Claude hello probe failed.` (exit code 1 —
`--model claude-opus-4-6` is invalid on Bedrock) | `INFO · Claude hello
probe succeeded.` |
| **Screenshot** | <img height="500" alt="Screenshot 2026-04-05 at 8 25
27 AM"
src="https://github.com/user-attachments/assets/476431f6-6139-425a-8abc-97875d653657"
/> | <img height="500" alt="Screenshot 2026-04-05 at 8 31 58 AM"
src="https://github.com/user-attachments/assets/d388ce87-c5e6-4574-b8d2-fd8b86135299"
/> |
4. Existing API key / subscription paths are completely untouched unless
Bedrock env vars are present
## Risks
- **Low risk.** All changes are additive — existing "api" and
"subscription" code paths are only entered when Bedrock env vars are
absent.
- When Bedrock is active, the `--model` flag is skipped, so the
Paperclip model dropdown selection is ignored in favor of the Claude
CLI's own model config. This is intentional since Bedrock requires
different model identifiers.
## Model Used
- Claude Opus 4.6 (`claude-opus-4-6`, 1M context window) via Claude Code
CLI
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-04-07 05:15:18 +09:00
|
|
|
aws_bedrock: "AWS Bedrock",
|
2026-03-08 03:35:23 +05:30
|
|
|
openai: "OpenAI",
|
2026-03-14 22:00:12 -05:00
|
|
|
openrouter: "OpenRouter",
|
|
|
|
|
chatgpt: "ChatGPT",
|
2026-03-08 03:35:23 +05:30
|
|
|
google: "Google",
|
|
|
|
|
cursor: "Cursor",
|
|
|
|
|
jetbrains: "JetBrains AI",
|
|
|
|
|
};
|
|
|
|
|
return map[provider.toLowerCase()] ?? provider;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-14 22:00:12 -05:00
|
|
|
export function billingTypeDisplayName(billingType: BillingType): string {
|
|
|
|
|
const map: Record<BillingType, string> = {
|
|
|
|
|
metered_api: "Metered API",
|
|
|
|
|
subscription_included: "Subscription",
|
|
|
|
|
subscription_overage: "Subscription overage",
|
|
|
|
|
credits: "Credits",
|
|
|
|
|
fixed: "Fixed",
|
|
|
|
|
unknown: "Unknown",
|
|
|
|
|
};
|
|
|
|
|
return map[billingType];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function quotaSourceDisplayName(source: string): string {
|
|
|
|
|
const map: Record<string, string> = {
|
|
|
|
|
"anthropic-oauth": "Anthropic OAuth",
|
|
|
|
|
"claude-cli": "Claude CLI",
|
feat: add AWS Bedrock auth support on "claude-local" (#2793)
Closes #2412
Related: #2681, #498, #128
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The Claude Code adapter spawns the `claude` CLI to run agent tasks
> - The adapter detects auth mode by checking for `ANTHROPIC_API_KEY` —
recognizing only "api" and "subscription" modes
> - But users running Claude Code via **AWS Bedrock**
(`CLAUDE_CODE_USE_BEDROCK=1`) fall through to the "subscription" path
> - This causes a misleading "ANTHROPIC_API_KEY is not set;
subscription-based auth can be used" message in the environment check
> - Additionally, the hello probe passes `--model claude-opus-4-6` which
is **not a valid Bedrock model identifier**, causing `400 The provided
model identifier is invalid` and a probe failure
> - This pull request adds Bedrock auth detection, skips the
Anthropic-style `--model` flag for Bedrock, and returns the correct
billing type
> - The benefit is that Bedrock users get a working environment check
and correct cost tracking out of the box
---
## Pain Point
Many enterprise teams use **Claude Code through AWS Bedrock** rather
than Anthropic's direct API — for compliance, billing consolidation, or
VPC requirements. Currently, these users hit a **hard wall during
onboarding**:
| Problem | Impact |
|---|---|
| :x: Adapter environment check **always fails** | Users cannot create
their first agent — blocked at step 1 |
| :x: `--model claude-opus-4-6` is **invalid on Bedrock** (requires
`us.anthropic.*` format) | Hello probe exits with code 1: `400 The
provided model identifier is invalid` |
| :x: Auth shown as _"subscription-based"_ | Misleading — Bedrock is
neither subscription nor API-key auth |
| :x: Quota polling hits Anthropic OAuth endpoint | Fails silently for
Bedrock users who have no Anthropic subscription |
> **Bottom line**: Paperclip is completely unusable for Bedrock users
out of the box.
## Why Bedrock Matters
AWS Bedrock is a major deployment path for Claude in enterprise
environments:
- **Enterprise compliance** — data stays within the customer's AWS
account and VPC
- **Unified billing** — Claude usage appears on the existing AWS
invoice, no separate Anthropic billing
- **IAM integration** — access controlled through AWS IAM roles and
policies
- **Regional deployment** — models run in the customer's preferred AWS
region
Supporting Bedrock unlocks Paperclip for organizations that **cannot**
use Anthropic's direct API due to procurement, security, or regulatory
constraints.
---
## What Changed
- **`execute.ts`**: Added `isBedrockAuth()` helper that checks
`CLAUDE_CODE_USE_BEDROCK` and `ANTHROPIC_BEDROCK_BASE_URL` env vars.
`resolveClaudeBillingType()` now returns `"metered_api"` for Bedrock.
Biller set to `"aws_bedrock"`. Skips `--model` flag when Bedrock is
active (Anthropic-style model IDs are invalid on Bedrock; the CLI uses
its own configured model).
- **`test.ts`**: Environment check now detects Bedrock env vars (from
adapter config or server env) and shows `"AWS Bedrock auth detected.
Claude will use Bedrock for inference."` instead of the misleading
subscription message. Also skips `--model` in the hello probe for
Bedrock.
- **`quota.ts`**: Early return with `{ ok: true, windows: [] }` when
Bedrock is active — Bedrock usage is billed through AWS, not Anthropic's
subscription quota system.
- **`ui/src/lib/utils.ts`**: Added `"aws_bedrock"` → `"AWS Bedrock"` to
`providerDisplayName()` and `quotaSourceDisplayName()`.
## Verification
1. `pnpm -r typecheck` — all packages pass
2. Unit tests added and passing (6/6)
3. Environment check with Bedrock env vars:
| | Before | After |
|---|---|---|
| **Status** | :red_circle: Failed | :white_check_mark: Passed |
| **Auth message** | `ANTHROPIC_API_KEY is not set; subscription-based
auth can be used if Claude is logged in.` | `AWS Bedrock auth detected.
Claude will use Bedrock for inference.` |
| **Hello probe** | `ERROR · Claude hello probe failed.` (exit code 1 —
`--model claude-opus-4-6` is invalid on Bedrock) | `INFO · Claude hello
probe succeeded.` |
| **Screenshot** | <img height="500" alt="Screenshot 2026-04-05 at 8 25
27 AM"
src="https://github.com/user-attachments/assets/476431f6-6139-425a-8abc-97875d653657"
/> | <img height="500" alt="Screenshot 2026-04-05 at 8 31 58 AM"
src="https://github.com/user-attachments/assets/d388ce87-c5e6-4574-b8d2-fd8b86135299"
/> |
4. Existing API key / subscription paths are completely untouched unless
Bedrock env vars are present
## Risks
- **Low risk.** All changes are additive — existing "api" and
"subscription" code paths are only entered when Bedrock env vars are
absent.
- When Bedrock is active, the `--model` flag is skipped, so the
Paperclip model dropdown selection is ignored in favor of the Claude
CLI's own model config. This is intentional since Bedrock requires
different model identifiers.
## Model Used
- Claude Opus 4.6 (`claude-opus-4-6`, 1M context window) via Claude Code
CLI
## Checklist
- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-04-07 05:15:18 +09:00
|
|
|
"bedrock": "AWS Bedrock",
|
2026-03-14 22:00:12 -05:00
|
|
|
"codex-rpc": "Codex app server",
|
|
|
|
|
"codex-wham": "ChatGPT WHAM",
|
|
|
|
|
};
|
|
|
|
|
return map[source] ?? source;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function coerceBillingType(value: unknown): BillingType | null {
|
|
|
|
|
if (
|
|
|
|
|
value === "metered_api" ||
|
|
|
|
|
value === "subscription_included" ||
|
|
|
|
|
value === "subscription_overage" ||
|
|
|
|
|
value === "credits" ||
|
|
|
|
|
value === "fixed" ||
|
|
|
|
|
value === "unknown"
|
|
|
|
|
) {
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function readRunCostUsd(payload: Record<string, unknown> | null): number {
|
|
|
|
|
if (!payload) return 0;
|
|
|
|
|
for (const key of ["costUsd", "cost_usd", "total_cost_usd"] as const) {
|
|
|
|
|
const value = payload[key];
|
|
|
|
|
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function visibleRunCostUsd(
|
|
|
|
|
usage: Record<string, unknown> | null,
|
|
|
|
|
result: Record<string, unknown> | null = null,
|
|
|
|
|
): number {
|
|
|
|
|
const billingType = coerceBillingType(usage?.billingType) ?? coerceBillingType(result?.billingType);
|
|
|
|
|
if (billingType === "subscription_included") return 0;
|
|
|
|
|
return readRunCostUsd(usage) || readRunCostUsd(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function financeEventKindDisplayName(eventKind: FinanceEventKind): string {
|
|
|
|
|
const map: Record<FinanceEventKind, string> = {
|
|
|
|
|
inference_charge: "Inference charge",
|
|
|
|
|
platform_fee: "Platform fee",
|
|
|
|
|
credit_purchase: "Credit purchase",
|
|
|
|
|
credit_refund: "Credit refund",
|
|
|
|
|
credit_expiry: "Credit expiry",
|
|
|
|
|
byok_fee: "BYOK fee",
|
|
|
|
|
gateway_overhead: "Gateway overhead",
|
|
|
|
|
log_storage_charge: "Log storage",
|
|
|
|
|
logpush_charge: "Logpush",
|
|
|
|
|
provisioned_capacity_charge: "Provisioned capacity",
|
|
|
|
|
training_charge: "Training",
|
|
|
|
|
custom_model_import_charge: "Custom model import",
|
|
|
|
|
custom_model_storage_charge: "Custom model storage",
|
|
|
|
|
manual_adjustment: "Manual adjustment",
|
|
|
|
|
};
|
|
|
|
|
return map[eventKind];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function financeDirectionDisplayName(direction: FinanceDirection): string {
|
|
|
|
|
return direction === "credit" ? "Credit" : "Debit";
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 16:04:05 -06:00
|
|
|
/** Build an issue URL using the human-readable identifier when available. */
|
|
|
|
|
export function issueUrl(issue: { id: string; identifier?: string | null }): string {
|
|
|
|
|
return `/issues/${issue.identifier ?? issue.id}`;
|
|
|
|
|
}
|
2026-03-02 16:44:03 -06:00
|
|
|
|
|
|
|
|
/** Build an agent route URL using the short URL key when available. */
|
|
|
|
|
export function agentRouteRef(agent: { id: string; urlKey?: string | null; name?: string | null }): string {
|
|
|
|
|
return agent.urlKey ?? deriveAgentUrlKey(agent.name, agent.id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Build an agent URL using the short URL key when available. */
|
|
|
|
|
export function agentUrl(agent: { id: string; urlKey?: string | null; name?: string | null }): string {
|
|
|
|
|
return `/agents/${agentRouteRef(agent)}`;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 16:33:48 +00:00
|
|
|
/** Build a project route reference, falling back to UUID when the derived key is ambiguous. */
|
2026-03-02 16:44:03 -06:00
|
|
|
export function projectRouteRef(project: { id: string; urlKey?: string | null; name?: string | null }): string {
|
2026-03-31 16:33:48 +00:00
|
|
|
const key = project.urlKey ?? deriveProjectUrlKey(project.name, project.id);
|
|
|
|
|
// Guard for rolling deploys or legacy data where the server returned a bare slug without UUID suffix.
|
|
|
|
|
if (key === normalizeProjectUrlKey(project.name) && hasNonAsciiContent(project.name)) return project.id;
|
|
|
|
|
return key;
|
2026-03-02 16:44:03 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Build a project URL using the short URL key when available. */
|
|
|
|
|
export function projectUrl(project: { id: string; urlKey?: string | null; name?: string | null }): string {
|
|
|
|
|
return `/projects/${projectRouteRef(project)}`;
|
|
|
|
|
}
|
2026-03-28 09:51:58 -05:00
|
|
|
|
|
|
|
|
/** Build a project workspace URL scoped under its project. */
|
|
|
|
|
export function projectWorkspaceUrl(
|
|
|
|
|
project: { id: string; urlKey?: string | null; name?: string | null },
|
|
|
|
|
workspaceId: string,
|
|
|
|
|
): string {
|
|
|
|
|
return `${projectUrl(project)}/workspaces/${workspaceId}`;
|
|
|
|
|
}
|