anhedral 0.1.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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ All Rights Reserved
2
+
3
+ Copyright (c) 2026 Anhedral, Inc.
4
+
5
+ THE CONTENTS OF THIS PROJECT ARE PROPRIETARY AND CONFIDENTIAL.
6
+ UNAUTHORIZED COPYING, TRANSFERRING OR REPRODUCTION OF THE CONTENTS
7
+ OF THIS PROJECT, VIA ANY MEDIUM, IS STRICTLY PROHIBITED.
8
+
9
+ The receipt or possession of the source code and/or any parts thereof
10
+ does not convey or imply any right to use them for any purpose other
11
+ than the purpose for which they were provided to you.
12
+
13
+ The software is provided "AS IS", without warranty of any kind,
14
+ express or implied, including but not limited to the warranties of
15
+ merchantability, fitness for a particular purpose and noninfringement.
16
+ In no event shall the authors or copyright holders be liable for any
17
+ claim, damages or other liability, whether in an action of contract,
18
+ tort or otherwise, arising from, out of or in connection with the
19
+ software or the use or other dealings in the software.
20
+
21
+ The above copyright notice and this permission notice shall be
22
+ included in all copies or substantial portions of the software.
package/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # anhedral
2
+
3
+ Opinionated init CLI for product teams shipping with Next.js, Expo, Fastify, Drizzle, Neon, Clerk, Stripe, RevenueCat, and Cloudflare R2.
4
+
5
+ ## Requirements
6
+
7
+ - Node.js 20+
8
+ - `pnpm` available on your machine
9
+
10
+ ## Usage
11
+
12
+ Run the CLI directly without installing it globally:
13
+
14
+ ```sh
15
+ pnpm dlx anhedral init next
16
+ pnpm dlx anhedral init next-fullstack
17
+ pnpm dlx anhedral init expo-fullstack
18
+ pnpm dlx anhedral init backend
19
+ ```
20
+
21
+ ```sh
22
+ npx anhedral init next
23
+ npx anhedral init next-fullstack
24
+ npx anhedral init expo-fullstack
25
+ npx anhedral init backend
26
+ ```
27
+
28
+ Global install also works:
29
+
30
+ ```sh
31
+ pnpm add -g anhedral
32
+ anhedral init next
33
+ ```
34
+
35
+ ```sh
36
+ npm install -g anhedral
37
+ anhedral init next
38
+ ```
39
+
40
+ ## Stacks
41
+
42
+ - `next`: Next.js App Router for the fastest web-only SaaS path.
43
+ - `next-fullstack`: Next.js frontend plus Fastify backend for clearer service boundaries.
44
+ - `expo-fullstack`: Expo app plus Fastify backend for mobile-first or multi-client products.
45
+ - `backend`: Fastify-only API foundation for headless products and integrations.
46
+
47
+ Each scaffold writes a `stack.json` file with the selected architecture and generated outputs.
48
+
49
+ ## Local Development
50
+
51
+ ```sh
52
+ pnpm install
53
+ pnpm build
54
+ pnpm test:all
55
+ ```
56
+
57
+ ## Publishing
58
+
59
+ This package is published once to the npm registry. `pnpm`, `npm`, `yarn`, and `bun` users all install that same published package.
60
+
61
+ ```sh
62
+ pnpm build
63
+ pnpm test:all
64
+ npm publish
65
+ ```
package/anhedral.svg ADDED
@@ -0,0 +1,4 @@
1
+ <svg width="4000" height="4000" viewBox="0 0 4000 4000" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M0 200C0 89.5431 89.5431 0 200 0H3800C3910.46 0 4000 89.5431 4000 200V3800C4000 3910.46 3910.46 4000 3800 4000H200C89.5431 4000 0 3910.46 0 3800V200Z" fill="black"/>
3
+ <path d="M2700.36 2965.84C2698.79 3089.66 2545.25 3146.21 2463.74 3052.98L1988.84 2509.79C2119.01 2206.09 2384.98 1972.11 2714.07 1888.26L2700.36 2965.84ZM2509.59 927.741C2599.33 861.325 2726.31 926.339 2724.89 1037.97L2714.28 1872.03C2679.28 1870.39 2643.87 1870.43 2608.15 1872.24C2239.16 1890.85 1922.8 2092.61 1742.64 2385.06L1027.5 2317.65C904.207 2306.03 860.316 2148.41 959.854 2074.73L2509.59 927.741Z" fill="white"/>
4
+ </svg>
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/index.js';
@@ -0,0 +1,3 @@
1
+ export declare const ANHEDRAL_LOGO_FILE_NAME = "anhedral.svg";
2
+ export declare const ANHEDRAL_LOGO_PUBLIC_PATH = "/anhedral.svg";
3
+ export declare function writeAnhedralLogo(root: string): void;
@@ -0,0 +1,9 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { writeFile } from './util.js';
4
+ export const ANHEDRAL_LOGO_FILE_NAME = 'anhedral.svg';
5
+ export const ANHEDRAL_LOGO_PUBLIC_PATH = `/${ANHEDRAL_LOGO_FILE_NAME}`;
6
+ const ANHEDRAL_LOGO_SVG = readFileSync(new URL('../anhedral.svg', import.meta.url), 'utf8');
7
+ export function writeAnhedralLogo(root) {
8
+ writeFile(path.join(root, 'public', ANHEDRAL_LOGO_FILE_NAME), ANHEDRAL_LOGO_SVG);
9
+ }
@@ -0,0 +1,12 @@
1
+ import type { InitMode } from './scaffold.js';
2
+ import { type ToolchainSpec } from './toolchain.js';
3
+ export type ScaffoldCommand = {
4
+ cmd: string;
5
+ stdinInput?: string;
6
+ skippable?: boolean;
7
+ };
8
+ export declare function getDefaultWebInitCommand(toolchain: ToolchainSpec, projectName: string): ScaffoldCommand;
9
+ export declare function getDefaultWebDependencyCommands(): ScaffoldCommand[];
10
+ export declare function getHeadlessInstallCommands(): ScaffoldCommand[];
11
+ export declare function getBackendInstallCommands(): ScaffoldCommand[];
12
+ export declare function getSkillCommands(mode: InitMode): string[];
@@ -0,0 +1,74 @@
1
+ import { toolPackageRef } from './toolchain.js';
2
+ const TYPESCRIPT_VERSION = '5.9.3';
3
+ const NODE_TYPES_VERSION = '25.5.0';
4
+ export function getDefaultWebInitCommand(toolchain, projectName) {
5
+ return {
6
+ cmd: [
7
+ 'pnpm',
8
+ 'dlx',
9
+ toolPackageRef('shadcn', toolchain.shadcn),
10
+ 'init',
11
+ '-t',
12
+ 'next',
13
+ '-n',
14
+ projectName,
15
+ '-d',
16
+ '-y',
17
+ '--css-variables',
18
+ ].join(' '),
19
+ };
20
+ }
21
+ export function getDefaultWebDependencyCommands() {
22
+ return [
23
+ {
24
+ cmd: 'pnpm add @clerk/nextjs @clerk/ui drizzle-orm @neondatabase/serverless stripe @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner',
25
+ skippable: true,
26
+ },
27
+ {
28
+ cmd: 'pnpm add -D babel-plugin-react-compiler drizzle-kit tsx dotenv',
29
+ skippable: true,
30
+ },
31
+ ];
32
+ }
33
+ export function getHeadlessInstallCommands() {
34
+ return [
35
+ {
36
+ cmd: 'pnpm add fastify @fastify/cors better-auth drizzle-orm @neondatabase/serverless @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner dotenv',
37
+ skippable: true,
38
+ },
39
+ {
40
+ cmd: `pnpm add -D typescript@${TYPESCRIPT_VERSION} tsx @types/node@${NODE_TYPES_VERSION} drizzle-kit`,
41
+ skippable: true,
42
+ },
43
+ ];
44
+ }
45
+ export function getBackendInstallCommands() {
46
+ return [
47
+ {
48
+ cmd: 'pnpm add fastify fastify-plugin @fastify/cors @fastify/env @fastify/compress @fastify/helmet @fastify/rate-limit @fastify/swagger @fastify/swagger-ui @fastify/multipart @clerk/fastify @neondatabase/serverless drizzle-orm @aws-sdk/client-s3 @aws-sdk/lib-storage @aws-sdk/s3-request-presigner dotenv',
49
+ },
50
+ {
51
+ cmd: 'pnpm add -D typescript@5.9.3 tsx @types/node@25.5.0 drizzle-kit vitest @vitest/coverage-v8 @vercel/node eslint@9.39.4 @eslint/js@9.39.4 globals typescript-eslint pino-pretty',
52
+ },
53
+ ];
54
+ }
55
+ export function getSkillCommands(mode) {
56
+ switch (mode) {
57
+ case 'next':
58
+ case 'next-fullstack':
59
+ return [
60
+ 'pnpm dlx skills add https://github.com/clerk/skills --skill clerk-custom-ui',
61
+ 'pnpm dlx skills add https://github.com/stripe/ai --skill stripe-best-practices',
62
+ ];
63
+ case 'expo-fullstack':
64
+ return [
65
+ 'pnpm dlx skills add https://github.com/clerk/skills --skill clerk-custom-ui',
66
+ 'pnpm dlx skills add https://github.com/revenuecat/revenuecat-skill --skill revenuecat',
67
+ 'pnpm dlx skills add https://github.com/stripe/ai --skill stripe-best-practices',
68
+ ];
69
+ case 'backend':
70
+ return [
71
+ 'pnpm dlx skills add https://github.com/better-auth/skills --skill better-auth-best-practices',
72
+ ];
73
+ }
74
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env tsx
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env tsx
2
+ import path from 'node:path';
3
+ import { argv, env, exit } from 'node:process';
4
+ import { STACK_IDS, scaffoldProject } from './scaffold.js';
5
+ import { TOOLCHAIN_CHANNELS, resolveToolchainChannel } from './toolchain.js';
6
+ const USAGE = `
7
+ anhedral init <next|next-fullstack|expo-fullstack|backend> [--toolchain <latest|stable>]
8
+
9
+ Commands:
10
+ anhedral init next
11
+ Next.js App Router, shadcn/ui, Tailwind CSS, TypeScript, Drizzle ORM, Neon Postgres, Clerk Auth, Stripe, Cloudflare R2
12
+
13
+ anhedral init next-fullstack
14
+ Next.js App Router, shadcn/ui, Tailwind CSS, TypeScript, Fastify, Drizzle ORM, Neon Postgres, Clerk Auth, Stripe, Cloudflare R2
15
+
16
+ anhedral init expo-fullstack
17
+ Expo, NativeWind/Tailwind CSS, TypeScript, Fastify, Drizzle ORM, Neon Postgres, Clerk Auth, RevenueCat + Stripe, Cloudflare R2
18
+
19
+ anhedral init backend
20
+ Fastify, TypeScript, Drizzle ORM, Neon Postgres, Clerk Auth or Better Auth, Stripe-ready integrations, Cloudflare R2
21
+ `;
22
+ function parseCli(args) {
23
+ const flags = {};
24
+ for (let index = 0; index < args.length; index += 1) {
25
+ const token = args[index];
26
+ if (!token.startsWith('--')) {
27
+ if (flags.stack != null) {
28
+ throw new Error(`Unknown flag: ${token}`);
29
+ }
30
+ flags.stack = token;
31
+ continue;
32
+ }
33
+ if (token === '--toolchain') {
34
+ const value = args[index + 1];
35
+ if (!value || value.startsWith('--')) {
36
+ throw new Error('Missing value for --toolchain');
37
+ }
38
+ flags.toolchain = value;
39
+ index += 1;
40
+ continue;
41
+ }
42
+ throw new Error(`Unknown flag: ${token}`);
43
+ }
44
+ if (!flags.stack) {
45
+ throw new Error(`Missing stack. Choose one of: ${STACK_IDS.join(', ')}`);
46
+ }
47
+ return flags;
48
+ }
49
+ function resolveMode(flags) {
50
+ if (!STACK_IDS.includes(flags.stack)) {
51
+ throw new Error(`Unknown stack: ${flags.stack}. Choose one of: ${STACK_IDS.join(', ')}`);
52
+ }
53
+ return flags.stack;
54
+ }
55
+ function deriveProjectName(cwd) {
56
+ const base = path.basename(cwd);
57
+ const sanitized = base
58
+ .trim()
59
+ .toLowerCase()
60
+ .replace(/[^a-z0-9_-]+/g, '-')
61
+ .replace(/^-+|-+$/g, '');
62
+ return sanitized || 'anhedral-app';
63
+ }
64
+ function deriveDisplayName(cwd) {
65
+ const base = path.basename(cwd).trim();
66
+ return base || 'Anhedral App';
67
+ }
68
+ function buildOptions(flags) {
69
+ const cwd = process.cwd();
70
+ const mode = resolveMode(flags);
71
+ const projectName = deriveProjectName(cwd);
72
+ const displayName = deriveDisplayName(cwd);
73
+ if (flags.toolchain != null && !TOOLCHAIN_CHANNELS.includes(flags.toolchain)) {
74
+ throw new Error(`--toolchain must be one of: ${TOOLCHAIN_CHANNELS.join(', ')}`);
75
+ }
76
+ switch (mode) {
77
+ case 'next':
78
+ return {
79
+ mode,
80
+ projectName,
81
+ displayName,
82
+ auth: 'clerk',
83
+ payments: 'stripe',
84
+ db: 'neon',
85
+ orm: 'drizzle',
86
+ storage: 'r2',
87
+ api: null,
88
+ monorepo: false,
89
+ toolchainChannel: resolveToolchainChannel(flags.toolchain ?? env.ANHEDRAL_TOOLCHAIN),
90
+ };
91
+ case 'next-fullstack':
92
+ return {
93
+ mode,
94
+ projectName,
95
+ displayName,
96
+ auth: 'clerk',
97
+ payments: 'stripe',
98
+ db: 'neon',
99
+ orm: 'drizzle',
100
+ storage: 'r2',
101
+ api: 'fastify',
102
+ monorepo: true,
103
+ toolchainChannel: resolveToolchainChannel(flags.toolchain ?? env.ANHEDRAL_TOOLCHAIN),
104
+ };
105
+ case 'expo-fullstack':
106
+ return {
107
+ mode,
108
+ projectName,
109
+ displayName,
110
+ auth: 'clerk',
111
+ payments: 'revenuecat',
112
+ db: 'neon',
113
+ orm: 'drizzle',
114
+ storage: 'r2',
115
+ api: 'fastify',
116
+ monorepo: true,
117
+ toolchainChannel: resolveToolchainChannel(flags.toolchain ?? env.ANHEDRAL_TOOLCHAIN),
118
+ };
119
+ case 'backend':
120
+ return {
121
+ mode,
122
+ projectName,
123
+ displayName,
124
+ auth: 'betterauth',
125
+ payments: 'none',
126
+ db: 'neon',
127
+ orm: 'drizzle',
128
+ storage: 'r2',
129
+ api: 'fastify',
130
+ monorepo: false,
131
+ toolchainChannel: resolveToolchainChannel(flags.toolchain ?? env.ANHEDRAL_TOOLCHAIN),
132
+ };
133
+ }
134
+ }
135
+ async function main() {
136
+ const args = argv.slice(2);
137
+ const command = args[0];
138
+ if (!command || command === '--help' || command === '-h') {
139
+ console.log(USAGE);
140
+ exit(0);
141
+ }
142
+ if (command !== 'init') {
143
+ console.error(`Unknown command: ${command}`);
144
+ console.log(USAGE);
145
+ exit(1);
146
+ }
147
+ try {
148
+ const rawArgs = args.slice(1);
149
+ const options = buildOptions(parseCli(rawArgs));
150
+ await scaffoldProject(options);
151
+ }
152
+ catch (error) {
153
+ console.error('Error:', error.message);
154
+ exit(1);
155
+ }
156
+ }
157
+ main();
@@ -0,0 +1,25 @@
1
+ import { type ToolchainChannel } from './toolchain.js';
2
+ export declare const STACK_IDS: readonly ["next", "next-fullstack", "expo-fullstack", "backend"];
3
+ export type InitMode = (typeof STACK_IDS)[number];
4
+ export type AuthMode = 'clerk' | 'betterauth';
5
+ export type PaymentsMode = 'none' | 'stripe' | 'revenuecat';
6
+ export interface InitOptions {
7
+ mode: InitMode;
8
+ projectName: string;
9
+ displayName: string;
10
+ auth: AuthMode;
11
+ payments: PaymentsMode;
12
+ db: 'neon';
13
+ orm: 'drizzle';
14
+ storage: 'r2';
15
+ api: 'fastify' | null;
16
+ monorepo: boolean;
17
+ toolchainChannel: ToolchainChannel;
18
+ }
19
+ export interface ProjectOptions {
20
+ projectName: string;
21
+ displayName: string;
22
+ githubOrg: string | null;
23
+ frontendUrl?: string;
24
+ }
25
+ export declare function scaffoldProject(options: InitOptions): Promise<void>;