failproofai 0.0.9 → 0.0.10-beta.0
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/.cursor/hooks.json +47 -0
- package/.next/standalone/.gemini/settings.json +147 -0
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +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 +2 -2
- package/.next/standalone/.next/server/app/_not-found.rsc +17 -17
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +17 -17
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +11 -11
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +2 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +16 -16
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +16 -16
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +11 -11
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
- package/.next/standalone/.next/server/app/policies/page.js +1 -1
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +2 -2
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +5 -5
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +2 -2
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0.~nmr9._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0yspgjy._.js → [root-of-the-server]__010i6f5._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08px0ym._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0b57.gk._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0dtn9lr._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0kjo7d_._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0vlhtkc._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0wu7fr7._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0yfq1yr._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0z4c5dj._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zso~62._.js +3 -0
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_0z7w.hh._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-2wr.c._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0.~m-w2._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__09icjsf._.js → [root-of-the-server]__0709m8.._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0bz245.._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0dl0kgt._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0gmhxyo._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0mup1hi._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ohb3gc._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qbpe_v._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s~gy6y._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0t5l7a5._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ymlddl._.js +152 -6
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0_b7pgn._.js → [root-of-the-server]__0ymn496._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01g_w_e._.js → [root-of-the-server]__10h.ggz._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_03d7qyt._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_07a1g.3._.js → _0zx~s__._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_10lm7or._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_0cdqd9w._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_0xerkr6._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_0q-m0y-._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/lib_codex-projects_ts_0eosib~._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_copilot-projects_ts_0r8xkn8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_cursor-projects_ts_0qt1scg._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_gemini-projects_ts_0sl~yqr._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_opencode-projects_ts_0op9gyp._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_pi-projects_ts_103tsh1._.js +3 -0
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +2 -2
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +9 -9
- package/.next/standalone/.next/static/chunks/{0n-_j_6fo6jex.js → 00ay03h8bq4b~.js} +2 -2
- package/.next/standalone/.next/static/chunks/{11kt_9zaooda3.js → 0agmlhk5ml7x5.js} +1 -1
- package/.next/standalone/.next/static/chunks/0bi2r.m~yokoo.js +1 -0
- package/.next/standalone/.next/static/chunks/{095l4hc7-h.~~.js → 0en4v5k2nnxks.js} +1 -1
- package/.next/standalone/.next/static/chunks/0q5bmqop--9yk.js +1 -0
- package/.next/standalone/.next/static/chunks/{0756i.7omnnl6.js → 0s6nux54y~l~r.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0t~iusm_fxoao.js → 0tpse0wu2wwo0.js} +1 -1
- package/.next/standalone/.next/static/chunks/12po2vpc-4_c1.css +1 -0
- package/.next/standalone/.next/static/chunks/{0u-ys71jc4y68.js → 1400rtd5ywbt..js} +2 -2
- package/.next/standalone/.next/static/chunks/{09ose_165ra4d.js → 14lmf8boay-zu.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0pr7k36o_.du1.js → 17htukxga7bil.js} +1 -1
- package/.next/standalone/.opencode/opencode.json +4 -0
- package/.next/standalone/.opencode/plugins/failproofai.mjs +131 -0
- package/.next/standalone/.pi/settings.json +5 -0
- package/.next/standalone/app/components/cli-badge.tsx +7 -11
- package/.next/standalone/app/components/project-list.tsx +32 -4
- package/.next/standalone/app/policies/hooks-client.tsx +31 -15
- package/.next/standalone/app/project/[name]/page.tsx +52 -16
- package/.next/standalone/app/project/[name]/session/[sessionId]/page.tsx +92 -15
- package/.next/standalone/assets/logos/copilot-dark.svg +1 -0
- package/.next/standalone/assets/logos/copilot-light.svg +1 -0
- package/.next/standalone/assets/logos/cursor-dark.svg +1 -0
- package/.next/standalone/assets/logos/cursor-light.svg +1 -0
- package/.next/standalone/assets/logos/gemini-dark.svg +13 -0
- package/.next/standalone/assets/logos/gemini-light.svg +13 -0
- package/.next/standalone/assets/logos/opencode-dark.svg +1 -0
- package/.next/standalone/assets/logos/opencode-light.svg +1 -0
- package/.next/standalone/assets/logos/pi-dark.svg +7 -0
- package/.next/standalone/assets/logos/pi-light.svg +7 -0
- package/.next/standalone/lib/cli-registry.ts +107 -0
- package/.next/standalone/lib/codex-projects.ts +3 -3
- package/.next/standalone/lib/copilot-projects.ts +224 -0
- package/.next/standalone/lib/copilot-sessions.ts +395 -0
- package/.next/standalone/lib/cursor-projects.ts +312 -0
- package/.next/standalone/lib/cursor-sessions.ts +467 -0
- package/.next/standalone/lib/gemini-projects.ts +203 -0
- package/.next/standalone/lib/gemini-sessions.ts +365 -0
- package/.next/standalone/lib/opencode-projects.ts +232 -0
- package/.next/standalone/lib/opencode-sessions.ts +237 -0
- package/.next/standalone/lib/pi-projects.ts +230 -0
- package/.next/standalone/lib/pi-sessions.ts +325 -0
- package/.next/standalone/lib/projects.ts +67 -31
- package/.next/standalone/next.config.ts +5 -4
- package/.next/standalone/package.json +2 -1
- package/.next/standalone/pi-extension/index.ts +373 -0
- package/.next/standalone/pi-extension/package.json +12 -0
- package/.next/standalone/server.js +1 -1
- package/README.md +37 -3
- package/bin/failproofai.mjs +61 -21
- package/dist/cli.mjs +2248 -246
- package/lib/cli-registry.ts +107 -0
- package/lib/codex-projects.ts +3 -3
- package/lib/copilot-projects.ts +224 -0
- package/lib/copilot-sessions.ts +395 -0
- package/lib/cursor-projects.ts +312 -0
- package/lib/cursor-sessions.ts +467 -0
- package/lib/gemini-projects.ts +203 -0
- package/lib/gemini-sessions.ts +365 -0
- package/lib/opencode-projects.ts +232 -0
- package/lib/opencode-sessions.ts +237 -0
- package/lib/pi-projects.ts +230 -0
- package/lib/pi-sessions.ts +325 -0
- package/lib/projects.ts +67 -31
- package/package.json +2 -1
- package/pi-extension/index.ts +373 -0
- package/pi-extension/package.json +12 -0
- package/scripts/translate-docs/mdx-translator.ts +56 -2
- package/scripts/translate-docs/translator.ts +1 -1
- package/src/hooks/builtin-policies.ts +84 -14
- package/src/hooks/handler.ts +67 -5
- package/src/hooks/install-prompt.ts +33 -10
- package/src/hooks/integrations.ts +1007 -6
- package/src/hooks/policy-evaluator.ts +299 -3
- package/src/hooks/resolve-permission-mode.ts +23 -0
- package/src/hooks/types.ts +307 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g72weg._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0su~k6f._.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_codex-projects_ts_07qqk1g._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01743wx._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__092s1ta._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0g.lg8b._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0gs6wz4._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h..k-e._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0it81ys._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u4a9jq._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11pa2ra._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12.h2mg._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__12t-wym._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/_04w00cm._.js +0 -3
- package/.next/standalone/.next/static/chunks/0.rk1iwdt1d7c.css +0 -1
- package/.next/standalone/.next/static/chunks/06x4-d1~o-opr.js +0 -1
- package/.next/standalone/.next/static/chunks/0n~s0gafwnp2y.js +0 -1
- /package/.next/standalone/.next/static/{A_Ax17P33facL0OmIwFXj → 68TLSFdjAQYIulNHfP0QY}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{A_Ax17P33facL0OmIwFXj → 68TLSFdjAQYIulNHfP0QY}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{A_Ax17P33facL0OmIwFXj → 68TLSFdjAQYIulNHfP0QY}/_ssgManifest.js +0 -0
|
@@ -27,6 +27,7 @@ import { updatePolicyParamsAction } from "@/app/actions/update-policy-params";
|
|
|
27
27
|
import { useAutoRefresh } from "@/contexts/AutoRefreshContext";
|
|
28
28
|
import { useUrlParams } from "@/lib/use-url-params";
|
|
29
29
|
import { pageToParam, paramToPage } from "@/lib/url-filter-serializers";
|
|
30
|
+
import { getCliLabel, getCliBadgeClasses, KNOWN_CLI_IDS, isKnownCli, type CliId } from "@/lib/cli-registry";
|
|
30
31
|
import { formatRelativeTime } from "@/lib/format-duration";
|
|
31
32
|
import { Button } from "@/components/ui/button";
|
|
32
33
|
|
|
@@ -86,10 +87,24 @@ function SessionCell({
|
|
|
86
87
|
const short = shortenSession(sessionId);
|
|
87
88
|
|
|
88
89
|
const isCodex = integration === "codex" || (transcriptPath?.includes("/.codex/") ?? false);
|
|
89
|
-
|
|
90
|
+
const isCopilot =
|
|
91
|
+
integration === "copilot" ||
|
|
92
|
+
(transcriptPath?.includes("/.copilot/session-state/") ?? false);
|
|
93
|
+
const isCursor =
|
|
94
|
+
integration === "cursor" || (transcriptPath?.includes("/.cursor/") ?? false);
|
|
95
|
+
const isOpenCode =
|
|
96
|
+
integration === "opencode" ||
|
|
97
|
+
(transcriptPath?.includes("/.local/share/opencode/") ?? false) ||
|
|
98
|
+
(transcriptPath?.includes("/.opencode/") ?? false);
|
|
99
|
+
const isPi =
|
|
100
|
+
integration === "pi" || (transcriptPath?.includes("/.pi/") ?? false);
|
|
101
|
+
const isGemini =
|
|
102
|
+
integration === "gemini" || (transcriptPath?.includes("/.gemini/") ?? false);
|
|
103
|
+
if (isCodex || isCopilot || isCursor || isOpenCode || isPi || isGemini) {
|
|
90
104
|
// The session route auto-detects CLI by file location, so [name] only
|
|
91
105
|
// affects the breadcrumb. Encode the cwd Claude-style when we have it.
|
|
92
|
-
const
|
|
106
|
+
const fallbackSeg = isCodex ? "codex" : isCopilot ? "copilot" : isCursor ? "cursor" : isOpenCode ? "opencode" : isPi ? "pi" : "gemini";
|
|
107
|
+
const projectSeg = cwd ? encodeCwdForUrl(cwd) : fallbackSeg;
|
|
93
108
|
return (
|
|
94
109
|
<Link
|
|
95
110
|
href={`/project/${encodeURIComponent(projectSeg)}/session/${encodeURIComponent(sessionId)}`}
|
|
@@ -153,16 +168,11 @@ function EventTypeBadge({ eventType }: { eventType: string }) {
|
|
|
153
168
|
|
|
154
169
|
function IntegrationBadge({ integration }: { integration?: string }) {
|
|
155
170
|
if (!integration) return null;
|
|
156
|
-
const label =
|
|
157
|
-
|
|
158
|
-
const isCodex = integration === "codex";
|
|
171
|
+
const label = getCliLabel(integration);
|
|
172
|
+
const classes = getCliBadgeClasses(integration);
|
|
159
173
|
return (
|
|
160
174
|
<span
|
|
161
|
-
className={`inline-flex items-center rounded px-1.5 py-0.5 text-[0.6rem] font-medium border ${
|
|
162
|
-
isCodex
|
|
163
|
-
? "bg-purple-500/10 text-purple-400 border-purple-500/20"
|
|
164
|
-
: "bg-orange-500/10 text-orange-400 border-orange-500/20"
|
|
165
|
-
}`}
|
|
175
|
+
className={`inline-flex items-center rounded px-1.5 py-0.5 text-[0.6rem] font-medium border ${classes}`}
|
|
166
176
|
title={`Agent CLI: ${label}`}
|
|
167
177
|
>
|
|
168
178
|
{label}
|
|
@@ -366,9 +376,9 @@ function ActivityTab({
|
|
|
366
376
|
const [filterEventType, setFilterEventType] = useState(() => url.get("event") ?? "");
|
|
367
377
|
const [filterPolicy, setFilterPolicy] = useState(() => url.get("policy") ?? "");
|
|
368
378
|
const [filterSessionId, setFilterSessionId] = useState(() => url.get("session") ?? "");
|
|
369
|
-
const [filterCli, setFilterCli] = useState<"" |
|
|
379
|
+
const [filterCli, setFilterCli] = useState<"" | CliId>(() => {
|
|
370
380
|
const v = url.get("cli");
|
|
371
|
-
return v
|
|
381
|
+
return isKnownCli(v) ? v : "";
|
|
372
382
|
});
|
|
373
383
|
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
374
384
|
const filtersRef = useRef({ filterDecision, filterEventType, filterPolicy, filterSessionId, filterCli });
|
|
@@ -470,13 +480,19 @@ function ActivityTab({
|
|
|
470
480
|
</select>
|
|
471
481
|
<select
|
|
472
482
|
value={filterCli}
|
|
473
|
-
onChange={(e) =>
|
|
483
|
+
onChange={(e) => {
|
|
484
|
+
const v = e.target.value;
|
|
485
|
+
setFilterCli(v === "" || isKnownCli(v) ? v : "");
|
|
486
|
+
}}
|
|
474
487
|
className="h-7 rounded-md border border-border bg-background px-2 text-xs text-foreground focus:outline-none focus:ring-2 focus:ring-primary/40 focus:border-primary/40 transition-shadow"
|
|
475
488
|
aria-label="Filter by CLI"
|
|
476
489
|
>
|
|
477
490
|
<option value="">All CLIs</option>
|
|
478
|
-
|
|
479
|
-
|
|
491
|
+
{KNOWN_CLI_IDS.map((id) => (
|
|
492
|
+
<option key={id} value={id}>
|
|
493
|
+
{getCliLabel(id)}
|
|
494
|
+
</option>
|
|
495
|
+
))}
|
|
480
496
|
</select>
|
|
481
497
|
<div className="relative">
|
|
482
498
|
<input
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
import { Suspense } from "react";
|
|
3
3
|
import { resolveProjectPath, getCachedSessionFiles, type SessionFile } from "@/lib/projects";
|
|
4
4
|
import { getCachedCodexSessionsByEncodedName } from "@/lib/codex-projects";
|
|
5
|
+
import { getCachedCopilotSessionsByEncodedName } from "@/lib/copilot-projects";
|
|
6
|
+
import { getCachedCursorSessionsByEncodedName } from "@/lib/cursor-projects";
|
|
7
|
+
import { getCachedOpenCodeSessionsByEncodedName } from "@/lib/opencode-projects";
|
|
8
|
+
import { getCachedPiSessionsByEncodedName } from "@/lib/pi-projects";
|
|
9
|
+
import { getCachedGeminiSessionsByEncodedName } from "@/lib/gemini-projects";
|
|
5
10
|
import { logWarn } from "@/lib/logger";
|
|
6
11
|
import { decodeFolderName } from "@/lib/paths";
|
|
7
12
|
import { notFound } from "next/navigation";
|
|
@@ -23,7 +28,8 @@ interface ProjectPageProps {
|
|
|
23
28
|
export default async function ProjectPage({ params }: ProjectPageProps) {
|
|
24
29
|
const { name } = await params;
|
|
25
30
|
// Resolve under ~/.claude/projects/. Validation may throw RangeError; on bad input
|
|
26
|
-
// we still want to try
|
|
31
|
+
// we still want to try the external CLIs, since a non-Claude-only cwd never
|
|
32
|
+
// escapes this check.
|
|
27
33
|
let claudeProjectPath: string | null = null;
|
|
28
34
|
try {
|
|
29
35
|
claudeProjectPath = resolveProjectPath(name);
|
|
@@ -39,18 +45,39 @@ export default async function ProjectPage({ params }: ProjectPageProps) {
|
|
|
39
45
|
claudeSessions = await getCachedSessionFiles(claudeProjectPath);
|
|
40
46
|
}
|
|
41
47
|
// Note: decodeFolderName is lossy when cwds contain `-` (every `-` becomes `/`),
|
|
42
|
-
// so
|
|
43
|
-
const codex = await
|
|
48
|
+
// so each external CLI looks up sessions by re-encoding cwd and matching the slug.
|
|
49
|
+
const [codex, copilot, cursor, opencode, pi, gemini] = await Promise.all([
|
|
50
|
+
getCachedCodexSessionsByEncodedName(name),
|
|
51
|
+
getCachedCopilotSessionsByEncodedName(name),
|
|
52
|
+
getCachedCursorSessionsByEncodedName(name),
|
|
53
|
+
getCachedOpenCodeSessionsByEncodedName(name),
|
|
54
|
+
getCachedPiSessionsByEncodedName(name),
|
|
55
|
+
getCachedGeminiSessionsByEncodedName(name),
|
|
56
|
+
]);
|
|
44
57
|
const codexSessions = codex.sessions;
|
|
58
|
+
const copilotSessions = copilot.sessions;
|
|
59
|
+
const cursorSessions = cursor.sessions;
|
|
60
|
+
const opencodeSessions = opencode.sessions;
|
|
61
|
+
const piSessions = pi.sessions;
|
|
62
|
+
const geminiSessions = gemini.sessions;
|
|
45
63
|
|
|
46
|
-
if (
|
|
64
|
+
if (
|
|
65
|
+
!claudeExists &&
|
|
66
|
+
codexSessions.length === 0 &&
|
|
67
|
+
copilotSessions.length === 0 &&
|
|
68
|
+
cursorSessions.length === 0 &&
|
|
69
|
+
opencodeSessions.length === 0 &&
|
|
70
|
+
piSessions.length === 0 &&
|
|
71
|
+
geminiSessions.length === 0
|
|
72
|
+
) {
|
|
47
73
|
notFound();
|
|
48
74
|
}
|
|
49
75
|
|
|
50
|
-
// Prefer
|
|
51
|
-
// ambiguous for cwds containing `-` (every `-`
|
|
52
|
-
//
|
|
53
|
-
|
|
76
|
+
// Prefer a canonical cwd recovered from any external store when available —
|
|
77
|
+
// `decodeFolderName(name)` is ambiguous for cwds containing `-` (every `-`
|
|
78
|
+
// becomes `/`). Each external transcript records the literal cwd, so they
|
|
79
|
+
// round-trip correctly. First non-null wins (Codex → Copilot → Cursor → OpenCode → Pi → Gemini).
|
|
80
|
+
const canonicalRoot = codex.cwd ?? copilot.cwd ?? cursor.cwd ?? opencode.cwd ?? pi.cwd ?? gemini.cwd ?? decodedName;
|
|
54
81
|
|
|
55
82
|
// Project header metadata
|
|
56
83
|
let lastModified: Date | null = null;
|
|
@@ -64,18 +91,27 @@ export default async function ProjectPage({ params }: ProjectPageProps) {
|
|
|
64
91
|
logWarn(`Failed to get stats for project ${decodedName}:`, error);
|
|
65
92
|
}
|
|
66
93
|
}
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
94
|
+
const newestExternal = [codexSessions[0], copilotSessions[0], cursorSessions[0], opencodeSessions[0], piSessions[0], geminiSessions[0]]
|
|
95
|
+
.filter((s): s is SessionFile => !!s)
|
|
96
|
+
.map((s) => s.lastModified)
|
|
97
|
+
.reduce<Date | null>((acc, d) => (!acc || d.getTime() > acc.getTime() ? d : acc), null);
|
|
98
|
+
if (newestExternal && (!lastModified || newestExternal.getTime() > lastModified.getTime())) {
|
|
99
|
+
lastModified = newestExternal;
|
|
100
|
+
lastModifiedFormatted = formatDate(newestExternal);
|
|
71
101
|
}
|
|
72
102
|
|
|
73
|
-
const sessionFiles: SessionFile[] = [
|
|
74
|
-
|
|
75
|
-
|
|
103
|
+
const sessionFiles: SessionFile[] = [
|
|
104
|
+
...claudeSessions,
|
|
105
|
+
...codexSessions,
|
|
106
|
+
...copilotSessions,
|
|
107
|
+
...cursorSessions,
|
|
108
|
+
...opencodeSessions,
|
|
109
|
+
...piSessions,
|
|
110
|
+
...geminiSessions,
|
|
111
|
+
].sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
76
112
|
|
|
77
113
|
// Path line: prefer the Claude storage dir if present (matches existing UX);
|
|
78
|
-
// otherwise show the canonical
|
|
114
|
+
// otherwise show the canonical cwd recovered from the first external store.
|
|
79
115
|
const displayPath = claudeExists && claudeProjectPath ? claudeProjectPath : canonicalRoot;
|
|
80
116
|
|
|
81
117
|
return (
|
|
@@ -4,6 +4,11 @@ import { ArrowLeft, Download } from "lucide-react";
|
|
|
4
4
|
import { notFound } from "next/navigation";
|
|
5
5
|
import { getCachedSessionLog, type LogEntry } from "@/lib/log-entries";
|
|
6
6
|
import { getCachedCodexSessionLog } from "@/lib/codex-sessions";
|
|
7
|
+
import { getCachedCopilotSessionLog } from "@/lib/copilot-sessions";
|
|
8
|
+
import { getCachedCursorSessionLog } from "@/lib/cursor-sessions";
|
|
9
|
+
import { getCachedOpenCodeSessionLog } from "@/lib/opencode-sessions";
|
|
10
|
+
import { getCachedPiSessionLog } from "@/lib/pi-sessions";
|
|
11
|
+
import { getCachedGeminiSessionLog } from "@/lib/gemini-sessions";
|
|
7
12
|
import { decodeFolderName } from "@/lib/paths";
|
|
8
13
|
import { baseSessionId } from "@/lib/utils/session-id";
|
|
9
14
|
import { resolveProjectPath, UUID_RE } from "@/lib/projects";
|
|
@@ -30,13 +35,17 @@ export default async function SessionPage({ params }: SessionPageProps) {
|
|
|
30
35
|
}
|
|
31
36
|
const decodedName = decodeFolderName(name);
|
|
32
37
|
const decodedSessionId = baseSessionId(sessionId);
|
|
33
|
-
|
|
38
|
+
// OpenCode session IDs are not UUIDs — they use `ses_*` prefixes (e.g.
|
|
39
|
+
// `ses_21ad60d14ffewMeRRKMLdS7vOI`). The other four CLIs use UUIDs. Accept
|
|
40
|
+
// either; the per-CLI loader returns null for unknown IDs anyway.
|
|
41
|
+
const OPENCODE_SESSION_RE = /^ses_[A-Za-z0-9]+$/;
|
|
42
|
+
if (!UUID_RE.test(decodedSessionId) && !OPENCODE_SESSION_RE.test(decodedSessionId)) notFound();
|
|
34
43
|
|
|
35
44
|
let entries: LogEntry[] | null = null;
|
|
36
45
|
let rawLines: Record<string, unknown>[] | null = null;
|
|
37
46
|
let error: string | null = null;
|
|
38
|
-
let cli: "claude" | "codex" = "claude";
|
|
39
|
-
let
|
|
47
|
+
let cli: "claude" | "codex" | "copilot" | "cursor" | "opencode" | "pi" | "gemini" = "claude";
|
|
48
|
+
let externalCwd: string | undefined;
|
|
40
49
|
|
|
41
50
|
try {
|
|
42
51
|
// Use raw folder name for file operations — decodedName is for display only
|
|
@@ -46,36 +55,89 @@ export default async function SessionPage({ params }: SessionPageProps) {
|
|
|
46
55
|
} catch (e) {
|
|
47
56
|
const isNotFound = (e as NodeJS.ErrnoException).code === "ENOENT";
|
|
48
57
|
if (isNotFound) {
|
|
49
|
-
// Fall back
|
|
50
|
-
//
|
|
51
|
-
//
|
|
58
|
+
// Fall back through external stores in order: Codex → Copilot → Cursor → OpenCode → Pi.
|
|
59
|
+
// Each store keys by sessionId rather than the project slug, so the
|
|
60
|
+
// [name] segment is irrelevant on these branches.
|
|
52
61
|
const codex = await getCachedCodexSessionLog(decodedSessionId);
|
|
53
62
|
if (codex) {
|
|
54
63
|
entries = codex.entries;
|
|
55
64
|
rawLines = codex.rawLines;
|
|
56
|
-
|
|
65
|
+
externalCwd = codex.cwd;
|
|
57
66
|
cli = "codex";
|
|
58
67
|
} else {
|
|
59
|
-
|
|
68
|
+
const copilot = await getCachedCopilotSessionLog(decodedSessionId);
|
|
69
|
+
if (copilot) {
|
|
70
|
+
entries = copilot.entries;
|
|
71
|
+
rawLines = copilot.rawLines;
|
|
72
|
+
externalCwd = copilot.cwd;
|
|
73
|
+
cli = "copilot";
|
|
74
|
+
} else {
|
|
75
|
+
const cursor = await getCachedCursorSessionLog(decodedSessionId);
|
|
76
|
+
if (cursor) {
|
|
77
|
+
entries = cursor.entries;
|
|
78
|
+
rawLines = cursor.rawLines;
|
|
79
|
+
externalCwd = cursor.cwd;
|
|
80
|
+
cli = "cursor";
|
|
81
|
+
} else {
|
|
82
|
+
const opencode = await getCachedOpenCodeSessionLog(decodedSessionId);
|
|
83
|
+
if (opencode) {
|
|
84
|
+
entries = opencode.entries;
|
|
85
|
+
rawLines = opencode.rawLines;
|
|
86
|
+
externalCwd = opencode.cwd;
|
|
87
|
+
cli = "opencode";
|
|
88
|
+
} else {
|
|
89
|
+
const pi = await getCachedPiSessionLog(decodedSessionId);
|
|
90
|
+
if (pi) {
|
|
91
|
+
entries = pi.entries;
|
|
92
|
+
rawLines = pi.rawLines;
|
|
93
|
+
externalCwd = pi.cwd;
|
|
94
|
+
cli = "pi";
|
|
95
|
+
} else {
|
|
96
|
+
const gemini = await getCachedGeminiSessionLog(decodedSessionId);
|
|
97
|
+
if (gemini) {
|
|
98
|
+
entries = gemini.entries;
|
|
99
|
+
rawLines = gemini.rawLines;
|
|
100
|
+
externalCwd = gemini.cwd;
|
|
101
|
+
cli = "gemini";
|
|
102
|
+
} else {
|
|
103
|
+
error = "Session log file not found.";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
60
109
|
}
|
|
61
110
|
} else {
|
|
62
111
|
error = "Failed to read session log.";
|
|
63
112
|
}
|
|
64
113
|
}
|
|
65
114
|
|
|
66
|
-
const
|
|
67
|
-
const headerLabel =
|
|
68
|
-
const headerValue =
|
|
115
|
+
const isExternal = cli !== "claude";
|
|
116
|
+
const headerLabel = isExternal ? "CLI" : "Project";
|
|
117
|
+
const headerValue =
|
|
118
|
+
cli === "codex"
|
|
119
|
+
? `OpenAI Codex${externalCwd ? ` · ${externalCwd}` : ""}`
|
|
120
|
+
: cli === "copilot"
|
|
121
|
+
? `GitHub Copilot${externalCwd ? ` · ${externalCwd}` : ""}`
|
|
122
|
+
: cli === "cursor"
|
|
123
|
+
? `Cursor Agent${externalCwd ? ` · ${externalCwd}` : ""}`
|
|
124
|
+
: cli === "opencode"
|
|
125
|
+
? `OpenCode${externalCwd ? ` · ${externalCwd}` : ""}`
|
|
126
|
+
: cli === "pi"
|
|
127
|
+
? `Pi${externalCwd ? ` · ${externalCwd}` : ""}`
|
|
128
|
+
: cli === "gemini"
|
|
129
|
+
? `Gemini CLI${externalCwd ? ` · ${externalCwd}` : ""}`
|
|
130
|
+
: decodedName;
|
|
69
131
|
|
|
70
132
|
return (
|
|
71
133
|
<main className="min-h-screen bg-background">
|
|
72
134
|
<div className="container mx-auto p-8">
|
|
73
135
|
<Link
|
|
74
|
-
href={
|
|
136
|
+
href={isExternal ? "/policies?tab=activity" : `/project/${encodeURIComponent(name)}`}
|
|
75
137
|
className="inline-flex items-center gap-2 text-muted-foreground hover:text-foreground mb-6 transition-colors"
|
|
76
138
|
>
|
|
77
139
|
<ArrowLeft className="w-4 h-4" />
|
|
78
|
-
<span>{
|
|
140
|
+
<span>{isExternal ? "Back to Activity" : "Back to Sessions"}</span>
|
|
79
141
|
</Link>
|
|
80
142
|
|
|
81
143
|
<div className="mb-8">
|
|
@@ -99,7 +161,7 @@ export default async function SessionPage({ params }: SessionPageProps) {
|
|
|
99
161
|
<p className="text-muted-foreground">
|
|
100
162
|
<span className="font-medium">{rawLines.length}</span> log lines
|
|
101
163
|
</p>
|
|
102
|
-
{!
|
|
164
|
+
{!isExternal && (
|
|
103
165
|
<a
|
|
104
166
|
href={`/api/download/${encodeURIComponent(name)}/${encodeURIComponent(decodedSessionId)}`}
|
|
105
167
|
download
|
|
@@ -122,7 +184,22 @@ export default async function SessionPage({ params }: SessionPageProps) {
|
|
|
122
184
|
{!error && entries && (
|
|
123
185
|
<LazyLogViewer
|
|
124
186
|
entries={entries}
|
|
125
|
-
projectName={
|
|
187
|
+
projectName={
|
|
188
|
+
isExternal
|
|
189
|
+
? (externalCwd ??
|
|
190
|
+
(cli === "codex"
|
|
191
|
+
? "OpenAI Codex"
|
|
192
|
+
: cli === "copilot"
|
|
193
|
+
? "GitHub Copilot"
|
|
194
|
+
: cli === "cursor"
|
|
195
|
+
? "Cursor Agent"
|
|
196
|
+
: cli === "opencode"
|
|
197
|
+
? "OpenCode"
|
|
198
|
+
: cli === "pi"
|
|
199
|
+
? "Pi"
|
|
200
|
+
: "Gemini CLI"))
|
|
201
|
+
: decodedName
|
|
202
|
+
}
|
|
126
203
|
sessionId={decodedSessionId}
|
|
127
204
|
/>
|
|
128
205
|
)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="#FFFFFF"><title>GitHub Copilot</title><path d="M23.922 16.997C23.061 18.492 18.063 22.02 12 22.02 5.937 22.02.939 18.492.078 16.997A.641.641 0 0 1 0 16.741v-2.869a.883.883 0 0 1 .053-.22c.372-.935 1.347-2.292 2.605-2.656.167-.429.414-1.055.644-1.517a10.098 10.098 0 0 1-.052-1.086c0-1.331.282-2.499 1.132-3.368.397-.406.89-.717 1.474-.952C7.255 2.937 9.248 1.98 11.978 1.98c2.731 0 4.767.957 6.166 2.093.584.235 1.077.546 1.474.952.85.869 1.132 2.037 1.132 3.368 0 .368-.014.733-.052 1.086.23.462.477 1.088.644 1.517 1.258.364 2.233 1.721 2.605 2.656a.841.841 0 0 1 .053.22v2.869a.641.641 0 0 1-.078.256Zm-11.75-5.992h-.344a4.359 4.359 0 0 1-.355.508c-.77.947-1.918 1.492-3.508 1.492-1.725 0-2.989-.359-3.782-1.259a2.137 2.137 0 0 1-.085-.104L4 11.746v6.585c1.435.779 4.514 2.179 8 2.179 3.486 0 6.565-1.4 8-2.179v-6.585l-.098-.104s-.033.045-.085.104c-.793.9-2.057 1.259-3.782 1.259-1.59 0-2.738-.545-3.508-1.492a4.359 4.359 0 0 1-.355-.508Zm2.328 3.25c.549 0 1 .451 1 1v2c0 .549-.451 1-1 1-.549 0-1-.451-1-1v-2c0-.549.451-1 1-1Zm-5 0c.549 0 1 .451 1 1v2c0 .549-.451 1-1 1-.549 0-1-.451-1-1v-2c0-.549.451-1 1-1Zm3.313-6.185c.136 1.057.403 1.913.878 2.497.442.544 1.134.938 2.344.938 1.573 0 2.292-.337 2.657-.751.384-.435.558-1.15.558-2.361 0-1.14-.243-1.847-.705-2.319-.477-.488-1.319-.862-2.824-1.025-1.487-.161-2.192.138-2.533.529-.269.307-.437.808-.438 1.578v.021c0 .265.021.562.063.893Zm-1.626 0c.042-.331.063-.628.063-.894v-.02c-.001-.77-.169-1.271-.438-1.578-.341-.391-1.046-.69-2.533-.529-1.505.163-2.347.537-2.824 1.025-.462.472-.705 1.179-.705 2.319 0 1.211.175 1.926.558 2.361.365.414 1.084.751 2.657.751 1.21 0 1.902-.394 2.344-.938.475-.584.742-1.44.878-2.497Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="#000000"><title>GitHub Copilot</title><path d="M23.922 16.997C23.061 18.492 18.063 22.02 12 22.02 5.937 22.02.939 18.492.078 16.997A.641.641 0 0 1 0 16.741v-2.869a.883.883 0 0 1 .053-.22c.372-.935 1.347-2.292 2.605-2.656.167-.429.414-1.055.644-1.517a10.098 10.098 0 0 1-.052-1.086c0-1.331.282-2.499 1.132-3.368.397-.406.89-.717 1.474-.952C7.255 2.937 9.248 1.98 11.978 1.98c2.731 0 4.767.957 6.166 2.093.584.235 1.077.546 1.474.952.85.869 1.132 2.037 1.132 3.368 0 .368-.014.733-.052 1.086.23.462.477 1.088.644 1.517 1.258.364 2.233 1.721 2.605 2.656a.841.841 0 0 1 .053.22v2.869a.641.641 0 0 1-.078.256Zm-11.75-5.992h-.344a4.359 4.359 0 0 1-.355.508c-.77.947-1.918 1.492-3.508 1.492-1.725 0-2.989-.359-3.782-1.259a2.137 2.137 0 0 1-.085-.104L4 11.746v6.585c1.435.779 4.514 2.179 8 2.179 3.486 0 6.565-1.4 8-2.179v-6.585l-.098-.104s-.033.045-.085.104c-.793.9-2.057 1.259-3.782 1.259-1.59 0-2.738-.545-3.508-1.492a4.359 4.359 0 0 1-.355-.508Zm2.328 3.25c.549 0 1 .451 1 1v2c0 .549-.451 1-1 1-.549 0-1-.451-1-1v-2c0-.549.451-1 1-1Zm-5 0c.549 0 1 .451 1 1v2c0 .549-.451 1-1 1-.549 0-1-.451-1-1v-2c0-.549.451-1 1-1Zm3.313-6.185c.136 1.057.403 1.913.878 2.497.442.544 1.134.938 2.344.938 1.573 0 2.292-.337 2.657-.751.384-.435.558-1.15.558-2.361 0-1.14-.243-1.847-.705-2.319-.477-.488-1.319-.862-2.824-1.025-1.487-.161-2.192.138-2.533.529-.269.307-.437.808-.438 1.578v.021c0 .265.021.562.063.893Zm-1.626 0c.042-.331.063-.628.063-.894v-.02c-.001-.77-.169-1.271-.438-1.578-.341-.391-1.046-.69-2.533-.529-1.505.163-2.347.537-2.824 1.025-.462.472-.705 1.179-.705 2.319 0 1.211.175 1.926.558 2.361.365.414 1.084.751 2.657.751 1.21 0 1.902-.394 2.344-.938.475-.584.742-1.44.878-2.497Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="395 393 175 197" role="img"><title>Cursor Agent</title><path fill="#72716D" d="M483.395 490.5L566 538.297C565.493 539.178 564.757 539.93 563.845 540.456L486.636 585.13C484.632 586.29 482.159 586.29 480.154 585.13L402.945 540.456C402.034 539.93 401.297 539.178 400.79 538.297L483.395 490.5Z"/><path fill="#55544F" d="M483.395 395V490.5L400.79 538.297C400.282 537.416 400 536.398 400 535.346V445.654C400 443.545 401.122 441.6 402.945 440.544L480.15 395.87C481.154 395.29 482.273 395 483.391 395H483.395Z"/><path fill="#43413C" d="M565.996 442.703C565.489 441.822 564.752 441.07 563.841 440.544L486.632 395.87C485.632 395.29 484.513 395 483.395 395V490.5L566 538.297C566.507 537.416 566.789 536.398 566.789 535.346V445.654C566.789 444.598 566.511 443.588 566 442.703H565.996Z"/><path fill="#D6D5D2" d="M560.218 446.049C560.686 446.858 560.751 447.896 560.218 448.82L485.235 578.974C484.732 579.855 483.392 579.493 483.392 578.479V492.713C483.392 492.029 483.209 491.37 482.877 490.794L560.215 446.045H560.218V446.049Z"/><path fill="#FFFFFF" d="M560.218 446.049L482.88 490.797C482.552 490.224 482.073 489.737 481.48 489.394L407.369 446.511C406.49 446.006 406.851 444.663 407.862 444.663H557.824C558.889 444.663 559.754 445.239 560.218 446.049Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="395 393 175 197" role="img"><title>Cursor Agent</title><path fill="#72716D" d="M483.395 490.5L566 538.297C565.493 539.178 564.757 539.93 563.845 540.456L486.636 585.13C484.632 586.29 482.159 586.29 480.154 585.13L402.945 540.456C402.034 539.93 401.297 539.178 400.79 538.297L483.395 490.5Z"/><path fill="#55544F" d="M483.395 395V490.5L400.79 538.297C400.282 537.416 400 536.398 400 535.346V445.654C400 443.545 401.122 441.6 402.945 440.544L480.15 395.87C481.154 395.29 482.273 395 483.391 395H483.395Z"/><path fill="#43413C" d="M565.996 442.703C565.489 441.822 564.752 441.07 563.841 440.544L486.632 395.87C485.632 395.29 484.513 395 483.395 395V490.5L566 538.297C566.507 537.416 566.789 536.398 566.789 535.346V445.654C566.789 444.598 566.511 443.588 566 442.703H565.996Z"/><path fill="#D6D5D2" d="M560.218 446.049C560.686 446.858 560.751 447.896 560.218 448.82L485.235 578.974C484.732 579.855 483.392 579.493 483.392 578.479V492.713C483.392 492.029 483.209 491.37 482.877 490.794L560.215 446.045H560.218V446.049Z"/><path fill="#FFFFFF" d="M560.218 446.049L482.88 490.797C482.552 490.224 482.073 489.737 481.48 489.394L407.369 446.511C406.49 446.006 406.851 444.663 407.862 444.663H557.824C558.889 444.663 559.754 445.239 560.218 446.049Z"/></svg>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64" aria-label="Gemini CLI">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="gemini-grad-dark" x1="0" y1="0" x2="24" y2="24" gradientUnits="userSpaceOnUse">
|
|
4
|
+
<stop offset="0" stop-color="#74c0fc" />
|
|
5
|
+
<stop offset="0.5" stop-color="#9775fa" />
|
|
6
|
+
<stop offset="1" stop-color="#b197fc" />
|
|
7
|
+
</linearGradient>
|
|
8
|
+
</defs>
|
|
9
|
+
<path
|
|
10
|
+
fill="url(#gemini-grad-dark)"
|
|
11
|
+
d="M12 1c.4 5.6 4.4 9.6 10 10-5.6.4-9.6 4.4-10 10-.4-5.6-4.4-9.6-10-10C7.6 10.6 11.6 6.6 12 1z"
|
|
12
|
+
/>
|
|
13
|
+
</svg>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64" aria-label="Gemini CLI">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="gemini-grad-light" x1="0" y1="0" x2="24" y2="24" gradientUnits="userSpaceOnUse">
|
|
4
|
+
<stop offset="0" stop-color="#1c7ed6" />
|
|
5
|
+
<stop offset="0.5" stop-color="#5e60ce" />
|
|
6
|
+
<stop offset="1" stop-color="#7048e8" />
|
|
7
|
+
</linearGradient>
|
|
8
|
+
</defs>
|
|
9
|
+
<path
|
|
10
|
+
fill="url(#gemini-grad-light)"
|
|
11
|
+
d="M12 1c.4 5.6 4.4 9.6 10 10-5.6.4-9.6 4.4-10 10-.4-5.6-4.4-9.6-10-10C7.6 10.6 11.6 6.6 12 1z"
|
|
12
|
+
/>
|
|
13
|
+
</svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512" fill="none" role="img"><title>OpenCode</title><path d="M320 224V352H192V224H320Z" fill="#5A5858"/><path fill-rule="evenodd" clip-rule="evenodd" d="M384 416H128V96H384V416ZM320 160H192V352H320V160Z" fill="white"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512" fill="none" role="img"><title>OpenCode</title><path d="M320 224V352H192V224H320Z" fill="#A5A2A2"/><path fill-rule="evenodd" clip-rule="evenodd" d="M384 416H128V96H384V416ZM320 160H192V352H320V160Z" fill="#131010"/></svg>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800">
|
|
3
|
+
<!-- P shape: outer boundary clockwise, inner hole counter-clockwise -->
|
|
4
|
+
<path fill="#fff" fill-rule="evenodd" d="M165.29 165.29 H517.36 V400 H400 V517.36 H282.65 V634.72 H165.29 Z M282.65 282.65 V400 H400 V282.65 Z"/>
|
|
5
|
+
<!-- i dot -->
|
|
6
|
+
<path fill="#fff" d="M517.36 400 H634.72 V634.72 H517.36 Z"/>
|
|
7
|
+
</svg>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800">
|
|
3
|
+
<!-- P shape: outer boundary clockwise, inner hole counter-clockwise -->
|
|
4
|
+
<path fill="#000" fill-rule="evenodd" d="M165.29 165.29 H517.36 V400 H400 V517.36 H282.65 V634.72 H165.29 Z M282.65 282.65 V400 H400 V282.65 Z"/>
|
|
5
|
+
<!-- i dot -->
|
|
6
|
+
<path fill="#000" d="M517.36 400 H634.72 V634.72 H517.36 Z"/>
|
|
7
|
+
</svg>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for agent-CLI metadata used by the dashboard, the
|
|
3
|
+
* `bin/failproofai.mjs` argv parser, the install prompt, and the badge UI.
|
|
4
|
+
*
|
|
5
|
+
* This module is **client-safe** — it only exports plain string metadata. The
|
|
6
|
+
* server-side project / session providers live in their own files
|
|
7
|
+
* (`lib/codex-projects.ts`, `lib/codex-sessions.ts`, `lib/copilot-projects.ts`,
|
|
8
|
+
* `lib/copilot-sessions.ts`, `lib/cursor-projects.ts`, `lib/cursor-sessions.ts`,
|
|
9
|
+
* `lib/opencode-projects.ts`, `lib/opencode-sessions.ts`,
|
|
10
|
+
* `lib/pi-projects.ts`, `lib/pi-sessions.ts`)
|
|
11
|
+
* and are imported lazily by `lib/projects.ts` and the session viewer page so
|
|
12
|
+
* Turbopack doesn't drag Node-only deps (`fs/promises`, `os`) into client
|
|
13
|
+
* bundles.
|
|
14
|
+
*
|
|
15
|
+
* Adding a new agent CLI = three steps:
|
|
16
|
+
* 1. Extend `INTEGRATION_TYPES` in `src/hooks/types.ts` (server-side hook contract).
|
|
17
|
+
* 2. Add an `Integration` impl in `src/hooks/integrations.ts` (install/uninstall plumbing).
|
|
18
|
+
* 3. Add an entry to `CLI_REGISTRY` below — display label and badge classes.
|
|
19
|
+
* Optionally add a project provider (`lib/<cli>-projects.ts`) and a
|
|
20
|
+
* session loader (`lib/<cli>-sessions.ts`); wire them into
|
|
21
|
+
* `lib/projects.ts#getProjectFolders` and the session viewer page's
|
|
22
|
+
* fallback chain (both already iterate over per-CLI providers).
|
|
23
|
+
*
|
|
24
|
+
* Filter dropdown, badge component, and project-list filter all read from
|
|
25
|
+
* this registry and pick up new CLIs without further code changes.
|
|
26
|
+
*/
|
|
27
|
+
import type { IntegrationType } from "@/src/hooks/types";
|
|
28
|
+
|
|
29
|
+
/** Canonical CLI ids the registry knows about. Mirrors `INTEGRATION_TYPES`. */
|
|
30
|
+
export const KNOWN_CLI_IDS = ["claude", "codex", "copilot", "cursor", "opencode", "pi", "gemini"] as const satisfies readonly IntegrationType[];
|
|
31
|
+
export type CliId = (typeof KNOWN_CLI_IDS)[number];
|
|
32
|
+
|
|
33
|
+
/** Per-CLI metadata consumed by the dashboard. */
|
|
34
|
+
export interface CliEntry {
|
|
35
|
+
id: CliId;
|
|
36
|
+
label: string;
|
|
37
|
+
/** Tailwind utility classes for the small CLI badge (background + text + border). */
|
|
38
|
+
badgeClasses: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const CLI_ENTRIES: Record<CliId, CliEntry> = {
|
|
42
|
+
claude: {
|
|
43
|
+
id: "claude",
|
|
44
|
+
label: "Claude Code",
|
|
45
|
+
badgeClasses: "bg-orange-500/10 text-orange-400 border-orange-500/20",
|
|
46
|
+
},
|
|
47
|
+
codex: {
|
|
48
|
+
id: "codex",
|
|
49
|
+
label: "OpenAI Codex",
|
|
50
|
+
badgeClasses: "bg-purple-500/10 text-purple-400 border-purple-500/20",
|
|
51
|
+
},
|
|
52
|
+
copilot: {
|
|
53
|
+
id: "copilot",
|
|
54
|
+
label: "GitHub Copilot",
|
|
55
|
+
badgeClasses: "bg-blue-500/10 text-blue-400 border-blue-500/20",
|
|
56
|
+
},
|
|
57
|
+
cursor: {
|
|
58
|
+
id: "cursor",
|
|
59
|
+
label: "Cursor Agent",
|
|
60
|
+
badgeClasses: "bg-emerald-500/10 text-emerald-400 border-emerald-500/20",
|
|
61
|
+
},
|
|
62
|
+
opencode: {
|
|
63
|
+
id: "opencode",
|
|
64
|
+
label: "OpenCode",
|
|
65
|
+
badgeClasses: "bg-amber-500/10 text-amber-400 border-amber-500/20",
|
|
66
|
+
},
|
|
67
|
+
pi: {
|
|
68
|
+
id: "pi",
|
|
69
|
+
label: "Pi",
|
|
70
|
+
badgeClasses: "bg-pink-500/10 text-pink-400 border-pink-500/20",
|
|
71
|
+
},
|
|
72
|
+
gemini: {
|
|
73
|
+
id: "gemini",
|
|
74
|
+
label: "Gemini CLI",
|
|
75
|
+
badgeClasses: "bg-sky-500/10 text-sky-400 border-sky-500/20",
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export function getCliEntry(id: string): CliEntry | undefined {
|
|
80
|
+
return CLI_ENTRIES[id as CliId];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function listCliEntries(): CliEntry[] {
|
|
84
|
+
return KNOWN_CLI_IDS.map((id) => CLI_ENTRIES[id]);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** External CLIs (everything except Claude) that contribute project listings. */
|
|
88
|
+
export function listExternalCliEntries(): CliEntry[] {
|
|
89
|
+
return listCliEntries().filter((c) => c.id !== "claude");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Display label for a CLI id. Returns the id itself if unknown. */
|
|
93
|
+
export function getCliLabel(id: string): string {
|
|
94
|
+
return getCliEntry(id)?.label ?? id;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Badge classes for a CLI id. Falls back to Claude's classes if unknown. */
|
|
98
|
+
export function getCliBadgeClasses(id: string): string {
|
|
99
|
+
return getCliEntry(id)?.badgeClasses ?? CLI_ENTRIES.claude.badgeClasses;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Predicate: is this id a known CLI? Useful when validating user input. */
|
|
103
|
+
export function isKnownCli(id: string | null | undefined): id is CliId {
|
|
104
|
+
// hasOwnProperty.call (not `id in CLI_ENTRIES`) so inherited Object.prototype
|
|
105
|
+
// keys like "toString" / "constructor" / "hasOwnProperty" don't pass.
|
|
106
|
+
return typeof id === "string" && Object.prototype.hasOwnProperty.call(CLI_ENTRIES, id);
|
|
107
|
+
}
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
* convention (see `encodeFolderName` in `lib/paths.ts`), so a cwd present in both stores
|
|
10
10
|
* naturally produces the same `name` and can be merged on the Claude side.
|
|
11
11
|
*/
|
|
12
|
-
import { open, readdir } from "fs/promises";
|
|
13
|
-
import { homedir } from "os";
|
|
14
|
-
import { join } from "path";
|
|
12
|
+
import { open, readdir } from "node:fs/promises";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { join } from "node:path";
|
|
15
15
|
import { encodeFolderName } from "./paths";
|
|
16
16
|
import type { ProjectFolder, SessionFile } from "./projects";
|
|
17
17
|
import { runtimeCache } from "./runtime-cache";
|