@lumerahq/cli 0.7.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 (37) hide show
  1. package/README.md +118 -0
  2. package/dist/auth-7RGL7GXU.js +311 -0
  3. package/dist/chunk-2CR762KB.js +18 -0
  4. package/dist/chunk-AVKPM7C4.js +199 -0
  5. package/dist/chunk-D2BLSEGR.js +59 -0
  6. package/dist/chunk-NDLYGKS6.js +77 -0
  7. package/dist/chunk-V2XXMMEI.js +147 -0
  8. package/dist/dev-UTZC4ZJ7.js +87 -0
  9. package/dist/index.js +157 -0
  10. package/dist/init-OQCIET53.js +363 -0
  11. package/dist/migrate-2DZ6RQ5K.js +190 -0
  12. package/dist/resources-PNK3NESI.js +1350 -0
  13. package/dist/run-4NDI2CN4.js +257 -0
  14. package/dist/skills-56EUKHGY.js +414 -0
  15. package/dist/status-BEVUV6RY.js +131 -0
  16. package/package.json +37 -0
  17. package/templates/default/CLAUDE.md +245 -0
  18. package/templates/default/README.md +59 -0
  19. package/templates/default/biome.json +33 -0
  20. package/templates/default/index.html +13 -0
  21. package/templates/default/package.json.hbs +46 -0
  22. package/templates/default/platform/automations/.gitkeep +0 -0
  23. package/templates/default/platform/collections/example_items.json +28 -0
  24. package/templates/default/platform/hooks/.gitkeep +0 -0
  25. package/templates/default/pyproject.toml.hbs +14 -0
  26. package/templates/default/scripts/seed-demo.py +35 -0
  27. package/templates/default/src/components/Sidebar.tsx +84 -0
  28. package/templates/default/src/components/StatCard.tsx +31 -0
  29. package/templates/default/src/components/layout.tsx +13 -0
  30. package/templates/default/src/lib/queries.ts +27 -0
  31. package/templates/default/src/main.tsx +137 -0
  32. package/templates/default/src/routes/__root.tsx +10 -0
  33. package/templates/default/src/routes/index.tsx +90 -0
  34. package/templates/default/src/routes/settings.tsx +25 -0
  35. package/templates/default/src/styles.css +40 -0
  36. package/templates/default/tsconfig.json +23 -0
  37. package/templates/default/vite.config.ts +27 -0
