@agent-native/core 0.69.0 → 0.70.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.
Files changed (200) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +48 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/changelog/parse.ts +245 -0
  5. package/corpus/core/src/cli/changelog.ts +199 -0
  6. package/corpus/core/src/cli/index.ts +19 -0
  7. package/corpus/core/src/cli/recap.ts +24 -1
  8. package/corpus/core/src/cli/skills.ts +72 -45
  9. package/corpus/core/src/client/CommandMenu.tsx +183 -75
  10. package/corpus/core/src/client/agent-chat-adapter.ts +44 -7
  11. package/corpus/core/src/client/analytics.ts +181 -0
  12. package/corpus/core/src/client/changelog/Changelog.tsx +321 -0
  13. package/corpus/core/src/client/index.ts +11 -0
  14. package/corpus/core/src/client/mcp-apps/McpAppRenderer.tsx +54 -0
  15. package/corpus/core/src/client/settings/useBuilderStatus.ts +82 -20
  16. package/corpus/core/src/mcp/build-server.ts +1 -1
  17. package/corpus/core/src/mcp/embed-app.ts +112 -6
  18. package/corpus/core/src/server/attribution.ts +211 -0
  19. package/corpus/core/src/server/auth.ts +6 -0
  20. package/corpus/core/src/server/better-auth-instance.ts +49 -5
  21. package/corpus/core/src/server/builder-browser.ts +27 -1
  22. package/corpus/core/src/server/core-routes-plugin.ts +58 -0
  23. package/corpus/core/src/server/google-oauth.ts +19 -6
  24. package/corpus/core/src/shared/mcp-embed-headers.ts +1 -1
  25. package/corpus/core/src/styles/agent-conversation.css +21 -0
  26. package/corpus/core/src/templates/workspace-core/.agents/skills/changelog/SKILL.md +111 -0
  27. package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +46 -14
  28. package/corpus/templates/analytics/CHANGELOG.md +10 -0
  29. package/corpus/templates/analytics/actions/install-dashboard-template.ts +2 -2
  30. package/corpus/templates/analytics/actions/open-traffic-dashboard.ts +1 -1
  31. package/corpus/templates/analytics/actions/update-dashboard.ts +5 -3
  32. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +180 -144
  33. package/corpus/templates/analytics/app/components/layout/Header.tsx +2 -2
  34. package/corpus/templates/analytics/app/components/layout/Layout.tsx +7 -5
  35. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +221 -213
  36. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +6 -3
  37. package/corpus/templates/analytics/app/lib/demo-dashboard-path.ts +1 -1
  38. package/corpus/templates/analytics/app/lib/last-opened.ts +2 -1
  39. package/corpus/templates/analytics/app/pages/Settings.tsx +4 -0
  40. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -3
  41. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +2 -2
  42. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +101 -99
  43. package/corpus/templates/analytics/app/pages/overview/OverviewPage.tsx +1 -1
  44. package/corpus/templates/analytics/app/routes/adhoc.$id.tsx +18 -5
  45. package/corpus/templates/analytics/app/routes/catalog.tsx +1 -1
  46. package/corpus/templates/analytics/app/routes/dashboards.$id.tsx +9 -0
  47. package/corpus/templates/analytics/app/routes/traffic.tsx +1 -1
  48. package/corpus/templates/analytics/changelog/2026-06-23-the-dashboards-and-analyses-sidebar-settings-buttons-now-sta.md +6 -0
  49. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +145 -0
  50. package/corpus/templates/analytics/server/db/index.ts +1 -1
  51. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +9 -2
  52. package/corpus/templates/analytics/server/lib/demo-dashboards.ts +1 -1
  53. package/corpus/templates/analytics/server/plugins/agent-chat.ts +2 -2
  54. package/corpus/templates/analytics/server/plugins/core-routes.ts +6 -6
  55. package/corpus/templates/assets/CHANGELOG.md +10 -0
  56. package/corpus/templates/assets/app/root.tsx +7 -1
  57. package/corpus/templates/assets/app/routes/settings.tsx +6 -0
  58. package/corpus/templates/brain/CHANGELOG.md +10 -0
  59. package/corpus/templates/brain/app/root.tsx +7 -1
  60. package/corpus/templates/brain/app/routes/settings.tsx +8 -1
  61. package/corpus/templates/calendar/CHANGELOG.md +10 -0
  62. package/corpus/templates/calendar/app/pages/Settings.tsx +4 -0
  63. package/corpus/templates/calendar/app/root.tsx +7 -1
  64. package/corpus/templates/chat/CHANGELOG.md +10 -0
  65. package/corpus/templates/chat/app/root.tsx +7 -1
  66. package/corpus/templates/clips/CHANGELOG.md +10 -0
  67. package/corpus/templates/clips/app/components/player/share-dialog.tsx +33 -7
  68. package/corpus/templates/clips/app/components/player/sign-in-prompt-dialog.tsx +8 -0
  69. package/corpus/templates/clips/app/components/player/video-player.tsx +150 -20
  70. package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +7 -11
  71. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +31 -0
  72. package/corpus/templates/clips/app/components/sharing/slack-share-hint.tsx +71 -0
  73. package/corpus/templates/clips/app/root.tsx +7 -1
  74. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +15 -1
  75. package/corpus/templates/clips/app/routes/record.tsx +78 -5
  76. package/corpus/templates/clips/app/routes/share.$shareId.tsx +86 -5
  77. package/corpus/templates/clips/changelog/2026-06-23-clips-shared-in-slack-no-longer-show-a-could-not-start-playb.md +6 -0
  78. package/corpus/templates/clips/changelog/2026-06-23-made-the-play-button-on-shared-and-embedded-clips-scale-with.md +6 -0
  79. package/corpus/templates/clips/changelog/2026-06-23-public-clip-share-links-now-show-whether-they-ll-play-inline.md +6 -0
  80. package/corpus/templates/clips/changelog/2026-06-23-screen-camera-recordings-now-keep-the-presenter-s-face-in-th.md +6 -0
  81. package/corpus/templates/clips/desktop/src/lib/recorder.ts +20 -0
  82. package/corpus/templates/clips/desktop/src/main.tsx +3 -0
  83. package/corpus/templates/clips/desktop/src/overlays/region-record-border.tsx +76 -0
  84. package/corpus/templates/clips/desktop/src/styles.css +49 -0
  85. package/corpus/templates/clips/desktop/src-tauri/capabilities/default.json +2 -1
  86. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +67 -1
  87. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +2 -0
  88. package/corpus/templates/clips/desktop/src-tauri/src/util.rs +4 -1
  89. package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +51 -1
  90. package/corpus/templates/clips/shared/share-attribution.ts +79 -0
  91. package/corpus/templates/content/CHANGELOG.md +10 -0
  92. package/corpus/templates/content/app/root.tsx +7 -1
  93. package/corpus/templates/design/CHANGELOG.md +10 -0
  94. package/corpus/templates/design/actions/generate-design.ts +4 -0
  95. package/corpus/templates/design/app/root.tsx +7 -1
  96. package/corpus/templates/design/app/routes/settings.tsx +10 -2
  97. package/corpus/templates/dispatch/CHANGELOG.md +10 -0
  98. package/corpus/templates/dispatch/app/root.tsx +7 -1
  99. package/corpus/templates/forms/CHANGELOG.md +10 -0
  100. package/corpus/templates/forms/app/root.tsx +7 -1
  101. package/corpus/templates/macros/CHANGELOG.md +10 -0
  102. package/corpus/templates/macros/app/root.tsx +7 -1
  103. package/corpus/templates/mail/CHANGELOG.md +10 -0
  104. package/corpus/templates/mail/app/components/layout/CommandPalette.tsx +3 -0
  105. package/corpus/templates/mail/app/pages/SettingsPage.tsx +27 -0
  106. package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +31 -17
  107. package/corpus/templates/plan/.agents/skills/visual-plan/references/wireframe.md +7 -4
  108. package/corpus/templates/plan/.agents/skills/visual-recap/SKILL.md +34 -24
  109. package/corpus/templates/plan/.agents/skills/visual-recap/references/wireframe.md +7 -4
  110. package/corpus/templates/plan/AGENTS.md +4 -0
  111. package/corpus/templates/plan/CHANGELOG.md +10 -0
  112. package/corpus/templates/plan/actions/view-screen.ts +5 -0
  113. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +98 -1
  114. package/corpus/templates/plan/app/pages/PlansPage.tsx +107 -7
  115. package/corpus/templates/plan/app/root.tsx +7 -1
  116. package/corpus/templates/plan/shared/share-attribution.ts +72 -0
  117. package/corpus/templates/slides/CHANGELOG.md +10 -0
  118. package/corpus/templates/slides/app/root.tsx +7 -1
  119. package/corpus/templates/videos/CHANGELOG.md +4 -151
  120. package/corpus/templates/videos/app/root.tsx +7 -1
  121. package/dist/changelog/parse.d.ts +70 -0
  122. package/dist/changelog/parse.d.ts.map +1 -0
  123. package/dist/changelog/parse.js +200 -0
  124. package/dist/changelog/parse.js.map +1 -0
  125. package/dist/cli/changelog.d.ts +4 -0
  126. package/dist/cli/changelog.d.ts.map +1 -0
  127. package/dist/cli/changelog.js +166 -0
  128. package/dist/cli/changelog.js.map +1 -0
  129. package/dist/cli/index.js +18 -0
  130. package/dist/cli/index.js.map +1 -1
  131. package/dist/cli/recap.d.ts.map +1 -1
  132. package/dist/cli/recap.js +22 -1
  133. package/dist/cli/recap.js.map +1 -1
  134. package/dist/cli/skills.d.ts +3 -3
  135. package/dist/cli/skills.d.ts.map +1 -1
  136. package/dist/cli/skills.js +72 -45
  137. package/dist/cli/skills.js.map +1 -1
  138. package/dist/client/CommandMenu.d.ts +17 -1
  139. package/dist/client/CommandMenu.d.ts.map +1 -1
  140. package/dist/client/CommandMenu.js +49 -13
  141. package/dist/client/CommandMenu.js.map +1 -1
  142. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  143. package/dist/client/agent-chat-adapter.js +43 -6
  144. package/dist/client/agent-chat-adapter.js.map +1 -1
  145. package/dist/client/analytics.d.ts +18 -0
  146. package/dist/client/analytics.d.ts.map +1 -1
  147. package/dist/client/analytics.js +177 -0
  148. package/dist/client/analytics.js.map +1 -1
  149. package/dist/client/changelog/Changelog.d.ts +47 -0
  150. package/dist/client/changelog/Changelog.d.ts.map +1 -0
  151. package/dist/client/changelog/Changelog.js +138 -0
  152. package/dist/client/changelog/Changelog.js.map +1 -0
  153. package/dist/client/index.d.ts +2 -1
  154. package/dist/client/index.d.ts.map +1 -1
  155. package/dist/client/index.js +2 -1
  156. package/dist/client/index.js.map +1 -1
  157. package/dist/client/mcp-apps/McpAppRenderer.d.ts +1 -0
  158. package/dist/client/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  159. package/dist/client/mcp-apps/McpAppRenderer.js +42 -1
  160. package/dist/client/mcp-apps/McpAppRenderer.js.map +1 -1
  161. package/dist/client/settings/useBuilderStatus.d.ts +2 -0
  162. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  163. package/dist/client/settings/useBuilderStatus.js +58 -10
  164. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  165. package/dist/mcp/build-server.js +1 -1
  166. package/dist/mcp/build-server.js.map +1 -1
  167. package/dist/mcp/embed-app.d.ts.map +1 -1
  168. package/dist/mcp/embed-app.js +112 -6
  169. package/dist/mcp/embed-app.js.map +1 -1
  170. package/dist/server/attribution.d.ts +83 -0
  171. package/dist/server/attribution.d.ts.map +1 -0
  172. package/dist/server/attribution.js +178 -0
  173. package/dist/server/attribution.js.map +1 -0
  174. package/dist/server/auth.d.ts.map +1 -1
  175. package/dist/server/auth.js +7 -0
  176. package/dist/server/auth.js.map +1 -1
  177. package/dist/server/better-auth-instance.d.ts +9 -1
  178. package/dist/server/better-auth-instance.d.ts.map +1 -1
  179. package/dist/server/better-auth-instance.js +32 -2
  180. package/dist/server/better-auth-instance.js.map +1 -1
  181. package/dist/server/builder-browser.d.ts +4 -0
  182. package/dist/server/builder-browser.d.ts.map +1 -1
  183. package/dist/server/builder-browser.js +20 -1
  184. package/dist/server/builder-browser.js.map +1 -1
  185. package/dist/server/core-routes-plugin.d.ts +25 -0
  186. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  187. package/dist/server/core-routes-plugin.js +38 -0
  188. package/dist/server/core-routes-plugin.js.map +1 -1
  189. package/dist/server/google-oauth.d.ts.map +1 -1
  190. package/dist/server/google-oauth.js +16 -2
  191. package/dist/server/google-oauth.js.map +1 -1
  192. package/dist/shared/mcp-embed-headers.js +1 -1
  193. package/dist/shared/mcp-embed-headers.js.map +1 -1
  194. package/dist/styles/agent-conversation.css +21 -0
  195. package/dist/templates/workspace-core/.agents/skills/changelog/SKILL.md +111 -0
  196. package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +46 -14
  197. package/package.json +1 -1
  198. package/src/templates/workspace-core/.agents/skills/changelog/SKILL.md +111 -0
  199. package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +46 -14
  200. /package/corpus/templates/analytics/app/routes/{dashboards.tsx → dashboards._index.tsx} +0 -0
