@lumerahq/cli 0.10.1 → 0.11.1
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/dist/{chunk-WTDV3MTG.js → chunk-CHRKCAIZ.js} +54 -3
- package/dist/{chunk-WRAZC6SJ.js → chunk-HIYM7EM2.js} +17 -1
- package/dist/dev-2HVDP3NX.js +155 -0
- package/dist/index.js +164 -16
- package/dist/{init-OH433IPH.js → init-4JSHTLX2.js} +139 -77
- package/dist/{resources-PGBVCS2K.js → resources-ZRAW4EFI.js} +60 -41
- package/dist/{run-WIRQDYYX.js → run-47GF5VVS.js} +2 -4
- package/dist/templates-6KMZWOYH.js +194 -0
- package/package.json +1 -1
- package/dist/chunk-2CR762KB.js +0 -18
- package/dist/dev-BHBF4ECH.js +0 -87
- package/dist/templates-67O6PVFK.js +0 -70
|
@@ -5,34 +5,61 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
listAllTemplates,
|
|
7
7
|
resolveTemplate
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-CHRKCAIZ.js";
|
|
9
9
|
import "./chunk-D2BLSEGR.js";
|
|
10
10
|
|
|
11
11
|
// src/commands/init.ts
|
|
12
|
-
import
|
|
12
|
+
import pc2 from "picocolors";
|
|
13
13
|
import prompts from "prompts";
|
|
14
14
|
import { execSync } from "child_process";
|
|
15
15
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, rmSync } from "fs";
|
|
16
16
|
import { join, resolve } from "path";
|
|
17
|
+
|
|
18
|
+
// src/lib/spinner.ts
|
|
19
|
+
import pc from "picocolors";
|
|
20
|
+
var frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
21
|
+
function spinner(message) {
|
|
22
|
+
if (!process.stdout.isTTY) {
|
|
23
|
+
process.stdout.write(` ${message}
|
|
24
|
+
`);
|
|
25
|
+
return (doneMessage) => {
|
|
26
|
+
if (doneMessage) {
|
|
27
|
+
process.stdout.write(` ${doneMessage}
|
|
28
|
+
`);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
let i = 0;
|
|
33
|
+
const interval = setInterval(() => {
|
|
34
|
+
const frame = frames[i % frames.length];
|
|
35
|
+
process.stdout.write(`\r ${pc.cyan(frame)} ${message}`);
|
|
36
|
+
i++;
|
|
37
|
+
}, 80);
|
|
38
|
+
return (doneMessage) => {
|
|
39
|
+
clearInterval(interval);
|
|
40
|
+
process.stdout.write(`\r${" ".repeat(message.length + 10)}\r`);
|
|
41
|
+
if (doneMessage) {
|
|
42
|
+
process.stdout.write(` ${doneMessage}
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/commands/init.ts
|
|
17
49
|
function toTitleCase(str) {
|
|
18
50
|
return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
19
51
|
}
|
|
20
|
-
|
|
21
|
-
projectName: "my-lumera-app",
|
|
22
|
-
projectTitle: "My Lumera App"
|
|
23
|
-
};
|
|
24
|
-
function processTemplate(content, vars) {
|
|
52
|
+
function processTemplate(content, replacements) {
|
|
25
53
|
let result = content;
|
|
26
|
-
for (const [
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
result = result.replaceAll(defaultValue, value);
|
|
54
|
+
for (const [from, to] of replacements) {
|
|
55
|
+
if (from !== to) {
|
|
56
|
+
result = result.replaceAll(from, to);
|
|
30
57
|
}
|
|
31
58
|
}
|
|
32
59
|
return result;
|
|
33
60
|
}
|
|
34
61
|
var TEMPLATE_EXCLUDE = /* @__PURE__ */ new Set(["template.json"]);
|
|
35
|
-
function copyDir(src, dest,
|
|
62
|
+
function copyDir(src, dest, replacements, isRoot = true) {
|
|
36
63
|
if (!existsSync(dest)) {
|
|
37
64
|
mkdirSync(dest, { recursive: true });
|
|
38
65
|
}
|
|
@@ -41,10 +68,10 @@ function copyDir(src, dest, vars, isRoot = true) {
|
|
|
41
68
|
const srcPath = join(src, entry.name);
|
|
42
69
|
const destPath = join(dest, entry.name);
|
|
43
70
|
if (entry.isDirectory()) {
|
|
44
|
-
copyDir(srcPath, destPath,
|
|
71
|
+
copyDir(srcPath, destPath, replacements, false);
|
|
45
72
|
} else {
|
|
46
73
|
const content = readFileSync(srcPath, "utf-8");
|
|
47
|
-
const processed = processTemplate(content,
|
|
74
|
+
const processed = processTemplate(content, replacements);
|
|
48
75
|
writeFileSync(destPath, processed);
|
|
49
76
|
}
|
|
50
77
|
}
|
|
@@ -82,13 +109,13 @@ function installUv() {
|
|
|
82
109
|
try {
|
|
83
110
|
try {
|
|
84
111
|
execSync("curl -LsSf https://astral.sh/uv/install.sh | sh", {
|
|
85
|
-
stdio: "
|
|
112
|
+
stdio: "ignore",
|
|
86
113
|
shell: "/bin/bash"
|
|
87
114
|
});
|
|
88
115
|
return true;
|
|
89
116
|
} catch {
|
|
90
117
|
execSync("wget -qO- https://astral.sh/uv/install.sh | sh", {
|
|
91
|
-
stdio: "
|
|
118
|
+
stdio: "ignore",
|
|
92
119
|
shell: "/bin/bash"
|
|
93
120
|
});
|
|
94
121
|
return true;
|
|
@@ -106,12 +133,40 @@ function createPythonVenv(targetDir) {
|
|
|
106
133
|
return false;
|
|
107
134
|
}
|
|
108
135
|
}
|
|
136
|
+
function detectEditor() {
|
|
137
|
+
const envEditor = process.env.VISUAL || process.env.EDITOR;
|
|
138
|
+
if (envEditor) return envEditor;
|
|
139
|
+
const editors = ["cursor", "code", "zed", "subl"];
|
|
140
|
+
for (const editor of editors) {
|
|
141
|
+
try {
|
|
142
|
+
execSync(`which ${editor}`, { stdio: "ignore" });
|
|
143
|
+
return editor;
|
|
144
|
+
} catch {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
function openInEditor(targetDir) {
|
|
151
|
+
const editor = detectEditor();
|
|
152
|
+
if (!editor) {
|
|
153
|
+
console.log(pc2.yellow(" \u26A0"), pc2.dim("No editor detected. Set VISUAL or EDITOR env var."));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
execSync(`${editor} "${targetDir}"`, { stdio: "ignore" });
|
|
158
|
+
console.log(pc2.green(" \u2713"), pc2.dim(`Opened in ${editor}`));
|
|
159
|
+
} catch {
|
|
160
|
+
console.log(pc2.yellow(" \u26A0"), pc2.dim(`Failed to open in ${editor}`));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
109
163
|
function parseArgs(args) {
|
|
110
164
|
const result = {
|
|
111
165
|
projectName: void 0,
|
|
112
166
|
directory: void 0,
|
|
113
167
|
template: void 0,
|
|
114
|
-
install:
|
|
168
|
+
install: true,
|
|
169
|
+
open: false,
|
|
115
170
|
yes: false,
|
|
116
171
|
force: false,
|
|
117
172
|
help: false
|
|
@@ -120,8 +175,10 @@ function parseArgs(args) {
|
|
|
120
175
|
const arg = args[i];
|
|
121
176
|
if (arg === "--help" || arg === "-h") {
|
|
122
177
|
result.help = true;
|
|
123
|
-
} else if (arg === "--install"
|
|
124
|
-
result.install =
|
|
178
|
+
} else if (arg === "--no-install") {
|
|
179
|
+
result.install = false;
|
|
180
|
+
} else if (arg === "--open" || arg === "-o") {
|
|
181
|
+
result.open = true;
|
|
125
182
|
} else if (arg === "--yes" || arg === "-y") {
|
|
126
183
|
result.yes = true;
|
|
127
184
|
} else if (arg === "--force" || arg === "-f") {
|
|
@@ -138,25 +195,26 @@ function parseArgs(args) {
|
|
|
138
195
|
}
|
|
139
196
|
function showHelp() {
|
|
140
197
|
console.log(`
|
|
141
|
-
${
|
|
198
|
+
${pc2.dim("Usage:")}
|
|
142
199
|
lumera init [name] [options]
|
|
143
200
|
|
|
144
|
-
${
|
|
201
|
+
${pc2.dim("Description:")}
|
|
145
202
|
Scaffold a new Lumera project from a template.
|
|
146
203
|
|
|
147
|
-
${
|
|
148
|
-
--template, -t <name> Template to use (run ${
|
|
204
|
+
${pc2.dim("Options:")}
|
|
205
|
+
--template, -t <name> Template to use (run ${pc2.cyan("lumera templates")} to see options)
|
|
149
206
|
--yes, -y Non-interactive mode (requires project name)
|
|
150
207
|
--dir, -d <path> Target directory (defaults to project name)
|
|
151
208
|
--force, -f Overwrite existing directory without prompting
|
|
152
|
-
--install
|
|
209
|
+
--no-install Skip dependency installation
|
|
210
|
+
--open, -o Open project in editor after scaffolding
|
|
153
211
|
--help, -h Show this help
|
|
154
212
|
|
|
155
|
-
${
|
|
213
|
+
${pc2.dim("Examples:")}
|
|
156
214
|
lumera init my-app # Interactive mode
|
|
157
215
|
lumera init my-app -t invoice-processing # Use a specific template
|
|
158
216
|
lumera init my-app -y # Non-interactive (default template)
|
|
159
|
-
lumera init my-app -t invoice-processing -y -
|
|
217
|
+
lumera init my-app -t invoice-processing -y -o # Full non-interactive + open editor
|
|
160
218
|
`);
|
|
161
219
|
}
|
|
162
220
|
async function init(args) {
|
|
@@ -166,34 +224,36 @@ async function init(args) {
|
|
|
166
224
|
return;
|
|
167
225
|
}
|
|
168
226
|
console.log();
|
|
169
|
-
console.log(
|
|
227
|
+
console.log(pc2.cyan(pc2.bold(" Create Lumera App")));
|
|
170
228
|
console.log();
|
|
171
229
|
let projectName = opts.projectName;
|
|
172
230
|
let directory = opts.directory;
|
|
173
231
|
const nonInteractive = opts.yes;
|
|
174
232
|
if (nonInteractive && !projectName) {
|
|
175
|
-
console.log(
|
|
176
|
-
console.log(
|
|
233
|
+
console.log(pc2.red(" Error: Project name is required in non-interactive mode"));
|
|
234
|
+
console.log(pc2.dim(" Usage: lumera init <name> -y"));
|
|
177
235
|
process.exit(1);
|
|
178
236
|
}
|
|
179
237
|
let templateName = opts.template;
|
|
180
238
|
if (!templateName && !nonInteractive) {
|
|
181
239
|
try {
|
|
240
|
+
const stop = spinner("Fetching templates...");
|
|
182
241
|
const available = await listAllTemplates();
|
|
242
|
+
stop();
|
|
183
243
|
if (available.length > 1) {
|
|
184
244
|
const response = await prompts({
|
|
185
245
|
type: "select",
|
|
186
246
|
name: "template",
|
|
187
247
|
message: "Choose a template",
|
|
188
248
|
choices: available.map((t) => ({
|
|
189
|
-
title: `${t.title} ${
|
|
249
|
+
title: `${t.title} ${pc2.dim(`(${t.name})`)}`,
|
|
190
250
|
description: t.description,
|
|
191
251
|
value: t.name
|
|
192
252
|
})),
|
|
193
253
|
initial: 0
|
|
194
254
|
});
|
|
195
255
|
if (!response.template) {
|
|
196
|
-
console.log(
|
|
256
|
+
console.log(pc2.red("Cancelled"));
|
|
197
257
|
process.exit(1);
|
|
198
258
|
}
|
|
199
259
|
templateName = response.template;
|
|
@@ -204,13 +264,15 @@ async function init(args) {
|
|
|
204
264
|
if (!templateName) {
|
|
205
265
|
templateName = "default";
|
|
206
266
|
}
|
|
267
|
+
const stopResolve = spinner("Resolving template...");
|
|
207
268
|
const templateDir = await resolveTemplate(templateName);
|
|
269
|
+
stopResolve();
|
|
208
270
|
if (!projectName) {
|
|
209
271
|
const response = await prompts({
|
|
210
272
|
type: "text",
|
|
211
273
|
name: "projectName",
|
|
212
274
|
message: "What is your project name?",
|
|
213
|
-
initial: "my-
|
|
275
|
+
initial: "my-app",
|
|
214
276
|
validate: (value) => {
|
|
215
277
|
if (!value) return "Project name is required";
|
|
216
278
|
if (!/^[a-z0-9-]+$/.test(value)) {
|
|
@@ -220,13 +282,13 @@ async function init(args) {
|
|
|
220
282
|
}
|
|
221
283
|
});
|
|
222
284
|
if (!response.projectName) {
|
|
223
|
-
console.log(
|
|
285
|
+
console.log(pc2.red("Cancelled"));
|
|
224
286
|
process.exit(1);
|
|
225
287
|
}
|
|
226
288
|
projectName = response.projectName;
|
|
227
289
|
}
|
|
228
290
|
if (!/^[a-z0-9-]+$/.test(projectName)) {
|
|
229
|
-
console.log(
|
|
291
|
+
console.log(pc2.red(" Error: Project name must use lowercase letters, numbers, and hyphens only"));
|
|
230
292
|
process.exit(1);
|
|
231
293
|
}
|
|
232
294
|
if (!directory) {
|
|
@@ -240,7 +302,7 @@ async function init(args) {
|
|
|
240
302
|
initial: projectName
|
|
241
303
|
});
|
|
242
304
|
if (!response.directory) {
|
|
243
|
-
console.log(
|
|
305
|
+
console.log(pc2.red("Cancelled"));
|
|
244
306
|
process.exit(1);
|
|
245
307
|
}
|
|
246
308
|
directory = response.directory;
|
|
@@ -253,8 +315,8 @@ async function init(args) {
|
|
|
253
315
|
if (opts.force) {
|
|
254
316
|
rmSync(targetDir, { recursive: true });
|
|
255
317
|
} else {
|
|
256
|
-
console.log(
|
|
257
|
-
console.log(
|
|
318
|
+
console.log(pc2.red(` Error: Directory ${directory} already exists`));
|
|
319
|
+
console.log(pc2.dim(" Use --force (-f) to overwrite"));
|
|
258
320
|
process.exit(1);
|
|
259
321
|
}
|
|
260
322
|
} else {
|
|
@@ -265,7 +327,7 @@ async function init(args) {
|
|
|
265
327
|
initial: false
|
|
266
328
|
});
|
|
267
329
|
if (!overwrite) {
|
|
268
|
-
console.log(
|
|
330
|
+
console.log(pc2.red("Cancelled"));
|
|
269
331
|
process.exit(1);
|
|
270
332
|
}
|
|
271
333
|
rmSync(targetDir, { recursive: true });
|
|
@@ -274,77 +336,75 @@ async function init(args) {
|
|
|
274
336
|
mkdirSync(targetDir, { recursive: true });
|
|
275
337
|
console.log();
|
|
276
338
|
if (templateName !== "default") {
|
|
277
|
-
console.log(
|
|
339
|
+
console.log(pc2.dim(` Creating ${projectName} from template ${pc2.cyan(templateName)}...`));
|
|
278
340
|
} else {
|
|
279
|
-
console.log(
|
|
341
|
+
console.log(pc2.dim(` Creating ${projectName}...`));
|
|
280
342
|
}
|
|
281
343
|
console.log();
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
344
|
+
const templatePkgPath = join(templateDir, "package.json");
|
|
345
|
+
const templatePkg = existsSync(templatePkgPath) ? JSON.parse(readFileSync(templatePkgPath, "utf-8")) : { name: "my-lumera-app" };
|
|
346
|
+
const sourceName = templatePkg.name || "my-lumera-app";
|
|
347
|
+
const sourceTitle = templatePkg.lumera?.name || toTitleCase(sourceName);
|
|
348
|
+
const replacements = [
|
|
349
|
+
[sourceName, projectName],
|
|
350
|
+
[sourceTitle, projectTitle]
|
|
351
|
+
];
|
|
352
|
+
copyDir(templateDir, targetDir, replacements);
|
|
287
353
|
function listFiles(dir, prefix = "") {
|
|
288
354
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
289
355
|
const relativePath = prefix + entry.name;
|
|
290
356
|
if (entry.isDirectory()) {
|
|
291
357
|
listFiles(join(dir, entry.name), relativePath + "/");
|
|
292
358
|
} else {
|
|
293
|
-
console.log(
|
|
359
|
+
console.log(pc2.green(" \u2713"), pc2.dim(relativePath));
|
|
294
360
|
}
|
|
295
361
|
}
|
|
296
362
|
}
|
|
297
363
|
listFiles(targetDir);
|
|
298
364
|
if (isGitInstalled()) {
|
|
299
|
-
|
|
300
|
-
console.log(pc.dim(" Initializing git repository..."));
|
|
365
|
+
const stopGit = spinner("Initializing git repository...");
|
|
301
366
|
if (initGitRepo(targetDir, projectName)) {
|
|
302
|
-
|
|
367
|
+
stopGit(pc2.green("\u2713") + pc2.dim(" Git repository initialized with initial commit"));
|
|
303
368
|
} else {
|
|
304
|
-
|
|
369
|
+
stopGit(pc2.yellow("\u26A0") + pc2.dim(" Failed to initialize git repository"));
|
|
305
370
|
}
|
|
306
371
|
} else {
|
|
307
|
-
console.log();
|
|
308
|
-
console.log(pc.yellow(" \u26A0"), pc.dim("Git not found - skipping repository initialization"));
|
|
372
|
+
console.log(pc2.yellow(" \u26A0"), pc2.dim("Git not found \u2014 skipping repository initialization"));
|
|
309
373
|
}
|
|
310
374
|
let uvAvailable = isUvInstalled();
|
|
311
375
|
if (!uvAvailable) {
|
|
312
|
-
|
|
313
|
-
console.log(pc.dim(" Installing uv (Python package manager)..."));
|
|
376
|
+
const stopUv = spinner("Installing uv (Python package manager)...");
|
|
314
377
|
if (installUv()) {
|
|
315
|
-
|
|
378
|
+
stopUv(pc2.green("\u2713") + pc2.dim(" uv installed successfully"));
|
|
316
379
|
uvAvailable = true;
|
|
317
380
|
} else {
|
|
318
|
-
|
|
381
|
+
stopUv(pc2.yellow("\u26A0") + pc2.dim(" Failed to install uv \u2014 install manually: https://docs.astral.sh/uv/"));
|
|
319
382
|
}
|
|
320
383
|
}
|
|
321
384
|
if (uvAvailable) {
|
|
322
|
-
|
|
323
|
-
console.log(pc.dim(" Creating Python venv with Lumera SDK..."));
|
|
385
|
+
const stopVenv = spinner("Creating Python venv with Lumera SDK...");
|
|
324
386
|
if (createPythonVenv(targetDir)) {
|
|
325
|
-
|
|
387
|
+
stopVenv(pc2.green("\u2713") + pc2.dim(" Python venv created (.venv/) with lumera SDK"));
|
|
326
388
|
} else {
|
|
327
|
-
|
|
389
|
+
stopVenv(pc2.yellow("\u26A0") + pc2.dim(" Failed to create Python venv"));
|
|
328
390
|
}
|
|
329
391
|
}
|
|
330
392
|
if (opts.install) {
|
|
331
|
-
|
|
332
|
-
console.log(pc.dim(" Installing dependencies..."));
|
|
393
|
+
const stopInstall = spinner("Installing dependencies...");
|
|
333
394
|
try {
|
|
334
|
-
execSync("pnpm install", { cwd: targetDir, stdio: "
|
|
335
|
-
|
|
395
|
+
execSync("pnpm install", { cwd: targetDir, stdio: "ignore" });
|
|
396
|
+
stopInstall(pc2.green("\u2713") + pc2.dim(" Dependencies installed"));
|
|
336
397
|
} catch {
|
|
337
|
-
|
|
398
|
+
stopInstall(pc2.yellow("\u26A0") + pc2.dim(" Failed to install dependencies"));
|
|
338
399
|
}
|
|
339
400
|
}
|
|
340
|
-
|
|
341
|
-
console.log(pc.dim(" Installing Lumera skills for AI agents..."));
|
|
401
|
+
const stopSkills = spinner("Installing Lumera skills for AI agents...");
|
|
342
402
|
try {
|
|
343
403
|
const { installed, failed } = await installAllSkills(targetDir);
|
|
344
404
|
if (failed > 0) {
|
|
345
|
-
|
|
405
|
+
stopSkills(pc2.yellow("\u26A0") + pc2.dim(` Installed ${installed} skills (${failed} failed)`));
|
|
346
406
|
} else {
|
|
347
|
-
|
|
407
|
+
stopSkills(pc2.green("\u2713") + pc2.dim(` ${installed} Lumera skills installed`));
|
|
348
408
|
}
|
|
349
409
|
syncClaudeMd(targetDir);
|
|
350
410
|
if (isGitInstalled()) {
|
|
@@ -357,20 +417,22 @@ async function init(args) {
|
|
|
357
417
|
}
|
|
358
418
|
}
|
|
359
419
|
} catch (err) {
|
|
360
|
-
|
|
420
|
+
stopSkills(pc2.yellow("\u26A0") + pc2.dim(` Failed to install skills: ${err}`));
|
|
361
421
|
}
|
|
362
422
|
console.log();
|
|
363
|
-
console.log(
|
|
423
|
+
console.log(pc2.green(pc2.bold(" Done!")), "Next steps:");
|
|
364
424
|
console.log();
|
|
365
|
-
console.log(
|
|
425
|
+
console.log(pc2.cyan(` cd ${directory}`));
|
|
366
426
|
if (!opts.install) {
|
|
367
|
-
console.log(
|
|
427
|
+
console.log(pc2.cyan(" pnpm install"));
|
|
368
428
|
}
|
|
369
|
-
console.log(
|
|
370
|
-
console.log(
|
|
371
|
-
console.log(pc.cyan(" lumera run scripts/seed-demo.py"));
|
|
372
|
-
console.log(pc.cyan(" lumera dev"));
|
|
429
|
+
console.log(pc2.cyan(" lumera login"));
|
|
430
|
+
console.log(pc2.cyan(" lumera dev"));
|
|
373
431
|
console.log();
|
|
432
|
+
if (opts.open) {
|
|
433
|
+
openInEditor(targetDir);
|
|
434
|
+
console.log();
|
|
435
|
+
}
|
|
374
436
|
}
|
|
375
437
|
export {
|
|
376
438
|
init
|