@agent-native/recap-cli 0.0.0-beta-20260820202238

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.
Files changed (54) hide show
  1. package/dist/cli.d.ts +3 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +10 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/copy-recap-workflow.d.ts +2 -0
  6. package/dist/copy-recap-workflow.d.ts.map +1 -0
  7. package/dist/copy-recap-workflow.js +9 -0
  8. package/dist/copy-recap-workflow.js.map +1 -0
  9. package/dist/index.d.ts +7 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +6 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/openai-compatible-endpoint.d.ts +3 -0
  14. package/dist/openai-compatible-endpoint.d.ts.map +1 -0
  15. package/dist/openai-compatible-endpoint.js +23 -0
  16. package/dist/openai-compatible-endpoint.js.map +1 -0
  17. package/dist/plan-blocks.d.ts +19 -0
  18. package/dist/plan-blocks.d.ts.map +1 -0
  19. package/dist/plan-blocks.js +96 -0
  20. package/dist/plan-blocks.js.map +1 -0
  21. package/dist/plan-publish-store.d.ts +62 -0
  22. package/dist/plan-publish-store.d.ts.map +1 -0
  23. package/dist/plan-publish-store.js +128 -0
  24. package/dist/plan-publish-store.js.map +1 -0
  25. package/dist/pr-visual-recap-workflow.d.ts +3 -0
  26. package/dist/pr-visual-recap-workflow.d.ts.map +1 -0
  27. package/dist/pr-visual-recap-workflow.js +10 -0
  28. package/dist/pr-visual-recap-workflow.js.map +1 -0
  29. package/dist/recap.d.ts +575 -0
  30. package/dist/recap.d.ts.map +1 -0
  31. package/dist/recap.js +4029 -0
  32. package/dist/recap.js.map +1 -0
  33. package/dist/skill-content/connection.d.ts +2 -0
  34. package/dist/skill-content/connection.d.ts.map +1 -0
  35. package/dist/skill-content/connection.js +53 -0
  36. package/dist/skill-content/connection.js.map +1 -0
  37. package/dist/skill-content/local-files.d.ts +2 -0
  38. package/dist/skill-content/local-files.d.ts.map +1 -0
  39. package/dist/skill-content/local-files.js +101 -0
  40. package/dist/skill-content/local-files.js.map +1 -0
  41. package/dist/skill-content/visual-recap-skill.d.ts +2 -0
  42. package/dist/skill-content/visual-recap-skill.d.ts.map +1 -0
  43. package/dist/skill-content/visual-recap-skill.js +554 -0
  44. package/dist/skill-content/visual-recap-skill.js.map +1 -0
  45. package/dist/skill-content/wireframe.d.ts +4 -0
  46. package/dist/skill-content/wireframe.d.ts.map +1 -0
  47. package/dist/skill-content/wireframe.js +347 -0
  48. package/dist/skill-content/wireframe.js.map +1 -0
  49. package/dist/skill-content.d.ts +9 -0
  50. package/dist/skill-content.d.ts.map +1 -0
  51. package/dist/skill-content.js +11 -0
  52. package/dist/skill-content.js.map +1 -0
  53. package/dist/workflows/pr-visual-recap.yml +1064 -0
  54. package/package.json +48 -0
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import { runRecap } from "./recap.js";
3
+ const argv = process.argv.slice(2);
4
+ if (argv[0] === "recap")
5
+ argv.shift();
6
+ runRecap(argv).catch((error) => {
7
+ console.error(error instanceof Error ? error.message : error);
8
+ process.exit(1);
9
+ });
10
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,OAAO;IAAE,IAAI,CAAC,KAAK,EAAE,CAAC;AAEtC,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IACtC,OAAO,CAAC,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAC9D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC","sourcesContent":["#!/usr/bin/env node\n\nimport { runRecap } from \"./recap.js\";\n\nconst argv = process.argv.slice(2);\nif (argv[0] === \"recap\") argv.shift();\n\nrunRecap(argv).catch((error: unknown) => {\n console.error(error instanceof Error ? error.message : error);\n process.exit(1);\n});\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=copy-recap-workflow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copy-recap-workflow.d.ts","sourceRoot":"","sources":["../src/copy-recap-workflow.ts"],"names":[],"mappings":""}
@@ -0,0 +1,9 @@
1
+ import { copyFileSync, mkdirSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ const distDir = path.dirname(fileURLToPath(import.meta.url));
5
+ const source = path.resolve(distDir, "../../../.github/workflows/pr-visual-recap.yml");
6
+ const destination = path.join(distDir, "workflows", "pr-visual-recap.yml");
7
+ mkdirSync(path.dirname(destination), { recursive: true });
8
+ copyFileSync(source, destination);
9
+ //# sourceMappingURL=copy-recap-workflow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copy-recap-workflow.js","sourceRoot":"","sources":["../src/copy-recap-workflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC7D,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CACzB,OAAO,EACP,gDAAgD,CACjD,CAAC;AACF,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,qBAAqB,CAAC,CAAC;AAE3E,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC1D,YAAY,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC","sourcesContent":["import { copyFileSync, mkdirSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst distDir = path.dirname(fileURLToPath(import.meta.url));\nconst source = path.resolve(\n distDir,\n \"../../../.github/workflows/pr-visual-recap.yml\",\n);\nconst destination = path.join(distDir, \"workflows\", \"pr-visual-recap.yml\");\n\nmkdirSync(path.dirname(destination), { recursive: true });\ncopyFileSync(source, destination);\n"]}
@@ -0,0 +1,7 @@
1
+ export * from "./recap.js";
2
+ export { DEFAULT_PLAN_APP_URL, defaultPlanBlocksOut, fetchPlanBlockCatalog, normalizePlanBlockFormat, planActionEndpoint, } from "./plan-blocks.js";
3
+ export type { FetchPlanBlockCatalogInput, FetchPlanBlockCatalogResult, PlanBlockFormat, } from "./plan-blocks.js";
4
+ export { isFirstPartyPlanHost, planPublishConfigPath, readPlanPublishAuth, writePlanPublishAuth, } from "./plan-publish-store.js";
5
+ export { PR_VISUAL_RECAP_WORKFLOW_YML } from "./pr-visual-recap-workflow.js";
6
+ export { CONNECTION_REFERENCE_MD, RECAP_REFERENCE_FILES, VISUAL_RECAP_SKILL_MD, } from "./skill-content.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,0BAA0B,EAC1B,2BAA2B,EAC3B,eAAe,GAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,4BAA4B,EAAE,MAAM,+BAA+B,CAAC;AAC7E,OAAO,EACL,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,oBAAoB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export * from "./recap.js";
2
+ export { DEFAULT_PLAN_APP_URL, defaultPlanBlocksOut, fetchPlanBlockCatalog, normalizePlanBlockFormat, planActionEndpoint, } from "./plan-blocks.js";
3
+ export { isFirstPartyPlanHost, planPublishConfigPath, readPlanPublishAuth, writePlanPublishAuth, } from "./plan-publish-store.js";
4
+ export { PR_VISUAL_RECAP_WORKFLOW_YML } from "./pr-visual-recap-workflow.js";
5
+ export { CONNECTION_REFERENCE_MD, RECAP_REFERENCE_FILES, VISUAL_RECAP_SKILL_MD, } from "./skill-content.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAM1B,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,4BAA4B,EAAE,MAAM,+BAA+B,CAAC;AAC7E,OAAO,EACL,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,oBAAoB,CAAC","sourcesContent":["export * from \"./recap.js\";\nexport {\n DEFAULT_PLAN_APP_URL,\n defaultPlanBlocksOut,\n fetchPlanBlockCatalog,\n normalizePlanBlockFormat,\n planActionEndpoint,\n} from \"./plan-blocks.js\";\nexport type {\n FetchPlanBlockCatalogInput,\n FetchPlanBlockCatalogResult,\n PlanBlockFormat,\n} from \"./plan-blocks.js\";\nexport {\n isFirstPartyPlanHost,\n planPublishConfigPath,\n readPlanPublishAuth,\n writePlanPublishAuth,\n} from \"./plan-publish-store.js\";\nexport { PR_VISUAL_RECAP_WORKFLOW_YML } from \"./pr-visual-recap-workflow.js\";\nexport {\n CONNECTION_REFERENCE_MD,\n RECAP_REFERENCE_FILES,\n VISUAL_RECAP_SKILL_MD,\n} from \"./skill-content.js\";\n"]}
@@ -0,0 +1,3 @@
1
+ export declare const OPENAI_BASE_URL_ENV_VAR = "OPENAI_BASE_URL";
2
+ export declare function normalizeOpenAiBaseUrl(value: string): string;
3
+ //# sourceMappingURL=openai-compatible-endpoint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-compatible-endpoint.d.ts","sourceRoot":"","sources":["../src/openai-compatible-endpoint.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB,oBAAoB,CAAC;AAEzD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAsB5D"}
@@ -0,0 +1,23 @@
1
+ export const OPENAI_BASE_URL_ENV_VAR = "OPENAI_BASE_URL";
2
+ export function normalizeOpenAiBaseUrl(value) {
3
+ const trimmed = value.trim();
4
+ if (!trimmed) {
5
+ throw new Error("Endpoint URL is required.");
6
+ }
7
+ let url;
8
+ try {
9
+ url = new URL(trimmed);
10
+ }
11
+ catch {
12
+ throw new Error("Endpoint URL must be a valid URL.");
13
+ }
14
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
15
+ throw new Error("Endpoint URL must start with http:// or https://.");
16
+ }
17
+ if (url.username || url.password) {
18
+ throw new Error("Endpoint URL must not include credentials.");
19
+ }
20
+ url.hash = "";
21
+ return url.toString().replace(/\/+$/, "");
22
+ }
23
+ //# sourceMappingURL=openai-compatible-endpoint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-compatible-endpoint.js","sourceRoot":"","sources":["../src/openai-compatible-endpoint.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,uBAAuB,GAAG,iBAAiB,CAAC;AAEzD,MAAM,UAAU,sBAAsB,CAAC,KAAa;IAClD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAC/C,CAAC;IAED,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACvD,CAAC;IAED,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAC1D,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IACvE,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;IAED,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC;IACd,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAC5C,CAAC","sourcesContent":["export const OPENAI_BASE_URL_ENV_VAR = \"OPENAI_BASE_URL\";\n\nexport function normalizeOpenAiBaseUrl(value: string): string {\n const trimmed = value.trim();\n if (!trimmed) {\n throw new Error(\"Endpoint URL is required.\");\n }\n\n let url: URL;\n try {\n url = new URL(trimmed);\n } catch {\n throw new Error(\"Endpoint URL must be a valid URL.\");\n }\n\n if (url.protocol !== \"https:\" && url.protocol !== \"http:\") {\n throw new Error(\"Endpoint URL must start with http:// or https://.\");\n }\n if (url.username || url.password) {\n throw new Error(\"Endpoint URL must not include credentials.\");\n }\n\n url.hash = \"\";\n return url.toString().replace(/\\/+$/, \"\");\n}\n"]}
@@ -0,0 +1,19 @@
1
+ export declare const DEFAULT_PLAN_APP_URL = "https://plan.agent-native.com";
2
+ export type PlanBlockFormat = "reference" | "schema";
3
+ export type FetchPlanBlockCatalogInput = {
4
+ appUrl?: string;
5
+ out?: string;
6
+ format?: PlanBlockFormat;
7
+ fetchFn?: typeof fetch;
8
+ };
9
+ export type FetchPlanBlockCatalogResult = {
10
+ ok: true;
11
+ out: string;
12
+ count?: number;
13
+ format: PlanBlockFormat;
14
+ };
15
+ export declare function planActionEndpoint(appUrl: string, action: string): string;
16
+ export declare function normalizePlanBlockFormat(value: string | undefined): PlanBlockFormat;
17
+ export declare function defaultPlanBlocksOut(format: PlanBlockFormat): string;
18
+ export declare function fetchPlanBlockCatalog(input: FetchPlanBlockCatalogInput): Promise<FetchPlanBlockCatalogResult>;
19
+ //# sourceMappingURL=plan-blocks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-blocks.d.ts","sourceRoot":"","sources":["../src/plan-blocks.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,oBAAoB,kCAAkC,CAAC;AAIpE,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,QAAQ,CAAC;AAErD,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,EAAE,EAAE,IAAI,CAAC;IACT,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,eAAe,CAAC;CACzB,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEzE;AAED,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,eAAe,CAIjB;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAEpE;AAkDD,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,2BAA2B,CAAC,CAqDtC"}
@@ -0,0 +1,96 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ export const DEFAULT_PLAN_APP_URL = "https://plan.agent-native.com";
4
+ const PLAN_BLOCKS_HTTP_TIMEOUT_MS = 45_000;
5
+ export function planActionEndpoint(appUrl, action) {
6
+ return `${appUrl.replace(/\/$/, "")}/_agent-native/actions/${action}`;
7
+ }
8
+ export function normalizePlanBlockFormat(value) {
9
+ if (!value || value === "reference")
10
+ return "reference";
11
+ if (value === "schema")
12
+ return "schema";
13
+ throw new Error(`Invalid --format "${value}" (expected reference or schema)`);
14
+ }
15
+ export function defaultPlanBlocksOut(format) {
16
+ return format === "schema" ? "plan-blocks.schema.json" : "plan-blocks.md";
17
+ }
18
+ function sanitizeFetchDetail(value, maxLength) {
19
+ const normalized = value.replace(/\s+/g, " ").trim();
20
+ if (normalized.length <= maxLength)
21
+ return normalized;
22
+ return `${normalized.slice(0, maxLength - 3)}...`;
23
+ }
24
+ function shouldRetryPlanBlocks(status) {
25
+ return (status === 404 ||
26
+ status === 408 ||
27
+ status === 409 ||
28
+ status === 425 ||
29
+ status === 429 ||
30
+ status >= 500);
31
+ }
32
+ function delay(ms) {
33
+ return new Promise((resolve) => setTimeout(resolve, ms));
34
+ }
35
+ async function fetchWithTimeout(url, init, fetchFn) {
36
+ return await fetchFn(url, {
37
+ ...init,
38
+ signal: init.signal ?? AbortSignal.timeout(PLAN_BLOCKS_HTTP_TIMEOUT_MS),
39
+ });
40
+ }
41
+ function renderBlockCatalogOutput(json, format) {
42
+ if (format === "schema") {
43
+ if (!Array.isArray(json.blocks)) {
44
+ throw new Error("get-plan-blocks returned no schema blocks.");
45
+ }
46
+ return `${JSON.stringify({ count: json.count, blocks: json.blocks }, null, 2)}\n`;
47
+ }
48
+ if (typeof json.reference !== "string" || json.reference.length === 0) {
49
+ throw new Error("get-plan-blocks returned no reference text.");
50
+ }
51
+ return json.reference;
52
+ }
53
+ export async function fetchPlanBlockCatalog(input) {
54
+ const fetchFn = input.fetchFn ?? fetch;
55
+ const appUrl = input.appUrl ?? DEFAULT_PLAN_APP_URL;
56
+ const format = input.format ?? "reference";
57
+ const out = input.out ?? defaultPlanBlocksOut(format);
58
+ const endpoint = new URL(planActionEndpoint(appUrl, "get-plan-blocks"));
59
+ endpoint.searchParams.set("format", format);
60
+ let lastError = "";
61
+ for (let attempt = 1; attempt <= 4; attempt += 1) {
62
+ try {
63
+ const response = await fetchWithTimeout(endpoint.toString(), { method: "GET", headers: { accept: "application/json" } }, fetchFn);
64
+ if (!response.ok) {
65
+ const detail = await response.text().catch(() => "");
66
+ lastError = `get-plan-blocks failed ${response.status} ${response.statusText}: ${sanitizeFetchDetail(detail, 500)}`;
67
+ if (attempt < 4 && shouldRetryPlanBlocks(response.status)) {
68
+ await delay(attempt * 1500);
69
+ continue;
70
+ }
71
+ throw new Error(lastError);
72
+ }
73
+ const json = (await response.json().catch(() => null));
74
+ if (!json)
75
+ throw new Error("get-plan-blocks returned invalid JSON.");
76
+ const text = renderBlockCatalogOutput(json, format);
77
+ fs.writeFileSync(path.resolve(out), text, "utf-8");
78
+ return {
79
+ ok: true,
80
+ out,
81
+ count: typeof json.count === "number" ? json.count : undefined,
82
+ format,
83
+ };
84
+ }
85
+ catch (err) {
86
+ lastError = err instanceof Error ? err.message : String(err);
87
+ if (attempt < 4) {
88
+ await delay(attempt * 1500);
89
+ continue;
90
+ }
91
+ throw new Error(lastError);
92
+ }
93
+ }
94
+ throw new Error(lastError || "get-plan-blocks failed.");
95
+ }
96
+ //# sourceMappingURL=plan-blocks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-blocks.js","sourceRoot":"","sources":["../src/plan-blocks.ts"],"names":[],"mappings":"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","sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport const DEFAULT_PLAN_APP_URL = \"https://plan.agent-native.com\";\n\nconst PLAN_BLOCKS_HTTP_TIMEOUT_MS = 45_000;\n\nexport type PlanBlockFormat = \"reference\" | \"schema\";\n\nexport type FetchPlanBlockCatalogInput = {\n appUrl?: string;\n out?: string;\n format?: PlanBlockFormat;\n fetchFn?: typeof fetch;\n};\n\nexport type FetchPlanBlockCatalogResult = {\n ok: true;\n out: string;\n count?: number;\n format: PlanBlockFormat;\n};\n\nexport function planActionEndpoint(appUrl: string, action: string): string {\n return `${appUrl.replace(/\\/$/, \"\")}/_agent-native/actions/${action}`;\n}\n\nexport function normalizePlanBlockFormat(\n value: string | undefined,\n): PlanBlockFormat {\n if (!value || value === \"reference\") return \"reference\";\n if (value === \"schema\") return \"schema\";\n throw new Error(`Invalid --format \"${value}\" (expected reference or schema)`);\n}\n\nexport function defaultPlanBlocksOut(format: PlanBlockFormat): string {\n return format === \"schema\" ? \"plan-blocks.schema.json\" : \"plan-blocks.md\";\n}\n\nfunction sanitizeFetchDetail(value: string, maxLength: number): string {\n const normalized = value.replace(/\\s+/g, \" \").trim();\n if (normalized.length <= maxLength) return normalized;\n return `${normalized.slice(0, maxLength - 3)}...`;\n}\n\nfunction shouldRetryPlanBlocks(status: number): boolean {\n return (\n status === 404 ||\n status === 408 ||\n status === 409 ||\n status === 425 ||\n status === 429 ||\n status >= 500\n );\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nasync function fetchWithTimeout(\n url: string,\n init: RequestInit,\n fetchFn: typeof fetch,\n): Promise<Response> {\n return await fetchFn(url, {\n ...init,\n signal: init.signal ?? AbortSignal.timeout(PLAN_BLOCKS_HTTP_TIMEOUT_MS),\n });\n}\n\nfunction renderBlockCatalogOutput(\n json: { reference?: unknown; blocks?: unknown; count?: unknown },\n format: PlanBlockFormat,\n): string {\n if (format === \"schema\") {\n if (!Array.isArray(json.blocks)) {\n throw new Error(\"get-plan-blocks returned no schema blocks.\");\n }\n return `${JSON.stringify({ count: json.count, blocks: json.blocks }, null, 2)}\\n`;\n }\n if (typeof json.reference !== \"string\" || json.reference.length === 0) {\n throw new Error(\"get-plan-blocks returned no reference text.\");\n }\n return json.reference;\n}\n\nexport async function fetchPlanBlockCatalog(\n input: FetchPlanBlockCatalogInput,\n): Promise<FetchPlanBlockCatalogResult> {\n const fetchFn = input.fetchFn ?? fetch;\n const appUrl = input.appUrl ?? DEFAULT_PLAN_APP_URL;\n const format = input.format ?? \"reference\";\n const out = input.out ?? defaultPlanBlocksOut(format);\n const endpoint = new URL(planActionEndpoint(appUrl, \"get-plan-blocks\"));\n endpoint.searchParams.set(\"format\", format);\n\n let lastError = \"\";\n for (let attempt = 1; attempt <= 4; attempt += 1) {\n try {\n const response = await fetchWithTimeout(\n endpoint.toString(),\n { method: \"GET\", headers: { accept: \"application/json\" } },\n fetchFn,\n );\n if (!response.ok) {\n const detail = await response.text().catch(() => \"\");\n lastError = `get-plan-blocks failed ${response.status} ${\n response.statusText\n }: ${sanitizeFetchDetail(detail, 500)}`;\n if (attempt < 4 && shouldRetryPlanBlocks(response.status)) {\n await delay(attempt * 1500);\n continue;\n }\n throw new Error(lastError);\n }\n\n const json = (await response.json().catch(() => null)) as {\n reference?: unknown;\n blocks?: unknown;\n count?: unknown;\n } | null;\n if (!json) throw new Error(\"get-plan-blocks returned invalid JSON.\");\n const text = renderBlockCatalogOutput(json, format);\n fs.writeFileSync(path.resolve(out), text, \"utf-8\");\n return {\n ok: true,\n out,\n count: typeof json.count === \"number\" ? json.count : undefined,\n format,\n };\n } catch (err) {\n lastError = err instanceof Error ? err.message : String(err);\n if (attempt < 4) {\n await delay(attempt * 1500);\n continue;\n }\n throw new Error(lastError);\n }\n }\n\n throw new Error(lastError || \"get-plan-blocks failed.\");\n}\n"]}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Canonical publish-token store for the local Plans server.
3
+ *
4
+ * `agent-native connect <hosted-url>` mints a bearer token and writes it into
5
+ * each coding agent's per-client MCP config (`.mcp.json` / Codex `config.toml`
6
+ * via `mcp-config-writers`). Those files are client-specific, so the local Plans
7
+ * server cannot read them for a server-to-server publish.
8
+ *
9
+ * To close that seam, the connect flow ALSO writes a single canonical record to
10
+ * `~/.agent-native/plan-publish.json` whenever it authenticates a first-party
11
+ * Plans app. The local server's `publish-visual-plan` action reads the exact
12
+ * same file (see `templates/plan/server/lib/plan-publish.ts`):
13
+ *
14
+ * { "url": "https://plan.agent-native.com", "token": "<bearer>" }
15
+ *
16
+ * This mirrors the existing device-token precedent in
17
+ * `code-agent-connector.ts` (`~/.agent-native/remote-device.json`): home-dir
18
+ * JSON, env-overridable path, atomic 0600 write. The write is additive — it
19
+ * merges into any existing file rather than clobbering sibling keys — and
20
+ * best-effort, since persisting MCP config is the primary contract and a failed
21
+ * canonical write must never fail the connect.
22
+ */
23
+ /**
24
+ * Absolute path to the canonical publish-token file. Honors
25
+ * `PLAN_PUBLISH_CONFIG_PATH` so connect and the local server agree on the
26
+ * location in tests and custom setups; defaults to
27
+ * `~/.agent-native/plan-publish.json`.
28
+ */
29
+ export declare function planPublishConfigPath(): string;
30
+ /**
31
+ * Whether `url`'s host is the first-party Agent-Native Plans app whose token
32
+ * we should mirror to the canonical publish file. Only the hosted Plans app
33
+ * (`plan.agent-native.com`) qualifies — mirroring tokens for other
34
+ * agent-native subdomains (assets, mail, …) would silently overwrite the
35
+ * canonical Plans endpoint with the wrong URL+token each time `connect --all`
36
+ * runs last-write-wins. A custom self-hosted origin (ngrok, localhost, a
37
+ * private deployment) is intentionally excluded: the user can still point the
38
+ * server at it via `PLAN_PUBLISH_URL` / `PLAN_PUBLISH_TOKEN` env vars.
39
+ */
40
+ export declare function isFirstPartyPlanHost(url: string): boolean;
41
+ /**
42
+ * Merge `{ url, token }` into the canonical publish file without clobbering any
43
+ * other keys the file already holds. Best-effort: returns the written path on
44
+ * success, or `null` if the write failed or the inputs were unusable.
45
+ *
46
+ * `filePath` is injectable for tests; production callers omit it and get the
47
+ * env-overridable home-dir path.
48
+ */
49
+ export declare function writePlanPublishAuth(params: {
50
+ url: string;
51
+ token: string;
52
+ }, filePath?: string): string | null;
53
+ /**
54
+ * Read the canonical Plans publish auth written by `agent-native connect`.
55
+ * Returns `null` for missing/corrupt/incomplete files so callers can treat the
56
+ * publish token as optional and guide the user to reconnect.
57
+ */
58
+ export declare function readPlanPublishAuth(filePath?: string): {
59
+ url: string;
60
+ token: string;
61
+ } | null;
62
+ //# sourceMappingURL=plan-publish-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-publish-store.d.ts","sourceRoot":"","sources":["../src/plan-publish-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AASH;;;;;GAKG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAK9C;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOzD;AAMD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EACtC,QAAQ,GAAE,MAAgC,GACzC,MAAM,GAAG,IAAI,CAmCf;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,GAAE,MAAgC,GACzC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAgBvC"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Canonical publish-token store for the local Plans server.
3
+ *
4
+ * `agent-native connect <hosted-url>` mints a bearer token and writes it into
5
+ * each coding agent's per-client MCP config (`.mcp.json` / Codex `config.toml`
6
+ * via `mcp-config-writers`). Those files are client-specific, so the local Plans
7
+ * server cannot read them for a server-to-server publish.
8
+ *
9
+ * To close that seam, the connect flow ALSO writes a single canonical record to
10
+ * `~/.agent-native/plan-publish.json` whenever it authenticates a first-party
11
+ * Plans app. The local server's `publish-visual-plan` action reads the exact
12
+ * same file (see `templates/plan/server/lib/plan-publish.ts`):
13
+ *
14
+ * { "url": "https://plan.agent-native.com", "token": "<bearer>" }
15
+ *
16
+ * This mirrors the existing device-token precedent in
17
+ * `code-agent-connector.ts` (`~/.agent-native/remote-device.json`): home-dir
18
+ * JSON, env-overridable path, atomic 0600 write. The write is additive — it
19
+ * merges into any existing file rather than clobbering sibling keys — and
20
+ * best-effort, since persisting MCP config is the primary contract and a failed
21
+ * canonical write must never fail the connect.
22
+ */
23
+ import fs from "node:fs";
24
+ import os from "node:os";
25
+ import path from "node:path";
26
+ /** Matches the env override read by `templates/plan/server/lib/plan-publish.ts`. */
27
+ const CONFIG_PATH_ENV = "PLAN_PUBLISH_CONFIG_PATH";
28
+ /**
29
+ * Absolute path to the canonical publish-token file. Honors
30
+ * `PLAN_PUBLISH_CONFIG_PATH` so connect and the local server agree on the
31
+ * location in tests and custom setups; defaults to
32
+ * `~/.agent-native/plan-publish.json`.
33
+ */
34
+ export function planPublishConfigPath() {
35
+ return path.resolve(process.env[CONFIG_PATH_ENV] ??
36
+ path.join(os.homedir(), ".agent-native", "plan-publish.json"));
37
+ }
38
+ /**
39
+ * Whether `url`'s host is the first-party Agent-Native Plans app whose token
40
+ * we should mirror to the canonical publish file. Only the hosted Plans app
41
+ * (`plan.agent-native.com`) qualifies — mirroring tokens for other
42
+ * agent-native subdomains (assets, mail, …) would silently overwrite the
43
+ * canonical Plans endpoint with the wrong URL+token each time `connect --all`
44
+ * runs last-write-wins. A custom self-hosted origin (ngrok, localhost, a
45
+ * private deployment) is intentionally excluded: the user can still point the
46
+ * server at it via `PLAN_PUBLISH_URL` / `PLAN_PUBLISH_TOKEN` env vars.
47
+ */
48
+ export function isFirstPartyPlanHost(url) {
49
+ try {
50
+ const host = new URL(url).hostname.toLowerCase();
51
+ return host === "plan.agent-native.com";
52
+ }
53
+ catch {
54
+ return false;
55
+ }
56
+ }
57
+ function stripTrailingSlash(url) {
58
+ return url.replace(/\/+$/, "");
59
+ }
60
+ /**
61
+ * Merge `{ url, token }` into the canonical publish file without clobbering any
62
+ * other keys the file already holds. Best-effort: returns the written path on
63
+ * success, or `null` if the write failed or the inputs were unusable.
64
+ *
65
+ * `filePath` is injectable for tests; production callers omit it and get the
66
+ * env-overridable home-dir path.
67
+ */
68
+ export function writePlanPublishAuth(params, filePath = planPublishConfigPath()) {
69
+ const url = stripTrailingSlash((params.url ?? "").trim());
70
+ const token = (params.token ?? "").trim();
71
+ if (!url || !token)
72
+ return null;
73
+ try {
74
+ let existing = {};
75
+ try {
76
+ const raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
77
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
78
+ existing = raw;
79
+ }
80
+ }
81
+ catch {
82
+ // No existing file (or unreadable/corrupt) — start fresh.
83
+ }
84
+ const merged = {
85
+ ...existing,
86
+ url,
87
+ token,
88
+ updatedAt: new Date().toISOString(),
89
+ };
90
+ fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
91
+ const tmp = `${filePath}.tmp`;
92
+ fs.writeFileSync(tmp, JSON.stringify(merged, null, 2) + "\n", {
93
+ mode: 0o600,
94
+ });
95
+ fs.renameSync(tmp, filePath);
96
+ return filePath;
97
+ }
98
+ catch {
99
+ // Best-effort: the per-client MCP config is the primary write. A failed
100
+ // canonical write must never fail the connect flow.
101
+ return null;
102
+ }
103
+ }
104
+ /**
105
+ * Read the canonical Plans publish auth written by `agent-native connect`.
106
+ * Returns `null` for missing/corrupt/incomplete files so callers can treat the
107
+ * publish token as optional and guide the user to reconnect.
108
+ */
109
+ export function readPlanPublishAuth(filePath = planPublishConfigPath()) {
110
+ try {
111
+ const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
112
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
113
+ return null;
114
+ }
115
+ const record = parsed;
116
+ const urlValue = record.url ?? record.baseUrl ?? record.hostedUrl;
117
+ const tokenValue = record.token ?? record.accessToken ?? record.bearerToken;
118
+ const url = typeof urlValue === "string" ? urlValue.trim() : "";
119
+ const token = typeof tokenValue === "string" ? tokenValue.trim() : "";
120
+ if (!url || !token)
121
+ return null;
122
+ return { url: stripTrailingSlash(url), token };
123
+ }
124
+ catch {
125
+ return null;
126
+ }
127
+ }
128
+ //# sourceMappingURL=plan-publish-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-publish-store.js","sourceRoot":"","sources":["../src/plan-publish-store.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Canonical publish-token store for the local Plans server.\n *\n * `agent-native connect <hosted-url>` mints a bearer token and writes it into\n * each coding agent's per-client MCP config (`.mcp.json` / Codex `config.toml`\n * via `mcp-config-writers`). Those files are client-specific, so the local Plans\n * server cannot read them for a server-to-server publish.\n *\n * To close that seam, the connect flow ALSO writes a single canonical record to\n * `~/.agent-native/plan-publish.json` whenever it authenticates a first-party\n * Plans app. The local server's `publish-visual-plan` action reads the exact\n * same file (see `templates/plan/server/lib/plan-publish.ts`):\n *\n * { \"url\": \"https://plan.agent-native.com\", \"token\": \"<bearer>\" }\n *\n * This mirrors the existing device-token precedent in\n * `code-agent-connector.ts` (`~/.agent-native/remote-device.json`): home-dir\n * JSON, env-overridable path, atomic 0600 write. The write is additive — it\n * merges into any existing file rather than clobbering sibling keys — and\n * best-effort, since persisting MCP config is the primary contract and a failed\n * canonical write must never fail the connect.\n */\n\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\n/** Matches the env override read by `templates/plan/server/lib/plan-publish.ts`. */\nconst CONFIG_PATH_ENV = \"PLAN_PUBLISH_CONFIG_PATH\";\n\n/**\n * Absolute path to the canonical publish-token file. Honors\n * `PLAN_PUBLISH_CONFIG_PATH` so connect and the local server agree on the\n * location in tests and custom setups; defaults to\n * `~/.agent-native/plan-publish.json`.\n */\nexport function planPublishConfigPath(): string {\n return path.resolve(\n process.env[CONFIG_PATH_ENV] ??\n path.join(os.homedir(), \".agent-native\", \"plan-publish.json\"),\n );\n}\n\n/**\n * Whether `url`'s host is the first-party Agent-Native Plans app whose token\n * we should mirror to the canonical publish file. Only the hosted Plans app\n * (`plan.agent-native.com`) qualifies — mirroring tokens for other\n * agent-native subdomains (assets, mail, …) would silently overwrite the\n * canonical Plans endpoint with the wrong URL+token each time `connect --all`\n * runs last-write-wins. A custom self-hosted origin (ngrok, localhost, a\n * private deployment) is intentionally excluded: the user can still point the\n * server at it via `PLAN_PUBLISH_URL` / `PLAN_PUBLISH_TOKEN` env vars.\n */\nexport function isFirstPartyPlanHost(url: string): boolean {\n try {\n const host = new URL(url).hostname.toLowerCase();\n return host === \"plan.agent-native.com\";\n } catch {\n return false;\n }\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, \"\");\n}\n\n/**\n * Merge `{ url, token }` into the canonical publish file without clobbering any\n * other keys the file already holds. Best-effort: returns the written path on\n * success, or `null` if the write failed or the inputs were unusable.\n *\n * `filePath` is injectable for tests; production callers omit it and get the\n * env-overridable home-dir path.\n */\nexport function writePlanPublishAuth(\n params: { url: string; token: string },\n filePath: string = planPublishConfigPath(),\n): string | null {\n const url = stripTrailingSlash((params.url ?? \"\").trim());\n const token = (params.token ?? \"\").trim();\n if (!url || !token) return null;\n\n try {\n let existing: Record<string, unknown> = {};\n try {\n const raw = JSON.parse(fs.readFileSync(filePath, \"utf-8\")) as unknown;\n if (raw && typeof raw === \"object\" && !Array.isArray(raw)) {\n existing = raw as Record<string, unknown>;\n }\n } catch {\n // No existing file (or unreadable/corrupt) — start fresh.\n }\n\n const merged = {\n ...existing,\n url,\n token,\n updatedAt: new Date().toISOString(),\n };\n\n fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });\n const tmp = `${filePath}.tmp`;\n fs.writeFileSync(tmp, JSON.stringify(merged, null, 2) + \"\\n\", {\n mode: 0o600,\n });\n fs.renameSync(tmp, filePath);\n return filePath;\n } catch {\n // Best-effort: the per-client MCP config is the primary write. A failed\n // canonical write must never fail the connect flow.\n return null;\n }\n}\n\n/**\n * Read the canonical Plans publish auth written by `agent-native connect`.\n * Returns `null` for missing/corrupt/incomplete files so callers can treat the\n * publish token as optional and guide the user to reconnect.\n */\nexport function readPlanPublishAuth(\n filePath: string = planPublishConfigPath(),\n): { url: string; token: string } | null {\n try {\n const parsed = JSON.parse(fs.readFileSync(filePath, \"utf-8\")) as unknown;\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return null;\n }\n const record = parsed as Record<string, unknown>;\n const urlValue = record.url ?? record.baseUrl ?? record.hostedUrl;\n const tokenValue = record.token ?? record.accessToken ?? record.bearerToken;\n const url = typeof urlValue === \"string\" ? urlValue.trim() : \"\";\n const token = typeof tokenValue === \"string\" ? tokenValue.trim() : \"\";\n if (!url || !token) return null;\n return { url: stripTrailingSlash(url), token };\n } catch {\n return null;\n }\n}\n"]}
@@ -0,0 +1,3 @@
1
+ /** Canonical PR Visual Recap workflow bundled by the CLI installer. */
2
+ export declare const PR_VISUAL_RECAP_WORKFLOW_YML: string;
3
+ //# sourceMappingURL=pr-visual-recap-workflow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pr-visual-recap-workflow.d.ts","sourceRoot":"","sources":["../src/pr-visual-recap-workflow.ts"],"names":[],"mappings":"AAUA,uEAAuE;AACvE,eAAO,MAAM,4BAA4B,QAAqC,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
5
+ const workflowPath = path.basename(moduleDir) === "dist"
6
+ ? path.join(moduleDir, "workflows", "pr-visual-recap.yml")
7
+ : path.resolve(moduleDir, "../../../.github/workflows/pr-visual-recap.yml");
8
+ /** Canonical PR Visual Recap workflow bundled by the CLI installer. */
9
+ export const PR_VISUAL_RECAP_WORKFLOW_YML = readFileSync(workflowPath, "utf8");
10
+ //# sourceMappingURL=pr-visual-recap-workflow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pr-visual-recap-workflow.js","sourceRoot":"","sources":["../src/pr-visual-recap-workflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC/D,MAAM,YAAY,GAChB,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,KAAK,MAAM;IACjC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,EAAE,qBAAqB,CAAC;IAC1D,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,gDAAgD,CAAC,CAAC;AAEhF,uEAAuE;AACvE,MAAM,CAAC,MAAM,4BAA4B,GAAG,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC","sourcesContent":["import { readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst moduleDir = path.dirname(fileURLToPath(import.meta.url));\nconst workflowPath =\n path.basename(moduleDir) === \"dist\"\n ? path.join(moduleDir, \"workflows\", \"pr-visual-recap.yml\")\n : path.resolve(moduleDir, \"../../../.github/workflows/pr-visual-recap.yml\");\n\n/** Canonical PR Visual Recap workflow bundled by the CLI installer. */\nexport const PR_VISUAL_RECAP_WORKFLOW_YML = readFileSync(workflowPath, \"utf8\");\n"]}