@novastorm-ai/cli 0.0.1 → 0.0.4

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/LICENSE.md ADDED
@@ -0,0 +1,100 @@
1
+ # Novastorm License
2
+
3
+ > **Summary:** Free for individuals and small teams (up to 3 developers). Larger teams require a paid license. After March 20, 2029, the entire codebase becomes MIT-licensed.
4
+
5
+ ---
6
+
7
+ ## Business Source License 1.1
8
+
9
+ **Licensor:** Uladzimir Pranevich (NIP: PL8992922668)
10
+
11
+ **Licensed Work:** Novastorm — an ambient development toolkit.
12
+ The Licensed Work is (c) 2026 Uladzimir Pranevich.
13
+
14
+ **Additional Use Grant:** You may use the Licensed Work for any purpose, including production commercial use, provided that you do not use it in a way that competes with the Licensed Work. A competing use is one that offers the Licensed Work to third parties as a commercial development tool or platform.
15
+
16
+ **Change Date:** March 20, 2029
17
+
18
+ **Change License:** MIT
19
+
20
+ ---
21
+
22
+ ## Free License
23
+
24
+ Copyright (c) 2026 Uladzimir Pranevich
25
+
26
+ Permission is hereby granted, free of charge, to any person or entity meeting the eligibility criteria below ("Eligible User"), to use, copy, modify, and run the Licensed Work, subject to the conditions of this license.
27
+
28
+ ### Eligibility
29
+
30
+ You qualify as an Eligible User if **any** of the following apply:
31
+
32
+ - You are an **individual developer** (sole proprietor, freelancer, hobbyist)
33
+ - You are part of a **team of 3 or fewer developers** (measured by unique git commit authors within a 90-day sliding window)
34
+ - You are using Novastorm for an **open-source project** published under an OSI-approved license
35
+ - You are a **student, educator, or academic researcher**
36
+ - You are **evaluating** Novastorm and have not yet used it in production
37
+
38
+ ### Permitted Use
39
+
40
+ - Use the Licensed Work to develop, modify, and deploy your own applications
41
+ - Fork and modify the source code for your own use cases
42
+ - Contribute modifications back to the project
43
+
44
+ ### Restrictions
45
+
46
+ You may **not**:
47
+
48
+ - Copy or modify the Licensed Work for the purpose of selling, renting, licensing, relicensing, or sublicensing your own derivative of Novastorm as a development tool or platform
49
+ - Remove or alter license validation, license notices, or telemetry attribution
50
+ - Misrepresent the origin of the Licensed Work
51
+
52
+ ---
53
+
54
+ ## Company License
55
+
56
+ If you do not meet the eligibility criteria above — specifically, if your project has **more than 3 unique human developers** committing within a 90-day window and the project is **not open source** — you must purchase a Company License.
57
+
58
+ Visit [https://cli.novastorm.ai/#pricing](https://cli.novastorm.ai/#pricing) for pricing and license keys.
59
+
60
+ ### How Developer Count Works
61
+
62
+ Novastorm counts unique git commit author emails within a 90-day sliding window. Bot accounts (dependabot, renovate, github-actions, etc.) are automatically excluded. Email normalization strips `+tags` and lowercases addresses to avoid counting the same person twice.
63
+
64
+ ### What Happens Without a License
65
+
66
+ Novastorm continues to work but displays periodic license nudge messages. It does **not** hard-block usage.
67
+
68
+ ---
69
+
70
+ ## Change Date — March 20, 2029
71
+
72
+ On the Change Date, the Licensed Work will be made available under the MIT License:
73
+
74
+ > MIT License
75
+ >
76
+ > Copyright (c) 2026 Uladzimir Pranevich
77
+ >
78
+ > Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
79
+ >
80
+ > The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
81
+
82
+ ---
83
+
84
+ ## Warranty Disclaimer
85
+
86
+ THE LICENSED WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE LICENSED WORK OR THE USE OR OTHER DEALINGS IN THE LICENSED WORK.
87
+
88
+ ---
89
+
90
+ ## Contributing
91
+
92
+ By contributing to Novastorm, you agree that your contributions will be licensed under the same Business Source License 1.1 terms, and will transition to MIT on the Change Date along with the rest of the codebase.
93
+
94
+ ---
95
+
96
+ ## Notice
97
+
98
+ This license is based on the Business Source License 1.1, available at [https://mariadb.com/bsl11/](https://mariadb.com/bsl11/).
99
+
100
+ For questions about licensing, see the [License FAQ](docs/license-faq.md).
package/dist/bin/nova.js CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  run
4
- } from "../chunk-NFNZMCLQ.js";
5
- import "../chunk-FYSTZ6K6.js";
4
+ } from "../chunk-CLQXFM4X.js";
5
+ import "../chunk-4AQQAQBM.js";
6
+ import "../chunk-QKD6A4EK.js";
7
+ import "../chunk-KKTDQOQX.js";
6
8
  import "../chunk-3RG5ZIWI.js";
