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/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
+ });