@sedim/cli 1.0.1 → 1.0.2

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 (3) hide show
  1. package/README.md +118 -117
  2. package/dist/index.js +90 -60
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,14 +1,13 @@
1
- # Sedim
2
- ![Sedim Banner](/assets/banner.jpeg)
1
+ # Sedim
3
2
 
4
- [![Discord](https://img.shields.io/discord/1340085528308782080?color=7289DA&logo=discord&logoColor=white&label=discord)](https://discord.com/invite/H7yutstM)
3
+ [![Discord](https://img.shields.io/discord/1507846093105397993?color=7289DA&logo=discord&logoColor=white&label=discord)](https://discord.com/invite/H7yutstM)
5
4
  [![GitHub Stars](https://img.shields.io/github/stars/Sedimie/Sedim?style=flat&logo=github)](https://github.com/Sedimie/Sedim)
6
5
  [![npm downloads](https://img.shields.io/npm/dw/@sedim/cli?label=weekly%20downloads)](https://www.npmjs.com/package/@sedim/cli)
7
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
8
7
 
9
8
  **Install complete features. Own every line.**
10
9
 
11
- Sedim is an open-source codegen CLI that stamps full-stack modules into your project, with out of the box working defaults. No runtime dependencies, no black boxes - every stamped file is readable, editable, and owned by you. Skip the 80% work done repetetively for just the 20% customizations on top.
10
+ Sedim is an open-source codegen CLI that stamps full-stack modules into your project, with out of the box working defaults. No runtime dependencies, no black boxes - every stamped file is readable, editable, and owned by you. Skip the 80% work done repetitively for just the 20% customizations on top.
12
11
 
13
12
  ```bash
14
13
  npm install -g @sedim/cli
@@ -17,67 +16,68 @@ sedim init # detects your stack
17
16
  sedim add auth # stamps auth into your project
18
17
  ```
19
18
 
20
- The first POC for the stamping model is the Auth Module. Currently supports NextJS ( full stack ) ,Hono and Express (with react and vue frontends).
19
+ The first POC for the stamping model is the Auth Module. Currently supports NextJS (full stack), Hono and Express (with React and Vue frontends).
21
20
 
22
21
  ---
23
-
22
+
24
23
  ## See It In Action
25
-
26
- <!-- Replace with your actual YouTube embed or linked thumbnail -->
27
- [![Sedim Full Walkthrough](https://img.youtube.com/vi/YOUR_VIDEO_ID/maxresdefault.jpg)](https://www.youtube.com/watch?v=YOUR_VIDEO_ID)
28
-
24
+
25
+ [![Sedim Full Walkthrough](https://img.youtube.com/vi/HPEjs7-O_vk/maxresdefault.jpg)](https://youtu.be/HPEjs7-O_vk)
26
+
29
27
  > 10-minute walkthrough - init, auth install, OAuth, TOTP, UI themes, and customization.
30
-
28
+
31
29
  ---
32
-
30
+
33
31
  ## Quick Start
34
-
32
+
35
33
  **Requirements:** Node ≥ 18, an existing project with a supported framework and ORM.
36
-
34
+
37
35
  ### 1. Install the CLI
38
-
36
+
39
37
  ```bash
40
38
  npm install -g @sedim/cli
41
39
  ```
42
-
40
+
43
41
  ### 2. Initialise your project
44
-
42
+
45
43
  ```bash
46
44
  cd my-project
47
45
  sedim init
48
46
  ```
49
-
47
+
50
48
  Sedim detects your framework, ORM, and language and writes a `sedim.config.ts`. If anything is detected incorrectly, edit the config file directly.
51
-
49
+
52
50
  ### 3. Add auth
53
-
51
+
54
52
  ```bash
55
53
  sedim add auth
56
54
  ```
57
- `Before writing the module directly, you can either run plan, diff or dry-run commands for the module as well!`
55
+
56
+ > Before writing the module directly, you can also run `plan`, `diff`, or `--dry-run` commands for the module!
58
57
 
59
58
  The CLI walks you through:
60
-
61
- - **Environment detection** : confirms your framework, ORM, and language
62
- - **Feature selection** : email/password, OAuth providers, TOTP, magic links, JWT, RBAC/ABAC
63
- - **Frontend style** : headless, Tailwind styled, or themed with CSS tokens
64
- - **Confirm the plan** : review exactly what files will be stamped before anything is written
65
- - **Stamp** : files land in `src/sedim/auth/`
66
- - **Environment variables** : add them on the spot or copy the printed list to your `.env`
59
+
60
+ - **Environment detection**: confirms your framework, ORM, and language
61
+ - **Feature selection**: email/password, OAuth providers, TOTP, magic links, JWT, RBAC/ABAC
62
+ - **Frontend style**: headless, Tailwind styled, or themed with CSS tokens
63
+ - **Confirm the plan**: review exactly what files will be stamped before anything is written
64
+ - **Stamp**: files land in `src/sedim/auth/`
65
+ - **Environment variables**: add them on the spot or copy the printed list to your `.env`
66
+
67
67
  ### 4. Migrate and run
68
-
68
+
69
69
  ```bash
70
70
  # Drizzle
71
71
  npx drizzle-kit push
72
-
72
+
73
73
  # or Prisma
74
74
  npx prisma migrate dev --name add_auth
75
-
75
+
76
76
  npm run dev
77
77
  ```
78
-
78
+
79
79
  ---
80
-
80
+
81
81
  ## What Gets Stamped
82
82
 
83
83
  Running `sedim add auth` generates this structure in your project. You own every file.
@@ -87,32 +87,32 @@ The exact files that get stamped depend on the features you select during `sedim
87
87
  ```
88
88
  src/sedim/auth/
89
89
  ├── core/
90
- │ ├── hash-password.ts — Argon2id password hashing
91
- │ ├── generate-token.ts — session tokens, OTP codes, backup codes, PKCE verifiers
92
- │ ├── session.ts — session building and sliding-window validation
93
- │ ├── pkce.ts — RFC 7636 PKCE (S256 only)
94
- │ ├── totp.ts — RFC 6238 TOTP (Google Authenticator compatible)
95
- │ ├── totp-crypto.ts — AES-256-GCM encryption for TOTP secrets
96
- │ ├── rate-limit.ts — sliding-window rate limiter
97
- │ ├── rbac.ts — role-based access control
98
- │ ├── abac.ts — attribute-based access control
99
- │ ├── jwt.ts — hybrid JWT (short-lived signed + DB-backed refresh)
100
- │ ├── oidc.ts — OIDC discovery and id_token validation
101
- │ └── email-transport.ts — multi-transport email (nodemailer/resend/postmark/ses)
90
+ │ ├── hash-password.ts - Argon2id password hashing
91
+ │ ├── generate-token.ts - session tokens, OTP codes, backup codes, PKCE verifiers
92
+ │ ├── session.ts - session building and sliding-window validation
93
+ │ ├── pkce.ts - RFC 7636 PKCE (S256 only)
94
+ │ ├── totp.ts - RFC 6238 TOTP (Google Authenticator compatible)
95
+ │ ├── totp-crypto.ts - AES-256-GCM encryption for TOTP secrets
96
+ │ ├── rate-limit.ts - sliding-window rate limiter
97
+ │ ├── rbac.ts - role-based access control
98
+ │ ├── abac.ts - attribute-based access control
99
+ │ ├── jwt.ts - hybrid JWT (short-lived signed + DB-backed refresh)
100
+ │ ├── oidc.ts - OIDC discovery and id_token validation
101
+ │ └── email-transport.ts - multi-transport email (nodemailer/resend/postmark/ses)
102
102
  ├── adapters/
103
103
  │ ├── framework/
104
- │ │ ├── nextjs.ts — Next.js App Router handler factory
105
- │ │ ├── express.ts — Express router
106
- │ │ ├── hono.ts — Hono route registration
107
- │ │ ├── operations.ts — all auth operations (login, signup, OAuth, TOTP, etc.)
108
- │ │ └── framework-config.ts — OAuth provider configuration
109
- │ ├── drizzle.ts — Drizzle ORM adapter
110
- │ ├── prisma.ts — Prisma ORM adapter
111
- │ └── types.ts — DatabaseAdapter interface and types
104
+ │ │ ├── nextjs.ts - Next.js App Router handler factory
105
+ │ │ ├── express.ts - Express router
106
+ │ │ ├── hono.ts - Hono route registration
107
+ │ │ ├── operations.ts - all auth operations (login, signup, OAuth, TOTP, etc.)
108
+ │ │ └── framework-config.ts - OAuth provider configuration
109
+ │ ├── drizzle.ts - Drizzle ORM adapter
110
+ │ ├── prisma.ts - Prisma ORM adapter
111
+ │ └── types.ts - DatabaseAdapter interface and types
112
112
  ├── ui/
113
- │ ├── auth-client.ts — client-side auth fetch utilities
114
- │ ├── use-auth.ts — React hook for auth state
115
- │ ├── headless/ — unstyled, logic-only components
113
+ │ ├── auth-client.ts - client-side auth fetch utilities
114
+ │ ├── use-auth.ts - React hook for auth state
115
+ │ ├── headless/ - unstyled, logic-only components
116
116
  │ │ ├── LoginForm.tsx
117
117
  │ │ ├── SignupForm.tsx
118
118
  │ │ ├── ForgotPasswordForm.tsx
@@ -120,76 +120,70 @@ src/sedim/auth/
120
120
  │ │ ├── MagicLinkForm.tsx
121
121
  │ │ ├── OAuthButton.tsx
122
122
  │ │ └── TotpVerifyForm.tsx
123
- │ ├── tailwind/ — Tailwind-styled components
124
- │ └── themed/ — pre-built themes with CSS tokens
125
- │ ├── modern-tokens.css — glassmorphism theme
126
- │ ├── minimal-tokens.css — neumorphism theme
127
- │ └── colorful-tokens.css — neubrutalism theme
123
+ │ ├── tailwind/ - Tailwind-styled components
124
+ │ └── themed/ - pre-built themes with CSS tokens
125
+ │ ├── modern-tokens.css - glassmorphism theme
126
+ │ ├── minimal-tokens.css - neumorphism theme
127
+ │ └── colorful-tokens.css - neubrutalism theme
128
128
  ├── emails/
129
- │ └── email-verification.ts — email template for verification
130
- ├── schema.ts — Drizzle schema (users, sessions, oauth_accounts, etc.)
131
- └── index.ts — module barrel export
129
+ │ └── email-verification.ts - email template for verification
130
+ ├── schema.ts - Drizzle schema (users, sessions, oauth_accounts, etc.)
131
+ └── index.ts - module barrel export
132
132
  ```
133
133
 
134
134
  The stamped auth route handler lands at `src/app/api/auth/[...all]/route.ts` (Next.js) or equivalent for other frameworks.
135
-
135
+
136
136
  ---
137
-
137
+
138
138
  ## Auth Module
139
-
140
- Auth is the first Sedim module. It ships with everything, none of it is a black box.
141
139
 
142
- [Screencast from 2026-05-24 17-31-01.webm](https://github.com/user-attachments/assets/7f444748-d0bf-4329-a4cc-60507ae8b4df)
140
+ Auth is the first Sedim module. It ships with everything, none of it is a black box.
143
141
 
144
-
145
142
  ### Features
146
-
143
+
147
144
  | Feature | Detail |
148
145
  |---|---|
149
146
  | **Password auth** | Argon2id (OWASP params), account lockout after 10 failed attempts |
150
147
  | **Session management** | SHA-256 hashed tokens, httpOnly cookies, full revocation |
151
- | **OAuth** | Google, GitHub, Discord : PKCE (RFC 7636) on all flows |
148
+ | **OAuth** | Google, GitHub, Discord: PKCE (RFC 7636) on all flows |
152
149
  | **TOTP** | RFC 6238, AES-256-GCM encrypted secrets at rest, backup codes |
153
150
  | **Magic links** | No email enumeration, supports SMTP / Resend / Postmark / SES |
154
151
  | **JWT** | Hybrid: short-lived signed JWTs + DB-backed refresh tokens |
155
152
  | **RBAC / ABAC** | Role and attribute-based access control middleware |
156
153
  | **Rate limiting** | Sliding window, in-memory or Redis store |
157
-
154
+
158
155
  ### Stack Support
159
-
156
+
160
157
  | Framework | Drizzle | Prisma |
161
158
  |-----------|:-------:|:------:|
162
159
  | Next.js (App Router) | ✓ | ✓ |
163
160
  | Express | ✓ | ✓ |
164
161
  | Hono | ✓ | ✓ |
165
-
162
+
166
163
  | Email provider | Supported |
167
164
  |---|:---:|
168
165
  | Nodemailer (SMTP) | ✓ |
169
166
  | Resend | ✓ |
170
167
  | Postmark | ✓ |
171
168
  | AWS SES | ✓ |
172
-
169
+
173
170
  ### UI Tiers
174
-
175
- Three levels so you use your own design system or ship immediately.
176
171
 
177
- [Screencast from 2026-05-24 17-36-47.webm](https://github.com/user-attachments/assets/2c910cd5-8de8-4468-a2fd-db5011edd8d2)
172
+ Three levels so you use your own design system or ship immediately.
178
173
 
179
-
180
174
  **Headless** - zero CSS, pure logic and markup. Bring your own styles.
181
-
175
+
182
176
  **Tailwind** - fully styled with Tailwind classes, works with your existing Tailwind config.
183
-
177
+
184
178
  **Themed** - pre-built themes with CSS tokens. Currently ships `minimal` and `glass`.
185
-
179
+
186
180
  ### Environment Variables
187
-
181
+
188
182
  ```bash
189
183
  # Required
190
184
  AUTH_SECRET= # min 32 chars, used for session signing
191
185
  DATABASE_URL= # your database connection string
192
-
186
+
193
187
  # OAuth - add only the providers you selected
194
188
  GOOGLE_CLIENT_ID=
195
189
  GOOGLE_CLIENT_SECRET=
@@ -197,7 +191,7 @@ GITHUB_CLIENT_ID=
197
191
  GITHUB_CLIENT_SECRET=
198
192
  DISCORD_CLIENT_ID=
199
193
  DISCORD_CLIENT_SECRET=
200
-
194
+
201
195
  # Email - add only the provider you selected
202
196
  SMTP_HOST=
203
197
  SMTP_PORT=
@@ -208,14 +202,14 @@ POSTMARK_API_KEY=
208
202
  AWS_ACCESS_KEY_ID=
209
203
  AWS_SECRET_ACCESS_KEY=
210
204
  AWS_SES_REGION=
211
-
205
+
212
206
  # Optional
213
207
  REDIS_URL= # if using Redis for rate limiting or sessions
214
- TOTP_ENCRYPTION_KEY= # required if TOTP is enabled, 32-byte hex
208
+ TOTP_ENCRYPTION_KEY= # required if TOTP is enabled, 32-byte hex
215
209
  ```
216
-
210
+
217
211
  ---
218
-
212
+
219
213
  ## Architecture
220
214
 
221
215
  Sedim uses a **stamp model**, not a runtime SDK:
@@ -236,33 +230,31 @@ The CLI has four components:
236
230
 
237
231
  Every module is described by a **manifest** (`registry/<module>/latest.json`) - a declarative spec of every file the module can stamp, what features gate each file, and what environment variables are required. The CLI reads the manifest, applies your choices, and stamps only what you need.
238
232
 
239
- ```mermaid
240
- flowchart LR
241
- Dev["Developer\nruns: sedim add auth"] --> Detector
242
- Detector --> Thinker
243
- RegistryJSON["Registry\nregistry/auth/latest.json"] --> Thinker
244
- Thinker --> Writer
245
- Writer --> StampedFiles["src/sedim/auth/\nuser-owned stamped files"]
246
-
247
- RegistryJSON --> Dev
248
-
249
- Thinker --> Plan["InstallPlan\napproved by user"]
250
- Plan --> Writer
233
+ ### Flow
251
234
 
252
- style Dev fill:#1a1a1a,color:#ffffff
253
- style StampedFiles fill:#1a1a1a,color:#ffffff
254
- style RegistryJSON fill:#333,color:#fff
255
- style Plan fill:#333,color:#fff
235
+ ```
236
+ Developer runs "sedim add auth"
237
+ ↓
238
+ Detector → identifies framework, ORM, language
239
+ ↓
240
+ Thinker → loads registry manifest + module plan-config
241
+ + feature selections → InstallPlan
242
+ ↓
243
+ Writer → stamps files to src/sedim/auth/
244
+ → writes migrations
245
+ → lists env vars
246
+ ↓
247
+ Showbaby → confirms what was written + next steps
256
248
  ```
257
249
 
258
250
  The module source lives in `packages/<module>/src/` - this is where auth logic, UI components, adapters, and templates live. The CLI reads from here when stamping. `packages/core/` holds shared TypeScript types used by both the CLI and all modules.
259
-
251
+
260
252
  ---
261
-
253
+
262
254
  ## Registry
263
-
255
+
264
256
  Module manifests live in the [`registry/`](https://github.com/Sedimie/Sedim/tree/main/registry) directory. The CLI uses the local registry during development and falls back to the GitHub raw URL for published releases.
265
-
257
+
266
258
  **Available modules:**
267
259
 
268
260
  | Module | Status |
@@ -274,25 +266,34 @@ Module manifests live in the [`registry/`](https://github.com/Sedimie/Sedim/tree
274
266
  | `WalletAdapters` | On the roadmap |
275
267
  | `ai-rag` | On the roadmap |
276
268
  | `payments` | On the roadmap |
277
-
269
+
278
270
  ---
271
+
279
272
  ## Philosophy
280
-
281
- Most tools give you an API to call. Customization is what's keeping modularizing full stack features at bay, but with codegen model, you own the code, and hence customize it however much you want. This is not just a library to cut your grunt work, you can learn the overall implementations it uses to be able to basically use the full stack features as boilerplate code.
282
-
273
+
274
+ Most tools give you an API to call. Customization is what's keeping modularizing full stack features at bay, but with the codegen model, you own the code, and hence customize it however much you want. This is not just a library to cut your grunt work - you can learn the overall implementations it uses to be able to basically use the full stack features as boilerplate code.
275
+
283
276
  When you run `sedim add auth`, you get the auth system, not a dependency on one. The files live in your repo, show up in your git history, and bend to whatever you need them to do. Sedim writes the first version. Everything after that is yours.
284
-
285
- *The best code is code you own.*
286
277
 
278
+ *The best code is code you own.*
287
279
 
288
280
  AI Agentic coding is another thing that this might clash with, but an AI tool might hallucinate and give 90% accuracy, leave out security considerations and make it tough to customize while also using up 3 hours and a considerable amount of tokens. This allows you to skip directly to the customizations part, with 100% hit-rate since it's static codegen, and 0 cost for the grunt work with upto date security standards being followed. Use AI tools on top of this to speed you up and make your workflow unbelievably fast.
289
281
 
290
282
  ---
291
283
 
292
284
  ### Check out the docs
285
+
293
286
  Full documentation - customization guides, adapter references, module authoring, and escape hatches - is at [sedim.dev/docs](https://sedim.dev/docs).
294
287
 
295
288
  ### Contributions
289
+
296
290
  To add a framework adapter, ORM adapter, or new module, see [CONTRIBUTING.md](./CONTRIBUTING.md).
297
-
291
+
298
292
  The module spec and adapter interface are documented there - adding a new adapter for a supported module takes a few hours once you understand the pattern.
293
+
294
+ ### Watch the Full Walkthrough
295
+
296
+ [![Sedim Full Walkthrough](https://img.youtube.com/vi/HPEjs7-O_vk/maxresdefault.jpg)](https://youtu.be/HPEjs7-O_vk)
297
+
298
+ > 10-minute walkthrough - init, auth install, OAuth, TOTP, UI themes, and customization.
299
+ > [Watch on YouTube](https://youtu.be/HPEjs7-O_vk)
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ var __export = (target, all) => {
10
10
  };
11
11
 
12
12
  // src/shared/constants.ts
13
- var SEDIM_DIR, SEDIM_CONFIG_FILE, SEDIM_CONFIG_CACHE, SEDIM_SESSION_FILE, SEDIM_MODULES_DIR, SEDIM_LOGS_DIR, SEDIM_LATEST_LOG, DEFAULT_REGISTRY_URL, MIN_NODE_VERSION, CLI_VERSION;
13
+ var SEDIM_DIR, SEDIM_CONFIG_FILE, SEDIM_CONFIG_CACHE, SEDIM_SESSION_FILE, SEDIM_MODULES_DIR, SEDIM_LOGS_DIR, SEDIM_LATEST_LOG, DEFAULT_REGISTRY_URL, DEFAULT_PACKAGES_URL, MIN_NODE_VERSION, CLI_VERSION;
14
14
  var init_constants = __esm({
15
15
  "src/shared/constants.ts"() {
16
16
  "use strict";
@@ -22,8 +22,9 @@ var init_constants = __esm({
22
22
  SEDIM_LOGS_DIR = `${SEDIM_DIR}/logs`;
23
23
  SEDIM_LATEST_LOG = `${SEDIM_LOGS_DIR}/latest.log`;
24
24
  DEFAULT_REGISTRY_URL = "https://raw.githubusercontent.com/sedimie/Sedim/main/registry";
25
+ DEFAULT_PACKAGES_URL = "https://raw.githubusercontent.com/sedimie/Sedim/main/packages";
25
26
  MIN_NODE_VERSION = 18;
26
- CLI_VERSION = "1.0.0";
27
+ CLI_VERSION = "1.0.2";
27
28
  }
28
29
  });
29
30
 
@@ -4254,26 +4255,16 @@ async function classifyConflicts(projectRoot, ctx, filesToCreate, schemaTables)
4254
4255
  }
4255
4256
 
4256
4257
  // src/thinker/resolve-template.ts
4257
- init_fs();
4258
+ init_constants();
4258
4259
  import path23 from "path";
4259
4260
  import { fileURLToPath as fileURLToPath3 } from "url";
4260
- import { existsSync } from "fs";
4261
4261
  var __dirname3 = path23.dirname(fileURLToPath3(import.meta.url));
4262
- function getPackageRoot(projectRoot, moduleName) {
4263
- return path23.resolve(projectRoot, "..", "..", "..", "packages", moduleName, "src");
4264
- }
4265
- async function resolveTemplate(templateKey, ctx, selectedFeatures = []) {
4262
+ async function resolveTemplate(templateKey, ctx, selectedFeatures = [], packagesUrl = DEFAULT_PACKAGES_URL) {
4266
4263
  const [moduleName, ...rest] = templateKey.split("/");
4267
4264
  if (!moduleName || rest.length === 0) {
4268
4265
  throw new Error(`Invalid templateKey: "${templateKey}"`);
4269
4266
  }
4270
4267
  const relPath = rest.join("/");
4271
- const projectRoot = ctx.projectRoot ?? process.cwd();
4272
- const packageRoot = getPackageRoot(projectRoot, moduleName);
4273
- const tryPackageRoot = (levels) => path23.resolve(__dirname3, levels, "packages", moduleName, "src");
4274
- const packageRoot3 = tryPackageRoot("../../..");
4275
- const packageRoot4 = tryPackageRoot("../../../../..");
4276
- const resolvedRoot = existsSync(packageRoot) ? packageRoot : existsSync(packageRoot3) ? packageRoot3 : existsSync(packageRoot4) ? packageRoot4 : packageRoot;
4277
4268
  const generatedKeys = {
4278
4269
  "ui/pages/login-page": (f) => buildLoginPage(f),
4279
4270
  "ui/pages/signup-page": (f) => buildSignupPage(f),
@@ -4285,58 +4276,60 @@ async function resolveTemplate(templateKey, ctx, selectedFeatures = []) {
4285
4276
  return generatedKeys[relPath](selectedFeatures, ctx);
4286
4277
  }
4287
4278
  let content;
4288
- const templatePath = path23.join(resolvedRoot, "templates", `${relPath}.ts`);
4289
- if (await exists(templatePath)) {
4290
- const raw = await readText(templatePath);
4291
- content = applySubstitutions(raw, ctx, selectedFeatures);
4279
+ let found = false;
4280
+ const asIsUrl = `${packagesUrl}/${moduleName}/src/${relPath}`;
4281
+ const asIsFetched = await tryFetch(asIsUrl);
4282
+ if (asIsFetched !== null) {
4283
+ content = asIsFetched;
4284
+ found = true;
4292
4285
  } else {
4293
- const templatePrismaPath = path23.join(resolvedRoot, "templates", `${relPath}.prisma`);
4294
- if (await exists(templatePrismaPath)) {
4295
- const raw = await readText(templatePrismaPath);
4296
- content = applySubstitutions(raw, ctx, selectedFeatures);
4286
+ for (const tryExt of ["ts", "tsx", "css", "prisma"]) {
4287
+ const url = `${packagesUrl}/${moduleName}/src/${relPath}.${tryExt}`;
4288
+ const raw = await tryFetch(url);
4289
+ if (raw !== null) {
4290
+ content = raw;
4291
+ found = true;
4292
+ break;
4293
+ }
4294
+ }
4295
+ }
4296
+ if (!found) {
4297
+ const templateAsIs = `${packagesUrl}/${moduleName}/src/templates/${relPath}`;
4298
+ const fetchedAsIs = await tryFetch(templateAsIs);
4299
+ if (fetchedAsIs !== null) {
4300
+ content = fetchedAsIs;
4301
+ found = true;
4297
4302
  } else {
4298
- const knownExts = [".ts", ".tsx", ".css", ".prisma"];
4299
- const hasExt = knownExts.some((e) => relPath.endsWith(e));
4300
- if (hasExt) {
4301
- const sourcePath = path23.join(resolvedRoot, relPath);
4302
- if (await exists(sourcePath)) {
4303
- content = await readText(sourcePath);
4304
- content = applySubstitutions(content, ctx, selectedFeatures);
4305
- } else {
4306
- content = "";
4307
- }
4308
- } else {
4309
- let found = false;
4310
- content = "";
4311
- for (const ext of [".ts", ".tsx", ".css"]) {
4312
- const sourcePath = path23.join(resolvedRoot, `${relPath}${ext}`);
4313
- if (await exists(sourcePath)) {
4314
- const raw = await readText(sourcePath);
4315
- content = applySubstitutions(raw, ctx, selectedFeatures);
4316
- found = true;
4317
- break;
4318
- }
4319
- }
4320
- if (!found) {
4321
- const sourcePrismaPath = path23.join(resolvedRoot, `${relPath}.prisma`);
4322
- if (await exists(sourcePrismaPath)) {
4323
- content = await readText(sourcePrismaPath);
4324
- } else {
4325
- throw new Error(
4326
- `Template not found for key "${templateKey}". Checked:
4327
- ${templatePath}
4328
- ${path23.join(resolvedRoot, relPath)}.[ts|tsx|css]`
4329
- );
4330
- }
4331
- }
4303
+ const templateUrl = `${packagesUrl}/${moduleName}/src/templates/${relPath}.ts`;
4304
+ const fetched = await tryFetch(templateUrl);
4305
+ if (fetched !== null) {
4306
+ content = fetched;
4307
+ found = true;
4332
4308
  }
4333
4309
  }
4334
4310
  }
4311
+ if (!found) {
4312
+ throw new Error(
4313
+ `Template not found for key "${templateKey}". Checked:
4314
+ ${packagesUrl}/${moduleName}/src/${relPath}.[ts|tsx|css|prisma]
4315
+ ${packagesUrl}/${moduleName}/src/templates/${relPath}.ts`
4316
+ );
4317
+ }
4318
+ content = applySubstitutions(content, ctx, selectedFeatures);
4335
4319
  if (!relPath.endsWith(".css") && !relPath.endsWith(".prisma")) {
4336
4320
  content = stripRelativeJsExtensions(content);
4337
4321
  }
4338
4322
  return content;
4339
4323
  }
4324
+ async function tryFetch(url) {
4325
+ try {
4326
+ const res = await fetch(url);
4327
+ if (!res.ok) return null;
4328
+ return res.text();
4329
+ } catch {
4330
+ return null;
4331
+ }
4332
+ }
4340
4333
  function stripRelativeJsExtensions(content) {
4341
4334
  return content.replace(/(from\s+['"])(\.\.?\/[^'"]*?)\.js(['"])/g, "$1$2$3");
4342
4335
  }
@@ -4475,7 +4468,7 @@ function buildLoginPage(features) {
4475
4468
  ` <p style={{ textAlign: 'center', fontSize: '0.8125rem', color: '${mutedColor}', margin: 0 }}>`
4476
4469
  );
4477
4470
  body.push(
4478
- ` Don&apos;t have an account?{' '}<a href="/signup" style={{ color: '${fgColor}', fontWeight: 600, textDecoration: 'none' }}>Sign up</a>`
4471
+ ` Don't have an account?{' '}<a href="/signup" style={{ color: '${fgColor}', fontWeight: 600, textDecoration: 'none' }}>Sign up</a>`
4479
4472
  );
4480
4473
  body.push(` </p>`);
4481
4474
  }
@@ -4640,7 +4633,7 @@ function buildForgotPage(features = []) {
4640
4633
  const inner = [
4641
4634
  ` <div style={{ textAlign: 'center' }}>`,
4642
4635
  ` <h1 style={{ fontSize: '1.5rem', fontWeight: 700, margin: '0 0 0.25rem' }}>Reset password</h1>`,
4643
- ` <p style={{ fontSize: '0.875rem', color: '${mutedColor}', margin: 0 }}>Enter your email and we&apos;ll send a reset link.</p>`,
4636
+ ` <p style={{ fontSize: '0.875rem', color: '${mutedColor}', margin: 0 }}>Enter your email and we'll send a reset link.</p>`,
4644
4637
  ` </div>`,
4645
4638
  ` <ForgotPasswordForm />`,
4646
4639
  ` <p style={{ textAlign: 'center', fontSize: '0.8125rem', margin: 0 }}>`,
@@ -5017,7 +5010,31 @@ function manifestToPlanConfig(manifest, selectedFeatures, _ctx) {
5017
5010
  }
5018
5011
 
5019
5012
  // src/thinker/load-plan-config.ts
5020
- async function loadPlanConfig(moduleName, manifest, ctx, selectedFeatures) {
5013
+ init_constants();
5014
+ import { createWriteStream } from "fs";
5015
+ import https from "https";
5016
+ import os from "os";
5017
+ import { pipeline } from "stream";
5018
+ import { promisify } from "util";
5019
+ var streamPipeline = promisify(pipeline);
5020
+ async function downloadToTemp(url, suffix) {
5021
+ const tmpDir = os.tmpdir();
5022
+ const tmpPath = `${tmpDir}/sedim-${suffix}-${Date.now()}.js`;
5023
+ await new Promise((resolve, reject) => {
5024
+ const req = https.get(url, (res) => {
5025
+ if (res.statusCode !== 200) {
5026
+ reject(new Error(`HTTP ${res.statusCode}`));
5027
+ return;
5028
+ }
5029
+ const file = createWriteStream(tmpPath);
5030
+ res.pipe(file);
5031
+ file.on("finish", () => file.close(() => resolve()));
5032
+ });
5033
+ req.on("error", reject);
5034
+ });
5035
+ return tmpPath;
5036
+ }
5037
+ async function loadPlanConfig(moduleName, manifest, ctx, selectedFeatures, packagesUrl = DEFAULT_PACKAGES_URL) {
5021
5038
  try {
5022
5039
  const fnName = `create${capitalize(moduleName)}PlanConfig`;
5023
5040
  const mod = await import(`@sedim/${moduleName}/plan-config`);
@@ -5027,7 +5044,20 @@ async function loadPlanConfig(moduleName, manifest, ctx, selectedFeatures) {
5027
5044
  selectedFeatures
5028
5045
  );
5029
5046
  }
5030
- } catch (err) {
5047
+ } catch {
5048
+ }
5049
+ try {
5050
+ const fnName = `create${capitalize(moduleName)}PlanConfig`;
5051
+ const remoteUrl = `${packagesUrl}/${moduleName}/planConfig/plan-config.js`;
5052
+ const tmpPath = await downloadToTemp(remoteUrl, moduleName);
5053
+ const mod = await import(tmpPath);
5054
+ if (mod && typeof mod[fnName] === "function") {
5055
+ return mod[fnName](
5056
+ ctx,
5057
+ selectedFeatures
5058
+ );
5059
+ }
5060
+ } catch {
5031
5061
  }
5032
5062
  return manifestToPlanConfig(manifest, selectedFeatures, ctx);
5033
5063
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sedim/cli",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Premium codegen CLI — stamp production-ready feature modules into your project",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",