@agent-native/core 0.84.55 → 0.84.56
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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +6 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/recap.ts +146 -5
- package/corpus/templates/plan/actions/create-visual-recap.ts +39 -0
- package/corpus/templates/plan/actions/list-visual-plans.ts +2 -0
- package/corpus/templates/plan/actions/view-screen.ts +2 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +279 -114
- package/corpus/templates/plan/changelog/2026-07-02-plan-comment-shortcuts-now-open-comment-mode-from-the-keyboa.md +6 -0
- package/corpus/templates/plan/server/db/schema.ts +3 -0
- package/corpus/templates/plan/server/lib/comment-notifications.ts +21 -2
- package/corpus/templates/plan/server/plans.ts +6 -0
- package/corpus/templates/plan/server/plugins/db.ts +11 -0
- package/corpus/templates/plan/shared/comment-context.ts +1 -0
- package/corpus/templates/plan/shared/types.ts +4 -0
- package/dist/cli/recap.d.ts +14 -0
- package/dist/cli/recap.d.ts.map +1 -1
- package/dist/cli/recap.js +88 -5
- package/dist/cli/recap.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.84.
|
|
3
|
+
"version": "0.84.56",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -1634,6 +1634,20 @@ type GitHubComment = {
|
|
|
1634
1634
|
|
|
1635
1635
|
type GitHubPullRequest = {
|
|
1636
1636
|
head?: { sha?: string | null } | null;
|
|
1637
|
+
user?: { login?: string | null; type?: string | null } | null;
|
|
1638
|
+
};
|
|
1639
|
+
|
|
1640
|
+
type GitHubUserProfile = {
|
|
1641
|
+
login?: string | null;
|
|
1642
|
+
name?: string | null;
|
|
1643
|
+
email?: string | null;
|
|
1644
|
+
};
|
|
1645
|
+
|
|
1646
|
+
type GitHubPullRequestCommit = {
|
|
1647
|
+
author?: { login?: string | null } | null;
|
|
1648
|
+
commit?: {
|
|
1649
|
+
author?: { name?: string | null; email?: string | null } | null;
|
|
1650
|
+
} | null;
|
|
1637
1651
|
};
|
|
1638
1652
|
|
|
1639
1653
|
function repoParts(repoFullName: string): { owner: string; repo: string } {
|
|
@@ -1642,6 +1656,20 @@ function repoParts(repoFullName: string): { owner: string; repo: string } {
|
|
|
1642
1656
|
return { owner, repo };
|
|
1643
1657
|
}
|
|
1644
1658
|
|
|
1659
|
+
function nonEmptyTrimmed(value: string | null | undefined): string | undefined {
|
|
1660
|
+
const trimmed = value?.trim();
|
|
1661
|
+
return trimmed || undefined;
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
function normalizeSourceAuthorEmail(
|
|
1665
|
+
email: string | null | undefined,
|
|
1666
|
+
): string | undefined {
|
|
1667
|
+
const trimmed = email?.trim().toLowerCase();
|
|
1668
|
+
if (!trimmed || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) return undefined;
|
|
1669
|
+
if (trimmed.endsWith("@users.noreply.github.com")) return undefined;
|
|
1670
|
+
return trimmed;
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1645
1673
|
async function githubRequest<T>(
|
|
1646
1674
|
token: string,
|
|
1647
1675
|
apiPath: string,
|
|
@@ -1667,6 +1695,71 @@ async function githubRequest<T>(
|
|
|
1667
1695
|
return (await res.json()) as T;
|
|
1668
1696
|
}
|
|
1669
1697
|
|
|
1698
|
+
export async function resolveGitHubPullRequestAuthor(input: {
|
|
1699
|
+
token: string;
|
|
1700
|
+
repo: string;
|
|
1701
|
+
pr: string;
|
|
1702
|
+
fetchFn?: typeof fetch;
|
|
1703
|
+
}): Promise<{
|
|
1704
|
+
email?: string;
|
|
1705
|
+
name?: string;
|
|
1706
|
+
login?: string;
|
|
1707
|
+
}> {
|
|
1708
|
+
const fn = input.fetchFn ?? fetch;
|
|
1709
|
+
const { owner, repo } = repoParts(input.repo);
|
|
1710
|
+
const pr = await githubRequest<GitHubPullRequest>(
|
|
1711
|
+
input.token,
|
|
1712
|
+
`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(
|
|
1713
|
+
repo,
|
|
1714
|
+
)}/pulls/${encodeURIComponent(input.pr)}`,
|
|
1715
|
+
{},
|
|
1716
|
+
fn,
|
|
1717
|
+
);
|
|
1718
|
+
const login = nonEmptyTrimmed(pr.user?.login);
|
|
1719
|
+
const profile = login
|
|
1720
|
+
? await githubRequest<GitHubUserProfile>(
|
|
1721
|
+
input.token,
|
|
1722
|
+
`/users/${encodeURIComponent(login)}`,
|
|
1723
|
+
{},
|
|
1724
|
+
fn,
|
|
1725
|
+
).catch(() => null)
|
|
1726
|
+
: null;
|
|
1727
|
+
const profileEmail = normalizeSourceAuthorEmail(profile?.email);
|
|
1728
|
+
const profileName = nonEmptyTrimmed(profile?.name);
|
|
1729
|
+
let commitEmail: string | undefined;
|
|
1730
|
+
let commitName: string | undefined;
|
|
1731
|
+
if (!profileEmail) {
|
|
1732
|
+
const commits = await githubRequest<GitHubPullRequestCommit[]>(
|
|
1733
|
+
input.token,
|
|
1734
|
+
`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(
|
|
1735
|
+
repo,
|
|
1736
|
+
)}/pulls/${encodeURIComponent(input.pr)}/commits?per_page=100`,
|
|
1737
|
+
{},
|
|
1738
|
+
fn,
|
|
1739
|
+
).catch(() => []);
|
|
1740
|
+
for (const commit of commits) {
|
|
1741
|
+
const commitLogin = nonEmptyTrimmed(commit.author?.login);
|
|
1742
|
+
if (
|
|
1743
|
+
login &&
|
|
1744
|
+
commitLogin &&
|
|
1745
|
+
commitLogin.toLowerCase() !== login.toLowerCase()
|
|
1746
|
+
) {
|
|
1747
|
+
continue;
|
|
1748
|
+
}
|
|
1749
|
+
const email = normalizeSourceAuthorEmail(commit.commit?.author?.email);
|
|
1750
|
+
if (!email) continue;
|
|
1751
|
+
commitEmail = email;
|
|
1752
|
+
commitName = nonEmptyTrimmed(commit.commit?.author?.name);
|
|
1753
|
+
break;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
return {
|
|
1757
|
+
email: profileEmail ?? commitEmail,
|
|
1758
|
+
name: profileName ?? commitName ?? login,
|
|
1759
|
+
login,
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1670
1763
|
export async function isPullRequestHeadCurrent(input: {
|
|
1671
1764
|
token: string;
|
|
1672
1765
|
owner: string;
|
|
@@ -1995,14 +2088,15 @@ export function buildCommentBody(env: NodeJS.ProcessEnv = process.env): string {
|
|
|
1995
2088
|
|
|
1996
2089
|
lines.push(`Here's a [visual recap](${safeUrl}) of what changed:`);
|
|
1997
2090
|
lines.push("");
|
|
1998
|
-
|
|
2091
|
+
const pictureParts = [`<picture>`];
|
|
1999
2092
|
if (lightImageUrl && darkImageUrl) {
|
|
2000
|
-
|
|
2093
|
+
pictureParts.push(
|
|
2001
2094
|
` <source media="(prefers-color-scheme: dark)" srcset="${darkImageUrl}">`,
|
|
2002
2095
|
);
|
|
2003
2096
|
}
|
|
2004
|
-
|
|
2005
|
-
|
|
2097
|
+
pictureParts.push(` <img alt="Visual recap" src="${fallbackImageUrl}">`);
|
|
2098
|
+
pictureParts.push(`</picture>`);
|
|
2099
|
+
lines.push(`<a href="${safeUrl}">${pictureParts.join("")}</a>`);
|
|
2006
2100
|
lines.push("");
|
|
2007
2101
|
lines.push(`**Open the [full interactive recap](${safeUrl})**`);
|
|
2008
2102
|
if (env.DIFF_HUGE === "true") {
|
|
@@ -2297,6 +2391,7 @@ function recapPublishIdempotencyKey(input: {
|
|
|
2297
2391
|
export async function publishRecapSource(input: {
|
|
2298
2392
|
appUrl: string;
|
|
2299
2393
|
token: string;
|
|
2394
|
+
githubToken?: string;
|
|
2300
2395
|
sourcePath?: string;
|
|
2301
2396
|
out?: string;
|
|
2302
2397
|
prevPlanId?: string;
|
|
@@ -2308,6 +2403,9 @@ export async function publishRecapSource(input: {
|
|
|
2308
2403
|
sourcePrNumber?: string;
|
|
2309
2404
|
sourcePrState?: string;
|
|
2310
2405
|
sourcePrMergedAt?: string;
|
|
2406
|
+
sourceAuthorEmail?: string;
|
|
2407
|
+
sourceAuthorName?: string;
|
|
2408
|
+
sourceAuthorLogin?: string;
|
|
2311
2409
|
fetchFn?: typeof fetch;
|
|
2312
2410
|
cwd?: string;
|
|
2313
2411
|
}): Promise<{ ok: true; url: string; out: string }> {
|
|
@@ -2330,6 +2428,34 @@ export async function publishRecapSource(input: {
|
|
|
2330
2428
|
(sourceRepo && sourcePrNumber ? "pull-request" : undefined);
|
|
2331
2429
|
const sourcePrState =
|
|
2332
2430
|
input.sourcePrState ?? (input.sourcePrMergedAt ? "merged" : undefined);
|
|
2431
|
+
const explicitSourceAuthor = {
|
|
2432
|
+
email: normalizeSourceAuthorEmail(input.sourceAuthorEmail),
|
|
2433
|
+
name: nonEmptyTrimmed(input.sourceAuthorName),
|
|
2434
|
+
login: nonEmptyTrimmed(input.sourceAuthorLogin),
|
|
2435
|
+
};
|
|
2436
|
+
let resolvedSourceAuthor:
|
|
2437
|
+
| Awaited<ReturnType<typeof resolveGitHubPullRequestAuthor>>
|
|
2438
|
+
| undefined;
|
|
2439
|
+
if (
|
|
2440
|
+
input.githubToken &&
|
|
2441
|
+
sourceRepo &&
|
|
2442
|
+
sourcePrNumber &&
|
|
2443
|
+
(!explicitSourceAuthor.email ||
|
|
2444
|
+
!explicitSourceAuthor.name ||
|
|
2445
|
+
!explicitSourceAuthor.login)
|
|
2446
|
+
) {
|
|
2447
|
+
resolvedSourceAuthor = await resolveGitHubPullRequestAuthor({
|
|
2448
|
+
token: input.githubToken,
|
|
2449
|
+
repo: sourceRepo,
|
|
2450
|
+
pr: sourcePrNumber,
|
|
2451
|
+
fetchFn: input.fetchFn,
|
|
2452
|
+
}).catch(() => undefined);
|
|
2453
|
+
}
|
|
2454
|
+
const sourceAuthor = {
|
|
2455
|
+
email: explicitSourceAuthor.email ?? resolvedSourceAuthor?.email,
|
|
2456
|
+
name: explicitSourceAuthor.name ?? resolvedSourceAuthor?.name,
|
|
2457
|
+
login: explicitSourceAuthor.login ?? resolvedSourceAuthor?.login,
|
|
2458
|
+
};
|
|
2333
2459
|
const idempotencyKey = recapPublishIdempotencyKey({
|
|
2334
2460
|
prevPlanId: input.prevPlanId,
|
|
2335
2461
|
repo: input.repo,
|
|
@@ -2353,6 +2479,9 @@ export async function publishRecapSource(input: {
|
|
|
2353
2479
|
...(input.sourcePrMergedAt
|
|
2354
2480
|
? { sourcePrMergedAt: input.sourcePrMergedAt }
|
|
2355
2481
|
: {}),
|
|
2482
|
+
...(sourceAuthor.email ? { sourceAuthorEmail: sourceAuthor.email } : {}),
|
|
2483
|
+
...(sourceAuthor.name ? { sourceAuthorName: sourceAuthor.name } : {}),
|
|
2484
|
+
...(sourceAuthor.login ? { sourceAuthorLogin: sourceAuthor.login } : {}),
|
|
2356
2485
|
currentFocus: "visual recap review",
|
|
2357
2486
|
status: "review",
|
|
2358
2487
|
mdx: source.mdx,
|
|
@@ -2470,6 +2599,10 @@ async function runPublish(
|
|
|
2470
2599
|
const result = await publishRecapSource({
|
|
2471
2600
|
appUrl,
|
|
2472
2601
|
token,
|
|
2602
|
+
githubToken:
|
|
2603
|
+
optionalArg(args, "github-token") ??
|
|
2604
|
+
process.env.GH_TOKEN ??
|
|
2605
|
+
process.env.GITHUB_TOKEN,
|
|
2473
2606
|
sourcePath: optionalArg(args, "source") ?? RECAP_SOURCE_FILENAME,
|
|
2474
2607
|
out,
|
|
2475
2608
|
prevPlanId: optionalArg(args, "prev-plan-id"),
|
|
@@ -2481,6 +2614,14 @@ async function runPublish(
|
|
|
2481
2614
|
sourcePrNumber: optionalArg(args, "source-pr-number"),
|
|
2482
2615
|
sourcePrState: optionalArg(args, "source-pr-state"),
|
|
2483
2616
|
sourcePrMergedAt: optionalArg(args, "source-pr-merged-at"),
|
|
2617
|
+
sourceAuthorEmail:
|
|
2618
|
+
optionalArg(args, "source-author-email") ?? process.env.PR_AUTHOR_EMAIL,
|
|
2619
|
+
sourceAuthorName:
|
|
2620
|
+
optionalArg(args, "source-author-name") ?? process.env.PR_AUTHOR_NAME,
|
|
2621
|
+
sourceAuthorLogin:
|
|
2622
|
+
optionalArg(args, "source-author-login") ??
|
|
2623
|
+
process.env.PR_AUTHOR_LOGIN ??
|
|
2624
|
+
process.env.GITHUB_ACTOR,
|
|
2484
2625
|
});
|
|
2485
2626
|
writeGitHubOutput("ok", "true");
|
|
2486
2627
|
writeGitHubOutput("plan_url", result.url);
|
|
@@ -4081,7 +4222,7 @@ Usage:
|
|
|
4081
4222
|
npx @agent-native/core@latest recap block-reference [--app-url <url>] [--out recap-blocks.md]
|
|
4082
4223
|
npx @agent-native/core@latest recap scan --diff <path> [--mode off|high-confidence|strict]
|
|
4083
4224
|
npx @agent-native/core@latest recap build-prompt --pr <n> [--repo owner/name] [--head <sha>] [--app-url <url>] [--diff <path>] [--stat <path>] [--block-reference recap-blocks.md] [--prev-plan-id <id>] [--huge] [--local-files] [--local-dir <folder>] [--skill-source auto|latest|repo] [--out <path>]
|
|
4084
|
-
npx @agent-native/core@latest recap publish [--source recap-source.json] [--out recap-url.txt] [--repo owner/name] [--pr <n>] [--prev-plan-id <id>] [--source-pr-state open|closed|merged] [--source-pr-merged-at <iso>] [--app-url <url>] [--token <planToken>]
|
|
4225
|
+
npx @agent-native/core@latest recap publish [--source recap-source.json] [--out recap-url.txt] [--repo owner/name] [--pr <n>] [--prev-plan-id <id>] [--source-pr-state open|closed|merged] [--source-pr-merged-at <iso>] [--source-author-email <email>] [--source-author-name <name>] [--source-author-login <login>] [--app-url <url>] [--token <planToken>] [--github-token <ghToken>]
|
|
4085
4226
|
npx @agent-native/core@latest recap shot --url <planUrl> [--token <planToken>] [--app-url <url>] [--out recap.png] [--theme light|dark] [--image-cache-key <key>]
|
|
4086
4227
|
npx @agent-native/core@latest recap usage --plan-url <planUrl> --result-file <path> --app-url <url> --token <planToken> [--agent claude|codex] [--model <id>]
|
|
4087
4228
|
npx @agent-native/core@latest recap agent-summary --result-file <path> [--stderr-file <path>] [--exit-code-file <path>] [--agent claude|codex]
|
|
@@ -44,12 +44,24 @@ const sourceTypeSchema = z
|
|
|
44
44
|
|
|
45
45
|
const sourcePrStateSchema = z.enum(["open", "closed", "merged", "unknown"]);
|
|
46
46
|
|
|
47
|
+
const sourceAuthorEmailSchema = z
|
|
48
|
+
.string()
|
|
49
|
+
.trim()
|
|
50
|
+
.email()
|
|
51
|
+
.transform((email) => email.toLowerCase())
|
|
52
|
+
.optional();
|
|
53
|
+
|
|
54
|
+
const sourceAuthorTextSchema = z.string().trim().min(1).optional();
|
|
55
|
+
|
|
47
56
|
type RecapSourceMetadata = {
|
|
48
57
|
sourceType?: string;
|
|
49
58
|
sourceRepo?: string;
|
|
50
59
|
sourcePrNumber?: number;
|
|
51
60
|
sourcePrState?: "open" | "closed" | "merged" | "unknown";
|
|
52
61
|
sourcePrMergedAt?: string;
|
|
62
|
+
sourceAuthorEmail?: string;
|
|
63
|
+
sourceAuthorName?: string;
|
|
64
|
+
sourceAuthorLogin?: string;
|
|
53
65
|
};
|
|
54
66
|
|
|
55
67
|
type RecapVisibility = "private" | "org" | "public";
|
|
@@ -82,17 +94,26 @@ function normalizeRecapSourceMetadata(args: {
|
|
|
82
94
|
sourcePrNumber?: number;
|
|
83
95
|
sourcePrState?: "open" | "closed" | "merged" | "unknown";
|
|
84
96
|
sourcePrMergedAt?: string;
|
|
97
|
+
sourceAuthorEmail?: string;
|
|
98
|
+
sourceAuthorName?: string;
|
|
99
|
+
sourceAuthorLogin?: string;
|
|
85
100
|
}): RecapSourceMetadata {
|
|
86
101
|
const inferred = inferGithubPullRequestSource(args.sourceUrl);
|
|
87
102
|
const sourcePrMergedAt = args.sourcePrMergedAt?.trim();
|
|
88
103
|
const sourcePrState =
|
|
89
104
|
args.sourcePrState ?? (sourcePrMergedAt ? "merged" : undefined);
|
|
105
|
+
const sourceAuthorEmail = args.sourceAuthorEmail?.trim().toLowerCase();
|
|
106
|
+
const sourceAuthorName = args.sourceAuthorName?.trim();
|
|
107
|
+
const sourceAuthorLogin = args.sourceAuthorLogin?.trim();
|
|
90
108
|
return {
|
|
91
109
|
sourceType: args.sourceType ?? inferred.sourceType,
|
|
92
110
|
sourceRepo: args.sourceRepo ?? inferred.sourceRepo,
|
|
93
111
|
sourcePrNumber: args.sourcePrNumber ?? inferred.sourcePrNumber,
|
|
94
112
|
sourcePrState,
|
|
95
113
|
sourcePrMergedAt: sourcePrMergedAt || undefined,
|
|
114
|
+
sourceAuthorEmail: sourceAuthorEmail || undefined,
|
|
115
|
+
sourceAuthorName: sourceAuthorName || undefined,
|
|
116
|
+
sourceAuthorLogin: sourceAuthorLogin || undefined,
|
|
96
117
|
};
|
|
97
118
|
}
|
|
98
119
|
|
|
@@ -229,6 +250,15 @@ export default defineAction({
|
|
|
229
250
|
.min(1)
|
|
230
251
|
.optional()
|
|
231
252
|
.describe("ISO timestamp for when the source pull request was merged."),
|
|
253
|
+
sourceAuthorEmail: sourceAuthorEmailSchema.describe(
|
|
254
|
+
"Email address for the human author of the source PR, used as the default human comment target for recap feedback.",
|
|
255
|
+
),
|
|
256
|
+
sourceAuthorName: sourceAuthorTextSchema.describe(
|
|
257
|
+
"Display name for the human author of the source PR.",
|
|
258
|
+
),
|
|
259
|
+
sourceAuthorLogin: sourceAuthorTextSchema.describe(
|
|
260
|
+
"GitHub login for the human author of the source PR.",
|
|
261
|
+
),
|
|
232
262
|
idempotencyKey: z
|
|
233
263
|
.string()
|
|
234
264
|
.trim()
|
|
@@ -328,6 +358,15 @@ export default defineAction({
|
|
|
328
358
|
...(sourceMetadata.sourcePrMergedAt !== undefined
|
|
329
359
|
? { sourcePrMergedAt: sourceMetadata.sourcePrMergedAt }
|
|
330
360
|
: {}),
|
|
361
|
+
...(sourceMetadata.sourceAuthorEmail !== undefined
|
|
362
|
+
? { sourceAuthorEmail: sourceMetadata.sourceAuthorEmail }
|
|
363
|
+
: {}),
|
|
364
|
+
...(sourceMetadata.sourceAuthorName !== undefined
|
|
365
|
+
? { sourceAuthorName: sourceMetadata.sourceAuthorName }
|
|
366
|
+
: {}),
|
|
367
|
+
...(sourceMetadata.sourceAuthorLogin !== undefined
|
|
368
|
+
? { sourceAuthorLogin: sourceMetadata.sourceAuthorLogin }
|
|
369
|
+
: {}),
|
|
331
370
|
...(idempotencyKey ? { recapIdempotencyKey: idempotencyKey } : {}),
|
|
332
371
|
};
|
|
333
372
|
if (Object.keys(planPatch).length > 0) {
|
|
@@ -77,6 +77,8 @@ export default defineAction({
|
|
|
77
77
|
hostedPlanId: schema.plans.hostedPlanId,
|
|
78
78
|
hostedPlanUrl: schema.plans.hostedPlanUrl,
|
|
79
79
|
sourceUrl: schema.plans.sourceUrl,
|
|
80
|
+
sourceAuthorName: schema.plans.sourceAuthorName,
|
|
81
|
+
sourceAuthorLogin: schema.plans.sourceAuthorLogin,
|
|
80
82
|
createdAt: schema.plans.createdAt,
|
|
81
83
|
updatedAt: schema.plans.updatedAt,
|
|
82
84
|
approvedAt: schema.plans.approvedAt,
|
|
@@ -152,6 +152,8 @@ export default defineAction({
|
|
|
152
152
|
hostedPlanId: schema.plans.hostedPlanId,
|
|
153
153
|
hostedPlanUrl: schema.plans.hostedPlanUrl,
|
|
154
154
|
sourceUrl: schema.plans.sourceUrl,
|
|
155
|
+
sourceAuthorName: schema.plans.sourceAuthorName,
|
|
156
|
+
sourceAuthorLogin: schema.plans.sourceAuthorLogin,
|
|
155
157
|
createdAt: schema.plans.createdAt,
|
|
156
158
|
updatedAt: schema.plans.updatedAt,
|
|
157
159
|
approvedAt: schema.plans.approvedAt,
|