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
|
@@ -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
|
+
}
|
package/lib/codex-projects.ts
CHANGED
|
@@ -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";
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Copilot CLI project discovery.
|
|
3
|
+
*
|
|
4
|
+
* Copilot stores per-session state at `~/.copilot/session-state/<sessionId>/`,
|
|
5
|
+
* with a `workspace.yaml` carrying flat scalars: id, cwd, git_root, branch,
|
|
6
|
+
* repository, host_type, user_named, summary_count, created_at, updated_at,
|
|
7
|
+
* (optional) name, summary. We read this file (always present, even before
|
|
8
|
+
* any interaction creates events.jsonl) to extract the cwd. Sessions are
|
|
9
|
+
* grouped by unique cwd into `ProjectFolder` rows.
|
|
10
|
+
*
|
|
11
|
+
* The encoded cwd doubles as the URL slug for `/project/[name]`, matching the
|
|
12
|
+
* Claude Code convention (see `encodeFolderName` in `lib/paths.ts`), so a cwd
|
|
13
|
+
* present in multiple stores naturally produces the same `name` and merges in
|
|
14
|
+
* `lib/projects.ts`.
|
|
15
|
+
*/
|
|
16
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
17
|
+
import { homedir } from "node:os";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
import { encodeFolderName } from "./paths";
|
|
20
|
+
import type { ProjectFolder, SessionFile } from "./projects";
|
|
21
|
+
import { runtimeCache } from "./runtime-cache";
|
|
22
|
+
import { batchAll } from "./concurrency";
|
|
23
|
+
import { formatDate } from "./format-date";
|
|
24
|
+
import { logWarn } from "./logger";
|
|
25
|
+
|
|
26
|
+
/** Inlined to avoid cross-module imports from `lib/copilot-sessions.ts` —
|
|
27
|
+
* keeping the dep tree independent prevents Turbopack from tracing
|
|
28
|
+
* Node-only modules (`fs/promises`, `os`) into the client bundle when the
|
|
29
|
+
* session viewer page statically imports `copilot-sessions`. Mirrors the
|
|
30
|
+
* pattern in `lib/codex-projects.ts`. */
|
|
31
|
+
function getCopilotSessionStateRoot(): string {
|
|
32
|
+
return join(process.env.COPILOT_HOME || join(homedir(), ".copilot"), "session-state");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface CopilotSessionMeta {
|
|
36
|
+
workspacePath: string;
|
|
37
|
+
eventsPath: string;
|
|
38
|
+
sessionId: string;
|
|
39
|
+
cwd: string;
|
|
40
|
+
/** Latest of (workspace.yaml mtime, events.jsonl mtime if present). */
|
|
41
|
+
fileMtime: Date;
|
|
42
|
+
/** True iff `events.jsonl` exists. Workspace-only sessions (initialized but
|
|
43
|
+
* never sent a prompt) skip the `/project` session list because the viewer
|
|
44
|
+
* would only render "Session log file not found." */
|
|
45
|
+
hasTranscript: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function safeReaddir(dir: string) {
|
|
49
|
+
try {
|
|
50
|
+
return await readdir(dir, { withFileTypes: true });
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Extract `cwd` from a workspace.yaml file. Permissive regex parser — avoids
|
|
57
|
+
* pulling in a YAML lib for one flat scalar. Copilot writes simple
|
|
58
|
+
* `key: value` lines without nesting in this file (verified against CLI 1.0.39). */
|
|
59
|
+
function parseCwdFromWorkspace(text: string): string | undefined {
|
|
60
|
+
const m = text.match(/^cwd\s*:\s*(.+?)\s*$/m);
|
|
61
|
+
if (!m) return undefined;
|
|
62
|
+
return m[1].replace(/^['"]|['"]$/g, "");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function statMtime(path: string): Promise<Date | null> {
|
|
66
|
+
try {
|
|
67
|
+
return (await stat(path)).mtime;
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function scanCopilotSessions(): Promise<CopilotSessionMeta[]> {
|
|
74
|
+
const root = getCopilotSessionStateRoot();
|
|
75
|
+
const entries = await safeReaddir(root);
|
|
76
|
+
if (!entries) return [];
|
|
77
|
+
|
|
78
|
+
const candidates = entries
|
|
79
|
+
.filter((e) => e.isDirectory())
|
|
80
|
+
.map((e) => ({
|
|
81
|
+
sessionId: e.name,
|
|
82
|
+
workspacePath: join(root, e.name, "workspace.yaml"),
|
|
83
|
+
eventsPath: join(root, e.name, "events.jsonl"),
|
|
84
|
+
}));
|
|
85
|
+
|
|
86
|
+
const settled = await batchAll(
|
|
87
|
+
candidates.map((c) => async (): Promise<CopilotSessionMeta | null> => {
|
|
88
|
+
let workspaceText: string;
|
|
89
|
+
try {
|
|
90
|
+
workspaceText = await readFile(c.workspacePath, "utf-8");
|
|
91
|
+
} catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const cwd = parseCwdFromWorkspace(workspaceText);
|
|
95
|
+
if (!cwd) return null;
|
|
96
|
+
// Prefer events.jsonl mtime when present (reflects last activity);
|
|
97
|
+
// fall back to workspace.yaml mtime for sessions without interaction.
|
|
98
|
+
const eventsMtime = await statMtime(c.eventsPath);
|
|
99
|
+
const wsMtime = await statMtime(c.workspacePath);
|
|
100
|
+
const hasTranscript = eventsMtime !== null;
|
|
101
|
+
const fileMtime =
|
|
102
|
+
eventsMtime && wsMtime
|
|
103
|
+
? new Date(Math.max(eventsMtime.getTime(), wsMtime.getTime()))
|
|
104
|
+
: eventsMtime ?? wsMtime ?? new Date(0);
|
|
105
|
+
return {
|
|
106
|
+
workspacePath: c.workspacePath,
|
|
107
|
+
eventsPath: c.eventsPath,
|
|
108
|
+
sessionId: c.sessionId,
|
|
109
|
+
cwd,
|
|
110
|
+
fileMtime,
|
|
111
|
+
hasTranscript,
|
|
112
|
+
};
|
|
113
|
+
}),
|
|
114
|
+
16,
|
|
115
|
+
);
|
|
116
|
+
return settled
|
|
117
|
+
.filter((r): r is PromiseFulfilledResult<CopilotSessionMeta | null> => r.status === "fulfilled")
|
|
118
|
+
.map((r) => r.value)
|
|
119
|
+
.filter((v): v is CopilotSessionMeta => v !== null);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const cachedScan = runtimeCache(scanCopilotSessions, 30);
|
|
123
|
+
|
|
124
|
+
/** Returns one ProjectFolder per unique cwd discovered in Copilot transcripts. */
|
|
125
|
+
export async function getCopilotProjects(): Promise<ProjectFolder[]> {
|
|
126
|
+
let metas: CopilotSessionMeta[];
|
|
127
|
+
try {
|
|
128
|
+
metas = await cachedScan();
|
|
129
|
+
} catch (error) {
|
|
130
|
+
logWarn("Failed to scan Copilot sessions:", error);
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Skip workspace-only sessions: their /projects rows would click through to
|
|
135
|
+
// an empty session list (metasToSessionFiles also filters on hasTranscript).
|
|
136
|
+
const byCwd = new Map<string, { latest: Date; cwd: string }>();
|
|
137
|
+
for (const m of metas) {
|
|
138
|
+
if (!m.hasTranscript) continue;
|
|
139
|
+
const existing = byCwd.get(m.cwd);
|
|
140
|
+
if (!existing || m.fileMtime.getTime() > existing.latest.getTime()) {
|
|
141
|
+
byCwd.set(m.cwd, { latest: m.fileMtime, cwd: m.cwd });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const folders: ProjectFolder[] = [];
|
|
146
|
+
for (const { cwd, latest } of byCwd.values()) {
|
|
147
|
+
folders.push({
|
|
148
|
+
name: encodeFolderName(cwd),
|
|
149
|
+
path: cwd,
|
|
150
|
+
isDirectory: true,
|
|
151
|
+
lastModified: latest,
|
|
152
|
+
lastModifiedFormatted: formatDate(latest),
|
|
153
|
+
cli: ["copilot"],
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
folders.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
157
|
+
return folders;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function metasToSessionFiles(metas: CopilotSessionMeta[]): SessionFile[] {
|
|
161
|
+
// Skip workspace-only sessions: their "openable" rows would lead to a
|
|
162
|
+
// 'Session log file not found' viewer because events.jsonl doesn't exist.
|
|
163
|
+
const files: SessionFile[] = metas
|
|
164
|
+
.filter((m) => m.hasTranscript)
|
|
165
|
+
.map((m) => ({
|
|
166
|
+
name: m.sessionId,
|
|
167
|
+
path: m.eventsPath,
|
|
168
|
+
lastModified: m.fileMtime,
|
|
169
|
+
lastModifiedFormatted: formatDate(m.fileMtime),
|
|
170
|
+
sessionId: m.sessionId,
|
|
171
|
+
cli: "copilot",
|
|
172
|
+
}));
|
|
173
|
+
files.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
174
|
+
return files;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Returns SessionFile entries for every Copilot transcript whose cwd matches `cwd` exactly. */
|
|
178
|
+
export async function getCopilotSessionsForCwd(cwd: string): Promise<SessionFile[]> {
|
|
179
|
+
let metas: CopilotSessionMeta[];
|
|
180
|
+
try {
|
|
181
|
+
metas = await cachedScan();
|
|
182
|
+
} catch (error) {
|
|
183
|
+
logWarn("Failed to scan Copilot sessions:", error);
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
return metasToSessionFiles(metas.filter((m) => m.cwd === cwd));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface CopilotProjectByName {
|
|
190
|
+
cwd: string | null;
|
|
191
|
+
sessions: SessionFile[];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Looks up Copilot sessions for a project URL slug. `decodeFolderName` is lossy
|
|
196
|
+
* (every `-` becomes `/`), so we re-encode each session's cwd and match in
|
|
197
|
+
* that direction. Returns both the canonical cwd and the matching sessions.
|
|
198
|
+
*/
|
|
199
|
+
export async function getCopilotSessionsByEncodedName(name: string): Promise<CopilotProjectByName> {
|
|
200
|
+
let metas: CopilotSessionMeta[];
|
|
201
|
+
try {
|
|
202
|
+
metas = await cachedScan();
|
|
203
|
+
} catch (error) {
|
|
204
|
+
logWarn("Failed to scan Copilot sessions:", error);
|
|
205
|
+
return { cwd: null, sessions: [] };
|
|
206
|
+
}
|
|
207
|
+
const matches = metas.filter((m) => m.hasTranscript && encodeFolderName(m.cwd) === name);
|
|
208
|
+
return {
|
|
209
|
+
cwd: matches[0]?.cwd ?? null,
|
|
210
|
+
sessions: metasToSessionFiles(matches),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export const getCachedCopilotProjects = runtimeCache(getCopilotProjects, 30);
|
|
215
|
+
export const getCachedCopilotSessionsForCwd = runtimeCache(
|
|
216
|
+
(cwd: string) => getCopilotSessionsForCwd(cwd),
|
|
217
|
+
30,
|
|
218
|
+
{ maxSize: 50 },
|
|
219
|
+
);
|
|
220
|
+
export const getCachedCopilotSessionsByEncodedName = runtimeCache(
|
|
221
|
+
(name: string) => getCopilotSessionsByEncodedName(name),
|
|
222
|
+
30,
|
|
223
|
+
{ maxSize: 50 },
|
|
224
|
+
);
|