Skip to content

Commit 05aeeab

Browse files
Merge pull request #73 from squaredup/work/ah/claude-console
Add Claude Console plugin
2 parents d0a55d8 + 597a743 commit 05aeeab

23 files changed

Lines changed: 1797 additions & 0 deletions
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"steps": [
3+
{
4+
"displayName": "Authenticate",
5+
"dataStream": {
6+
"name": "organization"
7+
},
8+
"required": true,
9+
"success": "Connected to your Claude Console organization.",
10+
"error": "Could not authenticate. Check that your Admin API key (sk-ant-admin...) is valid and belongs to an organization, not an individual account."
11+
}
12+
]
13+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[
2+
{
3+
"name": "Workspace",
4+
"sourceType": "Workspace",
5+
"icon": "layer-group",
6+
"singular": "Workspace",
7+
"plural": "Workspaces"
8+
},
9+
{
10+
"name": "API Key",
11+
"sourceType": "API Key",
12+
"icon": "key",
13+
"singular": "API Key",
14+
"plural": "API Keys"
15+
},
16+
{
17+
"name": "Member",
18+
"sourceType": "Member",
19+
"icon": "user",
20+
"singular": "Member",
21+
"plural": "Members"
22+
}
23+
]
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
{
2+
"name": "apiKeys",
3+
"displayName": "API Keys",
4+
"description": "API keys in your Claude Console organization",
5+
"tags": ["API Keys"],
6+
"baseDataSourceName": "httpRequestUnscoped",
7+
"config": {
8+
"httpMethod": "get",
9+
"endpointPath": "v1/organizations/api_keys",
10+
"getArgs": [
11+
{
12+
"key": "limit",
13+
"value": "1000"
14+
},
15+
{
16+
"key": "created_by_user_id",
17+
"value": "{{ (createdBy && createdBy.length) ? (Array.isArray(createdBy[0].rawId) ? createdBy[0].rawId[0] : createdBy[0].rawId) : null }}"
18+
}
19+
],
20+
"paging": {
21+
"mode": "none"
22+
},
23+
"expandInnerObjects": true,
24+
"postRequestScript": "apiKeys.js"
25+
},
26+
"matches": "none",
27+
"ui": [
28+
{
29+
"type": "objects",
30+
"name": "workspace",
31+
"label": "Workspace (optional)",
32+
"matches": {
33+
"sourceType": {
34+
"type": "oneOf",
35+
"values": ["Workspace"]
36+
}
37+
}
38+
},
39+
{
40+
"type": "objects",
41+
"name": "createdBy",
42+
"label": "Created by (optional)",
43+
"matches": {
44+
"sourceType": { "type": "oneOf", "values": ["Member"] }
45+
}
46+
}
47+
],
48+
"metadata": [
49+
{
50+
"name": "id",
51+
"displayName": "API Key ID",
52+
"shape": "string"
53+
},
54+
{
55+
"name": "name",
56+
"displayName": "Name",
57+
"shape": "string",
58+
"role": "label"
59+
},
60+
{
61+
"name": "status",
62+
"displayName": "Status",
63+
"shape": [
64+
"state",
65+
{
66+
"map": {
67+
"success": ["active"],
68+
"unknown": ["expired", "archived", "inactive"]
69+
}
70+
}
71+
]
72+
},
73+
{
74+
"name": "workspace_id",
75+
"displayName": "Workspace ID",
76+
"shape": "string"
77+
},
78+
{
79+
"name": "partial_key_hint",
80+
"displayName": "Key Hint",
81+
"shape": "string"
82+
},
83+
{
84+
"name": "created_at",
85+
"displayName": "Created",
86+
"shape": "date"
87+
},
88+
{
89+
"name": "expires_at",
90+
"displayName": "Expires",
91+
"shape": "date"
92+
},
93+
{
94+
"name": "created_by.id",
95+
"displayName": "Created By",
96+
"shape": "string"
97+
}
98+
],
99+
"timeframes": false
100+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
{
2+
"name": "cost",
3+
"displayName": "Cost",
4+
"description": "Organization spend in USD over time, one row per workspace/model/token-type bucket per day",
5+
"tags": ["Cost", "Billing"],
6+
"baseDataSourceName": "httpRequestUnscoped",
7+
"config": {
8+
"httpMethod": "get",
9+
"endpointPath": "v1/organizations/cost_report",
10+
"getArgs": [
11+
{
12+
"key": "starting_at",
13+
"value": "{{timeframe.start}}"
14+
},
15+
{
16+
"key": "ending_at",
17+
"value": "{{timeframe.end}}"
18+
},
19+
{
20+
"key": "bucket_width",
21+
"value": "1d"
22+
},
23+
{
24+
"key": "group_by[]",
25+
"value": "workspace_id"
26+
},
27+
{
28+
"key": "group_by[]",
29+
"value": "description"
30+
},
31+
{
32+
"key": "limit",
33+
"value": "31"
34+
}
35+
],
36+
"paging": {
37+
"mode": "none"
38+
},
39+
"postRequestScript": "cost.js"
40+
},
41+
"matches": "none",
42+
"ui": [
43+
{
44+
"type": "objects",
45+
"name": "workspace",
46+
"label": "Workspace (optional)",
47+
"matches": {
48+
"sourceType": {
49+
"type": "oneOf",
50+
"values": ["Workspace"]
51+
}
52+
}
53+
}
54+
],
55+
"metadata": [
56+
{
57+
"name": "date",
58+
"displayName": "Date",
59+
"shape": "date",
60+
"role": "timestamp"
61+
},
62+
{
63+
"name": "amount",
64+
"displayName": "Cost ($)",
65+
"shape": [
66+
"currency",
67+
{
68+
"code": "{{column.currency}}",
69+
"decimalPlaces": 2,
70+
"thousandsSeparator": true
71+
}
72+
],
73+
"role": "value"
74+
},
75+
{
76+
"name": "workspace_id",
77+
"displayName": "Workspace ID",
78+
"shape": "string"
79+
},
80+
{
81+
"name": "workspaceName",
82+
"displayName": "Workspace",
83+
"sourceId": "workspace_id",
84+
"sourceType": "Workspace",
85+
"objectPropertyPath": "name"
86+
},
87+
{
88+
"name": "description",
89+
"displayName": "Description",
90+
"shape": "string"
91+
},
92+
{
93+
"name": "currency",
94+
"displayName": "Currency",
95+
"shape": "string",
96+
"visible": false
97+
},
98+
{
99+
"pattern": ".*"
100+
}
101+
],
102+
"timeframes": ["last7days", "last30days", "thisMonth", "lastMonth"]
103+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
{
2+
"name": "members",
3+
"displayName": "Members",
4+
"description": "Organization members in your Claude Console organization, with email and role",
5+
"tags": [
6+
"Members"
7+
],
8+
"baseDataSourceName": "httpRequestUnscoped",
9+
"config": {
10+
"httpMethod": "get",
11+
"endpointPath": "v1/organizations/users",
12+
"getArgs": [
13+
{
14+
"key": "limit",
15+
"value": "1000"
16+
}
17+
],
18+
"paging": {
19+
"mode": "none"
20+
},
21+
"pathToData": "data"
22+
},
23+
"matches": "none",
24+
"metadata": [
25+
{
26+
"name": "id",
27+
"displayName": "User ID",
28+
"shape": "string"
29+
},
30+
{
31+
"name": "name",
32+
"displayName": "Name",
33+
"shape": "string"
34+
},
35+
{
36+
"name": "email",
37+
"displayName": "Email",
38+
"shape": "string"
39+
},
40+
{
41+
"name": "role",
42+
"displayName": "Role",
43+
"shape": "string"
44+
},
45+
{
46+
"name": "added_at",
47+
"displayName": "Added",
48+
"shape": "date"
49+
},
50+
{
51+
"name": "memberName",
52+
"displayName": "Member",
53+
"computed": true,
54+
"valueExpression": "{{ $['name']?.__isNone ? $['email'] : $['name'] }}",
55+
"shape": "string",
56+
"role": "label"
57+
}
58+
],
59+
"timeframes": false
60+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"name": "organization",
3+
"displayName": "Organization",
4+
"description": "Your Claude Console organization id, name, and type",
5+
"tags": [
6+
"Organization"
7+
],
8+
"baseDataSourceName": "httpRequestUnscoped",
9+
"config": {
10+
"httpMethod": "get",
11+
"endpointPath": "v1/organizations/me",
12+
"paging": {
13+
"mode": "none"
14+
},
15+
"getArgs": [],
16+
"headers": []
17+
},
18+
"matches": "none",
19+
"metadata": [
20+
{
21+
"name": "id",
22+
"displayName": "Organization ID",
23+
"shape": "string"
24+
},
25+
{
26+
"name": "name",
27+
"displayName": "Name",
28+
"shape": "string",
29+
"role": "label"
30+
},
31+
{
32+
"name": "type",
33+
"displayName": "Type",
34+
"shape": "string"
35+
}
36+
],
37+
"timeframes": false,
38+
"visibility": {
39+
"type": "hidden"
40+
}
41+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const unwrap = (v) => (Array.isArray(v) ? v[0] : v);
2+
3+
// Build set of selected workspace rawIds (empty → account-wide, no filter)
4+
const selected = (context.config && context.config.workspace) || [];
5+
const workspaceIds = new Set(
6+
selected.map((o) => unwrap(o.rawId)).filter(Boolean),
7+
);
8+
9+
let rows = data.data.map((a) => ({
10+
...a,
11+
// Claude does not let us filter by the default workspace on their api.
12+
workspace_id: a.workspace_id || "default",
13+
}));
14+
15+
// Apply optional workspace scope filter
16+
if (workspaceIds.size) {
17+
rows = rows.filter((r) => workspaceIds.has(r.workspace_id));
18+
}
19+
20+
result = rows;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// cost.js — flatten nested time-bucket/results structure, coerce amount string→number,
2+
// and optionally scope to selected workspace(s) via the `workspace` objects param.
3+
4+
const unwrap = (v) => (Array.isArray(v) ? v[0] : v);
5+
6+
// Build set of selected workspace rawIds (empty → account-wide, no filter)
7+
const selected = (context.config && context.config.workspace) || [];
8+
const workspaceIds = new Set(
9+
selected.map((o) => unwrap(o.rawId)).filter(Boolean),
10+
);
11+
12+
// Flatten buckets × results into one row per combination
13+
let rows = (data.data || []).flatMap((bucket) =>
14+
(bucket.results || []).map((r) => ({
15+
...r,
16+
date: bucket.starting_at,
17+
// API returns `amount` in lowest currency units (cents) as a decimal string,
18+
// e.g. "300" cents = $3.00 — divide by 100 to get USD. Verified empirically:
19+
// cost/token matches Claude's published per-model list prices exactly only in cents.
20+
amount: Number(r.amount) / 100,
21+
workspace_id: r.workspace_id ?? "default",
22+
description: r.description,
23+
})),
24+
);
25+
26+
// Apply optional workspace scope filter
27+
if (workspaceIds.size) {
28+
rows = rows.filter((r) => workspaceIds.has(r.workspace_id));
29+
}
30+
31+
result = rows;
32+

0 commit comments

Comments
 (0)