package/corpus/README.md CHANGED
@@ -27,5 +27,5 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
27
27
 
28
28
  ## Generated Counts
29
29
 
30
- - core files: 1138
31
- - template files: 3945
30
+ - core files: 1143
31
+ - template files: 3969
@@ -1,5 +1,53 @@
1
1
  # @agent-native/core
2
2
 
3
+ ## 0.70.0
4
+
5
+ ### Minor Changes
6
+
7
+ - b35c8cb: Add an in-app changelog ("What's new") surface. Every app can now ship a
8
+ user-facing `CHANGELOG.md` that renders in the command menu (Cmd+K) and in
9
+ settings. Core provides `<ChangelogDialog>`, `<ChangelogSettingsCard>`, a
10
+ `changelog` prop on `CommandMenu` (with an unseen-release dot), and an
11
+ `agent-native changelog add|release|list` CLI that authors changeset-style
12
+ pending entry files and rolls them up into the dated `CHANGELOG.md`.
13
+
14
+ ### Patch Changes
15
+
16
+ - b35c8cb: Fix MCP app embeds for OpenAI's default web-sandbox origin and keep an Open in new tab fallback available when inline app loading fails.
17
+ - b35c8cb: Return native redirect responses from web OAuth callbacks so successful sign-ins
18
+ land on the clean return URL instead of retaining provider callback query
19
+ parameters.
20
+ - b35c8cb: Add a public `GET /_agent-native/health` route that runs a trivial `SELECT 1`
21
+ to report database liveness and, as a side effect, keep a scale-to-zero
22
+ serverless database (e.g. Neon) warm. A scheduled ping against this endpoint
23
+ prevents the multi-second cold-start that otherwise stalls the first request
24
+ to an idle app. The probe always responds (apps with no database report
25
+ `db: false` rather than failing) and is never cached. Disable it with the
26
+ `disableHealth` core-routes option.
27
+ - b35c8cb: Fix MCP app embeds rendering only a flashing/permanent loading state in Codex
28
+ and Cursor. These standards-track hosts render the `ui://` resource in a strict
29
+ opaque-origin sandbox (`sandbox="allow-scripts"`) and talk to it over the
30
+ postMessage `ui/*` bridge. The shell's handshake was already correct, but for
31
+ these hosts it fell through to self-navigating the sandboxed iframe to the real
32
+ app origin, which tears down the host bridge and loses the opaque-origin auth
33
+ context. Any host connected through the native MCP Apps bridge (Codex, Cursor,
34
+ the SDK App fallback, our own renderer) now transplants the app document into
35
+ the shell — the same robust path Claude already uses — keeping the bridge alive
36
+ and loading via embed-token auth. Also handle the spec `host-context-changed`
37
+ notification and bump the cached resource shell version so hosts refetch.
38
+ - b35c8cb: Fix the missing inline screenshot on PR Visual Recap comments. The `recap shot`
39
+ command runs `page.evaluate`/`addInitScript` payloads that contain named inner
40
+ functions; when the CLI is run through `tsx`/esbuild (CI's trusted-workspace
41
+ path), esbuild's `keepNames` wraps those functions in `__name(...)`, which
42
+ Playwright then serializes into the browser where `__name` is undefined —
43
+ throwing `ReferenceError: __name is not defined`, dropping the screenshot, and
44
+ falling back to a link-only comment. `runShot` now injects an identity `__name`
45
+ shim as the first browser init script, so every main-world payload is safe
46
+ regardless of how the CLI was transpiled (the tsc-built published package, which
47
+ never emits `__name`, is unaffected).
48
+ - b35c8cb: When a long agent run is cut off mid-stream while assembling one large tool input (a generation that exceeds the ~40s soft-timeout window), the auto-continue nudge now points the resumed model at the incremental-edit path for that specific action instead of only handling `create-extension`. Designs (`generate-design` → `edit-design`), plans (`create-visual-plan`/`create-ui-plan` → `update-visual-plan`/`patch-visual-plan-source`), and dashboards (`update-dashboard` incremental `ops`) get tailored "ship a compact first version, then refine" guidance, with a generic compact-first fallback for any other large-payload action. This breaks the re-stream-the-same-oversized-payload thrash loop that could otherwise burn the whole continuation budget without making progress.
49
+ - b35c8cb: Capture first-touch referral attribution and enrich the signup event (`referral_source`, `referrer_user`, UTM, `first_touch_path`) to measure app virality. The browser records an anonymous visitor's first-touch context (`ref`/`via`/`utm_*` params, landing path, and referring host) into an `an_attribution` localStorage key and a first-party `an_ft` cookie, and the server-side `signup` event is enriched from that cookie so every template can see where new users came from.
50
+
3
51
  ## 0.69.0
