@lumerahq/cli 0.19.3-dev.0 → 0.19.3
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/README.md +5 -3
- package/dist/chunk-H357NP7T.js +77 -0
- package/dist/{chunk-GKI2HQJC.js → chunk-JKXLKK5I.js} +14 -1
- package/dist/chunk-P5HFNAVN.js +280 -0
- package/dist/{chunk-53NOF33P.js → chunk-SU26C4GL.js} +9 -49
- package/dist/{deps-ULTIIDYK.js → deps-EC3VRNN7.js} +4 -2
- package/dist/{dev-5JMHMS4U.js → dev-R43VQCZD.js} +9 -2
- package/dist/index.js +13 -13
- package/dist/{init-37XOMJLU.js → init-TDIQAOG4.js} +29 -9
- package/dist/{register-JJUMS4FI.js → register-JFJADKAJ.js} +1 -1
- package/dist/{resources-4IUZYKGX.js → resources-OP7EECKZ.js} +20 -92
- package/dist/{run-5ZOSPBGO.js → run-EJP5WCQU.js} +1 -1
- package/dist/{templates-M3RDNDDY.js → templates-LNUOTNLN.js} +2 -3
- package/package.json +1 -1
- package/templates/default/.agents/skills/.gitkeep +0 -0
- package/templates/default/AGENTS.md +151 -0
- package/templates/default/README.md +18 -0
- package/templates/default/_gitignore +14 -0
- package/templates/default/architecture.md +29 -0
- package/templates/default/biome.json +38 -0
- package/templates/default/components.json +21 -0
- package/templates/default/icon.svg +29 -0
- package/templates/default/index.html +16 -0
- package/templates/default/package.json +53 -0
- package/templates/default/platform/automations/.gitkeep +0 -0
- package/templates/default/platform/collections/.gitkeep +0 -0
- package/templates/default/platform/hooks/.gitkeep +0 -0
- package/templates/default/pyproject.toml +14 -0
- package/templates/default/scripts/.gitkeep +0 -0
- package/templates/default/src/components/layout.tsx +11 -0
- package/templates/default/src/lib/auth.ts +9 -0
- package/templates/default/src/lib/queries.ts +17 -0
- package/templates/default/src/lib/utils.ts +6 -0
- package/templates/default/src/main.tsx +130 -0
- package/templates/default/src/routes/__root.tsx +10 -0
- package/templates/default/src/routes/index.tsx +87 -0
- package/templates/default/src/styles.css +128 -0
- package/templates/default/template.json +7 -0
- package/templates/default/tsconfig.json +22 -0
- package/templates/default/vite.config.ts +28 -0
- package/dist/chunk-OQW5E7UT.js +0 -159
- package/dist/chunk-XDTWVFPE.js +0 -120
|
@@ -7,17 +7,17 @@ import {
|
|
|
7
7
|
} from "./chunk-BHYDYR75.js";
|
|
8
8
|
import {
|
|
9
9
|
createApiClient
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-JKXLKK5I.js";
|
|
11
11
|
import {
|
|
12
12
|
getToken,
|
|
13
13
|
init_auth,
|
|
14
14
|
setProjectId
|
|
15
15
|
} from "./chunk-ZH3NVYEQ.js";
|
|
16
|
+
import "./chunk-FJFIWC7G.js";
|
|
16
17
|
import {
|
|
17
18
|
listAllTemplates,
|
|
18
19
|
resolveTemplate
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
import "./chunk-FJFIWC7G.js";
|
|
20
|
+
} from "./chunk-H357NP7T.js";
|
|
21
21
|
import "./chunk-PNKVD2UK.js";
|
|
22
22
|
|
|
23
23
|
// src/commands/init.ts
|
|
@@ -25,7 +25,7 @@ init_auth();
|
|
|
25
25
|
import pc from "picocolors";
|
|
26
26
|
import prompts from "prompts";
|
|
27
27
|
import { execSync } from "child_process";
|
|
28
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, rmSync } from "fs";
|
|
28
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, rmSync, readlinkSync, symlinkSync } from "fs";
|
|
29
29
|
import { join, resolve } from "path";
|
|
30
30
|
function toTitleCase(str) {
|
|
31
31
|
return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
@@ -40,6 +40,7 @@ function processTemplate(content, replacements) {
|
|
|
40
40
|
return result;
|
|
41
41
|
}
|
|
42
42
|
var TEMPLATE_EXCLUDE = /* @__PURE__ */ new Set(["template.json"]);
|
|
43
|
+
var TEMPLATE_RENAMES = /* @__PURE__ */ new Map([["_gitignore", ".gitignore"]]);
|
|
43
44
|
function copyDir(src, dest, replacements, isRoot = true) {
|
|
44
45
|
if (!existsSync(dest)) {
|
|
45
46
|
mkdirSync(dest, { recursive: true });
|
|
@@ -47,8 +48,16 @@ function copyDir(src, dest, replacements, isRoot = true) {
|
|
|
47
48
|
for (const entry of readdirSync(src, { withFileTypes: true })) {
|
|
48
49
|
if (isRoot && TEMPLATE_EXCLUDE.has(entry.name)) continue;
|
|
49
50
|
const srcPath = join(src, entry.name);
|
|
50
|
-
const
|
|
51
|
-
|
|
51
|
+
const destName = TEMPLATE_RENAMES.get(entry.name) ?? entry.name;
|
|
52
|
+
const destPath = join(dest, destName);
|
|
53
|
+
if (entry.isSymbolicLink()) {
|
|
54
|
+
try {
|
|
55
|
+
symlinkSync(readlinkSync(srcPath), destPath);
|
|
56
|
+
} catch {
|
|
57
|
+
const content = readFileSync(srcPath, "utf-8");
|
|
58
|
+
writeFileSync(destPath, processTemplate(content, replacements));
|
|
59
|
+
}
|
|
60
|
+
} else if (entry.isDirectory()) {
|
|
52
61
|
copyDir(srcPath, destPath, replacements, false);
|
|
53
62
|
} else {
|
|
54
63
|
const content = readFileSync(srcPath, "utf-8");
|
|
@@ -57,6 +66,16 @@ function copyDir(src, dest, replacements, isRoot = true) {
|
|
|
57
66
|
}
|
|
58
67
|
}
|
|
59
68
|
}
|
|
69
|
+
function ensureClaudeInstructionsLink(projectRoot) {
|
|
70
|
+
const agentsMdPath = join(projectRoot, "AGENTS.md");
|
|
71
|
+
const claudeMdPath = join(projectRoot, "CLAUDE.md");
|
|
72
|
+
if (!existsSync(agentsMdPath) || existsSync(claudeMdPath)) return;
|
|
73
|
+
try {
|
|
74
|
+
symlinkSync("AGENTS.md", claudeMdPath);
|
|
75
|
+
} catch {
|
|
76
|
+
writeFileSync(claudeMdPath, readFileSync(agentsMdPath, "utf-8"));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
60
79
|
function isGitInstalled() {
|
|
61
80
|
try {
|
|
62
81
|
execSync("git --version", { stdio: "ignore" });
|
|
@@ -193,9 +212,9 @@ ${pc.dim("Options:")}
|
|
|
193
212
|
|
|
194
213
|
${pc.dim("Examples:")}
|
|
195
214
|
lumera init my-app # Interactive mode
|
|
196
|
-
lumera init my-app -t
|
|
215
|
+
lumera init my-app -t default # Use a specific template
|
|
197
216
|
lumera init my-app -y # Non-interactive (default template)
|
|
198
|
-
lumera init my-app -t
|
|
217
|
+
lumera init my-app -t default -y -o # Full non-interactive + open editor
|
|
199
218
|
`);
|
|
200
219
|
}
|
|
201
220
|
async function init(args) {
|
|
@@ -218,7 +237,7 @@ async function init(args) {
|
|
|
218
237
|
let templateName = opts.template;
|
|
219
238
|
if (!templateName && !nonInteractive) {
|
|
220
239
|
try {
|
|
221
|
-
const stop = spinner("
|
|
240
|
+
const stop = spinner("Loading templates...");
|
|
222
241
|
const available = await listAllTemplates();
|
|
223
242
|
stop();
|
|
224
243
|
if (available.length > 1) {
|
|
@@ -333,6 +352,7 @@ async function init(args) {
|
|
|
333
352
|
[sourceTitle, projectTitle]
|
|
334
353
|
];
|
|
335
354
|
copyDir(templateDir, targetDir, replacements);
|
|
355
|
+
ensureClaudeInstructionsLink(targetDir);
|
|
336
356
|
function listFiles(dir, prefix = "") {
|
|
337
357
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
338
358
|
const relativePath = prefix + entry.name;
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
syncDeps
|
|
3
|
-
} from "./chunk-XDTWVFPE.js";
|
|
4
1
|
import {
|
|
5
2
|
deploy
|
|
6
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-SU26C4GL.js";
|
|
4
|
+
import {
|
|
5
|
+
projectResourceDepsEnabled,
|
|
6
|
+
syncDeps
|
|
7
|
+
} from "./chunk-P5HFNAVN.js";
|
|
7
8
|
import {
|
|
8
9
|
loadEnv
|
|
9
10
|
} from "./chunk-2CR762KB.js";
|
|
10
11
|
import {
|
|
11
12
|
createApiClient
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-JKXLKK5I.js";
|
|
13
14
|
import {
|
|
14
15
|
findProjectRoot,
|
|
15
16
|
getApiUrl,
|
|
@@ -148,82 +149,13 @@ function safePrintLint(warnings, projectRoot) {
|
|
|
148
149
|
if (process.env.LUMERA_DEBUG) console.error("[lint] print failed:", err);
|
|
149
150
|
}
|
|
150
151
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
try {
|
|
158
|
-
execFileSync(command, ["--version"], { stdio: "ignore" });
|
|
159
|
-
return true;
|
|
160
|
-
} catch {
|
|
161
|
-
return false;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
function parsePackageManagerSpecifier(value) {
|
|
165
|
-
const raw = (value || "").trim();
|
|
166
|
-
if (!raw) return void 0;
|
|
167
|
-
const name = raw.split("@")[0];
|
|
168
|
-
return isPackageManager(name) ? name : void 0;
|
|
169
|
-
}
|
|
170
|
-
function packageManagerFromPackageJson(projectRoot) {
|
|
171
|
-
try {
|
|
172
|
-
const pkg = JSON.parse(readFileSync(join(projectRoot, "package.json"), "utf-8"));
|
|
173
|
-
return parsePackageManagerSpecifier(pkg.packageManager);
|
|
174
|
-
} catch {
|
|
175
|
-
return void 0;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
function getFlagValue(args, name) {
|
|
179
|
-
const long = `--${name}`;
|
|
180
|
-
for (let i = 0; i < args.length; i++) {
|
|
181
|
-
const arg = args[i];
|
|
182
|
-
if (arg === long) {
|
|
183
|
-
const next = args[i + 1];
|
|
184
|
-
return next && !next.startsWith("-") ? next : void 0;
|
|
185
|
-
}
|
|
186
|
-
if (arg.startsWith(`${long}=`)) {
|
|
187
|
-
return arg.slice(long.length + 1);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return void 0;
|
|
191
|
-
}
|
|
192
|
-
function getPositionalArgs(args) {
|
|
193
|
-
const out = [];
|
|
194
|
-
for (let i = 0; i < args.length; i++) {
|
|
195
|
-
const arg = args[i];
|
|
196
|
-
if (PACKAGE_MANAGER_VALUE_FLAGS.has(arg)) {
|
|
197
|
-
i++;
|
|
198
|
-
continue;
|
|
152
|
+
function detectPackageManager() {
|
|
153
|
+
for (const pm of ["bun", "pnpm", "yarn", "npm"]) {
|
|
154
|
+
try {
|
|
155
|
+
execFileSync(pm, ["--version"], { stdio: "ignore" });
|
|
156
|
+
return pm;
|
|
157
|
+
} catch {
|
|
199
158
|
}
|
|
200
|
-
if (arg.startsWith("-")) continue;
|
|
201
|
-
out.push(arg);
|
|
202
|
-
}
|
|
203
|
-
return out;
|
|
204
|
-
}
|
|
205
|
-
function detectPackageManager(projectRoot, override) {
|
|
206
|
-
const requested = parsePackageManagerSpecifier(override || process.env.LUMERA_PACKAGE_MANAGER);
|
|
207
|
-
if (requested) {
|
|
208
|
-
if (!commandAvailable(requested)) {
|
|
209
|
-
throw new Error(`Package manager '${requested}' was requested but is not available on PATH`);
|
|
210
|
-
}
|
|
211
|
-
return requested;
|
|
212
|
-
}
|
|
213
|
-
const declared = packageManagerFromPackageJson(projectRoot);
|
|
214
|
-
if (declared && commandAvailable(declared)) return declared;
|
|
215
|
-
const lockfileManagers = [
|
|
216
|
-
{ pm: "pnpm", file: "pnpm-lock.yaml" },
|
|
217
|
-
{ pm: "bun", file: "bun.lockb" },
|
|
218
|
-
{ pm: "bun", file: "bun.lock" },
|
|
219
|
-
{ pm: "yarn", file: "yarn.lock" },
|
|
220
|
-
{ pm: "npm", file: "package-lock.json" }
|
|
221
|
-
];
|
|
222
|
-
for (const { pm, file } of lockfileManagers) {
|
|
223
|
-
if (existsSync(join(projectRoot, file)) && commandAvailable(pm)) return pm;
|
|
224
|
-
}
|
|
225
|
-
for (const pm of ["pnpm", "bun", "yarn", "npm"]) {
|
|
226
|
-
if (commandAvailable(pm)) return pm;
|
|
227
159
|
}
|
|
228
160
|
return "npm";
|
|
229
161
|
}
|
|
@@ -503,9 +435,6 @@ ${pc2.dim("Resources:")}
|
|
|
503
435
|
${pc2.dim("Options:")}
|
|
504
436
|
--yes, -y Skip confirmation prompt (for CI/CD)
|
|
505
437
|
--skip-build Skip build step when applying app
|
|
506
|
-
--no-app Apply resources only; do not build/deploy the app
|
|
507
|
-
--resources-only Alias for --no-app
|
|
508
|
-
--package-manager Package manager for app builds (pnpm, bun, yarn, npm)
|
|
509
438
|
|
|
510
439
|
${pc2.dim("Examples:")}
|
|
511
440
|
lumera apply # Apply everything (shows plan, asks to confirm)
|
|
@@ -514,8 +443,6 @@ ${pc2.dim("Examples:")}
|
|
|
514
443
|
lumera apply agents -y # Apply agents without confirmation
|
|
515
444
|
lumera apply app # Deploy frontend
|
|
516
445
|
lumera apply app --skip-build # Deploy without rebuilding
|
|
517
|
-
lumera apply --no-app -y # Apply resources without app build/deploy
|
|
518
|
-
lumera apply app --package-manager pnpm
|
|
519
446
|
`);
|
|
520
447
|
}
|
|
521
448
|
function showPullHelp() {
|
|
@@ -1464,7 +1391,7 @@ async function applyApp(args) {
|
|
|
1464
1391
|
if (!skipBuild) {
|
|
1465
1392
|
console.log(pc2.dim(" Building..."));
|
|
1466
1393
|
try {
|
|
1467
|
-
const pm = detectPackageManager(
|
|
1394
|
+
const pm = detectPackageManager();
|
|
1468
1395
|
execSync(`${pm} run build`, { cwd: projectRoot, stdio: "inherit" });
|
|
1469
1396
|
} catch {
|
|
1470
1397
|
throw new Error("Build failed");
|
|
@@ -2568,13 +2495,9 @@ async function apply(args) {
|
|
|
2568
2495
|
const appName = getAppName(projectRoot);
|
|
2569
2496
|
const api = createApiClient(void 0, void 0, appName);
|
|
2570
2497
|
const projectId = getProjectId(projectRoot);
|
|
2571
|
-
const positionalArgs =
|
|
2498
|
+
const positionalArgs = args.filter((a) => !a.startsWith("-"));
|
|
2572
2499
|
const { type, name } = parseResource(positionalArgs[0]);
|
|
2573
2500
|
const autoConfirm = args.includes("--yes") || args.includes("-y") || !!process.env.CI;
|
|
2574
|
-
const skipApp = args.includes("--no-app") || args.includes("--resources-only");
|
|
2575
|
-
if (type === "app" && skipApp) {
|
|
2576
|
-
throw new Error("Cannot combine app resource with --no-app / --resources-only");
|
|
2577
|
-
}
|
|
2578
2501
|
if (type === "app") {
|
|
2579
2502
|
console.log();
|
|
2580
2503
|
console.log(pc2.cyan(pc2.bold(" Apply")));
|
|
@@ -2615,7 +2538,7 @@ async function apply(args) {
|
|
|
2615
2538
|
}
|
|
2616
2539
|
}
|
|
2617
2540
|
let willDeployApp = false;
|
|
2618
|
-
if (!type
|
|
2541
|
+
if (!type) {
|
|
2619
2542
|
try {
|
|
2620
2543
|
if (existsSync(join(projectRoot, "dist")) || existsSync(join(projectRoot, "src"))) {
|
|
2621
2544
|
willDeployApp = true;
|
|
@@ -2795,6 +2718,11 @@ async function pull(args) {
|
|
|
2795
2718
|
console.log(pc2.bold(" Collections:"));
|
|
2796
2719
|
await pullCollections(api, platformDir, name || void 0, appName);
|
|
2797
2720
|
console.log();
|
|
2721
|
+
if (!name && await projectResourceDepsEnabled(projectRoot)) {
|
|
2722
|
+
console.log(pc2.bold(" Resource shares:"));
|
|
2723
|
+
await syncDeps(projectRoot, { write: true, legacyWhenDisabled: false });
|
|
2724
|
+
console.log();
|
|
2725
|
+
}
|
|
2798
2726
|
}
|
|
2799
2727
|
if (!type || type === "automations") {
|
|
2800
2728
|
console.log(pc2.bold(" Automations:"));
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
listAllTemplates
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-FJFIWC7G.js";
|
|
3
|
+
} from "./chunk-H357NP7T.js";
|
|
5
4
|
import "./chunk-PNKVD2UK.js";
|
|
6
5
|
|
|
7
6
|
// src/commands/templates.ts
|
|
@@ -25,7 +24,7 @@ ${pc.dim("Examples:")}
|
|
|
25
24
|
lumera templates # List available templates
|
|
26
25
|
lumera templates list -v # Show with descriptions
|
|
27
26
|
lumera templates validate ./my-template # Validate a template dir
|
|
28
|
-
lumera init my-app -t
|
|
27
|
+
lumera init my-app -t default # Use a template
|
|
29
28
|
`);
|
|
30
29
|
}
|
|
31
30
|
async function list(args) {
|
package/package.json
CHANGED
|
File without changes
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# {{projectTitle}}
|
|
2
|
+
|
|
3
|
+
A Lumera app built with collections, automations, hooks, and a React frontend.
|
|
4
|
+
|
|
5
|
+
## Project Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
platform/
|
|
9
|
+
├── collections/ # Collection schemas (JSON) — deployed via lumera apply
|
|
10
|
+
├── automations/ # Python automations (config.json + run.py per automation)
|
|
11
|
+
├── hooks/ # JavaScript hooks on collection lifecycle events
|
|
12
|
+
src/
|
|
13
|
+
├── routes/index.tsx # Home page
|
|
14
|
+
├── lib/queries.ts # Data fetching helpers
|
|
15
|
+
├── components/ # Shared UI components
|
|
16
|
+
├── components/ui/ # shadcn UI primitives (installed via MCP)
|
|
17
|
+
scripts/ # Utility scripts (seed data, migrations, etc.)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Lumera Concepts
|
|
21
|
+
|
|
22
|
+
A Lumera app is built from these primitives — all defined as code in `platform/`:
|
|
23
|
+
|
|
24
|
+
- **Collections** (`platform/collections/*.json`) — Data tables with typed fields. Deployed via `lumera apply`.
|
|
25
|
+
- **Automations** (`platform/automations/*/`) — Python scripts that run on Lumera's servers. Each has a `config.json` and `run.py`.
|
|
26
|
+
- **Hooks** (`platform/hooks/*.js`) — JavaScript on collection lifecycle events (`before_create`, `after_update`, etc.).
|
|
27
|
+
- **Webhooks** — Receive events from external services (Stripe, GitHub, etc.). Events land in `lm_event_log`; process them with hooks or automations.
|
|
28
|
+
- **Mailbox** — Each tenant gets an email address. Inbound emails are persisted to `lm_mailbox_messages` — use hooks to trigger automations on new mail.
|
|
29
|
+
- **Email** — Send transactional emails from automations via `from lumera import email`. Logged to `lm_email_logs`.
|
|
30
|
+
|
|
31
|
+
All resources use **external IDs** in the format `<app-name>:<resource-name>` (auto-derived from `package.json` name + directory/file name).
|
|
32
|
+
|
|
33
|
+
## Project Namespacing
|
|
34
|
+
|
|
35
|
+
**All collection names are automatically namespaced by project.** Always use bare names (e.g. `orders`, not `myproject__orders`) everywhere — in collection JSON files, Python SDK calls, JavaScript hooks, and frontend queries. The platform resolves bare names to the project-scoped version transparently.
|
|
36
|
+
|
|
37
|
+
- `pb.ensure_collection("orders", ...)` → stored as `{project}__orders`
|
|
38
|
+
- `pb.search("orders", ...)` → resolves to `{project}__orders`
|
|
39
|
+
- `ctx.dao.find("orders", ...)` → resolves to `{project}__orders`
|
|
40
|
+
- `pbList("orders", ...)` in frontend → resolves via `X-Lumera-Project` header
|
|
41
|
+
|
|
42
|
+
**Never manually prefix collection names with `{project}__`.** Two projects can each have an `orders` collection — they are fully isolated.
|
|
43
|
+
|
|
44
|
+
For detailed technical reference (data models, relationships, design decisions), see [architecture.md](architecture.md).
|
|
45
|
+
|
|
46
|
+
## UI Components
|
|
47
|
+
|
|
48
|
+
**Always use shadcn components for UI.** Never hand-code primitives like buttons, dialogs, cards, inputs, tables, etc.
|
|
49
|
+
|
|
50
|
+
### Installing components
|
|
51
|
+
|
|
52
|
+
Use `bunx` to install shadcn components (the sandbox has `bun`, not `npm`/`npx`):
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
bunx shadcn@latest add button card dialog # Install specific components
|
|
56
|
+
bunx shadcn@latest add table input label select # Install more as needed
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Components install into `src/components/ui/` and are fully editable.
|
|
60
|
+
|
|
61
|
+
### shadcn MCP server
|
|
62
|
+
|
|
63
|
+
A `shadcn` MCP server is available to browse and search the registry. Use `mcp({ server: "shadcn" })` to list available tools, then:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
mcp({ search: "button" }) # Search across all MCP servers
|
|
67
|
+
mcp({ server: "shadcn" }) # List shadcn tools
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Useful MCP tools:
|
|
71
|
+
- **search_items_in_registries** — fuzzy search components by name
|
|
72
|
+
- **view_items_in_registries** — view component source code
|
|
73
|
+
- **get_item_examples_from_registries** — find usage examples with full code
|
|
74
|
+
- **get_add_command_for_items** — get the install command
|
|
75
|
+
|
|
76
|
+
All tools use the `@shadcn` registry (e.g. `@shadcn/button`, `@shadcn/card`).
|
|
77
|
+
|
|
78
|
+
### Project setup
|
|
79
|
+
|
|
80
|
+
- `components.json` — configures shadcn paths, aliases, and theme
|
|
81
|
+
- `src/styles.css` — shadcn neutral theme CSS variables
|
|
82
|
+
- `src/lib/utils.ts` — `cn()` utility, import from `@/lib/utils`
|
|
83
|
+
- `src/components/ui/` — installed shadcn components (editable)
|
|
84
|
+
|
|
85
|
+
**Do not hand-code UI primitives.** If you need a button, card, dialog, table, form, select, tabs, tooltip, or any standard UI element — install it from shadcn first.
|
|
86
|
+
|
|
87
|
+
## Frontend API Calls
|
|
88
|
+
|
|
89
|
+
Custom apps run in an **iframe**. **Always use the `@lumerahq/ui` bridge for API calls.** Never use raw `fetch()` against Lumera API endpoints.
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
// ✅ Correct — uses postMessage bridge, no CORS issues
|
|
93
|
+
import { pbSql, pbList, pbCreate, pbUpdate, pbDelete } from '@lumerahq/ui/lib';
|
|
94
|
+
|
|
95
|
+
// CRUD
|
|
96
|
+
const records = await pbList('orders', { filter: JSON.stringify({ status: "pending" }) });
|
|
97
|
+
await pbCreate('orders', { amount: 100 });
|
|
98
|
+
await pbUpdate('orders', recordId, { status: 'done' });
|
|
99
|
+
await pbDelete('orders', recordId);
|
|
100
|
+
|
|
101
|
+
// ❌ WRONG — direct fetch will fail with CORS errors
|
|
102
|
+
fetch('/api/pb/sql', { method: 'POST', body: JSON.stringify({ sql: '...' }) });
|
|
103
|
+
fetch('https://app.lumerahq.com/api/pb/sql', ...);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Why:** The bridge sends requests to the parent window via `postMessage`. The parent makes the actual API call on its own origin — no CORS, and auth is handled automatically.
|
|
107
|
+
|
|
108
|
+
**Rules:**
|
|
109
|
+
- Always import from `@lumerahq/ui/lib` — never write a custom `apiFetch` with raw `fetch()`
|
|
110
|
+
- Token is not needed — the bridge handles auth via the parent session
|
|
111
|
+
- `X-Lumera-Project` header is not needed — the parent adds it automatically
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
## Workflow
|
|
115
|
+
|
|
116
|
+
Follow the user's lead. If they tell you exactly what to build, build it. The workflow below is the default when they describe a goal and leave the approach to you.
|
|
117
|
+
|
|
118
|
+
### Step 1: Plan
|
|
119
|
+
1. **Read skills first** — Read the matching skill files for API details and patterns.
|
|
120
|
+
2. **Discuss the plan** — Start from the **simplest thing that works** — one collection, one screen, one feature. Propose incremental steps that layer on complexity but if the decisions are obvious, you can execute multiple steps in one go. Each step should be a complete horizontal slice (collection + backend logic + UI).
|
|
121
|
+
3. **Stop and ask the user to approve.** Iterate until they're happy with the plan. They may reorder steps, drop features, or add ones you didn't think of.
|
|
122
|
+
|
|
123
|
+
### Step 2: Build (one slice at a time)
|
|
124
|
+
4. **Build horizontally** — Pick the first step. Build the full slice: collection schema → `lumera apply` → seed data → UI route/components → commit. Each slice should be deployable and usable on its own.
|
|
125
|
+
5. **Stop and ask for feedback** — Tell the user to open the **Preview tab** to see the app. The dev server starts automatically — you do NOT need to run `pnpm dev` or start any server. Iterate on the slice until they're happy.
|
|
126
|
+
6. **Repeat** — Move to the next step. Build, deploy, get feedback.
|
|
127
|
+
|
|
128
|
+
### Rules
|
|
129
|
+
7. **Code is source of truth** — Edit files in `platform/`, then deploy with `lumera apply`. Don't edit in the Lumera UI.
|
|
130
|
+
8. **Keep docs current** — After each slice, update `architecture.md` with what was built (data models, relationships, hook logic, design decisions). Also update the project description at the top of this file (`AGENTS.md`) so it reflects what the project actually does now — not the original template description.
|
|
131
|
+
9. **Commit and push** — After each slice or significant change: `git add -A && git commit -m "descriptive message" && git push`. The sandbox is ephemeral — uncommitted work is lost if recycled.
|
|
132
|
+
10. **Deploy marker** — When your changes need `lumera apply`, include at the end of your response: `<!-- DEPLOY: short commit message -->`. Skip for frontend-only changes.
|
|
133
|
+
|
|
134
|
+
## File Artifacts
|
|
135
|
+
|
|
136
|
+
When you create a file the user should **see** in chat (HTML pages, SVG graphics, CSV exports, PDFs, images, charts, etc.), call `create_artifact` with the file path after writing it. This uploads the file and renders an inline preview or download card in the chat. Without this step, the file exists in the sandbox but the user cannot see it.
|
|
137
|
+
|
|
138
|
+
Always call `create_artifact` for: `.html`, `.svg`, `.csv`, `.json`, `.xml`, `.md`, `.txt`, `.pdf`, `.png`, `.jpg`, `.gif`, `.xlsx`, `.docx`, `.pptx`, `.zip` — any file the user asked to see or download.
|
|
139
|
+
|
|
140
|
+
## Python
|
|
141
|
+
|
|
142
|
+
Python 3.14 is pre-installed with common data packages (pandas, numpy, matplotlib, pdfplumber, openpyxl, etc.). To install additional packages use `uv add <package>` (preferred) or `pip install <package>`.
|
|
143
|
+
|
|
144
|
+
## Key Commands
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
lumera plan # Preview changes (dry run)
|
|
148
|
+
lumera apply # Deploy everything
|
|
149
|
+
lumera run scripts/seed.py # Run a script remotely
|
|
150
|
+
lumera status # Show sync status
|
|
151
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# {{projectTitle}}
|
|
2
|
+
|
|
3
|
+
Blank Lumera starter — a minimal React app with platform scaffolding. No pre-built collections, automations, or agents. Start building from scratch.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
lumera plan # Preview changes
|
|
9
|
+
lumera apply # Deploy resources
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Structure
|
|
13
|
+
|
|
14
|
+
- `platform/collections/` — Collection schemas (JSON)
|
|
15
|
+
- `platform/automations/` — Python automations
|
|
16
|
+
- `platform/hooks/` — JavaScript hooks
|
|
17
|
+
- `src/` — React frontend (TanStack Router + Query)
|
|
18
|
+
- `scripts/` — Utility scripts
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
> Update this file as you build — it should always reflect the current state of the project so any agent or developer can understand the system from this file alone.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This is a blank starter. No collections, automations, or hooks are defined yet. Build your app by adding resources to `platform/` and deploying with `lumera apply`.
|
|
8
|
+
|
|
9
|
+
## Collections
|
|
10
|
+
|
|
11
|
+
_None yet — add collection schemas to `platform/collections/` as you build._
|
|
12
|
+
|
|
13
|
+
## Automations
|
|
14
|
+
|
|
15
|
+
_None yet — add automation configs to `platform/automations/` as you build._
|
|
16
|
+
|
|
17
|
+
## Hooks
|
|
18
|
+
|
|
19
|
+
_None yet — add hook scripts to `platform/hooks/` as you build._
|
|
20
|
+
|
|
21
|
+
## Frontend Routes
|
|
22
|
+
|
|
23
|
+
| Route | Purpose |
|
|
24
|
+
|-------|---------|
|
|
25
|
+
| `/` | Home page — blank canvas, ready to build |
|
|
26
|
+
|
|
27
|
+
## Design Decisions
|
|
28
|
+
|
|
29
|
+
_Document key decisions here as you make them._
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
|
|
3
|
+
"vcs": {
|
|
4
|
+
"enabled": true,
|
|
5
|
+
"clientKind": "git",
|
|
6
|
+
"useIgnoreFile": true
|
|
7
|
+
},
|
|
8
|
+
"files": {
|
|
9
|
+
"ignoreUnknown": false,
|
|
10
|
+
"includes": ["**", "!!**/node_modules", "!!**/dist", "!!**/*routeTree.gen.ts"]
|
|
11
|
+
},
|
|
12
|
+
"formatter": {
|
|
13
|
+
"enabled": true,
|
|
14
|
+
"indentStyle": "space",
|
|
15
|
+
"indentWidth": 2,
|
|
16
|
+
"lineWidth": 100
|
|
17
|
+
},
|
|
18
|
+
"linter": {
|
|
19
|
+
"enabled": true,
|
|
20
|
+
"rules": {
|
|
21
|
+
"recommended": true,
|
|
22
|
+
"suspicious": { "noExplicitAny": "off" },
|
|
23
|
+
"style": { "noNonNullAssertion": "off" }
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"javascript": {
|
|
27
|
+
"formatter": {
|
|
28
|
+
"quoteStyle": "single",
|
|
29
|
+
"semicolons": "always",
|
|
30
|
+
"trailingCommas": "es5"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"css": {
|
|
34
|
+
"parser": {
|
|
35
|
+
"tailwindDirectives": true
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "radix-luma",
|
|
4
|
+
"rsc": false,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "src/styles.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"iconLibrary": "lucide",
|
|
14
|
+
"aliases": {
|
|
15
|
+
"components": "@/components",
|
|
16
|
+
"utils": "@/lib/utils",
|
|
17
|
+
"ui": "@/components/ui",
|
|
18
|
+
"lib": "@/lib",
|
|
19
|
+
"hooks": "@/hooks"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" fill="none">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="0" y1="0" x2="80" y2="80" gradientUnits="userSpaceOnUse">
|
|
4
|
+
<stop offset="0%" stop-color="#ecfdf5"/>
|
|
5
|
+
<stop offset="100%" stop-color="#d1fae5"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
<linearGradient id="g1" x1="0" y1="0" x2="1" y2="1">
|
|
8
|
+
<stop offset="0%" stop-color="#34d399"/>
|
|
9
|
+
<stop offset="100%" stop-color="#059669"/>
|
|
10
|
+
</linearGradient>
|
|
11
|
+
</defs>
|
|
12
|
+
<rect width="80" height="80" rx="18" fill="url(#bg)"/>
|
|
13
|
+
<!-- Rocket body -->
|
|
14
|
+
<path d="M40 12 C40 12 28 28 28 44 C28 52 33 58 40 58 C47 58 52 52 52 44 C52 28 40 12 40 12Z" fill="url(#g1)" opacity="0.9"/>
|
|
15
|
+
<!-- Window -->
|
|
16
|
+
<circle cx="40" cy="34" r="5" fill="#ecfdf5"/>
|
|
17
|
+
<circle cx="40" cy="34" r="3" fill="#059669" opacity="0.3"/>
|
|
18
|
+
<!-- Fins -->
|
|
19
|
+
<path d="M28 44 L20 54 L28 50Z" fill="#34d399" opacity="0.7"/>
|
|
20
|
+
<path d="M52 44 L60 54 L52 50Z" fill="#34d399" opacity="0.7"/>
|
|
21
|
+
<!-- Flame -->
|
|
22
|
+
<path d="M36 58 Q38 68 40 72 Q42 68 44 58" fill="#fbbf24"/>
|
|
23
|
+
<path d="M38 58 Q39 65 40 68 Q41 65 42 58" fill="#f97316"/>
|
|
24
|
+
<!-- Stars -->
|
|
25
|
+
<circle cx="18" cy="18" r="1.5" fill="#059669" opacity="0.5"/>
|
|
26
|
+
<circle cx="62" cy="14" r="1" fill="#059669" opacity="0.4"/>
|
|
27
|
+
<circle cx="14" cy="38" r="1" fill="#059669" opacity="0.3"/>
|
|
28
|
+
<circle cx="66" cy="32" r="1.5" fill="#059669" opacity="0.4"/>
|
|
29
|
+
</svg>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta name="theme-color" content="#000000" />
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
10
|
+
<title>{{projectTitle}}</title>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div id="app"></div>
|
|
14
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|