7
9
 
8
10
  // bin/nova.ts
@@ -0,0 +1,509 @@
1
+ // ../proxy/dist/index.js
2
+ import http from "http";
3
+ import path from "path";
4
+ import zlib from "zlib";
5
+ import fs from "fs";
6
+ import httpProxy from "http-proxy";
7
+ import { WebSocketServer as WsServer } from "ws";
8
+ import { spawn } from "child_process";
9
+ import http2 from "http";
10
+ import { URL } from "url";
11
+ var SCRIPT_TAG = '<script src="/nova-overlay.js"></script>';
12
+ var ProxyServer = class {
13
+ server = null;
14
+ proxy = null;
15
+ running = false;
16
+ projectMapApi = null;
17
+ /** Returns the underlying http.Server (used by WebSocketServer). */
18
+ getHttpServer() {
19
+ return this.server;
20
+ }
21
+ setProjectMapApi(api) {
22
+ this.projectMapApi = api;
23
+ }
24
+ async start(targetPort, proxyPort, overlayScriptPath) {
25
+ if (this.running) {
26
+ return;
27
+ }
28
+ this.proxy = httpProxy.createProxyServer({
29
+ target: `http://127.0.0.1:${targetPort}`,
30
+ selfHandleResponse: true
31
+ });
32
+ this.proxy.on("proxyRes", (proxyRes, req, res) => {
33
+ const headers = { ...proxyRes.headers };
34
+ delete headers["content-security-policy"];
35
+ delete headers["content-security-policy-report-only"];
36
+ const contentType = proxyRes.headers["content-type"] ?? "";
37
+ const isHtml = contentType.includes("text/html");
38
+ if (!isHtml) {
39
+ for (const [key, value] of Object.entries(headers)) {
40
+ if (value !== void 0) {
41
+ res.setHeader(key, value);
42
+ }
43
+ }
44
+ res.writeHead(proxyRes.statusCode ?? 200);
45
+ proxyRes.pipe(res);
46
+ return;
47
+ }
48
+ const encoding = proxyRes.headers["content-encoding"];
49
+ let stream = proxyRes;
50
+ if (encoding === "gzip") {
51
+ stream = proxyRes.pipe(zlib.createGunzip());
52
+ } else if (encoding === "br") {
53
+ stream = proxyRes.pipe(zlib.createBrotliDecompress());
54
+ } else if (encoding === "deflate") {
55
+ stream = proxyRes.pipe(zlib.createInflate());
56
+ }
57
+ const chunks = [];
58
+ stream.on("data", (chunk) => {
59
+ chunks.push(chunk);
60
+ });
61
+ stream.on("error", () => {
62
+ if (!res.headersSent) {
63
+ res.writeHead(502, { "Content-Type": "text/plain" });
64
+ res.end("Nova Proxy: failed to decompress response");
65
+ }
66
+ });
67
+ stream.on("end", () => {
68
+ let body = Buffer.concat(chunks).toString("utf-8");
69
+ if (body.includes("</body>")) {
70
+ body = body.replace("</body>", `${SCRIPT_TAG}</body>`);
71
+ } else if (body.includes("</html>")) {
72
+ body = body.replace("</html>", `${SCRIPT_TAG}</html>`);
73
+ } else {
74
+ body += SCRIPT_TAG;
75
+ }
76
+ delete headers["content-length"];
77
+ delete headers["content-encoding"];
78
+ delete headers["transfer-encoding"];
79
+ for (const [key, value] of Object.entries(headers)) {
80
+ if (value !== void 0) {
81
+ res.setHeader(key, value);
82
+ }
83
+ }
84
+ res.writeHead(proxyRes.statusCode ?? 200);
85
+ res.end(body);
86
+ });
87
+ });
88
+ const proxyRef = this.proxy;
89
+ this.proxy.on("error", (err, req, res) => {
90
+ if (res instanceof http.ServerResponse && !res.headersSent) {
91
+ proxyRef.web(req, res, { target: `http://[::1]:${targetPort}` }, (retryErr) => {
92
+ if (res instanceof http.ServerResponse && !res.headersSent) {
93
+ res.writeHead(502, { "Content-Type": "text/html" });
94
+ res.end(`
95
+ <html><body style="font-family:system-ui;display:flex;align-items:center;justify-content:center;height:100vh;margin:0">
96
+ <div style="text-align:center">
97
+ <h2>Nova Proxy</h2>
98
+ <p>Waiting for dev server on port ${targetPort}...</p>
99
+ <p style="color:#888">This page will auto-refresh.</p>
100
+ <script>setTimeout(() => location.reload(), 2000)</script>
101
+ </div>
102
+ </body></html>
103
+ `);
104
+ }
105
+ });
106
+ }
107
+ });
108
+ this.server = http.createServer((req, res) => {
109
+ if (req.url === "/nova-project-map") {
110
+ const mapPath = path.join(import.meta.dirname, "..", "static", "project-map.html");
111
+ fs.readFile(mapPath, (err, data) => {
112
+ if (err) {
113
+ res.writeHead(404, { "Content-Type": "text/plain" });
114
+ res.end("project-map.html not found");
115
+ return;
116
+ }
117
+ res.writeHead(200, {
118
+ "Content-Type": "text/html",
119
+ "Cache-Control": "no-cache"
120
+ });
121
+ res.end(data);
122
+ });
123
+ return;
124
+ }
125
+ if (req.url?.startsWith("/nova-api/") && this.projectMapApi) {
126
+ this.projectMapApi.handleRequest(req, res).catch(() => {
127
+ if (!res.headersSent) {
128
+ res.writeHead(500, { "Content-Type": "application/json" });
129
+ res.end(JSON.stringify({ error: "Internal server error" }));
130
+ }
131
+ });
132
+ return;
133
+ }
134
+ if (req.url === "/nova-overlay.js") {
135
+ fs.readFile(overlayScriptPath, (err, data) => {
136
+ if (err) {
137
+ res.writeHead(404, { "Content-Type": "text/plain" });
138
+ res.end("nova-overlay.js not found");
139
+ return;
140
+ }
141
+ res.writeHead(200, {
142
+ "Content-Type": "application/javascript",
143
+ "Cache-Control": "no-cache"
144
+ });
145
+ res.end(data);
146
+ });
147
+ return;
148
+ }
149
+ this.proxy.web(req, res);
150
+ });
151
+ await new Promise((resolve, reject) => {
152
+ this.server.on("error", (err) => {
153
+ if (err.code === "EADDRINUSE") {
154
+ reject(new Error(`Port ${proxyPort} is already in use`));
155
+ } else {
156
+ reject(err);
157
+ }
158
+ });
159
+ this.server.listen(proxyPort, () => {
160
+ this.running = true;
161
+ resolve();
162
+ });
163
+ });
164
+ }
165
+ async stop() {
166
+ if (!this.running) {
167
+ return;
168
+ }
169
+ await new Promise((resolve, reject) => {
170
+ this.proxy?.close();
171
+ this.server?.close((err) => {
172
+ this.running = false;
173
+ this.server = null;
174
+ this.proxy = null;
175
+ if (err) {
176
+ reject(err);
177
+ } else {
178
+ resolve();
179
+ }
180
+ });
181
+ });
182
+ }
183
+ isRunning() {
184
+ return this.running;
185
+ }
186
+ };
187
+ var WebSocketServer = class {
188
+ wss = null;
189
+ observationHandlers = [];
190
+ confirmHandlers = [];
191
+ cancelHandlers = [];
192
+ appendHandlers = [];
193
+ browserErrorHandlers = [];
194
+ secretsSubmitHandlers = [];
195
+ start(httpServer) {
196
+ this.wss = new WsServer({
197
+ server: httpServer,
198
+ path: "/nova-ws"
199
+ });
200
+ this.wss.on("connection", (ws) => {
201
+ ws.on("message", (data) => {
202
+ try {
203
+ const raw = typeof data === "string" ? data : data.toString("utf-8");
204
+ const parsed = JSON.parse(raw);
205
+ if (parsed.type === "confirm") {
206
+ for (const handler of this.confirmHandlers) {
207
+ handler();
208
+ }
209
+ return;
210
+ }
211
+ if (parsed.type === "cancel") {
212
+ for (const handler of this.cancelHandlers) {
213
+ handler();
214
+ }
215
+ return;
216
+ }
217
+ if (parsed.type === "append") {
218
+ const text = parsed.data?.text ?? "";
219
+ for (const handler of this.appendHandlers) {
220
+ handler(text);
221
+ }
222
+ return;
223
+ }
224
+ if (parsed.type === "browser_error") {
225
+ const error = parsed.data?.error ?? "";
226
+ for (const handler of this.browserErrorHandlers) {
227
+ handler(error);
228
+ }
229
+ return;
230
+ }
231
+ if (parsed.type === "secrets_submit") {
232
+ const secrets = parsed.data?.secrets ?? {};
233
+ for (const handler of this.secretsSubmitHandlers) {
234
+ handler(secrets);
235
+ }
236
+ return;
237
+ }
238
+ const obsData = parsed.data ?? parsed;
239
+ const observation = {
240
+ screenshot: obsData.screenshotBase64 ? Buffer.from(obsData.screenshotBase64, "base64") : obsData.screenshot instanceof Buffer ? obsData.screenshot : Buffer.alloc(0),
241
+ clickCoords: obsData.clickCoords,
242
+ domSnapshot: obsData.domSnapshot,
243
+ transcript: obsData.transcript,
244
+ currentUrl: obsData.currentUrl ?? "",
245
+ consoleErrors: obsData.consoleErrors,
246
+ timestamp: obsData.timestamp ?? Date.now(),
247
+ gestureContext: obsData.gestureContext
248
+ };
249
+ const autoExecute = obsData.autoExecute === true;
250
+ for (const handler of this.observationHandlers) {
251
+ handler(observation, autoExecute);
252
+ }
253
+ } catch {
254
+ }
255
+ });
256
+ });
257
+ }
258
+ onObservation(handler) {
259
+ this.observationHandlers.push(handler);
260
+ }
261
+ onConfirm(handler) {
262
+ this.confirmHandlers.push(handler);
263
+ }
264
+ onCancel(handler) {
265
+ this.cancelHandlers.push(handler);
266
+ }
267
+ onAppend(handler) {
268
+ this.appendHandlers.push(handler);
269
+ }
270
+ onBrowserError(handler) {
271
+ this.browserErrorHandlers.push(handler);
272
+ }
273
+ onSecretsSubmit(handler) {
274
+ this.secretsSubmitHandlers.push(handler);
275
+ }
276
+ sendEvent(event) {
277
+ if (!this.wss) return;
278
+ const payload = JSON.stringify(event);
279
+ for (const client of this.wss.clients) {
280
+ if (client.readyState === 1) {
281
+ client.send(payload);
282
+ }
283
+ }
284
+ }
285
+ getClientCount() {
286
+ return this.wss?.clients.size ?? 0;
287
+ }
288
+ };
289
+ var POLL_INTERVAL_MS = 500;
290
+ var MAX_WAIT_MS = 3e4;
291
+ var DevServerRunner = class {
292
+ process = null;
293
+ logs = [];
294
+ running = false;
295
+ readyHandler = null;
296
+ errorHandler = null;
297
+ outputHandlers = [];
298
+ async spawn(command, cwd, port) {
299
+ const [cmd, ...args] = command.split(" ");
300
+ this.process = spawn(cmd, args, {
301
+ cwd,
302
+ shell: true,
303
+ stdio: ["ignore", "pipe", "pipe"],
304
+ env: { ...process.env }
305
+ });
306
+ this.running = true;
307
+ this.logs = [];
308
+ this.process.stdout?.on("data", (data) => {
309
+ const text = data.toString();
310
+ this.logs.push(text);
311
+ for (const handler of this.outputHandlers) {
312
+ handler(text);
313
+ }
314
+ });
315
+ this.process.stderr?.on("data", (data) => {
316
+ const text = data.toString();
317
+ this.logs.push(text);
318
+ for (const handler of this.outputHandlers) {
319
+ handler(text);
320
+ }
321
+ });
322
+ this.process.on("exit", (code, signal) => {
323
+ this.running = false;
324
+ if (code !== 0 && code !== null) {
325
+ this.errorHandler?.(
326
+ `Dev server exited with code ${code}${signal ? ` (${signal})` : ""}`
327
+ );
328
+ } else if (signal) {
329
+ this.errorHandler?.(`Dev server killed by signal ${signal}`);
330
+ }
331
+ });
332
+ this.process.on("error", (err) => {
333
+ this.running = false;
334
+ this.errorHandler?.(err.message);
335
+ });
336
+ await this.pollUntilReady(port);
337
+ }
338
+ onReady(handler) {
339
+ this.readyHandler = handler;
340
+ }
341
+ onError(handler) {
342
+ this.errorHandler = handler;
343
+ }
344
+ onOutput(handler) {
345
+ this.outputHandlers.push(handler);
346
+ }
347
+ getLogs() {
348
+ return this.logs.join("");
349
+ }
350
+ async kill() {
351
+ if (!this.process || !this.running) {
352
+ return;
353
+ }
354
+ const proc = this.process;
355
+ await new Promise((resolve) => {
356
+ const killTimer = setTimeout(() => {
357
+ proc.kill("SIGKILL");
358
+ }, 5e3);
359
+ proc.on("exit", () => {
360
+ clearTimeout(killTimer);
361
+ this.running = false;
362
+ this.process = null;
363
+ resolve();
364
+ });
365
+ proc.kill("SIGTERM");
366
+ });
367
+ }
368
+ isRunning() {
369
+ return this.running;
370
+ }
371
+ pollUntilReady(port) {
372
+ return new Promise((resolve, reject) => {
373
+ const startTime = Date.now();
374
+ const check = () => {
375
+ if (!this.running) {
376
+ reject(
377
+ new Error(
378
+ `Dev server process exited before becoming ready. Logs:
379
+ ${this.getLogs()}`
380
+ )
381
+ );
382
+ return;
383
+ }
384
+ const tryConnect = (host, fallback) => {
385
+ const req = http2.get(
386
+ `http://${host}:${port}`,
387
+ (res) => {
388
+ res.resume();
389
+ this.readyHandler?.();
390
+ resolve();
391
+ }
392
+ );
393
+ req.on("error", () => {
394
+ if (fallback) {
395
+ tryConnect(fallback);
396
+ return;
397
+ }
398
+ if (Date.now() - startTime >= MAX_WAIT_MS) {
399
+ reject(
400
+ new Error(
401
+ `Dev server did not become ready within ${MAX_WAIT_MS / 1e3}s. Logs:
402
+ ${this.getLogs()}`
403
+ )
404
+ );
405
+ return;
406
+ }
407
+ setTimeout(check, POLL_INTERVAL_MS);
408
+ });
409
+ req.end();
410
+ };
411
+ tryConnect("127.0.0.1", "[::1]");
412
+ };
413
+ check();
414
+ });
415
+ }
416
+ };
417
+ var ProjectMapApi = class {
418
+ graphStore = null;
419
+ searchRouter = null;
420
+ analysis = null;
421
+ activeFiles = [];
422
+ setGraphStore(store) {
423
+ this.graphStore = store;
424
+ }
425
+ setSearchRouter(router) {
426
+ this.searchRouter = router;
427
+ }
428
+ setAnalysis(analysis) {
429
+ this.analysis = analysis;
430
+ }
431
+ setActiveFiles(files) {
432
+ this.activeFiles = files;
433
+ }
434
+ async handleRequest(req, res) {
435
+ const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
436
+ if (url.pathname === "/nova-api/project-map" && req.method === "GET") {
437
+ await this.handleGetMap(res);
438
+ return true;
439
+ }
440
+ if (url.pathname === "/nova-api/project-map/search" && req.method === "GET") {
441
+ const query = url.searchParams.get("q") ?? "";
442
+ await this.handleSearch(res, query);
443
+ return true;
444
+ }
445
+ return false;
446
+ }
447
+ async handleGetMap(res) {
448
+ if (!this.graphStore) {
449
+ this.sendJson(res, 503, { error: "Graph store not initialized" });
450
+ return;
451
+ }
452
+ const nodes = await this.graphStore.load();
453
+ const mapNodes = nodes.map((n) => {
454
+ const methods = this.analysis?.methods.filter((m) => m.filePath === n.filePath).map((m) => ({ name: m.name, signature: m.signature, purpose: m.purpose })) ?? [];
455
+ return {
456
+ id: n.filePath,
457
+ label: n.filePath.split("/").pop() ?? n.filePath,
458
+ type: n.type,
459
+ exports: n.exports,
460
+ keywords: n.keywords,
461
+ route: n.route,
462
+ methods
463
+ };
464
+ });
465
+ const mapEdges = [];
466
+ for (const node of nodes) {
467
+ for (const imp of node.imports) {
468
+ if (nodes.some((n) => n.filePath === imp)) {
469
+ mapEdges.push({ source: node.filePath, target: imp });
470
+ }
471
+ }
472
+ }
473
+ const data = {
474
+ nodes: mapNodes,
475
+ edges: mapEdges,
476
+ analysis: this.analysis,
477
+ activeFiles: this.activeFiles
478
+ };
479
+ this.sendJson(res, 200, data);
480
+ }
481
+ async handleSearch(res, query) {
482
+ if (!query) {
483
+ this.sendJson(res, 400, { error: 'Missing query parameter "q"' });
484
+ return;
485
+ }
486
+ if (!this.searchRouter) {
487
+ this.sendJson(res, 503, { error: "Search router not initialized" });
488
+ return;
489
+ }
490
+ const results = await this.searchRouter.search(query, 20);
491
+ this.sendJson(res, 200, { results });
492
+ }
493
+ sendJson(res, status, data) {
494
+ const body = JSON.stringify(data);
495
+ res.writeHead(status, {
496
+ "Content-Type": "application/json",
497
+ "Access-Control-Allow-Origin": "*",
498
+ "Cache-Control": "no-cache"
499
+ });
500
+ res.end(body);
501
+ }
502
+ };
503
+
504
+ export {
505
+ ProxyServer,
506
+ WebSocketServer,
507
+ DevServerRunner,
508
+ ProjectMapApi
509
+ };