@lumerahq/cli 0.10.0 → 0.11.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.
Files changed (35) hide show
  1. package/dist/chunk-CHRKCAIZ.js +155 -0
  2. package/dist/{chunk-WRAZC6SJ.js → chunk-HIYM7EM2.js} +17 -1
  3. package/dist/dev-2HVDP3NX.js +155 -0
  4. package/dist/index.js +166 -14
  5. package/dist/init-VNJNSU4Q.js +440 -0
  6. package/dist/{resources-PGBVCS2K.js → resources-GTG3QMVV.js} +2 -4
  7. package/dist/{run-WIRQDYYX.js → run-47GF5VVS.js} +2 -4
  8. package/dist/templates-6KMZWOYH.js +194 -0
  9. package/package.json +1 -1
  10. package/dist/chunk-2CR762KB.js +0 -18
  11. package/dist/dev-BHBF4ECH.js +0 -87
  12. package/dist/init-EDSRR3YM.js +0 -360
  13. package/templates/default/ARCHITECTURE.md +0 -80
  14. package/templates/default/CLAUDE.md +0 -238
  15. package/templates/default/README.md +0 -59
  16. package/templates/default/biome.json +0 -38
  17. package/templates/default/gitignore +0 -9
  18. package/templates/default/index.html +0 -13
  19. package/templates/default/package.json.hbs +0 -47
  20. package/templates/default/platform/automations/.gitkeep +0 -0
  21. package/templates/default/platform/collections/example_items.json +0 -26
  22. package/templates/default/platform/hooks/.gitkeep +0 -0
  23. package/templates/default/pyproject.toml.hbs +0 -14
  24. package/templates/default/scripts/seed-demo.py +0 -35
  25. package/templates/default/src/components/Sidebar.tsx +0 -82
  26. package/templates/default/src/components/StatCard.tsx +0 -25
  27. package/templates/default/src/components/layout.tsx +0 -13
  28. package/templates/default/src/lib/queries.ts +0 -27
  29. package/templates/default/src/main.tsx +0 -131
  30. package/templates/default/src/routes/__root.tsx +0 -10
  31. package/templates/default/src/routes/index.tsx +0 -88
  32. package/templates/default/src/routes/settings.tsx +0 -21
  33. package/templates/default/src/styles.css +0 -44
  34. package/templates/default/tsconfig.json +0 -23
  35. package/templates/default/vite.config.ts +0 -28