@@ -0,0 +1,363 @@
1
+ import {
2
+ getBaseUrl
3
+ } from "./chunk-D2BLSEGR.js";
4
+
5
+ // src/commands/init.ts
6
+ import pc from "picocolors";
7
+ import prompts from "prompts";
8
+ import { execSync } from "child_process";
9
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, rmSync } from "fs";
10
+ import { join, dirname, resolve } from "path";
11
+ import { fileURLToPath } from "url";
12
+ var __filename = fileURLToPath(import.meta.url);
13
+ var __dirname = dirname(__filename);
14
+ function getTemplatesDir() {
15
+ const candidates = [
16
+ // From dist/index.js or dist/commands/init.js
17
+ resolve(__dirname, "../templates/default"),
18
+ resolve(__dirname, "../../templates/default"),
19
+ // From src during development
20
+ resolve(__dirname, "../../../templates/default")
21
+ ];
22
+ for (const candidate of candidates) {
23
+ if (existsSync(candidate)) {
24
+ return candidate;
25
+ }
26
+ }
27
+ throw new Error(`Templates directory not found. Searched: ${candidates.join(", ")}`);
28
+ }
29
+ function toTitleCase(str) {
30
+ return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
31
+ }
32
+ function processTemplate(content, vars) {
33
+ let result = content;
34
+ for (const [key, value] of Object.entries(vars)) {
35
+ result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
36
+ }
37
+ return result;
38
+ }
39
+ function copyDir(src, dest, vars) {
40
+ if (!existsSync(dest)) {
41
+ mkdirSync(dest, { recursive: true });
42
+ }
43
+ for (const entry of readdirSync(src, { withFileTypes: true })) {
44
+ const srcPath = join(src, entry.name);
45
+ let destName = entry.name;
46
+ if (destName.endsWith(".hbs")) {
47
+ destName = destName.slice(0, -4);
48
+ }
49
+ const destPath = join(dest, destName);
50
+ if (entry.isDirectory()) {
51
+ copyDir(srcPath, destPath, vars);
52
+ } else {
53
+ const content = readFileSync(srcPath, "utf-8");
54
+ const processed = processTemplate(content, vars);
55
+ writeFileSync(destPath, processed);
56
+ }
57
+ }
58
+ }
59
+ function isGitInstalled() {
60
+ try {
61
+ execSync("git --version", { stdio: "ignore" });
62
+ return true;
63
+ } catch {
64
+ return false;
65
+ }
66
+ }
67
+ function initGitRepo(targetDir, projectName) {
68
+ try {
69
+ execSync("git init", { cwd: targetDir, stdio: "ignore" });
70
+ execSync("git add -A", { cwd: targetDir, stdio: "ignore" });
71
+ execSync(`git commit -m "Initial commit: scaffold ${projectName}"`, {
72
+ cwd: targetDir,
73
+ stdio: "ignore"
74
+ });
75
+ return true;
76
+ } catch {
77
+ return false;
78
+ }
79
+ }
80
+ function isUvInstalled() {
81
+ try {
82
+ execSync("uv --version", { stdio: "ignore" });
83
+ return true;
84
+ } catch {
85
+ return false;
86
+ }
87
+ }
88
+ function installUv() {
89
+ try {
90
+ try {
91
+ execSync("curl -LsSf https://astral.sh/uv/install.sh | sh", {
92
+ stdio: "inherit",
93
+ shell: "/bin/bash"
94
+ });
95
+ return true;
96
+ } catch {
97
+ execSync("wget -qO- https://astral.sh/uv/install.sh | sh", {
98
+ stdio: "inherit",
99
+ shell: "/bin/bash"
100
+ });
101
+ return true;
102
+ }
103
+ } catch {
104
+ return false;
105
+ }
106
+ }
107
+ function createPythonVenv(targetDir) {
108
+ try {
109
+ execSync("uv venv", { cwd: targetDir, stdio: "ignore" });
110
+ execSync("uv pip install lumera", { cwd: targetDir, stdio: "ignore" });
111
+ return true;
112
+ } catch {
113
+ return false;
114
+ }
115
+ }
116
+ async function installSkills(targetDir) {
117
+ const baseUrl = getBaseUrl();
118
+ const skillsApiUrl = `${baseUrl}/api/public/skills`;
119
+ const listRes = await fetch(skillsApiUrl);
120
+ if (!listRes.ok) {
121
+ throw new Error(`Failed to fetch skills list: ${listRes.status}`);
122
+ }
123
+ const skills = await listRes.json();
124
+ const skillsDir = join(targetDir, ".claude", "skills");
125
+ mkdirSync(skillsDir, { recursive: true });
126
+ for (const skill of skills) {
127
+ const mdRes = await fetch(`${skillsApiUrl}/${skill.slug}.md`);
128
+ if (!mdRes.ok) {
129
+ console.log(pc.yellow(" \u26A0"), pc.dim(`Failed to fetch skill ${skill.slug}`));
130
+ continue;
131
+ }
132
+ const content = await mdRes.text();
133
+ const slug = skill.slug.startsWith("lumera-") ? skill.slug : `lumera-${skill.slug}`;
134
+ const filename = `${slug.replace(/-/g, "_")}.md`;
135
+ writeFileSync(join(skillsDir, filename), content);
136
+ }
137
+ }
138
+ function parseArgs(args) {
139
+ const result = {
140
+ projectName: void 0,
141
+ directory: void 0,
142
+ install: false,
143
+ yes: false,
144
+ force: false,
145
+ help: false
146
+ };
147
+ for (let i = 0; i < args.length; i++) {
148
+ const arg = args[i];
149
+ if (arg === "--help" || arg === "-h") {
150
+ result.help = true;
151
+ } else if (arg === "--install" || arg === "-i") {
152
+ result.install = true;
153
+ } else if (arg === "--yes" || arg === "-y") {
154
+ result.yes = true;
155
+ } else if (arg === "--force" || arg === "-f") {
156
+ result.force = true;
157
+ } else if (arg === "--dir" || arg === "-d") {
158
+ result.directory = args[++i];
159
+ } else if (!arg.startsWith("-") && !result.projectName) {
160
+ result.projectName = arg;
161
+ }
162
+ }
163
+ return result;
164
+ }
165
+ function showHelp() {
166
+ console.log(`
167
+ ${pc.dim("Usage:")}
168
+ lumera init [name] [options]
169
+
170
+ ${pc.dim("Description:")}
171
+ Scaffold a new Lumera project.
172
+
173
+ ${pc.dim("Options:")}
174
+ --yes, -y Non-interactive mode (requires project name)
175
+ --dir, -d <path> Target directory (defaults to project name)
176
+ --force, -f Overwrite existing directory without prompting
177
+ --install, -i Install dependencies after scaffolding
178
+ --help, -h Show this help
179
+
180
+ ${pc.dim("Interactive mode:")}
181
+ lumera init # Prompt for project name and directory
182
+ lumera init my-project # Prompt for directory only
183
+
184
+ ${pc.dim("Non-interactive mode:")}
185
+ lumera init my-project -y # Create ./my-project
186
+ lumera init my-project -y -d ./apps # Create ./apps
187
+ lumera init my-project -y -f # Overwrite if exists
188
+ lumera init my-project -y -i # Create and install deps
189
+
190
+ ${pc.dim("CI/CD example:")}
191
+ lumera init my-app -y -f -i # Full non-interactive setup
192
+ `);
193
+ }
194
+ async function init(args) {
195
+ const opts = parseArgs(args);
196
+ if (opts.help) {
197
+ showHelp();
198
+ return;
199
+ }
200
+ console.log();
201
+ console.log(pc.cyan(pc.bold(" Create Lumera App")));
202
+ console.log();
203
+ let projectName = opts.projectName;
204
+ let directory = opts.directory;
205
+ const nonInteractive = opts.yes;
206
+ if (nonInteractive && !projectName) {
207
+ console.log(pc.red(" Error: Project name is required in non-interactive mode"));
208
+ console.log(pc.dim(" Usage: lumera init <name> -y"));
209
+ process.exit(1);
210
+ }
211
+ if (!projectName) {
212
+ const response = await prompts({
213
+ type: "text",
214
+ name: "projectName",
215
+ message: "What is your project name?",
216
+ initial: "my-lumera-app",
217
+ validate: (value) => {
218
+ if (!value) return "Project name is required";
219
+ if (!/^[a-z0-9-]+$/.test(value)) {
220
+ return "Use lowercase letters, numbers, and hyphens only";
221
+ }
222
+ return true;
223
+ }
224
+ });
225
+ if (!response.projectName) {
226
+ console.log(pc.red("Cancelled"));
227
+ process.exit(1);
228
+ }
229
+ projectName = response.projectName;
230
+ }
231
+ if (!/^[a-z0-9-]+$/.test(projectName)) {
232
+ console.log(pc.red(" Error: Project name must use lowercase letters, numbers, and hyphens only"));
233
+ process.exit(1);
234
+ }
235
+ if (!directory) {
236
+ if (nonInteractive) {
237
+ directory = projectName;
238
+ } else {
239
+ const response = await prompts({
240
+ type: "text",
241
+ name: "directory",
242
+ message: "Where should we create the project?",
243
+ initial: projectName
244
+ });
245
+ if (!response.directory) {
246
+ console.log(pc.red("Cancelled"));
247
+ process.exit(1);
248
+ }
249
+ directory = response.directory;
250
+ }
251
+ }
252
+ const projectTitle = toTitleCase(projectName);
253
+ const projectInitial = projectTitle.charAt(0).toUpperCase();
254
+ const targetDir = resolve(process.cwd(), directory);
255
+ if (existsSync(targetDir)) {
256
+ if (nonInteractive) {
257
+ if (opts.force) {
258
+ rmSync(targetDir, { recursive: true });
259
+ } else {
260
+ console.log(pc.red(` Error: Directory ${directory} already exists`));
261
+ console.log(pc.dim(" Use --force (-f) to overwrite"));
262
+ process.exit(1);
263
+ }
264
+ } else {
265
+ const { overwrite } = await prompts({
266
+ type: "confirm",
267
+ name: "overwrite",
268
+ message: `Directory ${directory} already exists. Overwrite?`,
269
+ initial: false
270
+ });
271
+ if (!overwrite) {
272
+ console.log(pc.red("Cancelled"));
273
+ process.exit(1);
274
+ }
275
+ rmSync(targetDir, { recursive: true });
276
+ }
277
+ }
278
+ mkdirSync(targetDir, { recursive: true });
279
+ console.log();
280
+ console.log(pc.dim(` Creating ${projectName} in ${directory}...`));
281
+ console.log();
282
+ const templatesDir = getTemplatesDir();
283
+ const vars = {
284
+ projectName,
285
+ projectTitle,
286
+ projectInitial
287
+ };
288
+ copyDir(templatesDir, targetDir, vars);
289
+ function listFiles(dir, prefix = "") {
290
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
291
+ const relativePath = prefix + entry.name;
292
+ if (entry.isDirectory()) {
293
+ listFiles(join(dir, entry.name), relativePath + "/");
294
+ } else {
295
+ console.log(pc.green(" \u2713"), pc.dim(relativePath));
296
+ }
297
+ }
298
+ }
299
+ listFiles(targetDir);
300
+ if (isGitInstalled()) {
301
+ console.log();
302
+ console.log(pc.dim(" Initializing git repository..."));
303
+ if (initGitRepo(targetDir, projectName)) {
304
+ console.log(pc.green(" \u2713"), pc.dim("Git repository initialized with initial commit"));
305
+ } else {
306
+ console.log(pc.yellow(" \u26A0"), pc.dim("Failed to initialize git repository"));
307
+ }
308
+ } else {
309
+ console.log();
310
+ console.log(pc.yellow(" \u26A0"), pc.dim("Git not found - skipping repository initialization"));
311
+ }
312
+ let uvAvailable = isUvInstalled();
313
+ if (!uvAvailable) {
314
+ console.log();
315
+ console.log(pc.dim(" Installing uv (Python package manager)..."));
316
+ if (installUv()) {
317
+ console.log(pc.green(" \u2713"), pc.dim("uv installed successfully"));
318
+ uvAvailable = true;
319
+ } else {
320
+ console.log(pc.yellow(" \u26A0"), pc.dim("Failed to install uv - install manually: https://docs.astral.sh/uv/"));
321
+ }
322
+ }
323
+ if (uvAvailable) {
324
+ console.log();
325
+ console.log(pc.dim(" Creating Python venv with Lumera SDK..."));
326
+ if (createPythonVenv(targetDir)) {
327
+ console.log(pc.green(" \u2713"), pc.dim("Python venv created (.venv/) with lumera SDK"));
328
+ } else {
329
+ console.log(pc.yellow(" \u26A0"), pc.dim("Failed to create Python venv"));
330
+ }
331
+ }
332
+ if (opts.install) {
333
+ console.log();
334
+ console.log(pc.dim(" Installing dependencies..."));
335
+ try {
336
+ execSync("pnpm install", { cwd: targetDir, stdio: "inherit" });
337
+ console.log(pc.green(" \u2713"), pc.dim("Dependencies installed"));
338
+ } catch {
339
+ console.log(pc.yellow(" \u26A0"), pc.dim("Failed to install dependencies"));
340
+ }
341
+ }
342
+ console.log();
343
+ console.log(pc.dim(" Installing Lumera skills for AI agents..."));
344
+ try {
345
+ await installSkills(targetDir);
346
+ console.log(pc.green(" \u2713"), pc.dim("Lumera skills installed"));
347
+ } catch (err) {
348
+ console.log(pc.yellow(" \u26A0"), pc.dim(`Failed to install skills: ${err}`));
349
+ }
350
+ console.log();
351
+ console.log(pc.green(pc.bold(" Done!")), "Next steps:");
352
+ console.log();
353
+ console.log(pc.cyan(` cd ${directory}`));
354
+ if (!opts.install) {
355
+ console.log(pc.cyan(" pnpm install"));
356
+ }
357
+ console.log(pc.cyan(" lumera login"));
358
+ console.log(pc.cyan(" pnpm dev"));
359
+ console.log();
360
+ }
361
+ export {
362
+ init
363
+ };
@@ -0,0 +1,190 @@
1
+ import {
2
+ detectProjectVersion,
3
+ findProjectRoot
4
+ } from "./chunk-D2BLSEGR.js";
5
+
6
+ // src/commands/migrate.ts
7
+ import pc from "picocolors";
8
+ import { existsSync, mkdirSync, renameSync, readdirSync, readFileSync, writeFileSync, rmSync } from "fs";
9
+ import { join } from "path";
10
+ function showHelp() {
11
+ console.log(`
12
+ ${pc.dim("Usage:")}
13
+ lumera migrate [options]
14
+
15
+ ${pc.dim("Description:")}
16
+ Upgrade legacy project structure (v0) to the current structure (v1).
17
+ Moves files from lumera_platform/ to platform/ and updates package.json.
18
+
19
+ ${pc.dim("Options:")}
20
+ --dry-run Preview migration changes without applying
21
+ --help, -h Show this help
22
+
23
+ ${pc.dim("Examples:")}
24
+ lumera migrate # Migrate to v1 structure
25
+ lumera migrate --dry-run # Preview migration changes
26
+ `);
27
+ }
28
+ async function migrate(args) {
29
+ if (args.includes("--help") || args.includes("-h")) {
30
+ showHelp();
31
+ return;
32
+ }
33
+ const dryRun = args.includes("--dry-run");
34
+ let projectRoot;
35
+ try {
36
+ projectRoot = findProjectRoot();
37
+ } catch {
38
+ console.error(pc.red("Not in a Lumera project directory."));
39
+ process.exit(1);
40
+ }
41
+ const version = detectProjectVersion(projectRoot);
42
+ if (version === 1) {
43
+ console.log(pc.green("Project is already using v1 structure."));
44
+ return;
45
+ }
46
+ if (version !== 0) {
47
+ console.error(pc.red(`Unknown project version: ${version}`));
48
+ process.exit(1);
49
+ }
50
+ const legacyDir = join(projectRoot, "lumera_platform");
51
+ if (!existsSync(legacyDir)) {
52
+ console.error(pc.red("Legacy lumera_platform/ directory not found."));
53
+ console.error(pc.dim("This project may already be migrated or is not a Lumera project."));
54
+ process.exit(1);
55
+ }
56
+ console.log(pc.bold("Migration Plan:"));
57
+ console.log();
58
+ const actions = [];
59
+ const newDirs = ["platform/collections", "platform/automations", "platform/hooks", "scripts"];
60
+ for (const dir of newDirs) {
61
+ const fullPath = join(projectRoot, dir);
62
+ if (!existsSync(fullPath)) {
63
+ actions.push({ type: "mkdir", to: dir, desc: `Create ${dir}/` });
64
+ }
65
+ }
66
+ const legacyCollections = join(legacyDir, "collections");
67
+ if (existsSync(legacyCollections)) {
68
+ for (const file of readdirSync(legacyCollections)) {
69
+ actions.push({
70
+ type: "move",
71
+ from: `lumera_platform/collections/${file}`,
72
+ to: `platform/collections/${file}`,
73
+ desc: `Move collections/${file}`
74
+ });
75
+ }
76
+ }
77
+ const legacyAutomations = join(legacyDir, "automations");
78
+ if (existsSync(legacyAutomations)) {
79
+ for (const item of readdirSync(legacyAutomations, { withFileTypes: true })) {
80
+ actions.push({
81
+ type: "move",
82
+ from: `lumera_platform/automations/${item.name}`,
83
+ to: `platform/automations/${item.name}`,
84
+ desc: `Move automations/${item.name}`
85
+ });
86
+ }
87
+ }
88
+ const legacyHooks = join(legacyDir, "hooks");
89
+ if (existsSync(legacyHooks)) {
90
+ for (const file of readdirSync(legacyHooks)) {
91
+ actions.push({
92
+ type: "move",
93
+ from: `lumera_platform/hooks/${file}`,
94
+ to: `platform/hooks/${file}`,
95
+ desc: `Move hooks/${file}`
96
+ });
97
+ }
98
+ }
99
+ const legacyScripts = join(legacyDir, "scripts");
100
+ const legacySeed = join(legacyDir, "seed");
101
+ for (const dir of [legacyScripts, legacySeed]) {
102
+ if (existsSync(dir)) {
103
+ for (const file of readdirSync(dir)) {
104
+ const fromDir = dir === legacyScripts ? "scripts" : "seed";
105
+ actions.push({
106
+ type: "move",
107
+ from: `lumera_platform/${fromDir}/${file}`,
108
+ to: `scripts/${file}`,
109
+ desc: `Move ${fromDir}/${file} to scripts/`
110
+ });
111
+ }
112
+ }
113
+ }
114
+ actions.push({
115
+ type: "update",
116
+ to: "package.json",
117
+ desc: "Add lumera.version: 1 to package.json"
118
+ });
119
+ actions.push({
120
+ type: "delete",
121
+ from: "lumera_platform/",
122
+ desc: "Remove legacy lumera_platform/ directory"
123
+ });
124
+ for (const action of actions) {
125
+ switch (action.type) {
126
+ case "mkdir":
127
+ console.log(pc.cyan(` + ${action.to}`));
128
+ break;
129
+ case "move":
130
+ console.log(pc.yellow(` \u2192 ${action.from} \u2192 ${action.to}`));
131
+ break;
132
+ case "update":
133
+ console.log(pc.blue(` \u270E ${action.to}`));
134
+ break;
135
+ case "delete":
136
+ console.log(pc.red(` - ${action.from}`));
137
+ break;
138
+ }
139
+ }
140
+ console.log();
141
+ if (dryRun) {
142
+ console.log(pc.dim("Dry run - no changes made."));
143
+ console.log(pc.dim("Run without --dry-run to apply changes."));
144
+ return;
145
+ }
146
+ console.log(pc.bold("Applying changes..."));
147
+ console.log();
148
+ for (const action of actions) {
149
+ try {
150
+ switch (action.type) {
151
+ case "mkdir":
152
+ mkdirSync(join(projectRoot, action.to), { recursive: true });
153
+ console.log(pc.green(` \u2713 Created ${action.to}`));
154
+ break;
155
+ case "move":
156
+ renameSync(
157
+ join(projectRoot, action.from),
158
+ join(projectRoot, action.to)
159
+ );
160
+ console.log(pc.green(` \u2713 Moved ${action.from} \u2192 ${action.to}`));
161
+ break;
162
+ case "update":
163
+ if (action.to === "package.json") {
164
+ const pkgPath = join(projectRoot, "package.json");
165
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
166
+ pkg.lumera = pkg.lumera || {};
167
+ pkg.lumera.version = 1;
168
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
169
+ console.log(pc.green(` \u2713 Updated package.json`));
170
+ }
171
+ break;
172
+ case "delete":
173
+ rmSync(join(projectRoot, action.from), { recursive: true, force: true });
174
+ console.log(pc.green(` \u2713 Removed ${action.from}`));
175
+ break;
176
+ }
177
+ } catch (err) {
178
+ console.error(pc.red(` \u2717 Failed: ${action.desc}`));
179
+ if (err instanceof Error) {
180
+ console.error(pc.dim(` ${err.message}`));
181
+ }
182
+ }
183
+ }
184
+ console.log();
185
+ console.log(pc.green("Migration complete!"));
186
+ console.log(pc.dim("Your project now uses the v1 structure."));
187
+ }
188
+ export {
189
+ migrate
190
+ };