@jlongo78/agent-spaces 0.9.8 → 0.9.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +3 -0
- package/.next/standalone/.next/build-manifest.json +2 -2
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/routes-manifest.json +23 -0
- package/.next/standalone/.next/server/app/(desktop)/admin/analytics/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/admin/analytics/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/admin/users/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/analytics/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/cortex/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/network/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/sessions/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/pane/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/remote/[nodeId]/[workspaceId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/workspaces/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +2 -2
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/analytics.html +1 -1
- package/.next/standalone/.next/server/app/admin/analytics.rsc +3 -3
- package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap/admin/analytics/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap/admin/analytics.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap/admin.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/analytics.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/admin/analytics.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/analytics.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/analytics.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/users.html +1 -1
- package/.next/standalone/.next/server/app/admin/users.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap/admin/users/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap/admin/users.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap/admin.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/users.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/users.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/users.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/users.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/analytics.html +1 -1
- package/.next/standalone/.next/server/app/analytics.rsc +2 -2
- package/.next/standalone/.next/server/app/analytics.segments/!KGRlc2t0b3Ap/analytics/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/analytics.segments/!KGRlc2t0b3Ap/analytics.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/analytics.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/analytics.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/analytics.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/analytics.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/analytics.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/chat/route.js +1 -1
- package/.next/standalone/.next/server/app/api/chat/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/claude/usage/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/claude/usage/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/claude/usage/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/claude/usage/route.js +8 -0
- package/.next/standalone/.next/server/app/api/claude/usage/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/claude/usage/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/claude/usage/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/cortex/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/chat/route.js +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/chat/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/whisper/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/[todoId]/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/[todoId]/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/[todoId]/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/[todoId]/route.js +8 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/[todoId]/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/[todoId]/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/[todoId]/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/route/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/route/build-manifest.json +11 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/route/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/route.js +8 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/route.js.map +5 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/route.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/api/workspaces/[id]/todos/route_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/cortex.html +1 -1
- package/.next/standalone/.next/server/app/cortex.rsc +2 -2
- package/.next/standalone/.next/server/app/cortex.segments/!KGRlc2t0b3Ap/cortex/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/cortex.segments/!KGRlc2t0b3Ap/cortex.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/cortex.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/cortex.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/cortex.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/cortex.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/cortex.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/login.html +1 -1
- package/.next/standalone/.next/server/app/login.rsc +2 -2
- package/.next/standalone/.next/server/app/login.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/login.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/login.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/login.segments/login/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/login.segments/login.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/projects.html +1 -1
- package/.next/standalone/.next/server/app/m/projects.rsc +2 -2
- package/.next/standalone/.next/server/app/m/projects.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/projects.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/projects.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/projects.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/projects.segments/m/projects/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/projects.segments/m/projects.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/projects.segments/m.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/sessions/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/sessions.html +1 -1
- package/.next/standalone/.next/server/app/m/sessions.rsc +2 -2
- package/.next/standalone/.next/server/app/m/sessions.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/sessions.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/sessions.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/sessions.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/sessions.segments/m/sessions/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/sessions.segments/m/sessions.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/sessions.segments/m.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/settings.html +1 -1
- package/.next/standalone/.next/server/app/m/settings.rsc +2 -2
- package/.next/standalone/.next/server/app/m/settings.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/settings.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/settings.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/settings.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/settings.segments/m/settings/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/settings.segments/m/settings.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/settings.segments/m.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/terminal/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/terminal.html +1 -1
- package/.next/standalone/.next/server/app/m/terminal.rsc +2 -2
- package/.next/standalone/.next/server/app/m/terminal.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/terminal.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/terminal.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/terminal.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/terminal.segments/m/terminal/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/terminal.segments/m/terminal.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/terminal.segments/m.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m.html +1 -1
- package/.next/standalone/.next/server/app/m.rsc +2 -2
- package/.next/standalone/.next/server/app/m.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m.segments/m/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m.segments/m.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/network.html +1 -1
- package/.next/standalone/.next/server/app/network.rsc +2 -2
- package/.next/standalone/.next/server/app/network.segments/!KGRlc2t0b3Ap/network/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/network.segments/!KGRlc2t0b3Ap/network.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/network.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/network.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/network.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/network.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/network.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/projects.html +1 -1
- package/.next/standalone/.next/server/app/projects.rsc +2 -2
- package/.next/standalone/.next/server/app/projects.segments/!KGRlc2t0b3Ap/projects/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/projects.segments/!KGRlc2t0b3Ap/projects.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/projects.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/projects.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/projects.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/projects.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/projects.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/sessions.html +1 -1
- package/.next/standalone/.next/server/app/sessions.rsc +2 -2
- package/.next/standalone/.next/server/app/sessions.segments/!KGRlc2t0b3Ap/sessions/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/sessions.segments/!KGRlc2t0b3Ap/sessions.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/sessions.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/sessions.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/sessions.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/sessions.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/sessions.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +3 -3
- package/.next/standalone/.next/server/app/settings.segments/!KGRlc2t0b3Ap/settings/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/!KGRlc2t0b3Ap/settings.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/terminal.html +1 -1
- package/.next/standalone/.next/server/app/terminal.rsc +3 -3
- package/.next/standalone/.next/server/app/terminal.segments/!KGRlc2t0b3Ap/terminal/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/terminal.segments/!KGRlc2t0b3Ap/terminal.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/terminal.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/terminal.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/terminal.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/terminal.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/terminal.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/vr/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/vr.html +1 -1
- package/.next/standalone/.next/server/app/vr.rsc +2 -2
- package/.next/standalone/.next/server/app/vr.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/vr.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/vr.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/vr.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/vr.segments/vr/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/vr.segments/vr.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/workspaces.html +1 -1
- package/.next/standalone/.next/server/app/workspaces.rsc +2 -2
- package/.next/standalone/.next/server/app/workspaces.segments/!KGRlc2t0b3Ap/workspaces/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/workspaces.segments/!KGRlc2t0b3Ap/workspaces.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/workspaces.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/workspaces.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/workspaces.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/workspaces.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/workspaces.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__00e90fc6._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__01ab8675._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__03974f05._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__046c9b91._.js +16 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__04ae6bf0._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__056fa416._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ac4ea3f._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0b8e64cb._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0facd39e._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__10bc76a3._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__115f3934._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__11f155f1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__160e7c73._.js +52 -24
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17a3b966._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17d3a2b2._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1a86c055._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fe0f452._.js +127 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__20b5e9c4._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__28d6fbd8._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__2a3f866b._.js +16 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__316617e7._.js +3 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__32ad8f71._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__35457394._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__35de78e6._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__3685ffcb._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__38954988._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__3a32b624._.js +111 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__426ad936._.js +17 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__4985c034._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__5c6ce9ed._.js +16 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__5cebe58a._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__5d5e4789._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__65676930._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__67cab326._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__698c6f01._.js +16 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__6c64af29._.js +22 -9
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__73aed9f5._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__79b6a9bb._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__7db704c6._.js +17 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__812ca02b._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__821f50fa._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__8716b86e._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__884ef754._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__88cdbd68._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__89d9aba9._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__8c2e1260._.js +111 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__8d536cb5._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__8df8c5d1._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__8f2ccc41._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__95c9d682._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__9e5d7774._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__9edcff87._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__a049dfc2._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__a5b4bb9a._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__a83262a1._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__a9cd1240._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__a9d7f822._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__ad08c221._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__ad585f2f._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__afcb8f7d._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__bc250d43._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__bce2a6e7._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__bfcd7fd4._.js +13 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__c011bf91._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__c37d6380._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__cae392eb._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__cc2616bb._.js +16 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d501fa9b._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d59c6c15._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d5c1db32._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__dba60c86._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__de14b9ae._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e10643d1._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e3477417._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e4db362e._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e4e70b86._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e54925af._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e8cbeaca._.js +111 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e8edc5b0._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__eab4d83b._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__f056fd83._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__f0e99572._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__fe1e16d0._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__ff9cd277._.js +15 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__ffaea2ce._.js +15 -2
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_claude_usage_route_actions_fe002ec1.js +3 -0
- package/.next/standalone/.next/server/chunks/_next-internal_server_app_api_workspaces_[id]_todos_route_actions_0d4ffac5.js +3 -0
- package/.next/standalone/.next/server/chunks/ce889_server_app_api_workspaces_[id]_todos_[todoId]_route_actions_754fe6b9.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__66aca5d4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_17b946fd._.js → _20c2cf3a._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_5f55bf8f._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_app_(desktop)_settings_page_tsx_f74824b3._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_app_(desktop)_terminal_page_tsx_de5e8d85._.js +1 -1
- package/.next/standalone/.next/server/edge/chunks/[root-of-the-server]__90eeddae._.js +1 -1
- package/.next/standalone/.next/server/edge/chunks/[root-of-the-server]__ca3f649e._.js +2 -2
- package/.next/standalone/.next/server/middleware-manifest.json +5 -5
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +2 -2
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/14216197f9dcbe5b.css +3 -0
- package/.next/standalone/.next/static/chunks/{0852575eb90c1e8d.js → 4c9fb0a38f041a3d.js} +2 -2
- package/.next/standalone/.next/static/chunks/8f6f93ab45a5ff5b.js +1 -0
- package/.next/standalone/.next/static/chunks/{e62bb488d02db247.js → d2566c2dcf53fef3.js} +1 -1
- package/.next/standalone/.next/static/chunks/{7f6a14f1849fa94d.js → ff7d85dade44d1f3.js} +1 -1
- package/.next/standalone/bin/cortex-hook.js +8 -3
- package/.next/standalone/bin/cortex-learn-hook.js +7 -2
- package/.next/standalone/bin/scrub-standalone.js +89 -0
- package/.next/standalone/bin/terminal-server.js +44 -16
- package/.next/standalone/docs/features/claude-usage.md +105 -0
- package/.next/standalone/docs/features/workspace-todos.md +127 -0
- package/.next/standalone/docs/superpowers/plans/2026-04-21-workspace-todos.md +1097 -0
- package/.next/standalone/docs/superpowers/plans/2026-04-22-claude-usage-display.md +749 -0
- package/.next/standalone/docs/superpowers/specs/2026-04-21-workspace-todos-design.md +180 -0
- package/.next/standalone/docs/superpowers/specs/2026-04-22-claude-usage-display-design.md +183 -0
- package/.next/standalone/package-lock.json +2 -2
- package/.next/standalone/package.json +2 -2
- package/.next/standalone/src/app/(desktop)/settings/page.tsx +40 -1
- package/.next/standalone/src/app/(desktop)/terminal/page.tsx +54 -2
- package/.next/standalone/src/app/api/chat/route.ts +56 -53
- package/.next/standalone/src/app/api/claude/usage/route.ts +33 -0
- package/.next/standalone/src/app/api/config/route.ts +3 -0
- package/.next/standalone/src/app/api/sessions/[id]/chat/route.ts +2 -1
- package/.next/standalone/src/app/api/workspaces/[id]/todos/[todoId]/route.ts +55 -0
- package/.next/standalone/src/app/api/workspaces/[id]/todos/route.ts +43 -0
- package/.next/standalone/src/components/claude/claude-usage-strip.tsx +175 -0
- package/.next/standalone/src/components/workspace/workspace-todos.tsx +265 -0
- package/.next/standalone/src/hooks/use-claude-usage.ts +66 -0
- package/.next/standalone/src/lib/claude/credentials.ts +36 -0
- package/.next/standalone/src/lib/claude/usage.ts +69 -0
- package/.next/standalone/src/lib/config.ts +3 -0
- package/.next/standalone/src/lib/db/queries.ts +103 -1
- package/.next/standalone/src/lib/db/schema.ts +18 -0
- package/.next/standalone/src/lib/spawn-env.ts +32 -0
- package/.next/standalone/src/lib/terminal/server.ts +3 -3
- package/.next/standalone/src/middleware.ts +23 -5
- package/.next/standalone/src/types/claude.ts +39 -0
- package/.next/standalone/tests/db/workspace-todos.test.ts +156 -0
- package/bin/cortex-hook.js +8 -3
- package/bin/cortex-learn-hook.js +7 -2
- package/bin/scrub-standalone.js +89 -0
- package/bin/terminal-server.js +44 -16
- package/package.json +2 -2
- package/.next/standalone/.claude/settings.local.json +0 -68
- package/.next/standalone/.claude/spaces-env.json +0 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e2a996e5._.js +0 -114
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__ead29015._.js +0 -13
- package/.next/standalone/.next/server/chunks/ssr/_3ba93bdd._.js +0 -3
- package/.next/standalone/.next/static/chunks/074df89a63b6a854.js +0 -1
- package/.next/standalone/.next/static/chunks/2b769d1597e4fc1c.css +0 -3
- package/.next/standalone/.spaces/cortex-context.md +0 -51
- package/.next/standalone/cortex-hook-debug.log +0 -57
- package/.next/standalone/docs/plans/2026-03-02-security-audit.md +0 -229
- /package/.next/standalone/.next/static/{u1pHON3drz1mBi7owkbBP → HNrWymrkgOP9Z4WIaYeOj}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{u1pHON3drz1mBi7owkbBP → HNrWymrkgOP9Z4WIaYeOj}/_clientMiddlewareManifest.json +0 -0
- /package/.next/standalone/.next/static/{u1pHON3drz1mBi7owkbBP → HNrWymrkgOP9Z4WIaYeOj}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
# Claude Usage Display Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Show a compact Anthropic plan-usage readout (mirroring `/usage` in Claude Code) in the Spaces terminal page header, visible when the active workspace has at least one Claude pane.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Spaces reads the OAuth access token from `~/.claude/.credentials.json` (already maintained by Claude Code), calls `https://api.anthropic.com/api/oauth/usage`, caches 30s server-side, and polls the route every 60s from a React hook. A three-bar strip component renders in the header; clicking expands a popover with all windows. Passive refresh: if the access token is expired or missing, the strip shows a "sign in via Claude Code" hint.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Next.js App Router, React 18 client components, Node `fetch` with `AbortController`, TypeScript, Tailwind, lucide-react.
|
|
10
|
+
|
|
11
|
+
**Spec:** `docs/superpowers/specs/2026-04-22-claude-usage-display-design.md`
|
|
12
|
+
|
|
13
|
+
**Repo workflow note:** Commit directly to `main`. No feature branch, no PR. Each task ends with a commit.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## File Structure
|
|
18
|
+
|
|
19
|
+
**Create:**
|
|
20
|
+
- `src/lib/claude/credentials.ts` — reads `.credentials.json`, extracts and validates the `claudeAiOauth` block.
|
|
21
|
+
- `src/lib/claude/usage.ts` — calls Anthropic's `/api/oauth/usage`, caches 30s.
|
|
22
|
+
- `src/app/api/claude/usage/route.ts` — `GET` handler wiring the above together.
|
|
23
|
+
- `src/hooks/use-claude-usage.ts` — 60s polling hook.
|
|
24
|
+
- `src/components/claude/claude-usage-strip.tsx` — compact 3-bar strip + expanded popover.
|
|
25
|
+
- `docs/features/claude-usage.md` — feature doc.
|
|
26
|
+
|
|
27
|
+
**Modify:**
|
|
28
|
+
- `src/types/claude.ts` — add `RateLimit`, `ExtraUsage`, `Utilization`, `UsageResponse` types.
|
|
29
|
+
- `src/app/(desktop)/terminal/page.tsx` — mount `<ClaudeUsageStrip>` conditionally in the header.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Task 1: Types + credentials reader
|
|
34
|
+
|
|
35
|
+
**Files:**
|
|
36
|
+
- Modify: `src/types/claude.ts`
|
|
37
|
+
- Create: `src/lib/claude/credentials.ts`
|
|
38
|
+
|
|
39
|
+
- [ ] **Step 1: Add types to `src/types/claude.ts`**
|
|
40
|
+
|
|
41
|
+
Append at the end of the file:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
// ─── Claude plan-usage (mirrors /usage in Claude Code) ───────
|
|
45
|
+
|
|
46
|
+
export interface RateLimit {
|
|
47
|
+
utilization: number | null; // 0-100
|
|
48
|
+
resets_at: string | null; // ISO 8601
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ExtraUsage {
|
|
52
|
+
is_enabled: boolean;
|
|
53
|
+
monthly_limit: number | null;
|
|
54
|
+
used_credits: number | null;
|
|
55
|
+
utilization: number | null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface Utilization {
|
|
59
|
+
five_hour?: RateLimit | null;
|
|
60
|
+
seven_day?: RateLimit | null;
|
|
61
|
+
seven_day_oauth_apps?: RateLimit | null;
|
|
62
|
+
seven_day_opus?: RateLimit | null;
|
|
63
|
+
seven_day_sonnet?: RateLimit | null;
|
|
64
|
+
extra_usage?: ExtraUsage | null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type UsageResponse =
|
|
68
|
+
| { status: 'ok'; data: Utilization }
|
|
69
|
+
| { status: 'unauthenticated' }
|
|
70
|
+
| { status: 'expired' }
|
|
71
|
+
| { status: 'upstream_error' };
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- [ ] **Step 2: Create `src/lib/claude/credentials.ts`**
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import path from 'path';
|
|
78
|
+
import os from 'os';
|
|
79
|
+
import { readFile } from 'fs/promises';
|
|
80
|
+
|
|
81
|
+
export interface ClaudeAiOAuthTokens {
|
|
82
|
+
accessToken: string;
|
|
83
|
+
refreshToken: string | null;
|
|
84
|
+
expiresAt: number | null; // epoch ms
|
|
85
|
+
scopes?: string[];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getCredentialsPath(): string {
|
|
89
|
+
const home = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
|
|
90
|
+
return path.join(home, '.credentials.json');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Reads ~/.claude/.credentials.json and returns the claudeAiOauth block.
|
|
95
|
+
* Returns null on any read/parse failure or if the block is absent.
|
|
96
|
+
*/
|
|
97
|
+
export async function readClaudeAiOauthTokens(): Promise<ClaudeAiOAuthTokens | null> {
|
|
98
|
+
try {
|
|
99
|
+
const raw = await readFile(getCredentialsPath(), 'utf8');
|
|
100
|
+
const parsed = JSON.parse(raw) as { claudeAiOauth?: ClaudeAiOAuthTokens };
|
|
101
|
+
const tokens = parsed.claudeAiOauth;
|
|
102
|
+
if (!tokens?.accessToken) return null;
|
|
103
|
+
return tokens;
|
|
104
|
+
} catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function isTokenExpired(tokens: ClaudeAiOAuthTokens, nowMs = Date.now()): boolean {
|
|
110
|
+
if (!tokens.expiresAt) return false; // no expiry set → treat as valid
|
|
111
|
+
return nowMs >= tokens.expiresAt;
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- [ ] **Step 3: Type-check**
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
cd /home/jlongo/development/spaces && npx tsc --noEmit
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Expected: no new errors.
|
|
122
|
+
|
|
123
|
+
- [ ] **Step 4: Commit**
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
cd /home/jlongo/development/spaces
|
|
127
|
+
git add src/types/claude.ts src/lib/claude/credentials.ts
|
|
128
|
+
git commit -m "feat(claude): read OAuth tokens from .credentials.json"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Task 2: Anthropic usage fetcher + cache
|
|
134
|
+
|
|
135
|
+
**Files:**
|
|
136
|
+
- Create: `src/lib/claude/usage.ts`
|
|
137
|
+
|
|
138
|
+
- [ ] **Step 1: Create the fetcher**
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
import type { Utilization } from '@/types/claude';
|
|
142
|
+
|
|
143
|
+
const ANTHROPIC_USAGE_URL = 'https://api.anthropic.com/api/oauth/usage';
|
|
144
|
+
const REQUEST_TIMEOUT_MS = 5_000;
|
|
145
|
+
const CACHE_TTL_MS = 30_000;
|
|
146
|
+
|
|
147
|
+
// UA matches the Claude Code format because Anthropic's endpoint classifies
|
|
148
|
+
// the caller by UA. Kept in sync manually if upstream ever changes.
|
|
149
|
+
const USER_AGENT = 'claude-code/spaces-integration';
|
|
150
|
+
|
|
151
|
+
interface CacheEntry {
|
|
152
|
+
fetchedAt: number;
|
|
153
|
+
data: Utilization;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const cache = new Map<string, CacheEntry>();
|
|
157
|
+
|
|
158
|
+
function cacheKey(accessToken: string): string {
|
|
159
|
+
return accessToken.slice(0, 16);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export type FetchOutcome =
|
|
163
|
+
| { ok: true; data: Utilization }
|
|
164
|
+
| { ok: false; reason: 'upstream_error' };
|
|
165
|
+
|
|
166
|
+
export async function fetchUtilization(accessToken: string): Promise<FetchOutcome> {
|
|
167
|
+
const key = cacheKey(accessToken);
|
|
168
|
+
const cached = cache.get(key);
|
|
169
|
+
if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
|
|
170
|
+
return { ok: true, data: cached.data };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const controller = new AbortController();
|
|
174
|
+
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const res = await fetch(ANTHROPIC_USAGE_URL, {
|
|
178
|
+
method: 'GET',
|
|
179
|
+
headers: {
|
|
180
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
181
|
+
'Content-Type': 'application/json',
|
|
182
|
+
'User-Agent': USER_AGENT,
|
|
183
|
+
},
|
|
184
|
+
signal: controller.signal,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
if (!res.ok) {
|
|
188
|
+
return { ok: false, reason: 'upstream_error' };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const data = (await res.json()) as Utilization;
|
|
192
|
+
cache.set(key, { fetchedAt: Date.now(), data });
|
|
193
|
+
return { ok: true, data };
|
|
194
|
+
} catch {
|
|
195
|
+
return { ok: false, reason: 'upstream_error' };
|
|
196
|
+
} finally {
|
|
197
|
+
clearTimeout(timer);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Testing/hot-reload helper. */
|
|
202
|
+
export function __clearUsageCache() {
|
|
203
|
+
cache.clear();
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
- [ ] **Step 2: Type-check**
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
cd /home/jlongo/development/spaces && npx tsc --noEmit
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Expected: no new errors.
|
|
214
|
+
|
|
215
|
+
- [ ] **Step 3: Commit**
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
cd /home/jlongo/development/spaces
|
|
219
|
+
git add src/lib/claude/usage.ts
|
|
220
|
+
git commit -m "feat(claude): fetch Anthropic plan-usage with 30s cache"
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Task 3: API route `GET /api/claude/usage`
|
|
226
|
+
|
|
227
|
+
**Files:**
|
|
228
|
+
- Create: `src/app/api/claude/usage/route.ts`
|
|
229
|
+
|
|
230
|
+
- [ ] **Step 1: Create the route**
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
234
|
+
import { getAuthUser, withUser } from '@/lib/auth';
|
|
235
|
+
import { ensureInitialized } from '@/lib/db/init';
|
|
236
|
+
import { readClaudeAiOauthTokens, isTokenExpired } from '@/lib/claude/credentials';
|
|
237
|
+
import { fetchUtilization } from '@/lib/claude/usage';
|
|
238
|
+
import type { UsageResponse } from '@/types/claude';
|
|
239
|
+
|
|
240
|
+
export async function GET(request: NextRequest) {
|
|
241
|
+
const user = getAuthUser(request);
|
|
242
|
+
return withUser(user, async () => {
|
|
243
|
+
await ensureInitialized();
|
|
244
|
+
|
|
245
|
+
const tokens = await readClaudeAiOauthTokens();
|
|
246
|
+
if (!tokens) {
|
|
247
|
+
const body: UsageResponse = { status: 'unauthenticated' };
|
|
248
|
+
return NextResponse.json(body);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (isTokenExpired(tokens)) {
|
|
252
|
+
const body: UsageResponse = { status: 'expired' };
|
|
253
|
+
return NextResponse.json(body);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const outcome = await fetchUtilization(tokens.accessToken);
|
|
257
|
+
if (!outcome.ok) {
|
|
258
|
+
const body: UsageResponse = { status: 'upstream_error' };
|
|
259
|
+
return NextResponse.json(body);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const body: UsageResponse = { status: 'ok', data: outcome.data };
|
|
263
|
+
return NextResponse.json(body);
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
- [ ] **Step 2: Smoke test against the running dev server**
|
|
269
|
+
|
|
270
|
+
Determine which dev server is listening (3458 for turbopack is typical; 3457 for production build):
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
curl -sS http://localhost:3458/api/workspaces -o /dev/null -w '%{http_code}\n' 2>/dev/null
|
|
274
|
+
curl -sS http://localhost:3457/api/workspaces -o /dev/null -w '%{http_code}\n' 2>/dev/null
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Use whichever returns `200`. Then:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
BASE=http://localhost:3458
|
|
281
|
+
curl -s "$BASE/api/claude/usage"
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Expected: JSON with one of the four statuses. If you're logged in, it should be `{"status":"ok","data":{...}}` with `five_hour`, `seven_day_opus`, etc. If logged out or no credentials file, it should be `{"status":"unauthenticated"}`.
|
|
285
|
+
|
|
286
|
+
Don't start a new dev server; use whatever is already running. If the only listener is a production build that won't hot-reload, report BLOCKED.
|
|
287
|
+
|
|
288
|
+
- [ ] **Step 3: Commit**
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
cd /home/jlongo/development/spaces
|
|
292
|
+
git add src/app/api/claude/usage/route.ts
|
|
293
|
+
git commit -m "feat(api): add GET /api/claude/usage"
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Task 4: `useClaudeUsage` hook
|
|
299
|
+
|
|
300
|
+
**Files:**
|
|
301
|
+
- Create: `src/hooks/use-claude-usage.ts`
|
|
302
|
+
|
|
303
|
+
- [ ] **Step 1: Create the hook**
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
'use client';
|
|
307
|
+
|
|
308
|
+
import { useEffect, useState } from 'react';
|
|
309
|
+
import { api } from '@/lib/api';
|
|
310
|
+
import type { UsageResponse } from '@/types/claude';
|
|
311
|
+
|
|
312
|
+
const POLL_INTERVAL_MS = 60_000;
|
|
313
|
+
|
|
314
|
+
export function useClaudeUsage(enabled: boolean): UsageResponse | null {
|
|
315
|
+
const [response, setResponse] = useState<UsageResponse | null>(null);
|
|
316
|
+
|
|
317
|
+
useEffect(() => {
|
|
318
|
+
if (!enabled) {
|
|
319
|
+
setResponse(null);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
let cancelled = false;
|
|
324
|
+
|
|
325
|
+
async function load() {
|
|
326
|
+
try {
|
|
327
|
+
const res = await fetch(api('/api/claude/usage'));
|
|
328
|
+
if (!res.ok) return;
|
|
329
|
+
const data: UsageResponse = await res.json();
|
|
330
|
+
if (!cancelled) setResponse(data);
|
|
331
|
+
} catch {
|
|
332
|
+
// Silent — the UI handles undefined state gracefully.
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
load();
|
|
337
|
+
const timer = setInterval(load, POLL_INTERVAL_MS);
|
|
338
|
+
|
|
339
|
+
return () => {
|
|
340
|
+
cancelled = true;
|
|
341
|
+
clearInterval(timer);
|
|
342
|
+
};
|
|
343
|
+
}, [enabled]);
|
|
344
|
+
|
|
345
|
+
return response;
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
- [ ] **Step 2: Type-check**
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
cd /home/jlongo/development/spaces && npx tsc --noEmit
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Expected: no new errors.
|
|
356
|
+
|
|
357
|
+
- [ ] **Step 3: Commit**
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
cd /home/jlongo/development/spaces
|
|
361
|
+
git add src/hooks/use-claude-usage.ts
|
|
362
|
+
git commit -m "feat(hooks): add useClaudeUsage polling hook"
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## Task 5: `<ClaudeUsageStrip />` component
|
|
368
|
+
|
|
369
|
+
**Files:**
|
|
370
|
+
- Create: `src/components/claude/claude-usage-strip.tsx`
|
|
371
|
+
|
|
372
|
+
- [ ] **Step 1: Create the component**
|
|
373
|
+
|
|
374
|
+
```tsx
|
|
375
|
+
'use client';
|
|
376
|
+
|
|
377
|
+
import { useEffect, useRef, useState } from 'react';
|
|
378
|
+
import { Zap, LogIn } from 'lucide-react';
|
|
379
|
+
import { useClaudeUsage } from '@/hooks/use-claude-usage';
|
|
380
|
+
import type { RateLimit } from '@/types/claude';
|
|
381
|
+
|
|
382
|
+
interface Props {
|
|
383
|
+
enabled: boolean;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function utilizationColor(pct: number | null): string {
|
|
387
|
+
if (pct == null) return '#52525b'; // zinc-600 — unknown
|
|
388
|
+
if (pct <= 70) return '#22c55e'; // green-500
|
|
389
|
+
if (pct <= 90) return '#f59e0b'; // amber-500
|
|
390
|
+
return '#ef4444'; // red-500
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function formatReset(resetsAt: string | null): string {
|
|
394
|
+
if (!resetsAt) return '';
|
|
395
|
+
const ms = new Date(resetsAt).getTime() - Date.now();
|
|
396
|
+
if (ms <= 0) return 'now';
|
|
397
|
+
const h = Math.floor(ms / 3_600_000);
|
|
398
|
+
const m = Math.floor((ms % 3_600_000) / 60_000);
|
|
399
|
+
if (h >= 24) {
|
|
400
|
+
const d = Math.floor(h / 24);
|
|
401
|
+
const rh = h - d * 24;
|
|
402
|
+
return `${d}d ${rh}h`;
|
|
403
|
+
}
|
|
404
|
+
if (h > 0) return `${h}h ${m}m`;
|
|
405
|
+
return `${m}m`;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function Bar({ label, limit }: { label: string; limit: RateLimit | null | undefined }) {
|
|
409
|
+
if (!limit || limit.utilization == null) return null;
|
|
410
|
+
const pct = Math.min(100, Math.max(0, limit.utilization));
|
|
411
|
+
const color = utilizationColor(pct);
|
|
412
|
+
const reset = formatReset(limit.resets_at);
|
|
413
|
+
return (
|
|
414
|
+
<div
|
|
415
|
+
className="flex items-center gap-1.5 text-[10px]"
|
|
416
|
+
title={reset ? `${label}: ${Math.floor(pct)}% used · resets in ${reset}` : `${label}: ${Math.floor(pct)}% used`}
|
|
417
|
+
>
|
|
418
|
+
<span className="text-zinc-400 font-medium">{label}</span>
|
|
419
|
+
<div className="w-12 h-1 bg-zinc-800 rounded-full overflow-hidden">
|
|
420
|
+
<div
|
|
421
|
+
className="h-full transition-all"
|
|
422
|
+
style={{ width: `${pct}%`, backgroundColor: color }}
|
|
423
|
+
/>
|
|
424
|
+
</div>
|
|
425
|
+
<span className="text-zinc-500 tabular-nums w-7 text-right">{Math.floor(pct)}%</span>
|
|
426
|
+
</div>
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export function ClaudeUsageStrip({ enabled }: Props) {
|
|
431
|
+
const response = useClaudeUsage(enabled);
|
|
432
|
+
const [expanded, setExpanded] = useState(false);
|
|
433
|
+
const popoverRef = useRef<HTMLDivElement>(null);
|
|
434
|
+
|
|
435
|
+
useEffect(() => {
|
|
436
|
+
if (!expanded) return;
|
|
437
|
+
function handleClick(e: MouseEvent) {
|
|
438
|
+
if (popoverRef.current && !popoverRef.current.contains(e.target as Node)) {
|
|
439
|
+
setExpanded(false);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
document.addEventListener('mousedown', handleClick);
|
|
443
|
+
return () => document.removeEventListener('mousedown', handleClick);
|
|
444
|
+
}, [expanded]);
|
|
445
|
+
|
|
446
|
+
if (!enabled || !response) return null;
|
|
447
|
+
|
|
448
|
+
if (response.status === 'unauthenticated' || response.status === 'expired') {
|
|
449
|
+
return (
|
|
450
|
+
<div
|
|
451
|
+
className="flex items-center gap-1.5 px-2 py-1 text-[10px] text-zinc-500 border border-zinc-800 rounded-md"
|
|
452
|
+
title={response.status === 'expired' ? 'OAuth token expired — run any Claude Code command to refresh' : 'No Claude.ai credentials found'}
|
|
453
|
+
>
|
|
454
|
+
<LogIn className="w-3 h-3" />
|
|
455
|
+
sign in via Claude Code
|
|
456
|
+
</div>
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (response.status === 'upstream_error') {
|
|
461
|
+
return null; // Silent — per spec.
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const { data } = response;
|
|
465
|
+
const extra = data.extra_usage;
|
|
466
|
+
const hasOverage = !!(extra?.is_enabled && extra?.used_credits && extra.used_credits > 0);
|
|
467
|
+
|
|
468
|
+
return (
|
|
469
|
+
<div className="relative">
|
|
470
|
+
<button
|
|
471
|
+
onClick={() => setExpanded(v => !v)}
|
|
472
|
+
className="flex items-center gap-2.5 px-2.5 py-1 border border-zinc-800 rounded-md hover:border-zinc-700 transition-colors"
|
|
473
|
+
title="Click for full usage detail"
|
|
474
|
+
>
|
|
475
|
+
<Zap className="w-3 h-3 text-amber-400" />
|
|
476
|
+
<Bar label="5h" limit={data.five_hour} />
|
|
477
|
+
<Bar label="Opus" limit={data.seven_day_opus} />
|
|
478
|
+
<Bar label="Sonnet" limit={data.seven_day_sonnet} />
|
|
479
|
+
{hasOverage && extra?.used_credits != null && (
|
|
480
|
+
<span className="text-[10px] text-amber-400 border-l border-zinc-800 pl-2">
|
|
481
|
+
+${extra.used_credits.toFixed(2)} overage
|
|
482
|
+
</span>
|
|
483
|
+
)}
|
|
484
|
+
</button>
|
|
485
|
+
|
|
486
|
+
{expanded && (
|
|
487
|
+
<div
|
|
488
|
+
ref={popoverRef}
|
|
489
|
+
className="absolute top-full right-0 mt-1.5 z-50 w-80 p-3 bg-zinc-900 border border-zinc-700 rounded-md shadow-xl"
|
|
490
|
+
>
|
|
491
|
+
<div className="text-[10px] uppercase tracking-wider text-zinc-500 font-medium mb-2">
|
|
492
|
+
Plan Usage
|
|
493
|
+
</div>
|
|
494
|
+
<div className="space-y-2">
|
|
495
|
+
<ExpandedRow label="5-hour window" limit={data.five_hour} />
|
|
496
|
+
<ExpandedRow label="7-day (overall)" limit={data.seven_day} />
|
|
497
|
+
<ExpandedRow label="7-day · Opus" limit={data.seven_day_opus} />
|
|
498
|
+
<ExpandedRow label="7-day · Sonnet" limit={data.seven_day_sonnet} />
|
|
499
|
+
<ExpandedRow label="7-day · OAuth apps" limit={data.seven_day_oauth_apps} />
|
|
500
|
+
</div>
|
|
501
|
+
{extra?.is_enabled && (
|
|
502
|
+
<div className="mt-3 pt-3 border-t border-zinc-800">
|
|
503
|
+
<div className="text-[10px] uppercase tracking-wider text-zinc-500 font-medium mb-1.5">
|
|
504
|
+
Overage credits
|
|
505
|
+
</div>
|
|
506
|
+
<div className="text-xs text-zinc-300">
|
|
507
|
+
{extra.used_credits != null ? `$${extra.used_credits.toFixed(2)}` : '—'}
|
|
508
|
+
{extra.monthly_limit != null && (
|
|
509
|
+
<span className="text-zinc-500"> / ${extra.monthly_limit.toFixed(2)} monthly</span>
|
|
510
|
+
)}
|
|
511
|
+
</div>
|
|
512
|
+
</div>
|
|
513
|
+
)}
|
|
514
|
+
</div>
|
|
515
|
+
)}
|
|
516
|
+
</div>
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function ExpandedRow({ label, limit }: { label: string; limit: RateLimit | null | undefined }) {
|
|
521
|
+
if (!limit || limit.utilization == null) {
|
|
522
|
+
return (
|
|
523
|
+
<div className="flex items-center justify-between text-xs">
|
|
524
|
+
<span className="text-zinc-400">{label}</span>
|
|
525
|
+
<span className="text-zinc-600">—</span>
|
|
526
|
+
</div>
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
const pct = Math.min(100, Math.max(0, limit.utilization));
|
|
530
|
+
const color = utilizationColor(pct);
|
|
531
|
+
const reset = formatReset(limit.resets_at);
|
|
532
|
+
return (
|
|
533
|
+
<div>
|
|
534
|
+
<div className="flex items-center justify-between text-xs mb-1">
|
|
535
|
+
<span className="text-zinc-300">{label}</span>
|
|
536
|
+
<span className="text-zinc-500 tabular-nums">
|
|
537
|
+
{Math.floor(pct)}%{reset && <span className="ml-1.5 text-zinc-600">· {reset}</span>}
|
|
538
|
+
</span>
|
|
539
|
+
</div>
|
|
540
|
+
<div className="h-1.5 bg-zinc-800 rounded-full overflow-hidden">
|
|
541
|
+
<div
|
|
542
|
+
className="h-full transition-all"
|
|
543
|
+
style={{ width: `${pct}%`, backgroundColor: color }}
|
|
544
|
+
/>
|
|
545
|
+
</div>
|
|
546
|
+
</div>
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
- [ ] **Step 2: Type-check**
|
|
552
|
+
|
|
553
|
+
```bash
|
|
554
|
+
cd /home/jlongo/development/spaces && npx tsc --noEmit
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
- [ ] **Step 3: Commit**
|
|
558
|
+
|
|
559
|
+
```bash
|
|
560
|
+
cd /home/jlongo/development/spaces
|
|
561
|
+
git add src/components/claude/claude-usage-strip.tsx
|
|
562
|
+
git commit -m "feat(ui): add ClaudeUsageStrip component"
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
---
|
|
566
|
+
|
|
567
|
+
## Task 6: Mount `<ClaudeUsageStrip />` in the terminal header
|
|
568
|
+
|
|
569
|
+
**Files:**
|
|
570
|
+
- Modify: `src/app/(desktop)/terminal/page.tsx`
|
|
571
|
+
|
|
572
|
+
- [ ] **Step 1: Add the import**
|
|
573
|
+
|
|
574
|
+
At the top of `src/app/(desktop)/terminal/page.tsx`, near the other component imports (around the `WorkspaceTodos` / `ProjectWizard` imports), add:
|
|
575
|
+
|
|
576
|
+
```tsx
|
|
577
|
+
import { ClaudeUsageStrip } from '@/components/claude/claude-usage-strip';
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
- [ ] **Step 2: Mount conditionally in the header**
|
|
581
|
+
|
|
582
|
+
Find the header's right-hand button cluster — the `<div className="flex items-center gap-2">` that contains the `Files`, `Collab`, `Close`, and `Add Pane` buttons (around lines 863-922, inside the entered-workspace `return`). Insert the strip **as the first child** of that cluster, before the `Files` button:
|
|
583
|
+
|
|
584
|
+
```tsx
|
|
585
|
+
<div className="flex items-center gap-2">
|
|
586
|
+
<ClaudeUsageStrip enabled={panes.some(p => p.agentType === 'claude' && !poppedOut.has(p.id))} />
|
|
587
|
+
<button
|
|
588
|
+
onClick={() => setShowFiles(!showFiles)}
|
|
589
|
+
...
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
The `enabled` prop is `true` when the current workspace has at least one visible (not popped-out) Claude pane. `panes` is already in scope in `TerminalPageInner`.
|
|
593
|
+
|
|
594
|
+
- [ ] **Step 3: Type-check**
|
|
595
|
+
|
|
596
|
+
```bash
|
|
597
|
+
cd /home/jlongo/development/spaces && npx tsc --noEmit
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
- [ ] **Step 4: Commit**
|
|
601
|
+
|
|
602
|
+
```bash
|
|
603
|
+
cd /home/jlongo/development/spaces
|
|
604
|
+
git add "src/app/(desktop)/terminal/page.tsx"
|
|
605
|
+
git commit -m "feat(terminal): mount ClaudeUsageStrip in header when Claude panes exist"
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
---
|
|
609
|
+
|
|
610
|
+
## Task 7: Feature doc
|
|
611
|
+
|
|
612
|
+
**Files:**
|
|
613
|
+
- Create: `docs/features/claude-usage.md`
|
|
614
|
+
|
|
615
|
+
- [ ] **Step 1: Write the doc**
|
|
616
|
+
|
|
617
|
+
```markdown
|
|
618
|
+
# Claude Plan-Usage Display
|
|
619
|
+
|
|
620
|
+
**Status:** stable
|
|
621
|
+
**Owner:** jlongo
|
|
622
|
+
**Last updated:** 2026-04-22
|
|
623
|
+
|
|
624
|
+
## Overview
|
|
625
|
+
|
|
626
|
+
A compact readout of the user's Anthropic plan-usage rate limits, surfaced in the terminal page header whenever the active workspace has at least one Claude pane. Shows 5-hour, 7-day Opus, and 7-day Sonnet windows at a glance; click expands to all windows plus overage credits. Data matches what `/usage` shows inside Claude Code.
|
|
627
|
+
|
|
628
|
+
## User-facing behavior
|
|
629
|
+
|
|
630
|
+
- Open any workspace on `/terminal` that has a Claude pane.
|
|
631
|
+
- A horizontal strip appears in the header (right side, before Files / Collab / Close / Add Pane).
|
|
632
|
+
- Three thin progress bars: **5h**, **Opus**, **Sonnet** — each with a percent label and a color that escalates green → amber → red.
|
|
633
|
+
- Hover a bar → tooltip `{label}: X% used · resets in 2h 14m`.
|
|
634
|
+
- Click the strip → popover with all five rate-limit windows + an overage-credits block if the account has overage enabled.
|
|
635
|
+
- If the OAuth token is missing or expired, the strip shows `sign in via Claude Code` with a small login icon. No auto-refresh.
|
|
636
|
+
- If the Anthropic endpoint fails or times out, the strip hides silently.
|
|
637
|
+
|
|
638
|
+
## Architecture
|
|
639
|
+
|
|
640
|
+
UI (`src/components/claude/claude-usage-strip.tsx`) → polling hook (`src/hooks/use-claude-usage.ts`) → API route (`src/app/api/claude/usage/route.ts`) → credentials reader (`src/lib/claude/credentials.ts`) + Anthropic fetcher with 30s cache (`src/lib/claude/usage.ts`) → `https://api.anthropic.com/api/oauth/usage`.
|
|
641
|
+
|
|
642
|
+
Polling: hook calls the route on mount and every 60s while the strip is enabled. Server caches the Anthropic response 30s per access-token prefix so rapid re-polls don't hammer Anthropic.
|
|
643
|
+
|
|
644
|
+
## Key files
|
|
645
|
+
|
|
646
|
+
| Path | Role |
|
|
647
|
+
|---|---|
|
|
648
|
+
| `src/lib/claude/credentials.ts` | Reads `~/.claude/.credentials.json`, extracts `claudeAiOauth`, exposes `isTokenExpired()`. |
|
|
649
|
+
| `src/lib/claude/usage.ts` | Calls Anthropic's `/api/oauth/usage`, 5s timeout, 30s in-memory cache. |
|
|
650
|
+
| `src/app/api/claude/usage/route.ts` | `GET` handler; returns one of `ok` / `unauthenticated` / `expired` / `upstream_error`. |
|
|
651
|
+
| `src/hooks/use-claude-usage.ts` | 60s polling hook, toggled by `enabled` arg. |
|
|
652
|
+
| `src/components/claude/claude-usage-strip.tsx` | Compact 3-bar strip + expanded popover. |
|
|
653
|
+
| `src/app/(desktop)/terminal/page.tsx` | Mounts the strip conditionally in the header. |
|
|
654
|
+
| `src/types/claude.ts` | `RateLimit`, `ExtraUsage`, `Utilization`, `UsageResponse`. |
|
|
655
|
+
|
|
656
|
+
## Data model
|
|
657
|
+
|
|
658
|
+
No DB tables. All state is transient:
|
|
659
|
+
- Server-side: in-memory `Map<tokenPrefix, { fetchedAt, data }>` in `src/lib/claude/usage.ts` with 30s TTL. Process-local; resets on server restart.
|
|
660
|
+
- Client-side: React state in the hook.
|
|
661
|
+
|
|
662
|
+
## APIs / contracts
|
|
663
|
+
|
|
664
|
+
### Internal endpoints
|
|
665
|
+
|
|
666
|
+
- `GET /api/claude/usage` — returns `UsageResponse`:
|
|
667
|
+
- `200 { status: "ok", data: Utilization }` — happy path.
|
|
668
|
+
- `200 { status: "unauthenticated" }` — no credentials file or no `claudeAiOauth`.
|
|
669
|
+
- `200 { status: "expired" }` — access token past `expiresAt`.
|
|
670
|
+
- `200 { status: "upstream_error" }` — Anthropic non-2xx or timeout.
|
|
671
|
+
|
|
672
|
+
Auth: same `withUser`/`getAuthUser` wrapping as every other `/api/*` route.
|
|
673
|
+
|
|
674
|
+
### External calls
|
|
675
|
+
|
|
676
|
+
- **Anthropic `GET https://api.anthropic.com/api/oauth/usage`** — uses the user's own OAuth access token read from `~/.claude/.credentials.json`. No secret of our own. User-Agent `claude-code/spaces-integration`.
|
|
677
|
+
|
|
678
|
+
## Configuration & environment
|
|
679
|
+
|
|
680
|
+
- `CLAUDE_CONFIG_DIR` — respected (defaults to `~/.claude`). Same semantics as Claude Code.
|
|
681
|
+
- No env vars, feature flags, or tier gates specific to this feature.
|
|
682
|
+
|
|
683
|
+
## How to run / test locally
|
|
684
|
+
|
|
685
|
+
```bash
|
|
686
|
+
npm install
|
|
687
|
+
npm run build
|
|
688
|
+
npm start
|
|
689
|
+
# visit http://localhost:3457/terminal, enter a workspace, add a Claude pane
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
Manual QA checklist:
|
|
693
|
+
|
|
694
|
+
1. With a valid OAuth session (logged in via any recent Claude Code run): the strip appears in the header; percents match `/usage` inside Claude Code.
|
|
695
|
+
2. Hover each bar → tooltip shows the reset timing.
|
|
696
|
+
3. Click → popover with all five windows and overage block (if applicable).
|
|
697
|
+
4. Delete the Claude pane from the workspace → strip disappears.
|
|
698
|
+
5. Set `claudeAiOauth.expiresAt` to a past timestamp in `.credentials.json`, reload → strip shows "sign in via Claude Code".
|
|
699
|
+
6. Move `.credentials.json` aside temporarily, reload → same sign-in hint.
|
|
700
|
+
7. Disconnect network → within ~90s the strip silently hides (`upstream_error` → null render).
|
|
701
|
+
|
|
702
|
+
No automated tests for v1.
|
|
703
|
+
|
|
704
|
+
## Known quirks & decisions
|
|
705
|
+
|
|
706
|
+
- **Passive refresh.** We do not refresh the OAuth access token. Claude Code owns the refresh lifecycle and writes `.credentials.json`; racing with it would corrupt the shared file.
|
|
707
|
+
- **Account-wide data, not per-pane.** The Anthropic endpoint returns account-level rate limits. Rendering once in the header (not per-pane) is intentional.
|
|
708
|
+
- **User-Agent string.** Anthropic classifies callers by UA; we send `claude-code/spaces-integration`. If upstream changes expectations, bump the constant in `src/lib/claude/usage.ts`.
|
|
709
|
+
- **30s cache + 60s poll.** Outer-bound staleness is ~90s; fine for windows that move in hours.
|
|
710
|
+
- **Token-prefix cache key.** First 16 chars of `accessToken`. Collision probability is effectively nil; avoids holding full tokens in the cache structure beyond the single request.
|
|
711
|
+
- **Silent upstream failure.** Errors hide the strip rather than showing a red toast — rate-limit info is nice-to-have, not mission-critical.
|
|
712
|
+
- **Ships direct to `main`.** Per repo convention.
|
|
713
|
+
|
|
714
|
+
## Open questions / TODOs
|
|
715
|
+
|
|
716
|
+
- [ ] Active OAuth refresh — mirror Claude Code's `/oauth/token` dance to keep the widget live past expiry. Deferred for v1.
|
|
717
|
+
- [ ] Session-cost complement using local JSONL token counts (different data source).
|
|
718
|
+
- [ ] Consider surfacing 7-day-overall alongside the current three for users who care.
|
|
719
|
+
|
|
720
|
+
## Changelog
|
|
721
|
+
|
|
722
|
+
- 2026-04-22 — Initial implementation. v1 ships direct to `main`.
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
- [ ] **Step 2: Commit**
|
|
726
|
+
|
|
727
|
+
```bash
|
|
728
|
+
cd /home/jlongo/development/spaces
|
|
729
|
+
git add docs/features/claude-usage.md
|
|
730
|
+
git commit -m "docs(features): add claude-usage feature doc"
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
---
|
|
734
|
+
|
|
735
|
+
## Self-Review Notes
|
|
736
|
+
|
|
737
|
+
**Spec coverage:** every section of `docs/superpowers/specs/2026-04-22-claude-usage-display-design.md` is implemented:
|
|
738
|
+
- Types → Task 1
|
|
739
|
+
- Credentials reader → Task 1
|
|
740
|
+
- Anthropic fetcher + cache → Task 2
|
|
741
|
+
- API route with four structured statuses → Task 3
|
|
742
|
+
- 60s polling hook → Task 4
|
|
743
|
+
- Component (compact + expanded) → Task 5
|
|
744
|
+
- Conditional header mount → Task 6
|
|
745
|
+
- Feature doc → Task 7
|
|
746
|
+
|
|
747
|
+
**Placeholder scan:** no TBD/TODO outside the feature doc's explicit "Open questions / TODOs" section.
|
|
748
|
+
|
|
749
|
+
**Type consistency:** `Utilization`, `RateLimit`, `ExtraUsage`, `UsageResponse` are defined once in Task 1 and referenced by the same names in Tasks 2 / 3 / 4 / 5 / 7.
|