@@ -1,87 +0,0 @@
1
- import {
2
- dev
3
- } from "./chunk-CDZZ3JYU.js";
4
- import {
5
- loadEnv
6
- } from "./chunk-2CR762KB.js";
7
- import {
8
- getToken
9
- } from "./chunk-NDLYGKS6.js";
10
- import {
11
- findProjectRoot,
12
- getApiUrl,
13
- getAppName,
14
- getAppTitle
15
- } from "./chunk-D2BLSEGR.js";
16
-
17
- // src/commands/dev.ts
18
- import pc from "picocolors";
19
- function parseFlags(args) {
20
- const result = {};
21
- for (let i = 0; i < args.length; i++) {
22
- const arg = args[i];
23
- if (arg.startsWith("--")) {
24
- const key = arg.slice(2);
25
- const next = args[i + 1];
26
- if (next && !next.startsWith("--")) {
27
- result[key] = next;
28
- i++;
29
- } else {
30
- result[key] = true;
31
- }
32
- }
33
- }
34
- return result;
35
- }
36
- function showHelp() {
37
- console.log(`
38
- ${pc.dim("Usage:")}
39
- lumera dev [options]
40
-
41
- ${pc.dim("Description:")}
42
- Start the development server with Lumera registration.
43
- Registers your app with Lumera and starts a local dev server.
44
-
45
- ${pc.dim("Options:")}
46
- --port <number> Dev server port (default: 8080)
47
- --url <url> App URL for dev mode (default: http://localhost:{port})
48
- --help, -h Show this help
49
-
50
- ${pc.dim("Environment variables:")}
51
- LUMERA_TOKEN API token (overrides \`lumera login\`)
52
- LUMERA_API_URL API base URL (default: https://app.lumerahq.com/api)
53
- PORT Dev server port (default: 8080)
54
- APP_URL App URL for dev mode
55
-
56
- ${pc.dim("Examples:")}
57
- lumera dev # Start dev server on port 8080
58
- lumera dev --port 3000 # Start dev server on port 3000
59
- lumera dev --url http://192.168.1.100:8080 # Custom URL for mobile testing
60
- `);
61
- }
62
- async function dev2(args) {
63
- if (args.includes("--help") || args.includes("-h")) {
64
- showHelp();
65
- return;
66
- }
67
- const flags = parseFlags(args);
68
- const projectRoot = findProjectRoot();
69
- loadEnv(projectRoot);
70
- const token = getToken(projectRoot);
71
- const appName = getAppName(projectRoot);
72
- const appTitle = getAppTitle(projectRoot);
73
- const apiUrl = getApiUrl();
74
- const port = Number(flags.port || process.env.PORT || "8080");
75
- const appUrl = typeof flags.url === "string" ? flags.url : process.env.APP_URL;
76
- await dev({
77
- token,
78
- appName,
79
- appTitle,
80
- port,
81
- appUrl,
82
- apiUrl
83
- });
84
- }
85
- export {
86
- dev2 as dev
87
- };
@@ -1,360 +0,0 @@
1
- import {
2
- installAllSkills,
3
- syncClaudeMd
4
- } from "./chunk-UP3GV4HN.js";
5
- import "./chunk-D2BLSEGR.js";
6
-
7
- // src/commands/init.ts
8
- import pc from "picocolors";
9
- import prompts from "prompts";
10
- import { execSync } from "child_process";
11
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, rmSync } from "fs";
12
- import { join, dirname, resolve } from "path";
13
- import { fileURLToPath } from "url";
14
- var __filename = fileURLToPath(import.meta.url);
15
- var __dirname = dirname(__filename);
16
- function getTemplatesDir() {
17
- const candidates = [
18
- // From dist/index.js or dist/commands/init.js
19
- resolve(__dirname, "../templates/default"),
20
- resolve(__dirname, "../../templates/default"),
21
- // From src during development
22
- resolve(__dirname, "../../../templates/default")
23
- ];
24
- for (const candidate of candidates) {
25
- if (existsSync(candidate)) {
26
- return candidate;
27
- }
28
- }
29
- throw new Error(`Templates directory not found. Searched: ${candidates.join(", ")}`);
30
- }
31
- function toTitleCase(str) {
32
- return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
33
- }
34
- function processTemplate(content, vars) {
35
- let result = content;
36
- for (const [key, value] of Object.entries(vars)) {
37
- result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
38
- }
39
- return result;
40
- }
41
- function copyDir(src, dest, vars) {
42
- if (!existsSync(dest)) {
43
- mkdirSync(dest, { recursive: true });
44
- }
45
- for (const entry of readdirSync(src, { withFileTypes: true })) {
46
- const srcPath = join(src, entry.name);
47
- let destName = entry.name;
48
- if (destName.endsWith(".hbs")) {
49
- destName = destName.slice(0, -4);
50
- }
51
- if (destName === "gitignore") {
52
- destName = ".gitignore";
53
- }
54
- const destPath = join(dest, destName);
55
- if (entry.isDirectory()) {
56
- copyDir(srcPath, destPath, vars);
57
- } else {
58
- const content = readFileSync(srcPath, "utf-8");
59
- const processed = processTemplate(content, vars);
60
- writeFileSync(destPath, processed);
61
- }
62
- }
63
- }
64
- function isGitInstalled() {
65
- try {
66
- execSync("git --version", { stdio: "ignore" });
67
- return true;
68
- } catch {
69
- return false;
70
- }
71
- }
72
- function initGitRepo(targetDir, projectName) {
73
- try {
74
- execSync("git init", { cwd: targetDir, stdio: "ignore" });
75
- execSync("git add -A", { cwd: targetDir, stdio: "ignore" });
76
- execSync(`git commit -m "Initial commit: scaffold ${projectName}"`, {
77
- cwd: targetDir,
78
- stdio: "ignore"
79
- });
80
- return true;
81
- } catch {
82
- return false;
83
- }
84
- }
85
- function isUvInstalled() {
86
- try {
87
- execSync("uv --version", { stdio: "ignore" });
88
- return true;
89
- } catch {
90
- return false;
91
- }
92
- }
93
- function installUv() {
94
- try {
95
- try {
96
- execSync("curl -LsSf https://astral.sh/uv/install.sh | sh", {
97
- stdio: "inherit",
98
- shell: "/bin/bash"
99
- });
100
- return true;
101
- } catch {
102
- execSync("wget -qO- https://astral.sh/uv/install.sh | sh", {
103
- stdio: "inherit",
104
- shell: "/bin/bash"
105
- });
106
- return true;
107
- }
108
- } catch {
109
- return false;
110
- }
111
- }
112
- function createPythonVenv(targetDir) {
113
- try {
114
- execSync("uv venv", { cwd: targetDir, stdio: "ignore" });
115
- execSync("uv pip install lumera", { cwd: targetDir, stdio: "ignore" });
116
- return true;
117
- } catch {
118
- return false;
119
- }
120
- }
121
- function parseArgs(args) {
122
- const result = {
123
- projectName: void 0,
124
- directory: void 0,
125
- install: false,
126
- yes: false,
127
- force: false,
128
- help: false
129
- };
130
- for (let i = 0; i < args.length; i++) {
131
- const arg = args[i];
132
- if (arg === "--help" || arg === "-h") {
133
- result.help = true;
134
- } else if (arg === "--install" || arg === "-i") {
135
- result.install = true;
136
- } else if (arg === "--yes" || arg === "-y") {
137
- result.yes = true;
138
- } else if (arg === "--force" || arg === "-f") {
139
- result.force = true;
140
- } else if (arg === "--dir" || arg === "-d") {
141
- result.directory = args[++i];
142
- } else if (!arg.startsWith("-") && !result.projectName) {
143
- result.projectName = arg;
144
- }
145
- }
146
- return result;
147
- }
148
- function showHelp() {
149
- console.log(`
150
- ${pc.dim("Usage:")}
151
- lumera init [name] [options]
152
-
153
- ${pc.dim("Description:")}
154
- Scaffold a new Lumera project.
155
-
156
- ${pc.dim("Options:")}
157
- --yes, -y Non-interactive mode (requires project name)
158
- --dir, -d <path> Target directory (defaults to project name)
159
- --force, -f Overwrite existing directory without prompting
160
- --install, -i Install dependencies after scaffolding
161
- --help, -h Show this help
162
-
163
- ${pc.dim("Interactive mode:")}
164
- lumera init # Prompt for project name and directory
165
- lumera init my-project # Prompt for directory only
166
-
167
- ${pc.dim("Non-interactive mode:")}
168
- lumera init my-project -y # Create ./my-project
169
- lumera init my-project -y -d ./apps # Create ./apps
170
- lumera init my-project -y -f # Overwrite if exists
171
- lumera init my-project -y -i # Create and install deps
172
-
173
- ${pc.dim("CI/CD example:")}
174
- lumera init my-app -y -f -i # Full non-interactive setup
175
- `);
176
- }
177
- async function init(args) {
178
- const opts = parseArgs(args);
179
- if (opts.help) {
180
- showHelp();
181
- return;
182
- }
183
- console.log();
184
- console.log(pc.cyan(pc.bold(" Create Lumera App")));
185
- console.log();
186
- let projectName = opts.projectName;
187
- let directory = opts.directory;
188
- const nonInteractive = opts.yes;
189
- if (nonInteractive && !projectName) {
190
- console.log(pc.red(" Error: Project name is required in non-interactive mode"));
191
- console.log(pc.dim(" Usage: lumera init <name> -y"));
192
- process.exit(1);
193
- }
194
- if (!projectName) {
195
- const response = await prompts({
196
- type: "text",
197
- name: "projectName",
198
- message: "What is your project name?",
199
- initial: "my-lumera-app",
200
- validate: (value) => {
201
- if (!value) return "Project name is required";
202
- if (!/^[a-z0-9-]+$/.test(value)) {
203
- return "Use lowercase letters, numbers, and hyphens only";
204
- }
205
- return true;
206
- }
207
- });
208
- if (!response.projectName) {
209
- console.log(pc.red("Cancelled"));
210
- process.exit(1);
211
- }
212
- projectName = response.projectName;
213
- }
214
- if (!/^[a-z0-9-]+$/.test(projectName)) {
215
- console.log(pc.red(" Error: Project name must use lowercase letters, numbers, and hyphens only"));
216
- process.exit(1);
217
- }
218
- if (!directory) {
219
- if (nonInteractive) {
220
- directory = projectName;
221
- } else {
222
- const response = await prompts({
223
- type: "text",
224
- name: "directory",
225
- message: "Where should we create the project?",
226
- initial: projectName
227
- });
228
- if (!response.directory) {
229
- console.log(pc.red("Cancelled"));
230
- process.exit(1);
231
- }
232
- directory = response.directory;
233
- }
234
- }
235
- const projectTitle = toTitleCase(projectName);
236
- const projectInitial = projectTitle.charAt(0).toUpperCase();
237
- const targetDir = resolve(process.cwd(), directory);
238
- if (existsSync(targetDir)) {
239
- if (nonInteractive) {
240
- if (opts.force) {
241
- rmSync(targetDir, { recursive: true });
242
- } else {
243
- console.log(pc.red(` Error: Directory ${directory} already exists`));
244
- console.log(pc.dim(" Use --force (-f) to overwrite"));
245
- process.exit(1);
246
- }
247
- } else {
248
- const { overwrite } = await prompts({
249
- type: "confirm",
250
- name: "overwrite",
251
- message: `Directory ${directory} already exists. Overwrite?`,
252
- initial: false
253
- });
254
- if (!overwrite) {
255
- console.log(pc.red("Cancelled"));
256
- process.exit(1);
257
- }
258
- rmSync(targetDir, { recursive: true });
259
- }
260
- }
261
- mkdirSync(targetDir, { recursive: true });
262
- console.log();
263
- console.log(pc.dim(` Creating ${projectName} in ${directory}...`));
264
- console.log();
265
- const templatesDir = getTemplatesDir();
266
- const vars = {
267
- projectName,
268
- projectTitle,
269
- projectInitial
270
- };
271
- copyDir(templatesDir, targetDir, vars);
272
- function listFiles(dir, prefix = "") {
273
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
274
- const relativePath = prefix + entry.name;
275
- if (entry.isDirectory()) {
276
- listFiles(join(dir, entry.name), relativePath + "/");
277
- } else {
278
- console.log(pc.green(" \u2713"), pc.dim(relativePath));
279
- }
280
- }
281
- }
282
- listFiles(targetDir);
283
- if (isGitInstalled()) {
284
- console.log();
285
- console.log(pc.dim(" Initializing git repository..."));
286
- if (initGitRepo(targetDir, projectName)) {
287
- console.log(pc.green(" \u2713"), pc.dim("Git repository initialized with initial commit"));
288
- } else {
289
- console.log(pc.yellow(" \u26A0"), pc.dim("Failed to initialize git repository"));
290
- }
291
- } else {
292
- console.log();
293
- console.log(pc.yellow(" \u26A0"), pc.dim("Git not found - skipping repository initialization"));
294
- }
295
- let uvAvailable = isUvInstalled();
296
- if (!uvAvailable) {
297
- console.log();
298
- console.log(pc.dim(" Installing uv (Python package manager)..."));
299
- if (installUv()) {
300
- console.log(pc.green(" \u2713"), pc.dim("uv installed successfully"));
301
- uvAvailable = true;
302
- } else {
303
- console.log(pc.yellow(" \u26A0"), pc.dim("Failed to install uv - install manually: https://docs.astral.sh/uv/"));
304
- }
305
- }
306
- if (uvAvailable) {
307
- console.log();
308
- console.log(pc.dim(" Creating Python venv with Lumera SDK..."));
309
- if (createPythonVenv(targetDir)) {
310
- console.log(pc.green(" \u2713"), pc.dim("Python venv created (.venv/) with lumera SDK"));
311
- } else {
312
- console.log(pc.yellow(" \u26A0"), pc.dim("Failed to create Python venv"));
313
- }
314
- }
315
- if (opts.install) {
316
- console.log();
317
- console.log(pc.dim(" Installing dependencies..."));
318
- try {
319
- execSync("pnpm install", { cwd: targetDir, stdio: "inherit" });
320
- console.log(pc.green(" \u2713"), pc.dim("Dependencies installed"));
321
- } catch {
322
- console.log(pc.yellow(" \u26A0"), pc.dim("Failed to install dependencies"));
323
- }
324
- }
325
- console.log();
326
- console.log(pc.dim(" Installing Lumera skills for AI agents..."));
327
- try {
328
- const { installed, failed } = await installAllSkills(targetDir);
329
- if (failed > 0) {
330
- console.log(pc.yellow(" \u26A0"), pc.dim(`Installed ${installed} skills (${failed} failed)`));
331
- } else {
332
- console.log(pc.green(" \u2713"), pc.dim(`${installed} Lumera skills installed`));
333
- }
334
- syncClaudeMd(targetDir);
335
- if (isGitInstalled()) {
336
- try {
337
- execSync('git add -A && git commit -m "chore: install lumera skills"', {
338
- cwd: targetDir,
339
- stdio: "ignore"
340
- });
341
- } catch {
342
- }
343
- }
344
- } catch (err) {
345
- console.log(pc.yellow(" \u26A0"), pc.dim(`Failed to install skills: ${err}`));
346
- }
347
- console.log();
348
- console.log(pc.green(pc.bold(" Done!")), "Next steps:");
349
- console.log();
350
- console.log(pc.cyan(` cd ${directory}`));
351
- if (!opts.install) {
352
- console.log(pc.cyan(" pnpm install"));
353
- }
354
- console.log(pc.cyan(" lumera login"));
355
- console.log(pc.cyan(" pnpm dev"));
356
- console.log();
357
- }
358
- export {
359
- init
360
- };
@@ -1,80 +0,0 @@
1
- # {{projectTitle}} — Architecture
2
-
3
- ## Overview
4
-
5
- {{projectTitle}} is a Lumera embedded app — a React frontend served inside the Lumera platform iframe, backed by collections, hooks, and automations managed through the Lumera CLI.
6
-
7
- ## System Diagram
8
-
9
- ```
10
- ┌─────────────────────────────────────────────────┐
11
- │ Lumera Platform │
12
- │ │
13
- │ ┌───────────┐ postMessage ┌────────────┐ │
14
- │ │ Host UI │ ◄──────────────► │ App │ │
15
- │ │ │ (auth, init) │ (iframe) │ │
16
- │ └─────┬─────┘ └─────┬──────┘ │
17
- │ │ │ │
18
- │ │ REST API │ │
19
- │ ▼ ▼ │
20
- │ ┌──────────────────────────────────────────┐ │
21
- │ │ Lumera API │ │
22
- │ │ - Collections (CRUD, SQL, search) │ │
23
- │ │ - Automations (run, poll, cancel) │ │
24
- │ │ - File storage (upload, download) │ │
25
- │ └──────────────┬───────────────────────────┘ │
26
- │ │ │
27
- │ ▼ │
28
- │ ┌──────────────────────────────────────────┐ │
29
- │ │ Tenant Database (PocketBase/SQLite) │ │
30
- │ │ - example_items │ │
31
- │ │ - (add your collections here) │ │
32
- │ └──────────────────────────────────────────┘ │
33
- └─────────────────────────────────────────────────┘
34
- ```
35
-
36
- ## Frontend (`src/`)
37
-
38
- React app using TanStack Router (file-based routing) and TanStack Query for data fetching. Embedded in Lumera via iframe with postMessage bridge for authentication.
39
-
40
- | Directory | Purpose |
41
- |------------------|--------------------------------------|
42
- | `src/routes/` | Pages — file names map to URL paths |
43
- | `src/components/`| Shared React components |
44
- | `src/lib/` | API helpers, query functions |
45
- | `src/main.tsx` | App entry — auth bridge, router init |
46
-
47
- **Key patterns:**
48
- - Auth context flows from `main.tsx` via `AuthContext`
49
- - Data fetching uses `pbList`, `pbSql` from `@lumerahq/ui/lib`
50
- - Styling via Tailwind CSS 4 with theme tokens in `styles.css`
51
-
52
- ## Platform Resources (`platform/`)
53
-
54
- Declarative definitions deployed via `lumera apply`.
55
-
56
- | Directory | Purpose |
57
- |--------------------------|----------------------------------|
58
- | `platform/collections/` | Collection schemas (JSON) |
59
- | `platform/automations/` | Background Python scripts |
60
- | `platform/hooks/` | Server-side JS on collection events |
61
-
62
- ## Scripts (`scripts/`)
63
-
64
- Local Python scripts run via `lumera run`. Used for seeding data, migrations, and ad-hoc operations. All scripts should be idempotent.
65
-
66
- ## Data Flow
67
-
68
- 1. **User opens app** → Lumera host sends auth payload via postMessage
69
- 2. **App authenticates** → Stores session token in `AuthContext`
70
- 3. **App fetches data** → Calls Lumera API via `@lumerahq/ui/lib` helpers
71
- 4. **Data mutations** → API calls trigger collection hooks if configured
72
- 5. **Background work** → Automations run async via `createRun` / `pollRun`
73
-
74
- ## Collections
75
-
76
- | Collection | Purpose |
77
- |------------------|----------------------------|
78
- | `example_items` | Starter collection (replace with your own) |
79
-
80
- _Update this table as you add collections._