4
52
 
5
53
  ### Minor Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.69.0",
3
+ "version": "0.70.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Shared, dependency-free changelog parsing + serialization.
3
+ *
4
+ * Used by BOTH:
5
+ * - the browser bundle (rendering an app's CHANGELOG.md in the command
6
+ * menu / settings "What's new" surface), and
7
+ * - the `agent-native changelog` CLI (rolling pending entry files up into
8
+ * CHANGELOG.md).
9
+ *
10
+ * Keep this file isomorphic: no Node, no browser, no third-party deps. The
11
+ * markdown shape is the conventional "Keep a Changelog" layout — a top-level
12
+ * `# Changelog` heading followed by one `## <release>` section per release.
13
+ */
14
+
15
+ /** A single released section of a CHANGELOG.md file. */
16
+ export interface ChangelogEntry {
17
+ /** Stable id derived from the heading — used for "unseen" tracking. */
18
+ id: string;
19
+ /** Raw heading text, e.g. `2026-06-23` or `v1.2.0 — 2026-06-23`. */
20
+ title: string;
21
+ /** ISO date (YYYY-MM-DD) extracted from the heading, if present. */
22
+ date?: string;
23
+ /** Version label extracted from the heading, if present. */
24
+ version?: string;
25
+ /** Markdown body beneath the heading (until the next `## ` section). */
26
+ body: string;
27
+ }
28
+
29
+ /** A not-yet-released entry authored as a `changelog/<file>.md` file. */
30
+ export interface PendingChangelogEntry {
31
+ /** Category — `added`, `improved`, `fixed`, `changed`, etc. */
32
+ type: ChangelogChangeType;
33
+ /** ISO date the entry was authored (YYYY-MM-DD). */
34
+ date?: string;
35
+ /** User-facing description (markdown, single bullet). */
36
+ text: string;
37
+ }
38
+
39
+ export type ChangelogChangeType =
40
+ | "added"
41
+ | "improved"
42
+ | "fixed"
43
+ | "changed"
44
+ | "removed"
45
+ | "security";
46
+
47
+ /**
48
+ * Order changes are grouped under a release heading. Anything not listed here
49
+ * falls back to the "changed" group, then renders in insertion order.
50
+ */
51
+ export const CHANGELOG_GROUP_ORDER: ChangelogChangeType[] = [
52
+ "added",
53
+ "improved",
54
+ "fixed",
55
+ "changed",
56
+ "removed",
57
+ "security",
58
+ ];
59
+
60
+ const GROUP_LABELS: Record<ChangelogChangeType, string> = {
61
+ added: "Added",
62
+ improved: "Improved",
63
+ fixed: "Fixed",
64
+ changed: "Changed",
65
+ removed: "Removed",
66
+ security: "Security",
67
+ };
68
+
69
+ const ISO_DATE = /(\d{4}-\d{2}-\d{2})/;
70
+
71
+ /** Lowercase, hyphenate, and strip to a URL/id-safe slug. */
72
+ export function changelogSlug(value: string): string {
73
+ return value
74
+ .toLowerCase()
75
+ .replace(/[^a-z0-9]+/g, "-")
76
+ .replace(/^-+|-+$/g, "")
77
+ .slice(0, 60);
78
+ }
79
+
80
+ function normalizeType(raw: string | undefined): ChangelogChangeType {
81
+ const value = (raw ?? "").trim().toLowerCase();
82
+ if ((CHANGELOG_GROUP_ORDER as string[]).includes(value)) {
83
+ return value as ChangelogChangeType;
84
+ }
85
+ // Friendly aliases.
86
+ if (value === "feature" || value === "new" || value === "add") return "added";
87
+ if (value === "improvement" || value === "enhancement" || value === "perf") {
88
+ return "improved";
89
+ }
90
+ if (value === "fix" || value === "bugfix" || value === "bug") return "fixed";
91
+ if (value === "remove" || value === "deprecated") return "removed";
92
+ return "changed";
93
+ }
94
+
95
+ /**
96
+ * Parse a CHANGELOG.md document into structured release entries.
97
+ *
98
+ * Tolerant by design: an empty or malformed file yields an empty list rather
99
+ * than throwing, so a missing/partial changelog never breaks the UI.
100
+ */
101
+ export function parseChangelog(markdown: string): ChangelogEntry[] {
102
+ if (!markdown || typeof markdown !== "string") return [];
103
+
104
+ const lines = markdown.split(/\r?\n/);
105
+ const entries: ChangelogEntry[] = [];
106
+ const seenIds = new Set<string>();
107
+
108
+ let currentTitle: string | null = null;
109
+ let bodyLines: string[] = [];
110
+
111
+ const flush = () => {
112
+ if (currentTitle === null) return;
113
+ const title = currentTitle.trim();
114
+ const date = title.match(ISO_DATE)?.[1];
115
+ const version = title.match(/v?\d+\.\d+(?:\.\d+)?/)?.[0];
116
+ // Build a stable, unique id from the heading.
117
+ let base = changelogSlug(title) || "entry";
118
+ let id = base;
119
+ let n = 2;
120
+ while (seenIds.has(id)) id = `${base}-${n++}`;
121
+ seenIds.add(id);
122
+ entries.push({
123
+ id,
124
+ title,
125
+ date,
126
+ version: version && version !== date ? version : undefined,
127
+ body: bodyLines.join("\n").trim(),
128
+ });
129
+ currentTitle = null;
130
+ bodyLines = [];
131
+ };
132
+
133
+ for (const line of lines) {
134
+ // `## ` (but not `### `) starts a new release section.
135
+ const match = /^##\s+(?!#)(.+?)\s*$/.exec(line);
136
+ if (match) {
137
+ flush();
138
+ currentTitle = stripBrackets(match[1]);
139
+ continue;
140
+ }
141
+ // Skip the top-level `# Changelog` title and anything before the first `##`.
142
+ if (currentTitle === null) continue;
143
+ bodyLines.push(line);
144
+ }
145
+ flush();
146
+
147
+ return entries;
148
+ }
149
+
150
+ /** `## [1.2.0]` → `1.2.0`; leaves un-bracketed headings untouched. */
151
+ function stripBrackets(title: string): string {
152
+ return title.replace(/^\[(.+?)\]\s*/, "$1 ").trim();
153
+ }
154
+
155
+ /**
156
+ * Parse a pending `changelog/<file>.md` entry: optional `---` frontmatter
157
+ * (`type:` / `date:`) followed by the markdown description body.
158
+ */
159
+ export function parsePendingEntry(content: string): PendingChangelogEntry {
160
+ let type: string | undefined;
161
+ let date: string | undefined;
162
+ let body = content;
163
+
164
+ const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(content);
165
+ if (fm) {
166
+ for (const raw of fm[1].split(/\r?\n/)) {
167
+ const kv = /^([a-zA-Z]+)\s*:\s*(.+?)\s*$/.exec(raw);
168
+ if (!kv) continue;
169
+ const key = kv[1].toLowerCase();
170
+ const value = kv[2].replace(/^["']|["']$/g, "").trim();
171
+ if (key === "type") type = value;
172
+ else if (key === "date") date = value.match(ISO_DATE)?.[1] ?? value;
173
+ }
174
+ body = content.slice(fm[0].length);
175
+ }
176
+
177
+ return {
178
+ type: normalizeType(type),
179
+ date,
180
+ text: body.trim(),
181
+ };
182
+ }
183
+
184
+ /**
185
+ * Render a set of pending entries as a single dated release section (the body
186
+ * that goes beneath a `## <date>` heading). Groups bullets by type.
187
+ */
188
+ export function renderReleaseBody(entries: PendingChangelogEntry[]): string {
189
+ const groups = new Map<ChangelogChangeType, string[]>();
190
+ for (const entry of entries) {
191
+ const text = entry.text.trim();
192
+ if (!text) continue;
193
+ const bullet = text.includes("\n")
194
+ ? // Preserve multi-line bodies, indenting continuation lines.
195
+ text
196
+ .split(/\r?\n/)
197
+ .map((l, i) => (i === 0 ? `- ${l}` : ` ${l}`))
198
+ .join("\n")
199
+ : `- ${text}`;
200
+ const list = groups.get(entry.type) ?? [];
201
+ list.push(bullet);
202
+ groups.set(entry.type, list);
203
+ }
204
+
205
+ const sections: string[] = [];
206
+ for (const type of CHANGELOG_GROUP_ORDER) {
207
+ const list = groups.get(type);
208
+ if (!list?.length) continue;
209
+ sections.push(`### ${GROUP_LABELS[type]}\n\n${list.join("\n")}`);
210
+ }
211
+ return sections.join("\n\n");
212
+ }
213
+
214
+ const CHANGELOG_HEADER =
215
+ "# Changelog\n\n" +
216
+ "All notable user-facing changes to this app are documented here.\n";
217
+
218
+ /**
219
+ * Roll a batch of pending entries into an existing CHANGELOG.md document,
220
+ * prepending a new `## <date>` section above the most recent release. Returns
221
+ * the full updated document. Pure — the CLI handles file IO and deletion.
222
+ */
223
+ export function rollupChangelog(
224
+ existing: string,
225
+ pending: PendingChangelogEntry[],
226
+ releaseDate: string,
227
+ ): string {
228
+ const body = renderReleaseBody(pending);
229
+ if (!body) return existing || `${CHANGELOG_HEADER}`;
230
+
231
+ const section = `## ${releaseDate}\n\n${body}\n`;
232
+
233
+ const doc = (existing || CHANGELOG_HEADER).replace(/\s+$/, "");
234
+ // Insert the new section immediately before the first existing `## ` release
235
+ // so the header/intro stays on top and releases stay newest-first.
236
+ const firstRelease = doc.search(/^##\s+(?!#)/m);
237
+ if (firstRelease === -1) {
238
+ return `${doc}\n\n${section}\n`;
239
+ }
240
+ const head = doc.slice(0, firstRelease).replace(/\s+$/, "");
241
+ const rest = doc.slice(firstRelease);
242
+ return `${head}\n\n${section}\n${rest}\n`;
243
+ }
244
+
245
+ export { CHANGELOG_HEADER, GROUP_LABELS };
@@ -0,0 +1,199 @@
1
+ /**
2
+ * `agent-native changelog` — author and roll up an app's user-facing changelog.
3
+ *
4
+ * The model mirrors changesets: instead of editing the shared CHANGELOG.md
5
+ * directly (which conflicts when many agents work in parallel), each change
6
+ * drops a small pending entry file under `changelog/`. A later `release` rolls
7
+ * every pending file up into a single dated section of CHANGELOG.md.
8
+ *
9
+ * agent-native changelog add "Recordings can be trimmed before sharing" --type added
10
+ * agent-native changelog release # roll pending → CHANGELOG.md (today)
11
+ * agent-native changelog list # show pending + released
12
+ *
13
+ * Runs in the current app directory (process.cwd()).
14
+ */
15
+ import fs from "fs";
16
+ import path from "path";
17
+
18
+ import {
19
+ parsePendingEntry,
20
+ parseChangelog,
21
+ rollupChangelog,
22
+ changelogSlug,
23
+ CHANGELOG_HEADER,
24
+ type ChangelogChangeType,
25
+ } from "../changelog/parse.js";
26
+
27
+ const CHANGELOG_FILE = "CHANGELOG.md";
28
+ const PENDING_DIR = "changelog";
29
+
30
+ function todayIso(): string {
31
+ // Local date in YYYY-MM-DD. The CLI is the one place a wall-clock read is
32
+ // appropriate (unlike workflow scripts), so `new Date()` is fine here.
33
+ const d = new Date();
34
+ const pad = (n: number) => String(n).padStart(2, "0");
35
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
36
+ }
37
+
38
+ /** Minimal flag parser: supports `--key value` and `--key=value`. */
39
+ function parseFlags(args: string[]): {
40
+ flags: Record<string, string>;
41
+ rest: string[];
42
+ } {
43
+ const flags: Record<string, string> = {};
44
+ const rest: string[] = [];
45
+ for (let i = 0; i < args.length; i++) {
46
+ const arg = args[i];
47
+ if (arg.startsWith("--")) {
48
+ const eq = arg.indexOf("=");
49
+ if (eq !== -1) {
50
+ flags[arg.slice(2, eq)] = arg.slice(eq + 1);
51
+ } else {
52
+ const next = args[i + 1];
53
+ if (next && !next.startsWith("--")) {
54
+ flags[arg.slice(2)] = next;
55
+ i++;
56
+ } else {
57
+ flags[arg.slice(2)] = "true";
58
+ }
59
+ }
60
+ } else {
61
+ rest.push(arg);
62
+ }
63
+ }
64
+ return { flags, rest };
65
+ }
66
+
67
+ function printUsage(): void {
68
+ console.log(
69
+ [
70
+ "Usage:",
71
+ ' agent-native changelog add "<summary>" [--type added|improved|fixed|changed|removed|security] [--date YYYY-MM-DD]',
72
+ " agent-native changelog release [--date YYYY-MM-DD]",
73
+ " agent-native changelog list",
74
+ "",
75
+ "Entries are user-facing notes. `add` writes a pending file under",
76
+ ` ${PENDING_DIR}/; \`release\` rolls all pending files into ${CHANGELOG_FILE}.`,
77
+ ].join("\n"),
78
+ );
79
+ }
80
+
81
+ function cmdAdd(args: string[]): number {
82
+ const { flags, rest } = parseFlags(args);
83
+ const summary = rest.join(" ").trim();
84
+ if (!summary) {
85
+ console.error('Provide a summary, e.g. changelog add "Faster search"');
86
+ return 1;
87
+ }
88
+ const type = (flags.type ?? "changed") as ChangelogChangeType;
89
+ const date = flags.date?.match(/\d{4}-\d{2}-\d{2}/)?.[0] ?? todayIso();
90
+
91
+ const dir = path.resolve(PENDING_DIR);
92
+ fs.mkdirSync(dir, { recursive: true });
93
+
94
+ const slug = changelogSlug(summary) || "entry";
95
+ let file = path.join(dir, `${date}-${slug}.md`);
96
+ let n = 2;
97
+ while (fs.existsSync(file)) {
98
+ file = path.join(dir, `${date}-${slug}-${n++}.md`);
99
+ }
100
+
101
+ // Blank line after the closing frontmatter delimiter so the generated file
102
+ // is already Prettier-clean (Markdown requires a blank line before body text)
103
+ // and never trips the repo fmt check.
104
+ const content = `---\ntype: ${type}\ndate: ${date}\n---\n\n${summary}\n`;
105
+ fs.writeFileSync(file, content, "utf-8");
106
+ console.log(`Added changelog entry: ${path.relative(process.cwd(), file)}`);
107
+ return 0;
108
+ }
109
+
110
+ function readPending(): { file: string; content: string }[] {
111
+ const dir = path.resolve(PENDING_DIR);
112
+ if (!fs.existsSync(dir)) return [];
113
+ return fs
114
+ .readdirSync(dir)
115
+ .filter((f) => f.endsWith(".md") && f.toLowerCase() !== "readme.md")
116
+ .sort()
117
+ .map((f) => ({
118
+ file: path.join(dir, f),
119
+ content: fs.readFileSync(path.join(dir, f), "utf-8"),
120
+ }));
121
+ }
122
+
123
+ function cmdRelease(args: string[]): number {
124
+ const { flags } = parseFlags(args);
125
+ const date = flags.date?.match(/\d{4}-\d{2}-\d{2}/)?.[0] ?? todayIso();
126
+
127
+ const pendingFiles = readPending();
128
+ if (pendingFiles.length === 0) {
129
+ console.log("No pending changelog entries to release.");
130
+ return 0;
131
+ }
132
+
133
+ const pending = pendingFiles.map((p) => parsePendingEntry(p.content));
134
+ const changelogPath = path.resolve(CHANGELOG_FILE);
135
+ const existing = fs.existsSync(changelogPath)
136
+ ? fs.readFileSync(changelogPath, "utf-8")
137
+ : "";
138
+
139
+ const next = rollupChangelog(existing, pending, date);
140
+ fs.writeFileSync(changelogPath, next, "utf-8");
141
+
142
+ // Remove the pending entries now that they're released.
143
+ for (const p of pendingFiles) fs.rmSync(p.file);
144
+
145
+ console.log(
146
+ `Released ${pendingFiles.length} entr${
147
+ pendingFiles.length === 1 ? "y" : "ies"
148
+ } into ${CHANGELOG_FILE} (## ${date}).`,
149
+ );
150
+ return 0;
151
+ }
152
+
153
+ function cmdList(): number {
154
+ const pending = readPending();
155
+ const changelogPath = path.resolve(CHANGELOG_FILE);
156
+ const released = fs.existsSync(changelogPath)
157
+ ? parseChangelog(fs.readFileSync(changelogPath, "utf-8"))
158
+ : [];
159
+
160
+ console.log(`Pending (${pending.length}):`);
161
+ for (const p of pending) {
162
+ const entry = parsePendingEntry(p.content);
163
+ console.log(
164
+ ` - [${entry.type}] ${entry.text.split("\n")[0]} (${path.basename(
165
+ p.file,
166
+ )})`,
167
+ );
168
+ }
169
+ console.log("");
170
+ console.log(`Released (${released.length}):`);
171
+ for (const r of released.slice(0, 10)) console.log(` - ${r.title}`);
172
+ return 0;
173
+ }
174
+
175
+ export async function runChangelog(args: string[]): Promise<number> {
176
+ const sub = args[0];
177
+ const rest = args.slice(1);
178
+ switch (sub) {
179
+ case "add":
180
+ return cmdAdd(rest);
181
+ case "release":
182
+ return cmdRelease(rest);
183
+ case "list":
184
+ return cmdList();
185
+ case undefined:
186
+ case "help":
187
+ case "--help":
188
+ case "-h":
189
+ printUsage();
190
+ return 0;
191
+ default:
192
+ console.error(`Unknown changelog subcommand: ${sub}\n`);
193
+ printUsage();
194
+ return 1;
195
+ }
196
+ }
197
+
198
+ // Re-export header for callers that want to seed an empty CHANGELOG.md.
199
+ export { CHANGELOG_HEADER };
@@ -827,6 +827,21 @@ switch (command) {
827
827
  break;
828
828
  }
829
829
 
830
+ case "changelog": {
831
+ // Author and roll up the app's user-facing changelog (changeset-style
832
+ // pending entry files → a dated CHANGELOG.md section).
833
+ import("./changelog.js")
834
+ .then(async (m) => {
835
+ const code = await m.runChangelog(args);
836
+ process.exit(code);
837
+ })
838
+ .catch((err) => {
839
+ console.error(err?.message ?? err);
840
+ process.exit(1);
841
+ });
842
+ break;
843
+ }
844
+
830
845
  case "--version":
831
846
  case "-v": {
832
847
  console.log(_version);
@@ -906,6 +921,10 @@ Usage:
906
921
  Pass a URL instead of a name for a generic
907
922
  research-and-integrate blueprint. --list to
908
923
  browse available blueprints.
924
+ agent-native changelog <cmd> Author the app's user-facing changelog.
925
+ cmds: add "<summary>" [--type added|fixed|...] |
926
+ release | list. Pending entries live in
927
+ changelog/; 'release' rolls them into CHANGELOG.md.
909
928
  agent-native audit-agent-web Audit a public URL for agent-readable surfaces
910
929
  agent-native eval [pattern] Run the app's evals (**/*.eval.ts, evals/*.ts)
911
930
  and exit non-zero if any scores below its
@@ -1932,7 +1932,7 @@ export function buildCommentBody(env: NodeJS.ProcessEnv = process.env): string {
1932
1932
  lines.push(`</a>`);
1933
1933
  lines.push("");
1934
1934
  }
1935
- lines.push(`**[Open the full interactive recap](${safeUrl})**`);
1935
+ lines.push(`**Open the [full interactive recap](${safeUrl})**`);
1936
1936
  if (env.DIFF_HUGE === "true") {
1937
1937
  lines.push("");
1938
1938
  lines.push(
@@ -2546,6 +2546,25 @@ const RECAP_SHOT_VIEWPORT = {
2546
2546
  };
2547
2547
  const RECAP_SHOT_DEVICE_SCALE_FACTOR = 2;
2548
2548
 
2549
+ /**
2550
+ * Identity shim for esbuild's `__name` helper, injected into the browser before
2551
+ * any screenshot init script or `page.evaluate` payload.
2552
+ *
2553
+ * esbuild/tsx `keepNames` (on by default) rewrites a named inner function — e.g.
2554
+ * `const readHeights = (…) => {…}` inside a `page.evaluate` callback — into
2555
+ * `__name(() => {…}, "readHeights")`. Playwright serializes that callback with
2556
+ * `Function.prototype.toString` and runs it in the page, where `__name` does not
2557
+ * exist, throwing `ReferenceError: __name is not defined` and silently dropping
2558
+ * the recap's inline PR-comment screenshot. CI's trusted-workspace path runs this
2559
+ * CLI through `tsx` (esbuild), so it fires there even though the tsc-built
2560
+ * published package never emits `__name`. Defining `__name` as an identity
2561
+ * function (esbuild's helper returns the target unchanged) makes every main-world
2562
+ * payload safe regardless of how the CLI was transpiled. Kept as a raw string so
2563
+ * esbuild can't rewrite the shim itself.
2564
+ */
2565
+ const RECAP_SHOT_NAME_SHIM =
2566
+ "globalThis.__name = globalThis.__name || function (value) { return value; };";
2567
+
2549
2568
  type PlaywrightModule = { chromium: import("playwright").BrowserType };
2550
2569
 
2551
2570
  async function defaultImportPlaywright(): Promise<PlaywrightModule> {
@@ -2704,6 +2723,10 @@ export async function runShot(
2704
2723
  deviceScaleFactor: RECAP_SHOT_DEVICE_SCALE_FACTOR,
2705
2724
  ...(theme ? { colorScheme: theme } : {}),
2706
2725
  });
2726
+ // Must run before the theme init script and every page.evaluate below so
2727
+ // esbuild/tsx `keepNames` wrappers don't throw `__name is not defined` in
2728
+ // the browser (see RECAP_SHOT_NAME_SHIM).
2729
+ await context.addInitScript(RECAP_SHOT_NAME_SHIM);
2707
2730
  if (theme) {
2708
2731
  await context.addInitScript(
2709
2732
  ({ background, nextTheme }) => {