@launchmatic/cli 0.3.1 → 0.4.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 (3) hide show
  1. package/README.md +178 -0
  2. package/dist/index.js +306 -3
  3. package/package.json +37 -37
package/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # @launchmatic/cli
2
+
3
+ Deploy from your terminal. The official CLI for [Launchmatic](https://launchmatic.io) — detect, build, and ship your apps to the cloud in seconds.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @launchmatic/cli
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ lm login # Authenticate with GitHub
15
+ lm quicklaunch # Detect, configure, and deploy — one command
16
+ ```
17
+
18
+ Or use **Lightspeed** to create an app from a prompt:
19
+
20
+ ```bash
21
+ c -c a real-time chat app with rooms
22
+ ```
23
+
24
+ ## Commands
25
+
26
+ ### Core
27
+
28
+ | Command | Alias | Description |
29
+ |---------|-------|-------------|
30
+ | `lm login` | | Authenticate via GitHub OAuth |
31
+ | `lm init` | | Initialize a project in the current directory |
32
+ | `lm deploy` | | Build and deploy the current service |
33
+ | `lm quicklaunch` | `lm ql` | Detect, configure, and deploy in one shot |
34
+ | `lm status` | | Show service status, URL, and last deployment |
35
+ | `lm logs` | | Stream live deployment logs |
36
+ | `lm destroy` | | Tear down a service (requires confirmation) |
37
+
38
+ ### AI / Lightspeed
39
+
40
+ | Command | Description |
41
+ |---------|-------------|
42
+ | `c -c <prompt>` | Create a new app from a natural language description |
43
+ | `c <prompt>` | Modify an existing service with AI |
44
+ | `lm lightspeed` | Same as `c`, integrated into the main CLI |
45
+
46
+ ### Environment & Domains
47
+
48
+ | Command | Description |
49
+ |---------|-------------|
50
+ | `lm env set KEY=VALUE ...` | Set environment variables |
51
+ | `lm env list` | List environment variables |
52
+ | `lm domains add <hostname>` | Add a custom domain (shows DNS config) |
53
+
54
+ ### Deployments
55
+
56
+ | Command | Alias | Description |
57
+ |---------|-------|-------------|
58
+ | `lm deployments list` | `lm deploys ls` | View deployment history |
59
+ | `lm deployments info <id>` | | Deployment details |
60
+ | `lm deployments cancel <id>` | | Cancel a running deployment |
61
+ | `lm deployments rollback <id>` | | Roll back to a previous deployment |
62
+
63
+ ### Preview Environments
64
+
65
+ | Command | Description |
66
+ |---------|-------------|
67
+ | `lm preview list` | List active preview environments |
68
+ | `lm preview create <branch>` | Create a preview deployment for a branch |
69
+ | `lm preview delete <id>` | Delete a preview environment |
70
+
71
+ ### Database
72
+
73
+ | Command | Alias | Description |
74
+ |---------|-------|-------------|
75
+ | `lm db create` | | Provision a new database |
76
+ | `lm db list` | `lm db ls` | List databases |
77
+ | `lm db connect` | `lm db url` | Get connection string |
78
+ | `lm db query <sql>` | `lm db q` | Run a SQL query |
79
+ | `lm db shell` | `lm db sh` | Interactive SQL shell |
80
+ | `lm db tables` | | List tables |
81
+ | `lm db dump` | | Export database dump |
82
+ | `lm db seed` | | Seed database |
83
+ | `lm db credentials` | `lm db creds` | Show database credentials |
84
+ | `lm db link` | | Link a database to a service |
85
+ | `lm db delete` | `lm db rm` | Delete a database |
86
+
87
+ Supports PostgreSQL, Redis, and MongoDB via `--engine`.
88
+
89
+ ### GitHub Repo
90
+
91
+ | Command | Alias | Description |
92
+ |---------|-------|-------------|
93
+ | `lm repo view` | `lm repo v` | Show repo info |
94
+ | `lm repo prs` | | List pull requests |
95
+ | `lm repo issues` | | List issues |
96
+ | `lm repo issue-create` | `lm repo ic` | Create an issue |
97
+ | `lm repo branches` | `lm repo br` | List branches |
98
+ | `lm repo commits` | `lm repo log` | Show commit history |
99
+ | `lm repo releases` | `lm repo rl` | List releases |
100
+ | `lm repo actions` | `lm repo runs` | View GitHub Actions runs |
101
+ | `lm repo browse` | `lm repo web` | Open repo in browser |
102
+ | `lm repo clone` | | Clone the repo |
103
+ | `lm repo diff` | | Show diff |
104
+ | `lm repo stash` | | Stash changes |
105
+
106
+ ### Infrastructure
107
+
108
+ | Command | Description |
109
+ |---------|-------------|
110
+ | `lm infra list -t <team>` | List pods with CPU, memory, and readiness |
111
+ | `lm infra logs <pod>` | Stream pod logs |
112
+ | `lm infra describe <pod>` | Describe a pod |
113
+ | `lm infra restart <pod>` | Restart a pod |
114
+ | `lm infra delete <pod>` | Delete a pod |
115
+
116
+ ### Browser Automation
117
+
118
+ | Command | Alias | Description |
119
+ |---------|-------|-------------|
120
+ | `lm browser screenshot [url]` | `lm b ss` | Take a screenshot |
121
+ | `lm browser pdf [url]` | | Generate a PDF |
122
+ | `lm browser open [url]` | | Open in headed browser |
123
+ | `lm browser test [url]` | | Run smoke tests (links, console, a11y) |
124
+ | `lm browser wait [url]` | | Wait for a page to be reachable |
125
+ | `lm browser codegen [url]` | | Record and generate test code |
126
+ | `lm browser devices` | | List available device profiles |
127
+
128
+ ### Utilities
129
+
130
+ | Command | Description |
131
+ |---------|-------------|
132
+ | `lm doctor` | Scan project for deployment issues and fixes |
133
+ | `lm usage summary` | View CPU, memory, network, and replica stats |
134
+ | `lm usage history` | Resource usage over time (default 24h) |
135
+ | `lm api-key list` | List API keys |
136
+ | `lm api-key create <name>` | Create an API key |
137
+ | `lm api-key delete <id>` | Delete an API key |
138
+
139
+ ## Examples
140
+
141
+ ```bash
142
+ # Deploy a project from the current directory
143
+ lm init
144
+ lm deploy
145
+
146
+ # One-command deploy with auto-detection
147
+ lm quicklaunch
148
+
149
+ # Create an app with AI
150
+ c -c a markdown blog with syntax highlighting
151
+
152
+ # Modify an existing app
153
+ c add a dark mode toggle to the settings page
154
+
155
+ # Check project health before deploying
156
+ lm doctor
157
+
158
+ # Manage environment variables
159
+ lm env set DATABASE_URL=postgres://... REDIS_URL=redis://...
160
+
161
+ # Stream logs
162
+ lm logs
163
+
164
+ # Roll back a bad deployment
165
+ lm deployments rollback <deployment-id>
166
+
167
+ # Interactive database shell
168
+ lm db shell
169
+ ```
170
+
171
+ ## Requirements
172
+
173
+ - Node.js >= 20
174
+ - A [Launchmatic](https://launchmatic.io) account
175
+
176
+ ## License
177
+
178
+ MIT
package/dist/index.js CHANGED
@@ -20211,6 +20211,60 @@ async function api(path6, options = {}) {
20211
20211
  }
20212
20212
  return res.json();
20213
20213
  }
20214
+ async function* streamApi(path6, options = {}) {
20215
+ const { accessToken } = getTokens();
20216
+ const url = `${getApiUrl()}${path6}`;
20217
+ const headers = {
20218
+ "Content-Type": "application/json",
20219
+ Accept: "text/event-stream",
20220
+ ...options.headers || {}
20221
+ };
20222
+ if (accessToken) headers["Authorization"] = `Bearer ${accessToken}`;
20223
+ const res = await fetch(url, { ...options, headers });
20224
+ if (!res.ok || !res.body) {
20225
+ let message = `HTTP ${res.status}`;
20226
+ try {
20227
+ const body = await res.json();
20228
+ if (body.error) message = body.error;
20229
+ } catch {
20230
+ }
20231
+ throw new ApiError(res.status, message);
20232
+ }
20233
+ const reader = res.body.getReader();
20234
+ const decoder = new TextDecoder();
20235
+ let buffer = "";
20236
+ try {
20237
+ while (true) {
20238
+ const { value, done } = await reader.read();
20239
+ if (done) break;
20240
+ buffer += decoder.decode(value, { stream: true });
20241
+ let boundary = buffer.indexOf("\n\n");
20242
+ while (boundary !== -1) {
20243
+ const frame = buffer.slice(0, boundary);
20244
+ buffer = buffer.slice(boundary + 2);
20245
+ const dataLines = [];
20246
+ for (const line of frame.split("\n")) {
20247
+ if (line.startsWith("data:")) {
20248
+ dataLines.push(line.slice(5).trimStart());
20249
+ }
20250
+ }
20251
+ if (dataLines.length > 0) {
20252
+ const json = dataLines.join("\n");
20253
+ try {
20254
+ yield JSON.parse(json);
20255
+ } catch {
20256
+ }
20257
+ }
20258
+ boundary = buffer.indexOf("\n\n");
20259
+ }
20260
+ }
20261
+ } finally {
20262
+ try {
20263
+ reader.releaseLock();
20264
+ } catch {
20265
+ }
20266
+ }
20267
+ }
20214
20268
 
20215
20269
  // src/commands/login.ts
20216
20270
  function authPage(success, error) {
@@ -20242,7 +20296,7 @@ function authPage(success, error) {
20242
20296
  <body>
20243
20297
  <div class="card">
20244
20298
  <div class="logo">
20245
- <svg width="24" height="24" viewBox="0 0 52 36" fill="none"><polygon points="44,18 8,6 13,18 8,30" stroke="#C8E0FF" stroke-width="2.5" fill="none"/></svg>
20299
+ <svg width="24" height="24" viewBox="0 0 52 36" fill="none" style="transform:rotate(-45deg)"><polygon points="44,18 8,6 13,18 8,30" stroke="#C8E0FF" stroke-width="2.5" fill="none"/></svg>
20246
20300
  Launchmatic
20247
20301
  </div>
20248
20302
  <div class="icon">${icon}</div>
@@ -20303,7 +20357,9 @@ function waitForOAuth(apiUrl) {
20303
20357
  if (settled) return false;
20304
20358
  settled = true;
20305
20359
  rl.close();
20306
- process.stdin.unref();
20360
+ if (typeof process.stdin.unref === "function") {
20361
+ process.stdin.unref();
20362
+ }
20307
20363
  server.close();
20308
20364
  server.unref();
20309
20365
  return true;
@@ -21328,6 +21384,20 @@ function detectLocal(cwd) {
21328
21384
  if (existsSync2(join(cwd, "package.json"))) {
21329
21385
  const pkg = readPkg(cwd);
21330
21386
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
21387
+ if (deps?.["stirrup-ai"]) {
21388
+ return {
21389
+ runtime: "nodejs",
21390
+ framework: "Stirrup",
21391
+ buildCmd: "npm install",
21392
+ startCmd: "npx stirrup serve",
21393
+ port: 3711,
21394
+ confidence: "high",
21395
+ recommendedStorageGi: 2,
21396
+ recommendedStorageMountPath: "/data",
21397
+ recommendedMemory: "1Gi",
21398
+ requiredEnvVars: ["ANTHROPIC_API_KEY"]
21399
+ };
21400
+ }
21331
21401
  const hasIndex = existsSync2(join(cwd, "index.html")) || existsSync2(join(cwd, "public", "index.html"));
21332
21402
  const hasServerDep = Object.keys(deps || {}).some(
21333
21403
  (d) => /^(express|fastify|next|nuxt|remix|gatsby|astro|vite|svelte|@angular|react-scripts|koa|hapi|nestjs)/.test(
@@ -25179,9 +25249,241 @@ function registerDeployments(program3) {
25179
25249
  });
25180
25250
  }
25181
25251
 
25252
+ // src/commands/agent.ts
25253
+ import readline6 from "readline";
25254
+ async function callAgent(message, threadId, systemHint) {
25255
+ return api("/api/v1/agent", {
25256
+ method: "POST",
25257
+ body: JSON.stringify({
25258
+ message,
25259
+ thread_id: threadId,
25260
+ system_hint: systemHint
25261
+ })
25262
+ });
25263
+ }
25264
+ async function streamAgentToTerminal(message, threadId, systemHint, showTools) {
25265
+ const events = streamApi("/api/v1/agent/stream", {
25266
+ method: "POST",
25267
+ body: JSON.stringify({
25268
+ message,
25269
+ thread_id: threadId,
25270
+ system_hint: systemHint
25271
+ })
25272
+ });
25273
+ const summary = {
25274
+ threadId: null,
25275
+ text: "",
25276
+ toolCalls: 0,
25277
+ iterations: 0,
25278
+ failed: false
25279
+ };
25280
+ let textStarted = false;
25281
+ for await (const event of events) {
25282
+ switch (event.type) {
25283
+ case "thread":
25284
+ summary.threadId = event.thread_id;
25285
+ break;
25286
+ case "iteration_start":
25287
+ summary.iterations = event.n;
25288
+ break;
25289
+ case "text_delta":
25290
+ if (!textStarted) {
25291
+ process.stdout.write(source_default.cyan("agent: "));
25292
+ textStarted = true;
25293
+ }
25294
+ process.stdout.write(event.delta);
25295
+ summary.text += event.delta;
25296
+ break;
25297
+ case "tool_call":
25298
+ if (showTools) {
25299
+ if (textStarted) process.stdout.write("\n");
25300
+ process.stdout.write(source_default.dim(` \u2192 ${event.name}`));
25301
+ summary.toolCalls++;
25302
+ }
25303
+ break;
25304
+ case "tool_result":
25305
+ if (showTools) {
25306
+ if (event.ok) process.stdout.write(source_default.dim(` \u2713 ${event.summary}
25307
+ `));
25308
+ else process.stdout.write(source_default.red(` \u2717 ${event.summary}
25309
+ `));
25310
+ textStarted = false;
25311
+ }
25312
+ break;
25313
+ case "error":
25314
+ if (textStarted) process.stdout.write("\n");
25315
+ process.stdout.write(source_default.red(`error: ${event.message}
25316
+ `));
25317
+ summary.failed = true;
25318
+ break;
25319
+ case "done":
25320
+ if (textStarted) process.stdout.write("\n");
25321
+ break;
25322
+ }
25323
+ }
25324
+ return summary;
25325
+ }
25326
+ function buildSystemHint(opts) {
25327
+ const parts = [];
25328
+ if (contextExists()) {
25329
+ try {
25330
+ const ctx = readContext();
25331
+ parts.push(`Default project: ${ctx.projectId}. Default service: ${ctx.serviceId}.`);
25332
+ } catch {
25333
+ }
25334
+ }
25335
+ if (opts.service) parts.push(`User explicitly scoped this session to service: ${opts.service}.`);
25336
+ if (opts.environment) parts.push(`User explicitly scoped this session to environment: ${opts.environment}.`);
25337
+ return parts.length > 0 ? parts.join(" ") : null;
25338
+ }
25339
+ function printToolTrace(calls) {
25340
+ for (const call of calls) {
25341
+ const icon = call.error ? source_default.red("\u2717") : source_default.dim("\u2192");
25342
+ const summary = call.error ? source_default.red(call.error) : source_default.dim("ok");
25343
+ console.log(` ${icon} ${source_default.cyan(call.name)} ${summary}`);
25344
+ }
25345
+ }
25346
+ function prompt5(question) {
25347
+ const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
25348
+ return new Promise((resolve4) => {
25349
+ rl.question(question, (answer) => {
25350
+ rl.close();
25351
+ resolve4(answer);
25352
+ });
25353
+ });
25354
+ }
25355
+ function registerAgent(program3) {
25356
+ program3.command("agent").description("Talk to the Launchmatic agent \u2014 multi-step reasoning over your services").option("-p, --prompt <text>", "Run a single prompt and exit (non-interactive)").option("--json", "Emit machine-readable JSON instead of human-friendly text (disables streaming)").option("--no-stream", "Buffer the full response before printing (default: stream)").option("--thread-id <id>", "Continue an existing thread").option("--service <serviceId>", "Scope the session to a specific service").option("--environment <env>", "Scope the session to a specific environment (e.g. preview, production)").option("--show-tools", "Show each tool call the agent makes (default off in human mode)").action(async (opts) => {
25357
+ if (!isLoggedIn()) {
25358
+ console.error(source_default.red('Not logged in. Run "lm login" first.'));
25359
+ process.exitCode = 1;
25360
+ return;
25361
+ }
25362
+ const systemHint = buildSystemHint(opts);
25363
+ let threadId = opts.threadId ?? null;
25364
+ const useStream = opts.stream !== false && !opts.json;
25365
+ if (opts.prompt) {
25366
+ if (opts.json) {
25367
+ try {
25368
+ const res = await callAgent(opts.prompt, threadId, systemHint);
25369
+ console.log(JSON.stringify(res, null, 2));
25370
+ } catch (err) {
25371
+ console.error(source_default.red(`Agent error: ${err instanceof Error ? err.message : String(err)}`));
25372
+ process.exitCode = 1;
25373
+ }
25374
+ return;
25375
+ }
25376
+ if (useStream) {
25377
+ try {
25378
+ const summary = await streamAgentToTerminal(opts.prompt, threadId, systemHint, opts.showTools ?? false);
25379
+ if (summary.failed) {
25380
+ process.exitCode = 1;
25381
+ return;
25382
+ }
25383
+ if (summary.threadId) {
25384
+ console.log();
25385
+ console.log(source_default.dim(`Thread: ${summary.threadId} (resume with --thread-id ${summary.threadId})`));
25386
+ }
25387
+ } catch (err) {
25388
+ console.error(source_default.red(`Agent error: ${err instanceof Error ? err.message : String(err)}`));
25389
+ process.exitCode = 1;
25390
+ }
25391
+ return;
25392
+ }
25393
+ const spinner = ora("Thinking...").start();
25394
+ try {
25395
+ const res = await callAgent(opts.prompt, threadId, systemHint);
25396
+ spinner.stop();
25397
+ if (opts.showTools && res.tool_calls.length > 0) {
25398
+ printToolTrace(res.tool_calls);
25399
+ console.log();
25400
+ }
25401
+ console.log(res.text);
25402
+ console.log();
25403
+ console.log(source_default.dim(`Thread: ${res.thread_id} (resume with --thread-id ${res.thread_id})`));
25404
+ } catch (err) {
25405
+ spinner.fail(source_default.red(`Agent error: ${err instanceof Error ? err.message : String(err)}`));
25406
+ process.exitCode = 1;
25407
+ }
25408
+ return;
25409
+ }
25410
+ console.log(source_default.bold.cyan("\n Launchmatic Agent") + source_default.dim(" \u2014 type your request, /help for commands, /quit to exit"));
25411
+ if (threadId) console.log(source_default.dim(` Resuming thread ${threadId}
25412
+ `));
25413
+ else console.log();
25414
+ const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
25415
+ const handleLine = async (line) => {
25416
+ const text = line.trim();
25417
+ if (!text) return false;
25418
+ if (text === "/quit" || text === "/exit") return true;
25419
+ if (text === "/help") {
25420
+ console.log(source_default.dim(" /thread show current thread id"));
25421
+ console.log(source_default.dim(" /new start a new thread"));
25422
+ console.log(source_default.dim(" /tools toggle tool-call display"));
25423
+ console.log(source_default.dim(" /quit exit"));
25424
+ return false;
25425
+ }
25426
+ if (text === "/thread") {
25427
+ console.log(source_default.dim(` ${threadId ?? "(no thread yet)"}`));
25428
+ return false;
25429
+ }
25430
+ if (text === "/new") {
25431
+ threadId = null;
25432
+ console.log(source_default.dim(" Started fresh thread."));
25433
+ return false;
25434
+ }
25435
+ if (text === "/tools") {
25436
+ opts.showTools = !opts.showTools;
25437
+ console.log(source_default.dim(` show-tools: ${opts.showTools ? "on" : "off"}`));
25438
+ return false;
25439
+ }
25440
+ if (useStream) {
25441
+ try {
25442
+ const summary = await streamAgentToTerminal(text, threadId, systemHint, opts.showTools ?? false);
25443
+ if (summary.threadId) threadId = summary.threadId;
25444
+ console.log();
25445
+ } catch (err) {
25446
+ console.error(source_default.red(`Agent error: ${err instanceof Error ? err.message : String(err)}`));
25447
+ }
25448
+ return false;
25449
+ }
25450
+ const spinner = ora("Thinking...").start();
25451
+ try {
25452
+ const res = await callAgent(text, threadId, systemHint);
25453
+ threadId = res.thread_id;
25454
+ spinner.stop();
25455
+ if (opts.showTools && res.tool_calls.length > 0) {
25456
+ printToolTrace(res.tool_calls);
25457
+ console.log();
25458
+ }
25459
+ console.log(source_default.cyan("agent:"), res.text);
25460
+ console.log();
25461
+ } catch (err) {
25462
+ spinner.fail(source_default.red(`Agent error: ${err instanceof Error ? err.message : String(err)}`));
25463
+ }
25464
+ return false;
25465
+ };
25466
+ let done = false;
25467
+ while (!done) {
25468
+ const line = await new Promise((resolve4) => {
25469
+ rl.question(source_default.cyan("> "), (answer) => resolve4(answer));
25470
+ rl.once("close", () => resolve4(null));
25471
+ });
25472
+ if (line === null) {
25473
+ done = true;
25474
+ break;
25475
+ }
25476
+ done = await handleLine(line);
25477
+ }
25478
+ rl.close();
25479
+ console.log(source_default.dim("\n Bye."));
25480
+ void prompt5;
25481
+ });
25482
+ }
25483
+
25182
25484
  // src/index.ts
25183
25485
  var program2 = new Command();
25184
- program2.name("lm").description("Launchmatic CLI \u2014 deploy from your terminal").version("0.3.0");
25486
+ program2.name("lm").description("Launchmatic CLI \u2014 deploy from your terminal").version("0.4.0");
25185
25487
  registerLogin(program2);
25186
25488
  registerInit(program2);
25187
25489
  registerDeploy(program2);
@@ -25201,4 +25503,5 @@ registerUsage(program2);
25201
25503
  registerPreview(program2);
25202
25504
  registerApiKey(program2);
25203
25505
  registerDeployments(program2);
25506
+ registerAgent(program2);
25204
25507
  program2.parse();
package/package.json CHANGED
@@ -1,37 +1,37 @@
1
- {
2
- "name": "@launchmatic/cli",
3
- "version": "0.3.1",
4
- "description": "Launchmatic CLI — deploy from your terminal",
5
- "private": false,
6
- "type": "module",
7
- "bin": {
8
- "lm": "dist/index.js",
9
- "c": "dist/c.js"
10
- },
11
- "files": [
12
- "dist"
13
- ],
14
- "scripts": {
15
- "dev": "tsx src/index.ts",
16
- "build": "tsup",
17
- "start": "node dist/index.js"
18
- },
19
- "dependencies": {
20
- "playwright-core": "^1.58.2"
21
- },
22
- "devDependencies": {
23
- "@launchmatic/tsconfig": "workspace:*",
24
- "@launchmatic/validator": "workspace:*",
25
- "@types/node": "^22.0.0",
26
- "@types/ws": "^8.5.12",
27
- "chalk": "^5.3.0",
28
- "commander": "^12.1.0",
29
- "conf": "^13.0.1",
30
- "open": "^10.1.0",
31
- "ora": "^8.1.0",
32
- "tsup": "^8.5.1",
33
- "tsx": "^4.19.0",
34
- "typescript": "^5.6.3",
35
- "ws": "^8.18.0"
36
- }
37
- }
1
+ {
2
+ "name": "@launchmatic/cli",
3
+ "version": "0.4.0",
4
+ "description": "Launchmatic CLI — deploy from your terminal",
5
+ "private": false,
6
+ "type": "module",
7
+ "bin": {
8
+ "lm": "dist/index.js",
9
+ "c": "dist/c.js"
10
+ },
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "scripts": {
15
+ "dev": "tsx src/index.ts",
16
+ "build": "tsup",
17
+ "start": "node dist/index.js"
18
+ },
19
+ "dependencies": {
20
+ "playwright-core": "^1.58.2"
21
+ },
22
+ "devDependencies": {
23
+ "@launchmatic/tsconfig": "workspace:*",
24
+ "@launchmatic/validator": "workspace:*",
25
+ "@types/node": "^22.0.0",
26
+ "@types/ws": "^8.5.12",
27
+ "chalk": "^5.3.0",
28
+ "commander": "^12.1.0",
29
+ "conf": "^13.0.1",
30
+ "open": "^10.1.0",
31
+ "ora": "^8.1.0",
32
+ "tsup": "^8.5.1",
33
+ "tsx": "^4.19.0",
34
+ "typescript": "^5.6.3",
35
+ "ws": "^8.18.0"
36
+ }
37
+ }