everything-dev 0.0.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/package.json +40 -0
- package/src/cli.ts +735 -0
- package/src/components/dev-view.tsx +243 -0
- package/src/components/status-view.tsx +173 -0
- package/src/components/streaming-view.ts +110 -0
- package/src/config.ts +214 -0
- package/src/contract.ts +364 -0
- package/src/index.ts +3 -0
- package/src/lib/env.ts +91 -0
- package/src/lib/near-cli.ts +289 -0
- package/src/lib/nova.ts +254 -0
- package/src/lib/orchestrator.ts +213 -0
- package/src/lib/process.ts +370 -0
- package/src/lib/secrets.ts +28 -0
- package/src/plugin.ts +930 -0
- package/src/utils/banner.ts +19 -0
- package/src/utils/run.ts +21 -0
- package/src/utils/theme.ts +101 -0
- package/tsconfig.json +22 -0
package/src/cli.ts
ADDED
|
@@ -0,0 +1,735 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { program } from "commander";
|
|
3
|
+
import { createPluginRuntime } from "every-plugin";
|
|
4
|
+
import { getConfigDir, getConfigPath, getPackages, getTitle, loadConfig } from "./config";
|
|
5
|
+
import BosPlugin from "./plugin";
|
|
6
|
+
import { printBanner } from "./utils/banner";
|
|
7
|
+
import { colors, frames, gradients, icons } from "./utils/theme";
|
|
8
|
+
|
|
9
|
+
async function main() {
|
|
10
|
+
let config: ReturnType<typeof loadConfig>;
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
config = loadConfig();
|
|
14
|
+
} catch {
|
|
15
|
+
console.error(colors.error(`${icons.err} Could not find bos.config.json`));
|
|
16
|
+
console.log(colors.dim(" Run 'bos create project <name>' to create a new project"));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const envPath = `${getConfigDir()}/.env.bos`;
|
|
21
|
+
const envFile = Bun.file(envPath);
|
|
22
|
+
if (await envFile.exists()) {
|
|
23
|
+
const content = await envFile.text();
|
|
24
|
+
for (const line of content.split("\n")) {
|
|
25
|
+
const trimmed = line.trim();
|
|
26
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
27
|
+
const eqIndex = trimmed.indexOf("=");
|
|
28
|
+
if (eqIndex === -1) continue;
|
|
29
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
30
|
+
const value = trimmed.slice(eqIndex + 1).trim();
|
|
31
|
+
if (key && !process.env[key]) {
|
|
32
|
+
process.env[key] = value;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const packages = getPackages();
|
|
38
|
+
const title = getTitle();
|
|
39
|
+
const configPath = getConfigPath();
|
|
40
|
+
|
|
41
|
+
printBanner(title);
|
|
42
|
+
|
|
43
|
+
const runtime = createPluginRuntime({
|
|
44
|
+
registry: {
|
|
45
|
+
"bos-cli": { module: BosPlugin }
|
|
46
|
+
},
|
|
47
|
+
secrets: {
|
|
48
|
+
NEAR_PRIVATE_KEY: process.env.NEAR_PRIVATE_KEY || "",
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: usePlugin is not a React hook
|
|
53
|
+
const result = await runtime.usePlugin("bos-cli", {
|
|
54
|
+
variables: {},
|
|
55
|
+
secrets: {
|
|
56
|
+
nearPrivateKey: process.env.NEAR_PRIVATE_KEY || "",
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const client = result.createClient();
|
|
61
|
+
|
|
62
|
+
function getHelpHeader(): string {
|
|
63
|
+
const host = config.app.host;
|
|
64
|
+
const lines: string[] = [];
|
|
65
|
+
|
|
66
|
+
lines.push("");
|
|
67
|
+
lines.push(colors.cyan(frames.top(52)));
|
|
68
|
+
lines.push(` ${icons.config} ${gradients.cyber("BOS CLI")} ${colors.dim("v1.0.0")}`);
|
|
69
|
+
lines.push(colors.cyan(frames.bottom(52)));
|
|
70
|
+
lines.push("");
|
|
71
|
+
lines.push(` ${colors.dim("Account")} ${colors.cyan(config.account)}`);
|
|
72
|
+
lines.push(` ${colors.dim("Gateway")} ${colors.white(config.gateway.production)}`);
|
|
73
|
+
lines.push(` ${colors.dim("Config ")} ${colors.dim(configPath)}`);
|
|
74
|
+
if (host.description) {
|
|
75
|
+
lines.push(` ${colors.dim("About ")} ${colors.white(host.description)}`);
|
|
76
|
+
}
|
|
77
|
+
lines.push("");
|
|
78
|
+
lines.push(colors.cyan(frames.top(52)));
|
|
79
|
+
lines.push("");
|
|
80
|
+
|
|
81
|
+
return lines.join("\n");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
program
|
|
85
|
+
.name("bos")
|
|
86
|
+
.version("1.0.0")
|
|
87
|
+
.addHelpText("before", getHelpHeader());
|
|
88
|
+
|
|
89
|
+
program
|
|
90
|
+
.command("info")
|
|
91
|
+
.description("Show current configuration")
|
|
92
|
+
.action(async () => {
|
|
93
|
+
const result = await client.info({});
|
|
94
|
+
|
|
95
|
+
console.log();
|
|
96
|
+
console.log(colors.cyan(frames.top(52)));
|
|
97
|
+
console.log(` ${icons.config} ${gradients.cyber("CONFIGURATION")}`);
|
|
98
|
+
console.log(colors.cyan(frames.bottom(52)));
|
|
99
|
+
console.log();
|
|
100
|
+
|
|
101
|
+
console.log(` ${colors.dim("Account")} ${colors.cyan(result.config.account)}`);
|
|
102
|
+
console.log(` ${colors.dim("Config ")} ${colors.dim(configPath)}`);
|
|
103
|
+
console.log();
|
|
104
|
+
|
|
105
|
+
const host = result.config.app.host;
|
|
106
|
+
console.log(colors.magenta(` ┌─ HOST ${"─".repeat(42)}┐`));
|
|
107
|
+
console.log(` ${colors.magenta("│")} ${colors.dim("title")} ${colors.white(host.title)}`);
|
|
108
|
+
if (host.description) {
|
|
109
|
+
console.log(` ${colors.magenta("│")} ${colors.dim("description")} ${colors.gray(host.description)}`);
|
|
110
|
+
}
|
|
111
|
+
console.log(` ${colors.magenta("│")} ${colors.dim("development")} ${colors.cyan(host.development)}`);
|
|
112
|
+
console.log(` ${colors.magenta("│")} ${colors.dim("production")} ${colors.green(host.production)}`);
|
|
113
|
+
console.log(colors.magenta(` └${"─".repeat(49)}┘`));
|
|
114
|
+
|
|
115
|
+
for (const remoteName of result.remotes) {
|
|
116
|
+
const remote = result.config.app[remoteName];
|
|
117
|
+
if (!remote || !("name" in remote)) continue;
|
|
118
|
+
|
|
119
|
+
console.log();
|
|
120
|
+
const color = remoteName === "ui" ? colors.cyan : colors.blue;
|
|
121
|
+
console.log(color(` ┌─ ${remoteName.toUpperCase()} ${"─".repeat(46 - remoteName.length)}┐`));
|
|
122
|
+
console.log(` ${color("│")} ${colors.dim("development")} ${colors.cyan(remote.development)}`);
|
|
123
|
+
console.log(` ${color("│")} ${colors.dim("production")} ${colors.green(remote.production)}`);
|
|
124
|
+
if ("ssr" in remote && remote.ssr) {
|
|
125
|
+
console.log(` ${color("│")} ${colors.dim("ssr")} ${colors.purple(remote.ssr as string)}`);
|
|
126
|
+
}
|
|
127
|
+
console.log(color(` └${"─".repeat(49)}┘`));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
console.log();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
program
|
|
134
|
+
.command("status")
|
|
135
|
+
.description("Check remote health")
|
|
136
|
+
.option("-e, --env <env>", "Environment (development | production)", "development")
|
|
137
|
+
.action(async (options: { env: string }) => {
|
|
138
|
+
const result = await client.status({ env: options.env as "development" | "production" });
|
|
139
|
+
|
|
140
|
+
console.log();
|
|
141
|
+
console.log(colors.cyan(frames.top(48)));
|
|
142
|
+
console.log(` ${icons.scan} ${gradients.cyber("ENDPOINT STATUS")}`);
|
|
143
|
+
console.log(colors.cyan(frames.bottom(48)));
|
|
144
|
+
console.log();
|
|
145
|
+
|
|
146
|
+
for (const endpoint of result.endpoints) {
|
|
147
|
+
const status = endpoint.healthy
|
|
148
|
+
? colors.green(`${icons.ok} healthy`)
|
|
149
|
+
: colors.error(`${icons.err} unhealthy`);
|
|
150
|
+
const latency = endpoint.latency ? colors.dim(` (${endpoint.latency}ms)`) : "";
|
|
151
|
+
console.log(` ${endpoint.name}: ${status}${latency}`);
|
|
152
|
+
console.log(colors.dim(` ${endpoint.url}`));
|
|
153
|
+
}
|
|
154
|
+
console.log();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
program
|
|
158
|
+
.command("dev")
|
|
159
|
+
.description(`Start development (${packages.join(", ")})`)
|
|
160
|
+
.option("--host <mode>", "Host mode: local (default) | remote", "local")
|
|
161
|
+
.option("--ui <mode>", "UI mode: local (default) | remote", "local")
|
|
162
|
+
.option("--api <mode>", "API mode: local (default) | remote", "local")
|
|
163
|
+
.option("--proxy", "Proxy API requests to production")
|
|
164
|
+
.option("-p, --port <port>", "Host port (default: from config)")
|
|
165
|
+
.option("--no-interactive", "Disable interactive UI (streaming logs)")
|
|
166
|
+
.action(async (options) => {
|
|
167
|
+
const result = await client.dev({
|
|
168
|
+
host: options.host as "local" | "remote",
|
|
169
|
+
ui: options.ui as "local" | "remote",
|
|
170
|
+
api: options.api as "local" | "remote",
|
|
171
|
+
proxy: options.proxy || false,
|
|
172
|
+
port: options.port ? parseInt(options.port, 10) : undefined,
|
|
173
|
+
interactive: options.interactive,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
if (result.status === "error") {
|
|
177
|
+
console.error(colors.error(`${icons.err} ${result.description}`));
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
program
|
|
183
|
+
.command("start")
|
|
184
|
+
.description("Start with production modules (all remotes from production URLs)")
|
|
185
|
+
.option("-p, --port <port>", "Host port (default: 3000)")
|
|
186
|
+
.option("--account <account>", "NEAR account to fetch config from social.near")
|
|
187
|
+
.option("--domain <domain>", "Gateway domain for config lookup")
|
|
188
|
+
.option("--no-interactive", "Disable interactive UI (streaming logs)")
|
|
189
|
+
.action(async (options) => {
|
|
190
|
+
const result = await client.start({
|
|
191
|
+
port: options.port ? parseInt(options.port, 10) : undefined,
|
|
192
|
+
account: options.account,
|
|
193
|
+
domain: options.domain,
|
|
194
|
+
interactive: options.interactive,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (result.status === "error") {
|
|
198
|
+
console.error(colors.error(`${icons.err} Failed to start`));
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
program
|
|
204
|
+
.command("serve")
|
|
205
|
+
.description("Run CLI as HTTP server (exposes /api)")
|
|
206
|
+
.option("-p, --port <port>", "Port to run on", "4000")
|
|
207
|
+
.action(async (options) => {
|
|
208
|
+
const result = await client.serve({
|
|
209
|
+
port: parseInt(options.port, 10),
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
console.log();
|
|
213
|
+
console.log(colors.cyan(frames.top(48)));
|
|
214
|
+
console.log(` ${icons.run} ${gradients.cyber("CLI SERVER")}`);
|
|
215
|
+
console.log(colors.cyan(frames.bottom(48)));
|
|
216
|
+
console.log();
|
|
217
|
+
console.log(` ${colors.dim("URL:")} ${colors.white(result.url)}`);
|
|
218
|
+
console.log(` ${colors.dim("RPC:")} ${colors.white(result.endpoints.rpc)}`);
|
|
219
|
+
console.log(` ${colors.dim("Docs:")} ${colors.white(result.endpoints.docs)}`);
|
|
220
|
+
console.log();
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
program
|
|
224
|
+
.command("build")
|
|
225
|
+
.description(`Build packages (${packages.join(", ")}). Deploys to Zephyr Cloud by default.`)
|
|
226
|
+
.argument("[package]", "Package to build", "all")
|
|
227
|
+
.option("--force", "Force rebuild")
|
|
228
|
+
.option("--no-deploy", "Build locally without Zephyr deploy")
|
|
229
|
+
.addHelpText("after", `
|
|
230
|
+
Zephyr Configuration:
|
|
231
|
+
Set ZE_SERVER_TOKEN and ZE_USER_EMAIL in .env.bos for CI/CD deployment.
|
|
232
|
+
Docs: https://docs.zephyr-cloud.io/features/ci-cd-server-token
|
|
233
|
+
`)
|
|
234
|
+
.action(async (pkg: string, options) => {
|
|
235
|
+
console.log();
|
|
236
|
+
console.log(` ${icons.pkg} Building${options.deploy ? " & deploying" : ""}...`);
|
|
237
|
+
|
|
238
|
+
const result = await client.build({
|
|
239
|
+
package: pkg,
|
|
240
|
+
force: options.force || false,
|
|
241
|
+
deploy: options.deploy !== false,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
if (result.status === "error") {
|
|
245
|
+
console.error(colors.error(`${icons.err} Build failed`));
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
console.log();
|
|
250
|
+
console.log(colors.green(`${icons.ok} Built: ${result.built.join(", ")}`));
|
|
251
|
+
if (result.deployed) {
|
|
252
|
+
console.log(colors.dim(` Deployed to Zephyr Cloud`));
|
|
253
|
+
}
|
|
254
|
+
console.log();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
program
|
|
258
|
+
.command("publish")
|
|
259
|
+
.description("Publish bos.config.json to on-chain registry (FastFS)")
|
|
260
|
+
.option("--network <network>", "Network: mainnet | testnet", "mainnet")
|
|
261
|
+
.option("--path <path>", "FastFS relative path", "bos.config.json")
|
|
262
|
+
.option("--dry-run", "Show what would be published without sending")
|
|
263
|
+
.action(async (options) => {
|
|
264
|
+
console.log();
|
|
265
|
+
console.log(` ${icons.pkg} Publishing to FastFS...`);
|
|
266
|
+
console.log(colors.dim(` Account: ${config.account}`));
|
|
267
|
+
console.log(colors.dim(` Network: ${options.network}`));
|
|
268
|
+
|
|
269
|
+
if (options.dryRun) {
|
|
270
|
+
console.log(colors.cyan(` ${icons.scan} Dry run mode - no transaction will be sent`));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const result = await client.publish({
|
|
274
|
+
network: options.network as "mainnet" | "testnet",
|
|
275
|
+
path: options.path,
|
|
276
|
+
dryRun: options.dryRun || false,
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
if (result.status === "error") {
|
|
280
|
+
console.error(colors.error(`${icons.err} Publish failed: ${result.error || "Unknown error"}`));
|
|
281
|
+
process.exit(1);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (result.status === "dry-run") {
|
|
285
|
+
console.log();
|
|
286
|
+
console.log(colors.cyan(`${icons.ok} Dry run complete`));
|
|
287
|
+
console.log(` ${colors.dim("Would publish to:")} ${result.registryUrl}`);
|
|
288
|
+
console.log();
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
console.log();
|
|
293
|
+
console.log(colors.green(`${icons.ok} Published!`));
|
|
294
|
+
console.log(` ${colors.dim("TX:")} ${result.txHash}`);
|
|
295
|
+
console.log(` ${colors.dim("URL:")} ${result.registryUrl}`);
|
|
296
|
+
console.log();
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
program
|
|
300
|
+
.command("clean")
|
|
301
|
+
.description("Clean build artifacts")
|
|
302
|
+
.action(async () => {
|
|
303
|
+
const result = await client.clean({});
|
|
304
|
+
|
|
305
|
+
console.log();
|
|
306
|
+
console.log(colors.green(`${icons.ok} Cleaned: ${result.removed.join(", ")}`));
|
|
307
|
+
console.log();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
const create = program
|
|
311
|
+
.command("create")
|
|
312
|
+
.description("Scaffold new projects and remotes");
|
|
313
|
+
|
|
314
|
+
create
|
|
315
|
+
.command("project")
|
|
316
|
+
.description("Create a new BOS project")
|
|
317
|
+
.argument("<name>", "Project name")
|
|
318
|
+
.option("-t, --template <url>", "Template URL")
|
|
319
|
+
.action(async (name: string, options: { template?: string }) => {
|
|
320
|
+
const result = await client.create({
|
|
321
|
+
type: "project",
|
|
322
|
+
name,
|
|
323
|
+
template: options.template,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
if (result.status === "error") {
|
|
327
|
+
console.error(colors.error(`${icons.err} Failed to create project`));
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
console.log();
|
|
332
|
+
console.log(colors.green(`${icons.ok} Created project at ${result.path}`));
|
|
333
|
+
console.log();
|
|
334
|
+
console.log(colors.dim(" Next steps:"));
|
|
335
|
+
console.log(` ${colors.dim("1.")} cd ${result.path}`);
|
|
336
|
+
console.log(` ${colors.dim("2.")} bun install`);
|
|
337
|
+
console.log(` ${colors.dim("3.")} bun bos dev`);
|
|
338
|
+
console.log();
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
create
|
|
342
|
+
.command("ui")
|
|
343
|
+
.description("Scaffold a new UI remote")
|
|
344
|
+
.option("-t, --template <url>", "Template URL")
|
|
345
|
+
.action(async (options: { template?: string }) => {
|
|
346
|
+
const result = await client.create({
|
|
347
|
+
type: "ui",
|
|
348
|
+
template: options.template,
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
if (result.status === "created") {
|
|
352
|
+
console.log(colors.green(`${icons.ok} Created UI at ${result.path}`));
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
create
|
|
357
|
+
.command("api")
|
|
358
|
+
.description("Scaffold a new API remote")
|
|
359
|
+
.option("-t, --template <url>", "Template URL")
|
|
360
|
+
.action(async (options: { template?: string }) => {
|
|
361
|
+
const result = await client.create({
|
|
362
|
+
type: "api",
|
|
363
|
+
template: options.template,
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
if (result.status === "created") {
|
|
367
|
+
console.log(colors.green(`${icons.ok} Created API at ${result.path}`));
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
create
|
|
372
|
+
.command("host")
|
|
373
|
+
.description("Scaffold a new host")
|
|
374
|
+
.option("-t, --template <url>", "Template URL")
|
|
375
|
+
.action(async (options: { template?: string }) => {
|
|
376
|
+
const result = await client.create({
|
|
377
|
+
type: "host",
|
|
378
|
+
template: options.template,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
if (result.status === "created") {
|
|
382
|
+
console.log(colors.green(`${icons.ok} Created host at ${result.path}`));
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
create
|
|
387
|
+
.command("cli")
|
|
388
|
+
.description("Scaffold a new CLI")
|
|
389
|
+
.option("-t, --template <url>", "Template URL")
|
|
390
|
+
.action(async (options: { template?: string }) => {
|
|
391
|
+
const result = await client.create({
|
|
392
|
+
type: "cli",
|
|
393
|
+
template: options.template,
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
if (result.status === "created") {
|
|
397
|
+
console.log(colors.green(`${icons.ok} Created CLI at ${result.path}`));
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
create
|
|
402
|
+
.command("gateway")
|
|
403
|
+
.description("Scaffold a new gateway")
|
|
404
|
+
.option("-t, --template <url>", "Template URL")
|
|
405
|
+
.action(async (options: { template?: string }) => {
|
|
406
|
+
const result = await client.create({
|
|
407
|
+
type: "gateway",
|
|
408
|
+
template: options.template,
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
if (result.status === "created") {
|
|
412
|
+
console.log(colors.green(`${icons.ok} Created gateway at ${result.path}`));
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
const gateway = program
|
|
417
|
+
.command("gateway")
|
|
418
|
+
.description("Manage gateway deployment");
|
|
419
|
+
|
|
420
|
+
gateway
|
|
421
|
+
.command("dev")
|
|
422
|
+
.description("Run gateway locally (wrangler dev)")
|
|
423
|
+
.action(async () => {
|
|
424
|
+
console.log();
|
|
425
|
+
console.log(` ${icons.run} Starting gateway dev server...`);
|
|
426
|
+
|
|
427
|
+
const result = await client.gatewayDev({});
|
|
428
|
+
|
|
429
|
+
if (result.status === "error") {
|
|
430
|
+
console.error(colors.error(`${icons.err} ${result.error || "Failed to start gateway"}`));
|
|
431
|
+
process.exit(1);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
console.log();
|
|
435
|
+
console.log(colors.green(`${icons.ok} Gateway running at ${result.url}`));
|
|
436
|
+
console.log();
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
gateway
|
|
440
|
+
.command("deploy")
|
|
441
|
+
.description("Deploy gateway to Cloudflare")
|
|
442
|
+
.option("-e, --env <env>", "Environment (production | staging)")
|
|
443
|
+
.action(async (options: { env?: string }) => {
|
|
444
|
+
console.log();
|
|
445
|
+
console.log(` ${icons.pkg} Deploying gateway...`);
|
|
446
|
+
if (options.env) {
|
|
447
|
+
console.log(colors.dim(` Environment: ${options.env}`));
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const result = await client.gatewayDeploy({
|
|
451
|
+
env: options.env as "production" | "staging" | undefined,
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
if (result.status === "error") {
|
|
455
|
+
console.error(colors.error(`${icons.err} ${result.error || "Deploy failed"}`));
|
|
456
|
+
process.exit(1);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
console.log();
|
|
460
|
+
console.log(colors.green(`${icons.ok} Deployed!`));
|
|
461
|
+
console.log(` ${colors.dim("URL:")} ${result.url}`);
|
|
462
|
+
console.log();
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
gateway
|
|
466
|
+
.command("sync")
|
|
467
|
+
.description("Sync wrangler.toml vars from bos.config.json")
|
|
468
|
+
.action(async () => {
|
|
469
|
+
console.log();
|
|
470
|
+
console.log(` ${icons.pkg} Syncing gateway config...`);
|
|
471
|
+
|
|
472
|
+
const result = await client.gatewaySync({});
|
|
473
|
+
|
|
474
|
+
if (result.status === "error") {
|
|
475
|
+
console.error(colors.error(`${icons.err} ${result.error || "Sync failed"}`));
|
|
476
|
+
process.exit(1);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
console.log();
|
|
480
|
+
console.log(colors.green(`${icons.ok} Synced!`));
|
|
481
|
+
console.log(` ${colors.dim("GATEWAY_DOMAIN:")} ${result.gatewayDomain}`);
|
|
482
|
+
console.log(` ${colors.dim("GATEWAY_ACCOUNT:")} ${result.gatewayAccount}`);
|
|
483
|
+
console.log();
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
program
|
|
487
|
+
.command("register")
|
|
488
|
+
.description("Register a new tenant on the gateway")
|
|
489
|
+
.argument("<name>", `Account name (will create <name>.${config.account})`)
|
|
490
|
+
.option("--network <network>", "Network: mainnet | testnet", "mainnet")
|
|
491
|
+
.action(async (name: string, options: { network: string }) => {
|
|
492
|
+
console.log();
|
|
493
|
+
console.log(` ${icons.pkg} Registering ${name}...`);
|
|
494
|
+
|
|
495
|
+
const result = await client.register({
|
|
496
|
+
name,
|
|
497
|
+
network: options.network as "mainnet" | "testnet",
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
if (result.status === "error") {
|
|
501
|
+
console.error(colors.error(`${icons.err} Registration failed: ${result.error || "Unknown error"}`));
|
|
502
|
+
process.exit(1);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
console.log();
|
|
506
|
+
console.log(colors.green(`${icons.ok} Registered!`));
|
|
507
|
+
console.log(` ${colors.dim("Account:")} ${result.account}`);
|
|
508
|
+
if (result.novaGroup) {
|
|
509
|
+
console.log(` ${colors.dim("NOVA Group:")} ${result.novaGroup}`);
|
|
510
|
+
}
|
|
511
|
+
console.log();
|
|
512
|
+
console.log(colors.dim(" Next steps:"));
|
|
513
|
+
console.log(` ${colors.dim("1.")} Update bos.config.json with account: "${result.account}"`);
|
|
514
|
+
console.log(` ${colors.dim("2.")} bos secrets sync --env .env.local`);
|
|
515
|
+
console.log(` ${colors.dim("3.")} bos publish`);
|
|
516
|
+
console.log();
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
const secrets = program
|
|
520
|
+
.command("secrets")
|
|
521
|
+
.description("Manage encrypted secrets via NOVA");
|
|
522
|
+
|
|
523
|
+
secrets
|
|
524
|
+
.command("sync")
|
|
525
|
+
.description("Sync secrets from .env file to NOVA")
|
|
526
|
+
.option("--env <path>", "Path to .env file", ".env.local")
|
|
527
|
+
.action(async (options: { env: string }) => {
|
|
528
|
+
console.log();
|
|
529
|
+
console.log(` ${icons.pkg} Syncing secrets from ${options.env}...`);
|
|
530
|
+
|
|
531
|
+
const result = await client.secretsSync({
|
|
532
|
+
envPath: options.env,
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
if (result.status === "error") {
|
|
536
|
+
console.error(colors.error(`${icons.err} Sync failed: ${result.error || "Unknown error"}`));
|
|
537
|
+
process.exit(1);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
console.log();
|
|
541
|
+
console.log(colors.green(`${icons.ok} Synced ${result.count} secrets`));
|
|
542
|
+
if (result.cid) {
|
|
543
|
+
console.log(` ${colors.dim("CID:")} ${result.cid}`);
|
|
544
|
+
}
|
|
545
|
+
console.log();
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
secrets
|
|
549
|
+
.command("set")
|
|
550
|
+
.description("Set a single secret")
|
|
551
|
+
.argument("<key=value>", "Secret key=value pair")
|
|
552
|
+
.action(async (keyValue: string) => {
|
|
553
|
+
const eqIndex = keyValue.indexOf("=");
|
|
554
|
+
if (eqIndex === -1) {
|
|
555
|
+
console.error(colors.error(`${icons.err} Invalid format. Use: bos secrets set KEY=value`));
|
|
556
|
+
process.exit(1);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const key = keyValue.slice(0, eqIndex);
|
|
560
|
+
const value = keyValue.slice(eqIndex + 1);
|
|
561
|
+
|
|
562
|
+
console.log();
|
|
563
|
+
console.log(` ${icons.pkg} Setting secret ${key}...`);
|
|
564
|
+
|
|
565
|
+
const result = await client.secretsSet({ key, value });
|
|
566
|
+
|
|
567
|
+
if (result.status === "error") {
|
|
568
|
+
console.error(colors.error(`${icons.err} Failed: ${result.error || "Unknown error"}`));
|
|
569
|
+
process.exit(1);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
console.log();
|
|
573
|
+
console.log(colors.green(`${icons.ok} Secret set`));
|
|
574
|
+
if (result.cid) {
|
|
575
|
+
console.log(` ${colors.dim("CID:")} ${result.cid}`);
|
|
576
|
+
}
|
|
577
|
+
console.log();
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
secrets
|
|
581
|
+
.command("list")
|
|
582
|
+
.description("List secret keys (not values)")
|
|
583
|
+
.action(async () => {
|
|
584
|
+
const result = await client.secretsList({});
|
|
585
|
+
|
|
586
|
+
if (result.status === "error") {
|
|
587
|
+
console.error(colors.error(`${icons.err} Failed: ${result.error || "Unknown error"}`));
|
|
588
|
+
process.exit(1);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
console.log();
|
|
592
|
+
console.log(colors.cyan(frames.top(48)));
|
|
593
|
+
console.log(` ${icons.config} ${gradients.cyber("SECRETS")}`);
|
|
594
|
+
console.log(colors.cyan(frames.bottom(48)));
|
|
595
|
+
console.log();
|
|
596
|
+
|
|
597
|
+
if (result.keys.length === 0) {
|
|
598
|
+
console.log(colors.dim(" No secrets configured"));
|
|
599
|
+
} else {
|
|
600
|
+
for (const key of result.keys) {
|
|
601
|
+
console.log(` ${colors.dim("•")} ${key}`);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
console.log();
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
secrets
|
|
608
|
+
.command("delete")
|
|
609
|
+
.description("Delete a secret")
|
|
610
|
+
.argument("<key>", "Secret key to delete")
|
|
611
|
+
.action(async (key: string) => {
|
|
612
|
+
console.log();
|
|
613
|
+
console.log(` ${icons.pkg} Deleting secret ${key}...`);
|
|
614
|
+
|
|
615
|
+
const result = await client.secretsDelete({ key });
|
|
616
|
+
|
|
617
|
+
if (result.status === "error") {
|
|
618
|
+
console.error(colors.error(`${icons.err} Failed: ${result.error || "Unknown error"}`));
|
|
619
|
+
process.exit(1);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
console.log();
|
|
623
|
+
console.log(colors.green(`${icons.ok} Secret deleted`));
|
|
624
|
+
console.log();
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
program
|
|
628
|
+
.command("login")
|
|
629
|
+
.description("Login to NOVA for encrypted secrets management")
|
|
630
|
+
.action(async () => {
|
|
631
|
+
const { default: open } = await import("open");
|
|
632
|
+
const { password, input } = await import("@inquirer/prompts");
|
|
633
|
+
|
|
634
|
+
console.log();
|
|
635
|
+
console.log(colors.cyan(frames.top(52)));
|
|
636
|
+
console.log(` ${icons.config} ${gradients.cyber("NOVA LOGIN")}`);
|
|
637
|
+
console.log(colors.cyan(frames.bottom(52)));
|
|
638
|
+
console.log();
|
|
639
|
+
console.log(colors.dim(" NOVA provides encrypted secrets storage for your plugins."));
|
|
640
|
+
console.log();
|
|
641
|
+
console.log(colors.white(" To get your credentials:"));
|
|
642
|
+
console.log(colors.dim(" 1. Login at nova-sdk.com"));
|
|
643
|
+
console.log(colors.dim(" 2. Copy your account ID and session token from your profile"));
|
|
644
|
+
console.log();
|
|
645
|
+
|
|
646
|
+
try {
|
|
647
|
+
const shouldOpen = await input({
|
|
648
|
+
message: "Press Enter to open nova-sdk.com (or 'skip')",
|
|
649
|
+
default: "",
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
if (shouldOpen !== "skip") {
|
|
653
|
+
await open("https://nova-sdk.com");
|
|
654
|
+
console.log();
|
|
655
|
+
console.log(colors.dim(" Browser opened. Login and copy your credentials..."));
|
|
656
|
+
console.log();
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
const accountId = await input({
|
|
660
|
+
message: "Account ID (e.g., alice.nova-sdk.near):",
|
|
661
|
+
validate: (value: string) => {
|
|
662
|
+
if (!value.trim()) return "Account ID is required";
|
|
663
|
+
if (!value.includes(".")) return "Invalid account ID format";
|
|
664
|
+
return true;
|
|
665
|
+
},
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
const sessionToken = await input({
|
|
669
|
+
message: "Session Token (paste the full token):",
|
|
670
|
+
validate: (value: string) => {
|
|
671
|
+
if (!value.trim()) return "Session token is required";
|
|
672
|
+
if (value.length < 50) return "Token seems too short";
|
|
673
|
+
return true;
|
|
674
|
+
},
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
console.log();
|
|
678
|
+
console.log(` ${icons.pkg} Verifying credentials...`);
|
|
679
|
+
console.log(colors.dim(` Token length: ${sessionToken.length} characters`));
|
|
680
|
+
|
|
681
|
+
const result = await client.login({
|
|
682
|
+
accountId: accountId.trim(),
|
|
683
|
+
token: sessionToken.trim(),
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
if (result.status === "error") {
|
|
687
|
+
console.error(colors.error(`${icons.err} Login failed: ${result.error || "Unknown error"}`));
|
|
688
|
+
process.exit(1);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
console.log();
|
|
692
|
+
console.log(colors.green(`${icons.ok} Logged in!`));
|
|
693
|
+
console.log(` ${colors.dim("Account:")} ${result.accountId}`);
|
|
694
|
+
console.log(` ${colors.dim("Saved to:")} .env.bos`);
|
|
695
|
+
console.log();
|
|
696
|
+
console.log(colors.dim(" You can now use 'bos register' and 'bos secrets' commands."));
|
|
697
|
+
console.log();
|
|
698
|
+
} catch (error) {
|
|
699
|
+
if (error instanceof Error && error.name === "ExitPromptError") {
|
|
700
|
+
console.log();
|
|
701
|
+
console.log(colors.dim(" Login cancelled."));
|
|
702
|
+
console.log();
|
|
703
|
+
process.exit(0);
|
|
704
|
+
}
|
|
705
|
+
throw error;
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
program
|
|
710
|
+
.command("logout")
|
|
711
|
+
.description("Logout from NOVA (removes credentials from .env.bos)")
|
|
712
|
+
.action(async () => {
|
|
713
|
+
console.log();
|
|
714
|
+
console.log(` ${icons.pkg} Logging out...`);
|
|
715
|
+
|
|
716
|
+
const result = await client.logout({});
|
|
717
|
+
|
|
718
|
+
if (result.status === "error") {
|
|
719
|
+
console.error(colors.error(`${icons.err} Logout failed: ${result.error || "Unknown error"}`));
|
|
720
|
+
process.exit(1);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
console.log();
|
|
724
|
+
console.log(colors.green(`${icons.ok} Logged out`));
|
|
725
|
+
console.log(colors.dim(" NOVA credentials removed from .env.bos"));
|
|
726
|
+
console.log();
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
program.parse();
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
main().catch((error) => {
|
|
733
|
+
console.error(colors.error(`${icons.err} Fatal error:`), error);
|
|
734
|
+
process.exit(1);
|
|
735
|
+
});
|