@forgeailab/create-spark 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/architecture-cutline/SKILL.md +96 -0
- package/.claude/skills/board-review/SKILL.md +77 -0
- package/.claude/skills/code-review/SKILL.md +76 -0
- package/.claude/skills/execute-task/SKILL.md +80 -0
- package/.claude/skills/idea-sharpen/SKILL.md +65 -0
- package/.claude/skills/implementation-brief/SKILL.md +87 -0
- package/.claude/skills/mvp-board/SKILL.md +95 -0
- package/.claude/skills/mvp-grill/SKILL.md +60 -0
- package/.claude/skills/mvp-spec/SKILL.md +78 -0
- package/.claude/skills/new-pack/SKILL.md +156 -0
- package/.claude/skills/next-task/SKILL.md +65 -0
- package/.claude/skills/pack-add/SKILL.md +64 -0
- package/.claude/skills/pack-resolve/SKILL.md +67 -0
- package/.claude/skills/parallel-execution/SKILL.md +68 -0
- package/.claude/skills/qa-verify/SKILL.md +77 -0
- package/.claude/skills/risk-check/SKILL.md +88 -0
- package/.claude/skills/sync-board/SKILL.md +76 -0
- package/.claude/skills/ux-theme/SKILL.md +93 -0
- package/.codex/skills/architecture-cutline/SKILL.md +94 -0
- package/.codex/skills/board-review/SKILL.md +75 -0
- package/.codex/skills/code-review/SKILL.md +73 -0
- package/.codex/skills/execute-task/SKILL.md +76 -0
- package/.codex/skills/idea-sharpen/SKILL.md +63 -0
- package/.codex/skills/implementation-brief/SKILL.md +85 -0
- package/.codex/skills/mvp-board/SKILL.md +93 -0
- package/.codex/skills/mvp-grill/SKILL.md +58 -0
- package/.codex/skills/mvp-spec/SKILL.md +76 -0
- package/.codex/skills/new-pack/SKILL.md +153 -0
- package/.codex/skills/next-task/SKILL.md +64 -0
- package/.codex/skills/pack-add/SKILL.md +62 -0
- package/.codex/skills/pack-resolve/SKILL.md +65 -0
- package/.codex/skills/parallel-execution/SKILL.md +66 -0
- package/.codex/skills/qa-verify/SKILL.md +74 -0
- package/.codex/skills/risk-check/SKILL.md +86 -0
- package/.codex/skills/sync-board/SKILL.md +72 -0
- package/.codex/skills/ux-theme/SKILL.md +91 -0
- package/package.json +8 -5
- package/packs/README.md +22 -24
- package/packs/ai-anthropic/files/lib/anthropic.ts +46 -3
- package/packs/ai-anthropic/pack.toml +2 -3
- package/packs/auth-better-auth/files/app/api/auth/[...all]/route.ts +2 -2
- package/packs/auth-better-auth/files/lib/auth.ts +40 -1
- package/packs/auth-better-auth/pack.toml +1 -5
- package/packs/auth-better-auth-pg/files/app/api/auth/[...all]/route.ts +2 -2
- package/packs/auth-better-auth-pg/files/lib/auth.ts +40 -1
- package/packs/auth-better-auth-pg/pack.toml +1 -5
- package/packs/payments-stripe/files/lib/stripe.ts +104 -6
- package/packs/payments-stripe/pack.toml +1 -5
- package/packs/sync-zero/files/components/ZeroProvider.tsx +11 -1
- package/packs/sync-zero/files/lib/zero/client.ts +3 -3
- package/packs/sync-zero/files/lib/zero/schema.ts +15 -2
- package/packs/sync-zero/pack.toml +0 -4
- package/scripts/sync-skills.ts +223 -0
- /package/templates/nextjs/{anvil.config.json → spark.config.json} +0 -0
|
@@ -1,9 +1,107 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import Stripe from 'stripe';
|
|
2
|
+
|
|
3
|
+
type StripeClientOptions = ConstructorParameters<typeof Stripe>[1];
|
|
4
|
+
|
|
5
|
+
declare module 'stripe' {
|
|
6
|
+
namespace Stripe {
|
|
7
|
+
export type StripeConstructorOptions = StripeClientOptions;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createStripeClient(
|
|
12
|
+
secretKey: string,
|
|
13
|
+
options?: Stripe.StripeConstructorOptions,
|
|
14
|
+
): Stripe {
|
|
15
|
+
return new Stripe(secretKey, options);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type CheckoutSessionCreateParams = NonNullable<
|
|
19
|
+
Parameters<Stripe['checkout']['sessions']['create']>[0]
|
|
20
|
+
>;
|
|
21
|
+
|
|
22
|
+
export type CreateCheckoutSessionInput = {
|
|
23
|
+
priceId: string;
|
|
24
|
+
customerId?: string;
|
|
25
|
+
customerEmail?: string;
|
|
26
|
+
successUrl: string;
|
|
27
|
+
cancelUrl: string;
|
|
28
|
+
mode?: CheckoutSessionCreateParams['mode'];
|
|
29
|
+
metadata?: Record<string, string>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
async function createStripeCheckoutSession(
|
|
33
|
+
stripeClient: Stripe,
|
|
34
|
+
{
|
|
35
|
+
priceId,
|
|
36
|
+
customerId,
|
|
37
|
+
customerEmail,
|
|
38
|
+
successUrl,
|
|
39
|
+
cancelUrl,
|
|
40
|
+
mode = 'subscription',
|
|
41
|
+
metadata,
|
|
42
|
+
}: CreateCheckoutSessionInput,
|
|
43
|
+
): Promise<{ url: string; sessionId: string }> {
|
|
44
|
+
if (!customerId && !customerEmail) {
|
|
45
|
+
throw new Error('customerId or customerEmail is required');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const session = await stripeClient.checkout.sessions.create({
|
|
49
|
+
mode,
|
|
50
|
+
customer: customerId,
|
|
51
|
+
customer_email: customerId ? undefined : customerEmail,
|
|
52
|
+
line_items: [
|
|
53
|
+
{
|
|
54
|
+
price: priceId,
|
|
55
|
+
quantity: 1,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
allow_promotion_codes: true,
|
|
59
|
+
success_url: successUrl,
|
|
60
|
+
cancel_url: cancelUrl,
|
|
61
|
+
metadata,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (!session.url) {
|
|
65
|
+
throw new Error('Stripe did not return a checkout URL');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
url: session.url,
|
|
70
|
+
sessionId: session.id,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export type VerifyWebhookSignatureInput = {
|
|
75
|
+
payload: string | Buffer;
|
|
76
|
+
signatureHeader: string;
|
|
77
|
+
secret: string;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
function verifyStripeWebhookSignature(
|
|
81
|
+
stripeClient: Stripe,
|
|
82
|
+
{ payload, signatureHeader, secret }: VerifyWebhookSignatureInput,
|
|
83
|
+
): Promise<Stripe.Event> {
|
|
84
|
+
return stripeClient.webhooks.constructEventAsync(payload, signatureHeader, secret);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type CreateBillingPortalSessionInput = {
|
|
88
|
+
customerId: string;
|
|
89
|
+
returnUrl: string;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
async function createStripeBillingPortalSession(
|
|
93
|
+
stripeClient: Stripe,
|
|
94
|
+
{ customerId, returnUrl }: CreateBillingPortalSessionInput,
|
|
95
|
+
): Promise<{ url: string }> {
|
|
96
|
+
const session = await stripeClient.billingPortal.sessions.create({
|
|
97
|
+
customer: customerId,
|
|
98
|
+
return_url: returnUrl,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
url: session.url,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
7
105
|
|
|
8
106
|
function requireEnv(name: string): string {
|
|
9
107
|
const value = process.env[name];
|
|
@@ -8,12 +8,8 @@ conflicts = ["payments"]
|
|
|
8
8
|
requires_runtime = ["server"]
|
|
9
9
|
compatible_scaffolds = ["nextjs"]
|
|
10
10
|
|
|
11
|
-
[runtime_package]
|
|
12
|
-
package = "@forgeailab/spark-stripe-helpers"
|
|
13
|
-
version = "^0.1"
|
|
14
|
-
|
|
15
11
|
[dependencies]
|
|
16
|
-
runtime = ["@stripe/stripe-js"]
|
|
12
|
+
runtime = ["@stripe/stripe-js", "stripe"]
|
|
17
13
|
|
|
18
14
|
[env]
|
|
19
15
|
required = [
|
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
4
|
+
import type { ZeroOptions } from '@rocicorp/zero';
|
|
5
|
+
import { ZeroProvider as RocicorpZeroProvider } from '@rocicorp/zero/react';
|
|
6
|
+
|
|
7
|
+
type ZeroProviderProps = ZeroOptions & {
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function ZeroProvider({ children, ...options }: ZeroProviderProps) {
|
|
12
|
+
return <RocicorpZeroProvider {...options}>{children}</RocicorpZeroProvider>;
|
|
13
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import type { ZeroOptions } from '@
|
|
3
|
+
import { Zero } from '@rocicorp/zero';
|
|
4
|
+
import type { ZeroOptions } from '@rocicorp/zero';
|
|
5
5
|
import { schema } from './schema';
|
|
6
6
|
|
|
7
7
|
const DEFAULT_ZERO_URL = 'http://localhost:4848';
|
|
@@ -14,5 +14,5 @@ export function createZeroOptions(): ZeroOptions {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export function createZeroClient(options: ZeroOptions = createZeroOptions()) {
|
|
17
|
-
return
|
|
17
|
+
return new Zero(options);
|
|
18
18
|
}
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
ANYONE_CAN,
|
|
3
|
+
createBuilder,
|
|
4
|
+
createSchema,
|
|
5
|
+
definePermissions,
|
|
6
|
+
number,
|
|
7
|
+
string,
|
|
8
|
+
table,
|
|
9
|
+
} from '@rocicorp/zero';
|
|
2
10
|
|
|
3
11
|
// Example schema. Replace with your app's tables.
|
|
4
12
|
const users = table('user')
|
|
@@ -10,8 +18,13 @@ const users = table('user')
|
|
|
10
18
|
})
|
|
11
19
|
.primaryKey('id');
|
|
12
20
|
|
|
13
|
-
export const
|
|
21
|
+
export const schema = createSchema({
|
|
14
22
|
tables: [users],
|
|
15
23
|
});
|
|
24
|
+
export const zql = createBuilder(schema);
|
|
16
25
|
|
|
17
26
|
export type Schema = typeof schema;
|
|
27
|
+
|
|
28
|
+
export const permissions = definePermissions<unknown, Schema>(schema, () => ({
|
|
29
|
+
user: { row: { select: ANYONE_CAN } },
|
|
30
|
+
}));
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { mkdtemp, mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { join, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import {
|
|
6
|
+
parseSkillFrontmatter,
|
|
7
|
+
serializeSkillFrontmatter,
|
|
8
|
+
toCodexFrontmatter,
|
|
9
|
+
} from "../packages/spark/src/internal/skill-utils.ts";
|
|
10
|
+
|
|
11
|
+
type SkillOutput = {
|
|
12
|
+
name: string;
|
|
13
|
+
content: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type Diff =
|
|
17
|
+
| { type: "missing"; path: string }
|
|
18
|
+
| { type: "extra"; path: string }
|
|
19
|
+
| { type: "changed"; path: string };
|
|
20
|
+
|
|
21
|
+
type SyncResult = {
|
|
22
|
+
ok: boolean;
|
|
23
|
+
count: number;
|
|
24
|
+
diffs: Diff[];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type TreeEntry = {
|
|
28
|
+
type: "dir" | "file";
|
|
29
|
+
content?: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export function transformSkillMarkdown(markdown: string, skillName: string): string {
|
|
33
|
+
const { frontmatter, body } = parseSkillFrontmatter(markdown);
|
|
34
|
+
const codexFrontmatter = toCodexFrontmatter(frontmatter);
|
|
35
|
+
const outputFrontmatter = serializeSkillFrontmatter(codexFrontmatter, {
|
|
36
|
+
trailingComments: [
|
|
37
|
+
`# Generated from .claude/skills/${skillName}/SKILL.md — DO NOT EDIT directly`,
|
|
38
|
+
],
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return `---\n${outputFrontmatter}\n---\n${body}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function collectSkillOutputs(root: string): Promise<SkillOutput[]> {
|
|
45
|
+
const sourceRoot = join(root, ".claude", "skills");
|
|
46
|
+
const entries = await readdir(sourceRoot, { withFileTypes: true });
|
|
47
|
+
const outputs: SkillOutput[] = [];
|
|
48
|
+
|
|
49
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
50
|
+
if (!entry.isDirectory()) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const skillName = entry.name;
|
|
55
|
+
const sourceFile = join(sourceRoot, skillName, "SKILL.md");
|
|
56
|
+
if (!existsSync(sourceFile)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const source = await readFile(sourceFile, "utf8");
|
|
61
|
+
outputs.push({
|
|
62
|
+
name: skillName,
|
|
63
|
+
content: transformSkillMarkdown(source, skillName),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return outputs;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function writeOutputs(targetRoot: string, outputs: SkillOutput[]): Promise<void> {
|
|
71
|
+
await rm(targetRoot, { force: true, recursive: true });
|
|
72
|
+
await mkdir(targetRoot, { recursive: true });
|
|
73
|
+
|
|
74
|
+
for (const output of outputs) {
|
|
75
|
+
const skillDir = join(targetRoot, output.name);
|
|
76
|
+
await mkdir(skillDir, { recursive: true });
|
|
77
|
+
await writeFile(join(skillDir, "SKILL.md"), output.content, "utf8");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function collectTree(root: string): Promise<Map<string, TreeEntry>> {
|
|
82
|
+
const tree = new Map<string, TreeEntry>();
|
|
83
|
+
|
|
84
|
+
if (!existsSync(root)) {
|
|
85
|
+
return tree;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function walk(dir: string): Promise<void> {
|
|
89
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
90
|
+
|
|
91
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
92
|
+
const absolutePath = join(dir, entry.name);
|
|
93
|
+
const relativePath = relative(root, absolutePath).split(sep).join("/");
|
|
94
|
+
|
|
95
|
+
if (entry.isDirectory()) {
|
|
96
|
+
tree.set(relativePath, { type: "dir" });
|
|
97
|
+
await walk(absolutePath);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (entry.isFile()) {
|
|
102
|
+
tree.set(relativePath, {
|
|
103
|
+
type: "file",
|
|
104
|
+
content: await readFile(absolutePath, "utf8"),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
await walk(root);
|
|
111
|
+
return tree;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function diffTrees(expectedRoot: string, actualRoot: string): Promise<Diff[]> {
|
|
115
|
+
const expected = await collectTree(expectedRoot);
|
|
116
|
+
const actual = await collectTree(actualRoot);
|
|
117
|
+
const paths = Array.from(new Set([...expected.keys(), ...actual.keys()])).sort();
|
|
118
|
+
const diffs: Diff[] = [];
|
|
119
|
+
|
|
120
|
+
for (const path of paths) {
|
|
121
|
+
const expectedEntry = expected.get(path);
|
|
122
|
+
const actualEntry = actual.get(path);
|
|
123
|
+
|
|
124
|
+
if (!expectedEntry) {
|
|
125
|
+
diffs.push({ type: "extra", path });
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!actualEntry) {
|
|
130
|
+
diffs.push({ type: "missing", path });
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (
|
|
135
|
+
expectedEntry.type !== actualEntry.type ||
|
|
136
|
+
(expectedEntry.type === "file" && expectedEntry.content !== actualEntry.content)
|
|
137
|
+
) {
|
|
138
|
+
diffs.push({ type: "changed", path });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return diffs;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function syncSkills(
|
|
146
|
+
targetRoot = process.cwd(),
|
|
147
|
+
options: { check?: boolean } = {},
|
|
148
|
+
): Promise<SyncResult> {
|
|
149
|
+
const root = resolve(targetRoot);
|
|
150
|
+
const targetRootPath = join(root, ".codex", "skills");
|
|
151
|
+
const outputs = await collectSkillOutputs(root);
|
|
152
|
+
|
|
153
|
+
if (!options.check) {
|
|
154
|
+
await writeOutputs(targetRootPath, outputs);
|
|
155
|
+
return { ok: true, count: outputs.length, diffs: [] };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const tempRoot = await mkdtemp(join(tmpdir(), "sync-skills-"));
|
|
159
|
+
const expectedRoot = join(tempRoot, "skills");
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
await writeOutputs(expectedRoot, outputs);
|
|
163
|
+
const diffs = await diffTrees(expectedRoot, targetRootPath);
|
|
164
|
+
return { ok: diffs.length === 0, count: outputs.length, diffs };
|
|
165
|
+
} finally {
|
|
166
|
+
await rm(tempRoot, { force: true, recursive: true });
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function parseArgs(argv: string[]): { check: boolean; root: string } {
|
|
171
|
+
const positional: string[] = [];
|
|
172
|
+
let check = false;
|
|
173
|
+
|
|
174
|
+
for (const arg of argv) {
|
|
175
|
+
if (arg === "--check") {
|
|
176
|
+
check = true;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (arg.startsWith("-")) {
|
|
181
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
positional.push(arg);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (positional.length > 1) {
|
|
188
|
+
throw new Error("Expected at most one positional argument: target project root");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
check,
|
|
193
|
+
root: positional[0] ?? process.cwd(),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function printDiffs(diffs: Diff[]): void {
|
|
198
|
+
for (const diff of diffs) {
|
|
199
|
+
console.error(`${diff.type}: ${diff.path}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (import.meta.main) {
|
|
204
|
+
try {
|
|
205
|
+
const args = parseArgs(process.argv.slice(2));
|
|
206
|
+
const result = await syncSkills(args.root, { check: args.check });
|
|
207
|
+
|
|
208
|
+
if (!result.ok) {
|
|
209
|
+
console.error(".codex/skills is out of sync. Run bun run scripts/sync-skills.ts.");
|
|
210
|
+
printDiffs(result.diffs);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (args.check) {
|
|
215
|
+
console.log(`.codex/skills is in sync (${result.count} skills).`);
|
|
216
|
+
} else {
|
|
217
|
+
console.log(`Synced ${result.count} skills into .codex/skills.`);
|
|
218
|
+
}
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
File without changes
|