@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 +100 -0
- package/dist/bin/nova.js +4 -2
- package/dist/chunk-4AQQAQBM.js +509 -0
- package/dist/{chunk-NFNZMCLQ.js → chunk-CLQXFM4X.js} +355 -48
- package/dist/chunk-KKTDQOQX.js +7359 -0
- package/dist/{chunk-FYSTZ6K6.js → chunk-QKD6A4EK.js} +6 -6
- package/dist/dist-6FOBVQ63.js +13 -0
- package/dist/dist-EMATXD3M.js +151 -0
- package/dist/index.js +4 -2
- package/dist/{package-3YCVE5UE.js → package-BDGFABJG.js} +12 -5
- package/dist/{setup-3KREUXRO.js → setup-L5TRND4P.js} +2 -1
- package/package.json +21 -12
|
@@ -1,7 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DevServerRunner,
|
|
3
|
+
ProxyServer,
|
|
4
|
+
WebSocketServer
|
|
5
|
+
} from "./chunk-4AQQAQBM.js";
|
|
1
6
|
import {
|
|
2
7
|
ConfigReader,
|
|
3
8
|
runSetup
|
|
4
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-QKD6A4EK.js";
|
|
10
|
+
import {
|
|
11
|
+
AgentPromptLoader,
|
|
12
|
+
Brain,
|
|
13
|
+
DEFAULT_CONFIG,
|
|
14
|
+
EnvDetector,
|
|
15
|
+
ExecutorPool,
|
|
16
|
+
GitManager,
|
|
17
|
+
Lane1Executor,
|
|
18
|
+
Lane2Executor,
|
|
19
|
+
Lane3Executor,
|
|
20
|
+
ManifestStore,
|
|
21
|
+
NovaDir,
|
|
22
|
+
NovaEventBus,
|
|
23
|
+
PathGuard,
|
|
24
|
+
ProjectIndexer,
|
|
25
|
+
ProjectScaffolder,
|
|
26
|
+
ProviderFactory,
|
|
27
|
+
SCAFFOLD_PRESETS
|
|
28
|
+
} from "./chunk-KKTDQOQX.js";
|
|
5
29
|
import {
|
|
6
30
|
__require
|
|
7
31
|
} from "./chunk-3RG5ZIWI.js";
|
|
@@ -18,27 +42,157 @@ import * as path from "path";
|
|
|
18
42
|
import chalk6 from "chalk";
|
|
19
43
|
import ora2 from "ora";
|
|
20
44
|
import { resolve as resolve2 } from "path";
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
|
|
46
|
+
// ../licensing/dist/index.js
|
|
47
|
+
import { createHash } from "crypto";
|
|
48
|
+
import { execFile } from "child_process";
|
|
49
|
+
var DEFAULT_WINDOW_DAYS = 90;
|
|
50
|
+
var BOT_PATTERNS = [
|
|
51
|
+
/\[bot\]@/i,
|
|
52
|
+
/^dependabot/i,
|
|
53
|
+
/^renovate/i,
|
|
54
|
+
/^github-actions/i,
|
|
55
|
+
/noreply\.github\.com$/i
|
|
56
|
+
];
|
|
57
|
+
function normalizeEmail(email) {
|
|
58
|
+
let normalized = email.toLowerCase().trim();
|
|
59
|
+
const atIndex = normalized.indexOf("@");
|
|
60
|
+
if (atIndex > 0) {
|
|
61
|
+
const local = normalized.slice(0, atIndex);
|
|
62
|
+
const domain = normalized.slice(atIndex);
|
|
63
|
+
const plusIndex = local.indexOf("+");
|
|
64
|
+
if (plusIndex > 0) {
|
|
65
|
+
normalized = local.slice(0, plusIndex) + domain;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return normalized;
|
|
69
|
+
}
|
|
70
|
+
function isBot(email) {
|
|
71
|
+
return BOT_PATTERNS.some((pattern) => pattern.test(email));
|
|
72
|
+
}
|
|
73
|
+
var TeamDetector = class {
|
|
74
|
+
detect(projectPath, options) {
|
|
75
|
+
const windowDays = options?.windowDays ?? DEFAULT_WINDOW_DAYS;
|
|
76
|
+
return new Promise((resolve4) => {
|
|
77
|
+
execFile(
|
|
78
|
+
"git",
|
|
79
|
+
["log", "--format=%ae", `--since=${windowDays} days ago`],
|
|
80
|
+
{ cwd: projectPath },
|
|
81
|
+
(error, stdout3) => {
|
|
82
|
+
if (error) {
|
|
83
|
+
resolve4({ devCount: 1, windowDays, botsFiltered: 0 });
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const rawEmails = stdout3.trim().split("\n").filter((line) => line.length > 0);
|
|
87
|
+
const normalizedEmails = rawEmails.map(normalizeEmail);
|
|
88
|
+
const humanEmails = /* @__PURE__ */ new Set();
|
|
89
|
+
const botEmails = /* @__PURE__ */ new Set();
|
|
90
|
+
for (const email of normalizedEmails) {
|
|
91
|
+
if (isBot(email)) {
|
|
92
|
+
botEmails.add(email);
|
|
93
|
+
} else {
|
|
94
|
+
humanEmails.add(email);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
resolve4({
|
|
98
|
+
devCount: humanEmails.size === 0 ? 1 : humanEmails.size,
|
|
99
|
+
windowDays,
|
|
100
|
+
botsFiltered: botEmails.size
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
var FREE_DEV_LIMIT = 3;
|
|
108
|
+
var KEY_PATTERN = /^NOVA-([A-Z2-7]+)-([a-f0-9]{4})$/;
|
|
109
|
+
function computeChecksum(base32Body) {
|
|
110
|
+
return createHash("sha256").update(base32Body).digest("hex").slice(0, 4);
|
|
111
|
+
}
|
|
112
|
+
function validateKey(key) {
|
|
113
|
+
const match = KEY_PATTERN.exec(key);
|
|
114
|
+
if (!match) return false;
|
|
115
|
+
const [, body, checksum] = match;
|
|
116
|
+
return computeChecksum(body) === checksum;
|
|
117
|
+
}
|
|
118
|
+
var LicenseChecker = class {
|
|
119
|
+
teamDetector = new TeamDetector();
|
|
120
|
+
async check(projectPath, _config) {
|
|
121
|
+
const teamInfo = await this.teamDetector.detect(projectPath);
|
|
122
|
+
const devCount = teamInfo.devCount;
|
|
123
|
+
if (devCount <= FREE_DEV_LIMIT) {
|
|
124
|
+
return { valid: true, tier: "free", devCount };
|
|
125
|
+
}
|
|
126
|
+
const key = _config.license?.key ?? process.env["NOVA_LICENSE_KEY"] ?? "";
|
|
127
|
+
if (!key) {
|
|
128
|
+
return {
|
|
129
|
+
valid: false,
|
|
130
|
+
tier: "company",
|
|
131
|
+
devCount,
|
|
132
|
+
message: "Company license required: this project has more than 3 contributors. Set NOVA_LICENSE_KEY to continue."
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (!validateKey(key)) {
|
|
136
|
+
return {
|
|
137
|
+
valid: false,
|
|
138
|
+
tier: "company",
|
|
139
|
+
devCount,
|
|
140
|
+
message: "Invalid license key format. Expected NOVA-{BASE32}-{CHECKSUM}."
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return { valid: true, tier: "company", devCount };
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
var TELEMETRY_ENDPOINT = "https://api.nova-architect.dev/v1/telemetry";
|
|
147
|
+
var TIMEOUT_MS = 3e3;
|
|
148
|
+
var Telemetry = class {
|
|
149
|
+
async send(payload) {
|
|
150
|
+
try {
|
|
151
|
+
const controller = new AbortController();
|
|
152
|
+
const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
153
|
+
try {
|
|
154
|
+
const response = await fetch(TELEMETRY_ENDPOINT, {
|
|
155
|
+
method: "POST",
|
|
156
|
+
headers: { "Content-Type": "application/json" },
|
|
157
|
+
body: JSON.stringify(payload),
|
|
158
|
+
signal: controller.signal
|
|
159
|
+
});
|
|
160
|
+
if (response.ok) {
|
|
161
|
+
const data = await response.json();
|
|
162
|
+
return { nudgeLevel: data.nudge_level ?? 0 };
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
} finally {
|
|
166
|
+
clearTimeout(timeout);
|
|
167
|
+
}
|
|
168
|
+
} catch {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
var NudgeRenderer = class {
|
|
174
|
+
render(context) {
|
|
175
|
+
switch (context.level) {
|
|
176
|
+
case 0:
|
|
177
|
+
return null;
|
|
178
|
+
case 1:
|
|
179
|
+
return "Nova Architect is free for teams of 3 or fewer. Learn more: https://nova-architect.dev/pricing";
|
|
180
|
+
case 2:
|
|
181
|
+
return `Your team has ${context.devCount} developers. A license is recommended. Visit https://nova-architect.dev/pricing`;
|
|
182
|
+
case 3:
|
|
183
|
+
return [
|
|
184
|
+
"+-------------------------------------------------+",
|
|
185
|
+
"| License Required |",
|
|
186
|
+
`| Your team of ${String(context.devCount).padEnd(3)} developers needs a |`,
|
|
187
|
+
"| commercial license. |",
|
|
188
|
+
"| -> https://nova-architect.dev/pricing |",
|
|
189
|
+
"+-------------------------------------------------+"
|
|
190
|
+
].join("\n");
|
|
191
|
+
default:
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
};
|
|
42
196
|
|
|
43
197
|
// src/logger.ts
|
|
44
198
|
import chalk from "chalk";
|
|
@@ -92,7 +246,6 @@ import { readFile, writeFile } from "fs/promises";
|
|
|
92
246
|
import { join } from "path";
|
|
93
247
|
import { select, input } from "@inquirer/prompts";
|
|
94
248
|
import { Separator } from "@inquirer/prompts";
|
|
95
|
-
import { ProjectScaffolder, SCAFFOLD_PRESETS } from "@novastorm-ai/core";
|
|
96
249
|
async function promptAndScaffold(projectPath) {
|
|
97
250
|
console.log(
|
|
98
251
|
chalk2.yellow("\nNo project detected.") + " What would you like to create?\n"
|
|
@@ -268,7 +421,6 @@ function mapDescriptionToCommand(desc) {
|
|
|
268
421
|
|
|
269
422
|
// src/autofix.ts
|
|
270
423
|
import chalk3 from "chalk";
|
|
271
|
-
import { Lane3Executor } from "@novastorm-ai/core";
|
|
272
424
|
var ERROR_PATTERNS = [
|
|
273
425
|
/Module not found: Can't resolve '([^']+)'/,
|
|
274
426
|
/Invalid src prop.*next\/image/i,
|
|
@@ -728,25 +880,25 @@ async function startCommand() {
|
|
|
728
880
|
spinner.succeed(`License OK (${license.tier}, ${license.devCount} dev(s)).`);
|
|
729
881
|
}
|
|
730
882
|
if (config.telemetry.enabled && process.env["NOVA_TELEMETRY"] !== "false") {
|
|
731
|
-
const { createHash } = await import("crypto");
|
|
883
|
+
const { createHash: createHash2 } = await import("crypto");
|
|
732
884
|
const os = await import("os");
|
|
733
|
-
const { execFile } = await import("child_process");
|
|
885
|
+
const { execFile: execFile2 } = await import("child_process");
|
|
734
886
|
const mac = Object.values(os.networkInterfaces()).flat().find((i) => !i?.internal && i?.mac !== "00:00:00:00:00:00")?.mac ?? "";
|
|
735
|
-
const machineId =
|
|
887
|
+
const machineId = createHash2("sha256").update(os.hostname() + os.userInfo().username + mac).digest("hex");
|
|
736
888
|
let projectHash;
|
|
737
889
|
try {
|
|
738
890
|
const remoteUrl = await new Promise((resolve4, reject) => {
|
|
739
|
-
|
|
891
|
+
execFile2("git", ["remote", "get-url", "origin"], { cwd }, (err, stdout3) => {
|
|
740
892
|
if (err) reject(err);
|
|
741
893
|
else resolve4(stdout3.trim());
|
|
742
894
|
});
|
|
743
895
|
});
|
|
744
|
-
projectHash =
|
|
896
|
+
projectHash = createHash2("sha256").update(remoteUrl).digest("hex");
|
|
745
897
|
} catch {
|
|
746
|
-
projectHash =
|
|
898
|
+
projectHash = createHash2("sha256").update(cwd).digest("hex");
|
|
747
899
|
}
|
|
748
900
|
const telemetry = new Telemetry();
|
|
749
|
-
const cliPkg = await import("./package-
|
|
901
|
+
const cliPkg = await import("./package-BDGFABJG.js").catch(
|
|
750
902
|
() => ({ default: { version: "0.0.1" } })
|
|
751
903
|
);
|
|
752
904
|
telemetry.send({
|
|
@@ -776,7 +928,7 @@ ${nudgeMessage}
|
|
|
776
928
|
});
|
|
777
929
|
}
|
|
778
930
|
spinner.start("Detecting project...");
|
|
779
|
-
const { StackDetector } = await import("
|
|
931
|
+
const { StackDetector } = await import("./dist-EMATXD3M.js");
|
|
780
932
|
const stackDetector = new StackDetector();
|
|
781
933
|
let stack = await stackDetector.detectStack(cwd);
|
|
782
934
|
let detectedDevCommand = await stackDetector.detectDevCommand(stack, cwd);
|
|
@@ -822,15 +974,15 @@ ${nudgeMessage}
|
|
|
822
974
|
throw err;
|
|
823
975
|
}
|
|
824
976
|
spinner.succeed("Project indexed.");
|
|
825
|
-
const { ProjectAnalyzer, RagIndexer, createEmbeddingService } = await import("
|
|
826
|
-
const { ProjectMapApi } = await import("
|
|
977
|
+
const { ProjectAnalyzer, RagIndexer, createEmbeddingService } = await import("./dist-EMATXD3M.js");
|
|
978
|
+
const { ProjectMapApi } = await import("./dist-6FOBVQ63.js");
|
|
827
979
|
const projectAnalyzer = new ProjectAnalyzer();
|
|
828
980
|
spinner.start("Analyzing project structure...");
|
|
829
981
|
const analysis = await projectAnalyzer.analyze(cwd, projectMap);
|
|
830
982
|
spinner.succeed(`Project analyzed: ${analysis.fileCount} files, ${analysis.methods.length} methods.`);
|
|
831
983
|
let ragIndexer = null;
|
|
832
984
|
try {
|
|
833
|
-
const { VectorStore } = await import("
|
|
985
|
+
const { VectorStore } = await import("./dist-EMATXD3M.js");
|
|
834
986
|
let embeddingProvider = "tfidf";
|
|
835
987
|
let embeddingApiKey;
|
|
836
988
|
let embeddingBaseUrl;
|
|
@@ -889,7 +1041,7 @@ ${nudgeMessage}
|
|
|
889
1041
|
wsServer.start(httpServer);
|
|
890
1042
|
}
|
|
891
1043
|
proxyServer.setProjectMapApi(projectMapApi);
|
|
892
|
-
const { GraphStore: GS, SearchRouter: SR } = await import("
|
|
1044
|
+
const { GraphStore: GS, SearchRouter: SR } = await import("./dist-EMATXD3M.js");
|
|
893
1045
|
const novaPath = novaDir.getPath(cwd);
|
|
894
1046
|
const graphStoreForApi = new GS(novaPath);
|
|
895
1047
|
const searchRouterForApi = new SR(graphStoreForApi);
|
|
@@ -913,7 +1065,7 @@ ${nudgeMessage}
|
|
|
913
1065
|
}
|
|
914
1066
|
if (!config.apiKeys.key && config.apiKeys.provider !== "ollama" && config.apiKeys.provider !== "claude-cli") {
|
|
915
1067
|
console.log(chalk6.yellow("\nNo API key configured. Running setup...\n"));
|
|
916
|
-
const { runSetup: runSetup2 } = await import("./setup-
|
|
1068
|
+
const { runSetup: runSetup2 } = await import("./setup-L5TRND4P.js");
|
|
917
1069
|
await runSetup2(cwd);
|
|
918
1070
|
const updatedConfig = await configReader.read(cwd);
|
|
919
1071
|
config.apiKeys = updatedConfig.apiKeys;
|
|
@@ -1336,7 +1488,6 @@ async function chatCommand() {
|
|
|
1336
1488
|
import * as path2 from "path";
|
|
1337
1489
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
1338
1490
|
import { stdin, stdout } from "process";
|
|
1339
|
-
import { DEFAULT_CONFIG } from "@novastorm-ai/core";
|
|
1340
1491
|
async function initCommand() {
|
|
1341
1492
|
const cwd = process.cwd();
|
|
1342
1493
|
const configReader = new ConfigReader();
|
|
@@ -1422,10 +1573,9 @@ async function watchCommand() {
|
|
|
1422
1573
|
|
|
1423
1574
|
// src/commands/license.ts
|
|
1424
1575
|
import chalk7 from "chalk";
|
|
1425
|
-
|
|
1426
|
-
var KEY_PATTERN = /^NOVA-([A-Z2-7]+)-([a-f0-9]{4})$/;
|
|
1576
|
+
var KEY_PATTERN2 = /^NOVA-([A-Z2-7]+)-([a-f0-9]{4})$/;
|
|
1427
1577
|
var VALIDATE_ENDPOINT = "https://api.nova-architect.dev/v1/license/validate";
|
|
1428
|
-
var
|
|
1578
|
+
var TIMEOUT_MS2 = 5e3;
|
|
1429
1579
|
async function licenseCommand(subcommand, key) {
|
|
1430
1580
|
const cwd = process.cwd();
|
|
1431
1581
|
const configReader = new ConfigReader();
|
|
@@ -1445,7 +1595,7 @@ async function licenseCommand(subcommand, key) {
|
|
|
1445
1595
|
}
|
|
1446
1596
|
}
|
|
1447
1597
|
async function showStatus(cwd, config) {
|
|
1448
|
-
const licenseChecker = new
|
|
1598
|
+
const licenseChecker = new LicenseChecker();
|
|
1449
1599
|
const teamDetector = new TeamDetector();
|
|
1450
1600
|
const [license, teamInfo] = await Promise.all([
|
|
1451
1601
|
licenseChecker.check(cwd, config),
|
|
@@ -1471,7 +1621,7 @@ async function showStatus(cwd, config) {
|
|
|
1471
1621
|
console.log("");
|
|
1472
1622
|
}
|
|
1473
1623
|
async function activateKey(cwd, configReader, key) {
|
|
1474
|
-
if (!
|
|
1624
|
+
if (!KEY_PATTERN2.test(key)) {
|
|
1475
1625
|
console.error(chalk7.red("Invalid key format. Expected: NOVA-{BASE32}-{CHECKSUM}"));
|
|
1476
1626
|
process.exit(1);
|
|
1477
1627
|
}
|
|
@@ -1479,7 +1629,7 @@ async function activateKey(cwd, configReader, key) {
|
|
|
1479
1629
|
let serverValid = true;
|
|
1480
1630
|
try {
|
|
1481
1631
|
const controller = new AbortController();
|
|
1482
|
-
const timeout = setTimeout(() => controller.abort(),
|
|
1632
|
+
const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS2);
|
|
1483
1633
|
try {
|
|
1484
1634
|
const response = await fetch(VALIDATE_ENDPOINT, {
|
|
1485
1635
|
method: "POST",
|
|
@@ -1513,14 +1663,12 @@ async function activateKey(cwd, configReader, key) {
|
|
|
1513
1663
|
import { createInterface as createInterface3 } from "readline/promises";
|
|
1514
1664
|
import { stdin as stdin2, stdout as stdout2 } from "process";
|
|
1515
1665
|
import chalk8 from "chalk";
|
|
1516
|
-
import { ManifestStore as ManifestStore2 } from "@novastorm-ai/core";
|
|
1517
|
-
import { NovaDir as NovaDir2 } from "@novastorm-ai/core";
|
|
1518
1666
|
var SERVICE_TYPES = ["frontend", "backend", "worker", "gateway"];
|
|
1519
1667
|
var ENTITY_TYPES = ["module", "external-service", "library", "shared-package"];
|
|
1520
1668
|
async function entityCommand(subcommand, name) {
|
|
1521
1669
|
const cwd = process.cwd();
|
|
1522
|
-
const store = new
|
|
1523
|
-
const novaDir = new
|
|
1670
|
+
const store = new ManifestStore();
|
|
1671
|
+
const novaDir = new NovaDir();
|
|
1524
1672
|
if (!novaDir.exists(cwd)) {
|
|
1525
1673
|
await novaDir.init(cwd);
|
|
1526
1674
|
}
|
|
@@ -1664,6 +1812,162 @@ async function entityRemove(cwd, store, name) {
|
|
|
1664
1812
|
}
|
|
1665
1813
|
}
|
|
1666
1814
|
|
|
1815
|
+
// src/commands/bible.ts
|
|
1816
|
+
import chalk9 from "chalk";
|
|
1817
|
+
var BIBLE_TEXT = `
|
|
1818
|
+
${chalk9.cyan.bold("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557")}
|
|
1819
|
+
${chalk9.cyan.bold("\u2551")} ${chalk9.white.bold("[DOCUMENT_CLASS: MANIFESTO]")} ${chalk9.gray("[STATUS: DECLASSIFIED]")} ${chalk9.cyan.bold("\u2551")}
|
|
1820
|
+
${chalk9.cyan.bold("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D")}
|
|
1821
|
+
|
|
1822
|
+
${chalk9.green.bold("Ambient Development")}
|
|
1823
|
+
${chalk9.gray("A manifesto for a new approach to building software")}
|
|
1824
|
+
${chalk9.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}
|
|
1825
|
+
|
|
1826
|
+
${chalk9.cyan("PART I")} \u2014 ${chalk9.white.bold("The problem everyone is solving from the wrong end")}
|
|
1827
|
+
|
|
1828
|
+
We live in an era where AI can write code. GPT, Claude, Gemini, dozens
|
|
1829
|
+
of models \u2014 all generating functions, components, entire applications.
|
|
1830
|
+
Every month a new tool appears. Each one promises a revolution. Each
|
|
1831
|
+
one does the same thing \u2014 helps turn text into code faster.
|
|
1832
|
+
|
|
1833
|
+
And here's the paradox: ${chalk9.green("code was never the real bottleneck.")}
|
|
1834
|
+
|
|
1835
|
+
${chalk9.gray("[RESEARCH] Bain 2025 \u2014 Where time actually goes")}
|
|
1836
|
+
${chalk9.gray("Writing & testing code .......")} ${chalk9.dim("25-35%")}
|
|
1837
|
+
${chalk9.green("Everything else .............")} ${chalk9.green("65-75%")}
|
|
1838
|
+
${chalk9.gray("(Understanding, formulation, review, integration, deploy)")}
|
|
1839
|
+
|
|
1840
|
+
By speeding up code generation 10x, we only sped up the entire
|
|
1841
|
+
process by 20-30%.
|
|
1842
|
+
|
|
1843
|
+
${chalk9.gray("Traditional")} \u2192 write code
|
|
1844
|
+
${chalk9.gray("Vibe coding")} \u2192 write prompt
|
|
1845
|
+
${chalk9.gray("Spec-driven")} \u2192 write spec
|
|
1846
|
+
${chalk9.gray("Visual-first")} \u2192 click in special editor
|
|
1847
|
+
${chalk9.green.bold("Ambient")} \u2192 ${chalk9.green("just use your app")}
|
|
1848
|
+
|
|
1849
|
+
${chalk9.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}
|
|
1850
|
+
|
|
1851
|
+
${chalk9.cyan("PART II")} \u2014 ${chalk9.white.bold("What is Ambient Development")}
|
|
1852
|
+
|
|
1853
|
+
${chalk9.white.bold("Ambient Development")} \u2014 an approach to building software where the
|
|
1854
|
+
system continuously observes the application in use and builds it
|
|
1855
|
+
out across every level of the stack based on user behavior, voice
|
|
1856
|
+
commands, and visual cues.
|
|
1857
|
+
|
|
1858
|
+
${chalk9.gray("\u266A Ambient Music")} \u2014 Creates atmosphere. Doesn't demand attention.
|
|
1859
|
+
${chalk9.gray("\u25D0 Ambient Lighting")} \u2014 Creates space. You don't think about bulbs.
|
|
1860
|
+
${chalk9.gray("\u25C8 Ambient Computing")} \u2014 Smart home, sensors. You live, it adapts.
|
|
1861
|
+
${chalk9.green("\u2318 Ambient Dev")} \u2014 ${chalk9.green("You use the product. Development happens around you.")}
|
|
1862
|
+
|
|
1863
|
+
You stop switching between the role of user and the role of developer.
|
|
1864
|
+
${chalk9.green("You are always the user. Development is ambient.")}
|
|
1865
|
+
|
|
1866
|
+
${chalk9.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}
|
|
1867
|
+
|
|
1868
|
+
${chalk9.cyan("PART III")} \u2014 ${chalk9.white.bold("Five principles")}
|
|
1869
|
+
|
|
1870
|
+
${chalk9.green("01")} ${chalk9.white.bold("Usage as specification")}
|
|
1871
|
+
The best specification is not one written in a document. The best
|
|
1872
|
+
specification is a person's behavior inside the product.
|
|
1873
|
+
|
|
1874
|
+
${chalk9.gray("[behavior]")} click on empty space \u2192 expects something there
|
|
1875
|
+
${chalk9.gray("[behavior]")} repeat action 5x \u2192 needs automation
|
|
1876
|
+
${chalk9.gray("[behavior]")} open page, leave in 1s \u2192 page doesn't deliver
|
|
1877
|
+
${chalk9.green("[ambient]")} ${chalk9.green("behavior never lies. behavior is the spec.")}
|
|
1878
|
+
|
|
1879
|
+
${chalk9.green("02")} ${chalk9.white.bold("Full stack vertical")}
|
|
1880
|
+
When you say "add a customers table with search," you don't mean
|
|
1881
|
+
"create a React component." You mean: I want to see my customers,
|
|
1882
|
+
and I want it to work.
|
|
1883
|
+
|
|
1884
|
+
${chalk9.cyan("UI Component")} \u2195 ${chalk9.cyan("API Endpoint")} \u2195 ${chalk9.cyan("Database Query")} \u2195 ${chalk9.cyan("Migration")}
|
|
1885
|
+
|
|
1886
|
+
${chalk9.green("03")} ${chalk9.white.bold("Three simultaneous modes")}
|
|
1887
|
+
${chalk9.gray("PASSIVE")} \u{1F441} \u2014 Silently observes. Suggests improvements.
|
|
1888
|
+
${chalk9.cyan("VOICE")} \u{1F3A4} \u2014 Say what you need without switching context.
|
|
1889
|
+
${chalk9.yellow("VISUAL")} \u{1F446} \u2014 Click, circle, drag. Point and speak.
|
|
1890
|
+
${chalk9.gray("All three work simultaneously. Not switches \u2014 layers.")}
|
|
1891
|
+
|
|
1892
|
+
${chalk9.green("04")} ${chalk9.white.bold("Speed lanes")}
|
|
1893
|
+
${chalk9.green("LANE 1")} <2s \u2014 CSS, texts, configs. No AI. Pattern matching.
|
|
1894
|
+
${chalk9.cyan("LANE 2")} 10-30s \u2014 Single-file changes. Fast model.
|
|
1895
|
+
${chalk9.yellow("LANE 3")} 1-5min \u2014 Multi-file features. Strong model.
|
|
1896
|
+
${chalk9.red("LANE 4")} min-hrs \u2014 Background refactoring. Async.
|
|
1897
|
+
|
|
1898
|
+
${chalk9.green("05")} ${chalk9.white.bold("Stack-agnostic")}
|
|
1899
|
+
${chalk9.green("[scan]")} package.json \u2192 Next.js + TypeScript
|
|
1900
|
+
${chalk9.green("[scan]")} .csproj \u2192 C# backend
|
|
1901
|
+
${chalk9.green("[scan]")} docker-compose.yml \u2192 PostgreSQL
|
|
1902
|
+
${chalk9.cyan("[ready]")} Stack detected. ${chalk9.green("Ambient mode activated.")}
|
|
1903
|
+
|
|
1904
|
+
${chalk9.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}
|
|
1905
|
+
|
|
1906
|
+
${chalk9.cyan("PART IV")} \u2014 ${chalk9.white.bold("What it looks like in practice")}
|
|
1907
|
+
|
|
1908
|
+
${chalk9.gray("MORNING")}
|
|
1909
|
+
${chalk9.gray("[you]")} open SaaS. check dashboard. "this table is slow."
|
|
1910
|
+
${chalk9.cyan("[ambient]")} found: SELECT * without pagination
|
|
1911
|
+
${chalk9.yellow("[lane 3]")} generating optimized query + pagination
|
|
1912
|
+
${chalk9.green("[done]")} hot reload. table loads instantly. 2 minutes.
|
|
1913
|
+
|
|
1914
|
+
${chalk9.gray("DAY")}
|
|
1915
|
+
${chalk9.gray("[you]")} "Save button too small on mobile"
|
|
1916
|
+
${chalk9.green("[lane 1]")} CSS injection. done. instant.
|
|
1917
|
+
${chalk9.gray("[you]")} "Add timezone picker to project form"
|
|
1918
|
+
${chalk9.cyan("[lane 2]")} component + API field. 20 seconds.
|
|
1919
|
+
|
|
1920
|
+
${chalk9.gray("EVENING")}
|
|
1921
|
+
${chalk9.green("[summary]")} 4 instant fixes | 2 fast changes | 1 feature
|
|
1922
|
+
${chalk9.green("[result]")} ${chalk9.green("zero IDE. zero prompts. zero context switches.")}
|
|
1923
|
+
|
|
1924
|
+
${chalk9.gray("NIGHT")}
|
|
1925
|
+
${chalk9.gray("[you]")} "Refactor auth module \u2014 split into services"
|
|
1926
|
+
${chalk9.yellow("[lane 4]")} background. agent running...
|
|
1927
|
+
${chalk9.green("[morning]")} PR ready. all tests green.
|
|
1928
|
+
|
|
1929
|
+
${chalk9.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}
|
|
1930
|
+
|
|
1931
|
+
${chalk9.cyan("PART V")} \u2014 ${chalk9.white.bold("Who is this for")}
|
|
1932
|
+
|
|
1933
|
+
${chalk9.green("\u2713")} Solo developer building a SaaS
|
|
1934
|
+
${chalk9.green("\u2713")} Startup team of 2-5 shipping daily
|
|
1935
|
+
${chalk9.green("\u2713")} Agency creating 10+ projects a year
|
|
1936
|
+
${chalk9.green("\u2713")} CTO prototyping ideas before allocating the team
|
|
1937
|
+
${chalk9.green("\u2713")} Enterprise teams looking for a multiplier
|
|
1938
|
+
|
|
1939
|
+
${chalk9.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}
|
|
1940
|
+
|
|
1941
|
+
${chalk9.cyan("PART VI")} \u2014 ${chalk9.white.bold("The future")}
|
|
1942
|
+
|
|
1943
|
+
What happens when you remove the formulation step entirely?
|
|
1944
|
+
|
|
1945
|
+
${chalk9.gray("Total time per task")} ${chalk9.green("\u2193 60-70%")}
|
|
1946
|
+
${chalk9.gray("Context switches")} ${chalk9.green("\u2192 0")}
|
|
1947
|
+
${chalk9.gray("Time-to-feedback")} ${chalk9.green("seconds, not hours")}
|
|
1948
|
+
|
|
1949
|
+
Code generation was step one. Ambient development is step two.
|
|
1950
|
+
Not "write code faster." ${chalk9.green.bold("Stop writing altogether.")}
|
|
1951
|
+
|
|
1952
|
+
${chalk9.gray("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550")}
|
|
1953
|
+
${chalk9.gray("Read the full version with infographics:")}
|
|
1954
|
+
${chalk9.cyan("https://cli.novastorm.ai/bible/")}
|
|
1955
|
+
|
|
1956
|
+
${chalk9.gray("GitHub:")} ${chalk9.cyan("https://github.com/novastorm-cli/nova")}
|
|
1957
|
+
${chalk9.gray("npm:")} ${chalk9.cyan("npm install -g @novastorm-ai/cli")}
|
|
1958
|
+
${chalk9.gray("X:")} ${chalk9.cyan("https://x.com/upranevich")}
|
|
1959
|
+
${chalk9.gray("TG:")} ${chalk9.cyan("https://t.me/novastormcli")}
|
|
1960
|
+
${chalk9.gray("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550")}
|
|
1961
|
+
`;
|
|
1962
|
+
async function bibleCommand(subcommand) {
|
|
1963
|
+
if (subcommand === "--read" || subcommand === "read" || !subcommand) {
|
|
1964
|
+
console.log(BIBLE_TEXT);
|
|
1965
|
+
} else {
|
|
1966
|
+
console.log(chalk9.yellow(`Unknown subcommand: ${subcommand}`));
|
|
1967
|
+
console.log(chalk9.gray("Usage: nova bible [--read]"));
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1667
1971
|
// src/index.ts
|
|
1668
1972
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
1669
1973
|
var pkg = JSON.parse(
|
|
@@ -1721,6 +2025,9 @@ function createCli() {
|
|
|
1721
2025
|
program.command("entity [subcommand] [name]").description("Manage manifest entities: nova entity <add|list|remove> [name]").action(async (subcommand, name) => {
|
|
1722
2026
|
await entityCommand(subcommand, name);
|
|
1723
2027
|
});
|
|
2028
|
+
program.command("bible [subcommand]").description("Read the Ambient Development manifesto: nova bible [--read]").action(async (subcommand) => {
|
|
2029
|
+
await bibleCommand(subcommand);
|
|
2030
|
+
});
|
|
1724
2031
|
return program;
|
|
1725
2032
|
}
|
|
1726
2033
|
var BANNER = `\x1B[96m
|