everything-dev 0.2.1 → 0.3.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 +80 -79
- package/src/cli.ts +1491 -1198
- package/src/components/monitor-view.tsx +423 -419
- package/src/config.ts +529 -241
- package/src/contract.ts +381 -364
- package/src/lib/env.ts +83 -65
- package/src/lib/nova.ts +207 -195
- package/src/lib/orchestrator.ts +232 -199
- package/src/lib/process-registry.ts +141 -132
- package/src/lib/process.ts +499 -409
- package/src/lib/resource-monitor/diff.ts +27 -9
- package/src/lib/resource-monitor/platform/darwin.ts +31 -18
- package/src/lib/resource-monitor/snapshot.ts +164 -151
- package/src/plugin.ts +2281 -1841
- package/src/types.ts +182 -83
- package/src/ui/head.ts +37 -26
- package/src/utils/banner.ts +7 -9
- package/src/utils/run.ts +27 -16
- package/src/lib/secrets.ts +0 -29
package/src/cli.ts
CHANGED
|
@@ -2,345 +2,409 @@
|
|
|
2
2
|
import { spinner } from "@clack/prompts";
|
|
3
3
|
import { program } from "commander";
|
|
4
4
|
import { createPluginRuntime } from "every-plugin";
|
|
5
|
-
import {
|
|
5
|
+
import { version } from "../package.json";
|
|
6
|
+
import { type BosConfig, getProjectRoot, loadConfig } from "./config";
|
|
6
7
|
import BosPlugin from "./plugin";
|
|
7
8
|
import { printBanner } from "./utils/banner";
|
|
8
9
|
import { colors, frames, gradients, icons } from "./utils/theme";
|
|
9
10
|
|
|
10
11
|
function getHelpHeader(config: BosConfig | null, configPath: string): string {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
12
|
+
const host = config?.app.host;
|
|
13
|
+
const lines: string[] = [];
|
|
14
|
+
|
|
15
|
+
lines.push("");
|
|
16
|
+
lines.push(colors.cyan(frames.top(52)));
|
|
17
|
+
lines.push(
|
|
18
|
+
` ${icons.config} ${gradients.cyber("everything-dev")} ${colors.dim(`v${version}`)}`,
|
|
19
|
+
);
|
|
20
|
+
lines.push(colors.cyan(frames.bottom(52)));
|
|
21
|
+
lines.push("");
|
|
22
|
+
|
|
23
|
+
if (config) {
|
|
24
|
+
lines.push(` ${colors.dim("Account")} ${colors.cyan(config.account)}`);
|
|
25
|
+
lines.push(
|
|
26
|
+
` ${colors.dim("Gateway")} ${colors.white(config.gateway?.production ?? "not configured")}`,
|
|
27
|
+
);
|
|
28
|
+
lines.push(` ${colors.dim("Config ")} ${colors.dim(configPath)}`);
|
|
29
|
+
} else {
|
|
30
|
+
lines.push(` ${colors.dim("No project config found")}`);
|
|
31
|
+
lines.push(
|
|
32
|
+
` ${colors.dim("Run")} ${colors.cyan("bos create project <name>")} ${colors.dim("to get started")}`,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
lines.push("");
|
|
37
|
+
lines.push(colors.cyan(frames.top(52)));
|
|
38
|
+
lines.push("");
|
|
39
|
+
|
|
40
|
+
return lines.join("\n");
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
function requireConfig(config: BosConfig | null): asserts config is BosConfig {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
if (!config) {
|
|
45
|
+
console.error(colors.error(`${icons.err} Could not find bos.config.json`));
|
|
46
|
+
console.log(
|
|
47
|
+
colors.dim(" Run 'bos create project <name>' to create a new project"),
|
|
48
|
+
);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
async function main() {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
54
|
+
// Check for --force flag in process.argv
|
|
55
|
+
const forceFlag = process.argv.includes("--force");
|
|
56
|
+
|
|
57
|
+
const configResult = await loadConfig({ force: forceFlag });
|
|
58
|
+
const config = configResult?.config ?? null;
|
|
59
|
+
const configPath = configResult?.source.path ?? process.cwd();
|
|
60
|
+
const packages = configResult?.packages.all ?? [];
|
|
61
|
+
|
|
62
|
+
if (config) {
|
|
63
|
+
const envPath = `${getProjectRoot()}/.env.bos`;
|
|
64
|
+
const envFile = Bun.file(envPath);
|
|
65
|
+
if (await envFile.exists()) {
|
|
66
|
+
const content = await envFile.text();
|
|
67
|
+
for (const line of content.split("\n")) {
|
|
68
|
+
const trimmed = line.trim();
|
|
69
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
70
|
+
const eqIndex = trimmed.indexOf("=");
|
|
71
|
+
if (eqIndex === -1) continue;
|
|
72
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
73
|
+
const value = trimmed.slice(eqIndex + 1).trim();
|
|
74
|
+
if (key && !process.env[key]) {
|
|
75
|
+
process.env[key] = value;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
printBanner("everything-dev", version);
|
|
82
|
+
|
|
83
|
+
const runtime = createPluginRuntime({
|
|
84
|
+
registry: {
|
|
85
|
+
"bos-cli": { module: BosPlugin },
|
|
86
|
+
},
|
|
87
|
+
secrets: {
|
|
88
|
+
NEAR_PRIVATE_KEY: process.env.NEAR_PRIVATE_KEY || "",
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: usePlugin is not a React hook
|
|
93
|
+
const result = await runtime.usePlugin("bos-cli", {
|
|
94
|
+
variables: {},
|
|
95
|
+
secrets: {
|
|
96
|
+
nearPrivateKey: process.env.NEAR_PRIVATE_KEY || "",
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const client = result.createClient();
|
|
101
|
+
|
|
102
|
+
program
|
|
103
|
+
.name("bos")
|
|
104
|
+
.version("1.0.0")
|
|
105
|
+
.addHelpText("before", getHelpHeader(config, configPath));
|
|
106
|
+
|
|
107
|
+
program
|
|
108
|
+
.command("info")
|
|
109
|
+
.description("Show current configuration")
|
|
110
|
+
.action(async () => {
|
|
111
|
+
const result = await client.info({});
|
|
112
|
+
|
|
113
|
+
console.log();
|
|
114
|
+
console.log(colors.cyan(frames.top(52)));
|
|
115
|
+
console.log(` ${icons.config} ${gradients.cyber("CONFIGURATION")}`);
|
|
116
|
+
console.log(colors.cyan(frames.bottom(52)));
|
|
117
|
+
console.log();
|
|
118
|
+
|
|
119
|
+
console.log(
|
|
120
|
+
` ${colors.dim("Account")} ${colors.cyan(result.config.account)}`,
|
|
121
|
+
);
|
|
122
|
+
console.log(` ${colors.dim("Config ")} ${colors.dim(configPath)}`);
|
|
123
|
+
console.log();
|
|
124
|
+
|
|
125
|
+
const host = result.config.app.host;
|
|
126
|
+
console.log(colors.magenta(` ┌─ HOST ${"─".repeat(42)}┐`));
|
|
127
|
+
console.log(
|
|
128
|
+
` ${colors.magenta("│")} ${colors.dim("development")} ${colors.cyan(host.development)}`,
|
|
129
|
+
);
|
|
130
|
+
console.log(
|
|
131
|
+
` ${colors.magenta("│")} ${colors.dim("production")} ${colors.green(host.production)}`,
|
|
132
|
+
);
|
|
133
|
+
console.log(colors.magenta(` └${"─".repeat(49)}┘`));
|
|
134
|
+
|
|
135
|
+
for (const remoteName of result.remotes) {
|
|
136
|
+
const remote = result.config.app[remoteName];
|
|
137
|
+
if (!remote || !("name" in remote)) continue;
|
|
138
|
+
|
|
139
|
+
console.log();
|
|
140
|
+
const color = remoteName === "ui" ? colors.cyan : colors.blue;
|
|
141
|
+
console.log(
|
|
142
|
+
color(
|
|
143
|
+
` ┌─ ${remoteName.toUpperCase()} ${"─".repeat(46 - remoteName.length)}┐`,
|
|
144
|
+
),
|
|
145
|
+
);
|
|
146
|
+
console.log(
|
|
147
|
+
` ${color("│")} ${colors.dim("development")} ${colors.cyan(remote.development)}`,
|
|
148
|
+
);
|
|
149
|
+
console.log(
|
|
150
|
+
` ${color("│")} ${colors.dim("production")} ${colors.green(remote.production)}`,
|
|
151
|
+
);
|
|
152
|
+
if ("ssr" in remote && remote.ssr) {
|
|
153
|
+
console.log(
|
|
154
|
+
` ${color("│")} ${colors.dim("ssr")} ${colors.purple(remote.ssr as string)}`,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
console.log(color(` └${"─".repeat(49)}┘`));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.log();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
program
|
|
164
|
+
.command("status")
|
|
165
|
+
.description("Check remote health")
|
|
166
|
+
.option(
|
|
167
|
+
"-e, --env <env>",
|
|
168
|
+
"Environment (development | production)",
|
|
169
|
+
"development",
|
|
170
|
+
)
|
|
171
|
+
.action(async (options: { env: string }) => {
|
|
172
|
+
const result = await client.status({
|
|
173
|
+
env: options.env as "development" | "production",
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
console.log();
|
|
177
|
+
console.log(colors.cyan(frames.top(48)));
|
|
178
|
+
console.log(` ${icons.scan} ${gradients.cyber("ENDPOINT STATUS")}`);
|
|
179
|
+
console.log(colors.cyan(frames.bottom(48)));
|
|
180
|
+
console.log();
|
|
181
|
+
|
|
182
|
+
for (const endpoint of result.endpoints) {
|
|
183
|
+
const status = endpoint.healthy
|
|
184
|
+
? colors.green(`${icons.ok} healthy`)
|
|
185
|
+
: colors.error(`${icons.err} unhealthy`);
|
|
186
|
+
const latency = endpoint.latency
|
|
187
|
+
? colors.dim(` (${endpoint.latency}ms)`)
|
|
188
|
+
: "";
|
|
189
|
+
console.log(` ${endpoint.name}: ${status}${latency}`);
|
|
190
|
+
console.log(colors.dim(` ${endpoint.url}`));
|
|
191
|
+
}
|
|
192
|
+
console.log();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
program
|
|
196
|
+
.command("dev")
|
|
197
|
+
.description(`Start development (${packages.join(", ")})`)
|
|
198
|
+
.option("--host <mode>", "Host mode: local (default) | remote", "local")
|
|
199
|
+
.option("--ui <mode>", "UI mode: local (default) | remote", "local")
|
|
200
|
+
.option("--api <mode>", "API mode: local (default) | remote", "local")
|
|
201
|
+
.option("--proxy", "Proxy API requests to production")
|
|
202
|
+
.option("-p, --port <port>", "Host port (default: from config)")
|
|
203
|
+
.option("--no-interactive", "Disable interactive UI (streaming logs)")
|
|
204
|
+
.option("--force", "Invalidate config cache and re-fetch BOS configs")
|
|
205
|
+
.action(async (options) => {
|
|
206
|
+
const result = await client.dev({
|
|
207
|
+
host: options.host as "local" | "remote",
|
|
208
|
+
ui: options.ui as "local" | "remote",
|
|
209
|
+
api: options.api as "local" | "remote",
|
|
210
|
+
proxy: options.proxy || false,
|
|
211
|
+
port: options.port ? parseInt(options.port, 10) : undefined,
|
|
212
|
+
interactive: options.interactive,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
if (result.status === "error") {
|
|
216
|
+
console.error(colors.error(`${icons.err} ${result.description}`));
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
program
|
|
222
|
+
.command("start")
|
|
223
|
+
.description(
|
|
224
|
+
"Start with production modules (all remotes from production URLs)",
|
|
225
|
+
)
|
|
226
|
+
.option("-p, --port <port>", "Host port (default: 3000)")
|
|
227
|
+
.option(
|
|
228
|
+
"--account <account>",
|
|
229
|
+
"NEAR account to fetch config from social.near",
|
|
230
|
+
)
|
|
231
|
+
.option("--domain <domain>", "Gateway domain for config lookup")
|
|
232
|
+
.option("--no-interactive", "Disable interactive UI (streaming logs)")
|
|
233
|
+
.option("--force", "Invalidate config cache and re-fetch BOS configs")
|
|
234
|
+
.action(async (options) => {
|
|
235
|
+
const result = await client.start({
|
|
236
|
+
port: options.port ? parseInt(options.port, 10) : undefined,
|
|
237
|
+
account: options.account,
|
|
238
|
+
domain: options.domain,
|
|
239
|
+
interactive: options.interactive,
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
if (result.status === "error") {
|
|
243
|
+
console.error(colors.error(`${icons.err} Failed to start`));
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
program
|
|
249
|
+
.command("serve")
|
|
250
|
+
.description("Run CLI as HTTP server (exposes /api)")
|
|
251
|
+
.option("-p, --port <port>", "Port to run on", "4000")
|
|
252
|
+
.action(async (options) => {
|
|
253
|
+
const port = parseInt(options.port, 10);
|
|
254
|
+
|
|
255
|
+
const { Hono } = await import("hono");
|
|
256
|
+
const { cors } = await import("hono/cors");
|
|
257
|
+
const { RPCHandler } = await import("@orpc/server/fetch");
|
|
258
|
+
const { OpenAPIHandler } = await import("@orpc/openapi/fetch");
|
|
259
|
+
const { OpenAPIReferencePlugin } = await import("@orpc/openapi/plugins");
|
|
260
|
+
const { ZodToJsonSchemaConverter } = await import("@orpc/zod/zod4");
|
|
261
|
+
const { onError } = await import("every-plugin/orpc");
|
|
262
|
+
const { formatORPCError } = await import("every-plugin/errors");
|
|
263
|
+
|
|
264
|
+
const app = new Hono();
|
|
265
|
+
|
|
266
|
+
app.use("/*", cors({ origin: "*", credentials: true }));
|
|
267
|
+
|
|
268
|
+
const rpcHandler = new RPCHandler(result.router, {
|
|
269
|
+
interceptors: [onError((error: unknown) => formatORPCError(error))],
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const apiHandler = new OpenAPIHandler(result.router, {
|
|
273
|
+
plugins: [
|
|
274
|
+
new OpenAPIReferencePlugin({
|
|
275
|
+
schemaConverters: [new ZodToJsonSchemaConverter()],
|
|
276
|
+
specGenerateOptions: {
|
|
277
|
+
info: { title: "everything-dev api", version: "1.0.0" },
|
|
278
|
+
servers: [{ url: `http://localhost:${port}/api` }],
|
|
279
|
+
},
|
|
280
|
+
}),
|
|
281
|
+
],
|
|
282
|
+
interceptors: [onError((error: unknown) => formatORPCError(error))],
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
app.get("/", (c) =>
|
|
286
|
+
c.json({
|
|
287
|
+
ok: true,
|
|
288
|
+
plugin: "everything-dev",
|
|
289
|
+
status: "ready",
|
|
290
|
+
endpoints: {
|
|
291
|
+
health: "/",
|
|
292
|
+
docs: "/api",
|
|
293
|
+
rpc: "/api/rpc",
|
|
294
|
+
},
|
|
295
|
+
}),
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
app.all("/api/rpc/*", async (c) => {
|
|
299
|
+
const rpcResult = await rpcHandler.handle(c.req.raw, {
|
|
300
|
+
prefix: "/api/rpc",
|
|
301
|
+
context: {},
|
|
302
|
+
});
|
|
303
|
+
return rpcResult.response
|
|
304
|
+
? new Response(rpcResult.response.body, rpcResult.response)
|
|
305
|
+
: c.text("Not Found", 404);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
app.all("/api", async (c) => {
|
|
309
|
+
const apiResult = await apiHandler.handle(c.req.raw, {
|
|
310
|
+
prefix: "/api",
|
|
311
|
+
context: {},
|
|
312
|
+
});
|
|
313
|
+
return apiResult.response
|
|
314
|
+
? new Response(apiResult.response.body, apiResult.response)
|
|
315
|
+
: c.text("Not Found", 404);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
app.all("/api/*", async (c) => {
|
|
319
|
+
const apiResult = await apiHandler.handle(c.req.raw, {
|
|
320
|
+
prefix: "/api",
|
|
321
|
+
context: {},
|
|
322
|
+
});
|
|
323
|
+
return apiResult.response
|
|
324
|
+
? new Response(apiResult.response.body, apiResult.response)
|
|
325
|
+
: c.text("Not Found", 404);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
console.log();
|
|
329
|
+
console.log(colors.cyan(frames.top(48)));
|
|
330
|
+
console.log(` ${icons.run} ${gradients.cyber("CLI SERVER")}`);
|
|
331
|
+
console.log(colors.cyan(frames.bottom(48)));
|
|
332
|
+
console.log();
|
|
333
|
+
console.log(
|
|
334
|
+
` ${colors.dim("URL:")} ${colors.white(`http://localhost:${port}`)}`,
|
|
335
|
+
);
|
|
336
|
+
console.log(
|
|
337
|
+
` ${colors.dim("RPC:")} ${colors.white(`http://localhost:${port}/api/rpc`)}`,
|
|
338
|
+
);
|
|
339
|
+
console.log(
|
|
340
|
+
` ${colors.dim("Docs:")} ${colors.white(`http://localhost:${port}/api`)}`,
|
|
341
|
+
);
|
|
342
|
+
console.log();
|
|
343
|
+
|
|
344
|
+
const server = Bun.serve({
|
|
345
|
+
port,
|
|
346
|
+
fetch: app.fetch,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
const shutdown = () => {
|
|
350
|
+
console.log();
|
|
351
|
+
console.log(colors.dim(" Shutting down..."));
|
|
352
|
+
server.stop();
|
|
353
|
+
process.exit(0);
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
process.on("SIGINT", shutdown);
|
|
357
|
+
process.on("SIGTERM", shutdown);
|
|
358
|
+
|
|
359
|
+
await new Promise(() => {});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
program
|
|
363
|
+
.command("build")
|
|
364
|
+
.description(`Build packages locally (${packages.join(", ")})`)
|
|
365
|
+
.argument(
|
|
366
|
+
"[packages]",
|
|
367
|
+
"Packages to build (comma-separated: host,ui,api)",
|
|
368
|
+
"all",
|
|
369
|
+
)
|
|
370
|
+
.option("--force", "Force rebuild")
|
|
371
|
+
.action(async (pkgs: string, options) => {
|
|
372
|
+
console.log();
|
|
373
|
+
console.log(` ${icons.pkg} Building...`);
|
|
374
|
+
|
|
375
|
+
const result = await client.build({
|
|
376
|
+
packages: pkgs,
|
|
377
|
+
force: options.force || false,
|
|
378
|
+
deploy: false,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
if (result.status === "error") {
|
|
382
|
+
console.error(colors.error(`${icons.err} Build failed`));
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
console.log();
|
|
387
|
+
console.log(
|
|
388
|
+
colors.green(`${icons.ok} Built: ${result.built.join(", ")}`),
|
|
389
|
+
);
|
|
390
|
+
console.log();
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
program
|
|
394
|
+
.command("publish")
|
|
395
|
+
.description("Build, deploy, and publish to Near Social (full release)")
|
|
396
|
+
.argument(
|
|
397
|
+
"[packages]",
|
|
398
|
+
"Packages to build/deploy (comma-separated: host,ui,api)",
|
|
399
|
+
"all",
|
|
400
|
+
)
|
|
401
|
+
.option("--force", "Force rebuild")
|
|
402
|
+
.option("--network <network>", "Network: mainnet | testnet", "mainnet")
|
|
403
|
+
.option("--path <path>", "Near Social relative path", "bos.config.json")
|
|
404
|
+
.option("--dry-run", "Show what would be published without sending")
|
|
405
|
+
.addHelpText(
|
|
406
|
+
"after",
|
|
407
|
+
`
|
|
344
408
|
Release Workflow:
|
|
345
409
|
1. Build packages (bun run build)
|
|
346
410
|
2. Deploy to Zephyr Cloud (updates production URLs)
|
|
@@ -349,877 +413,1106 @@ Release Workflow:
|
|
|
349
413
|
Zephyr Configuration:
|
|
350
414
|
Set ZE_SERVER_TOKEN and ZE_USER_EMAIL in .env.bos for CI/CD deployment.
|
|
351
415
|
Docs: https://docs.zephyr-cloud.io/features/ci-cd-server-token
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
});
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
416
|
+
`,
|
|
417
|
+
)
|
|
418
|
+
.action(async (pkgs: string, options) => {
|
|
419
|
+
console.log();
|
|
420
|
+
console.log(` ${icons.pkg} Starting release workflow...`);
|
|
421
|
+
console.log(colors.dim(` Account: ${config?.account}`));
|
|
422
|
+
console.log(colors.dim(` Network: ${options.network}`));
|
|
423
|
+
console.log();
|
|
424
|
+
|
|
425
|
+
if (!options.dryRun) {
|
|
426
|
+
console.log(` ${icons.pkg} Step 1/3: Building & deploying...`);
|
|
427
|
+
|
|
428
|
+
const buildResult = await client.build({
|
|
429
|
+
packages: pkgs,
|
|
430
|
+
force: options.force || false,
|
|
431
|
+
deploy: true,
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
if (buildResult.status === "error") {
|
|
435
|
+
console.error(colors.error(`${icons.err} Build/deploy failed`));
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
console.log(
|
|
440
|
+
colors.green(
|
|
441
|
+
` ${icons.ok} Built & deployed: ${buildResult.built.join(", ")}`,
|
|
442
|
+
),
|
|
443
|
+
);
|
|
444
|
+
console.log();
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
console.log(
|
|
448
|
+
` ${icons.pkg} ${options.dryRun ? "Dry run:" : "Step 2/3:"} Publishing to Near Social...`,
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
if (options.dryRun) {
|
|
452
|
+
console.log(
|
|
453
|
+
colors.cyan(
|
|
454
|
+
` ${icons.scan} Dry run mode - no transaction will be sent`,
|
|
455
|
+
),
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const result = await client.publish({
|
|
460
|
+
network: options.network as "mainnet" | "testnet",
|
|
461
|
+
path: options.path,
|
|
462
|
+
dryRun: options.dryRun || false,
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
if (result.status === "error") {
|
|
466
|
+
console.error(
|
|
467
|
+
colors.error(
|
|
468
|
+
`${icons.err} Publish failed: ${result.error || "Unknown error"}`,
|
|
469
|
+
),
|
|
470
|
+
);
|
|
471
|
+
process.exit(1);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (result.status === "dry-run") {
|
|
475
|
+
console.log();
|
|
476
|
+
console.log(colors.cyan(`${icons.ok} Dry run complete`));
|
|
477
|
+
console.log(
|
|
478
|
+
` ${colors.dim("Would publish to:")} ${result.registryUrl}`,
|
|
479
|
+
);
|
|
480
|
+
console.log();
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
console.log(colors.green(` ${icons.ok} Published to Near Social`));
|
|
485
|
+
console.log(` ${colors.dim("TX:")} ${result.txHash}`);
|
|
486
|
+
console.log(` ${colors.dim("URL:")} ${result.registryUrl}`);
|
|
487
|
+
console.log();
|
|
488
|
+
console.log(colors.green(`${icons.ok} Release complete!`));
|
|
489
|
+
console.log();
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
program
|
|
493
|
+
.command("clean")
|
|
494
|
+
.description("Clean build artifacts")
|
|
495
|
+
.action(async () => {
|
|
496
|
+
const result = await client.clean({});
|
|
497
|
+
|
|
498
|
+
console.log();
|
|
499
|
+
console.log(
|
|
500
|
+
colors.green(`${icons.ok} Cleaned: ${result.removed.join(", ")}`),
|
|
501
|
+
);
|
|
502
|
+
console.log();
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
const create = program
|
|
506
|
+
.command("create")
|
|
507
|
+
.description("Scaffold new projects and remotes");
|
|
508
|
+
|
|
509
|
+
create
|
|
510
|
+
.command("project")
|
|
511
|
+
.description("Create a new BOS project")
|
|
512
|
+
.argument("<name>", "Project name")
|
|
513
|
+
.option(
|
|
514
|
+
"-a, --account <account>",
|
|
515
|
+
"NEAR mainnet account (e.g., myname.near)",
|
|
516
|
+
)
|
|
517
|
+
.option("--testnet <account>", "NEAR testnet account (optional)")
|
|
518
|
+
.option(
|
|
519
|
+
"-t, --template <url>",
|
|
520
|
+
"Template BOS URL (default: bos://every.near/everything.dev)",
|
|
521
|
+
)
|
|
522
|
+
.option("--include-host", "Include host package locally")
|
|
523
|
+
.option("--include-gateway", "Include gateway package locally")
|
|
524
|
+
.action(
|
|
525
|
+
async (
|
|
526
|
+
name: string,
|
|
527
|
+
options: {
|
|
528
|
+
account?: string;
|
|
529
|
+
testnet?: string;
|
|
530
|
+
template?: string;
|
|
531
|
+
includeHost?: boolean;
|
|
532
|
+
includeGateway?: boolean;
|
|
533
|
+
},
|
|
534
|
+
) => {
|
|
535
|
+
const result = await client.create({
|
|
536
|
+
type: "project",
|
|
537
|
+
name,
|
|
538
|
+
account: options.account,
|
|
539
|
+
testnet: options.testnet,
|
|
540
|
+
template: options.template,
|
|
541
|
+
includeHost: options.includeHost,
|
|
542
|
+
includeGateway: options.includeGateway,
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
if (result.status === "error") {
|
|
546
|
+
console.error(colors.error(`${icons.err} Failed to create project`));
|
|
547
|
+
if (result.error) {
|
|
548
|
+
console.error(colors.dim(` ${result.error}`));
|
|
549
|
+
}
|
|
550
|
+
process.exit(1);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
console.log();
|
|
554
|
+
console.log(
|
|
555
|
+
colors.green(`${icons.ok} Created project at ${result.path}`),
|
|
556
|
+
);
|
|
557
|
+
console.log();
|
|
558
|
+
console.log(colors.dim(" Next steps:"));
|
|
559
|
+
console.log(` ${colors.dim("1.")} cd ${result.path}`);
|
|
560
|
+
console.log(` ${colors.dim("2.")} bun install`);
|
|
561
|
+
console.log(` ${colors.dim("3.")} cp .env.example .env`);
|
|
562
|
+
console.log(` ${colors.dim("4.")} bos dev`);
|
|
563
|
+
console.log();
|
|
564
|
+
},
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
create
|
|
568
|
+
.command("ui")
|
|
569
|
+
.description("Scaffold a new UI remote")
|
|
570
|
+
.option("-t, --template <url>", "Template URL")
|
|
571
|
+
.action(async (options: { template?: string }) => {
|
|
572
|
+
const result = await client.create({
|
|
573
|
+
type: "ui",
|
|
574
|
+
template: options.template,
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
if (result.status === "created") {
|
|
578
|
+
console.log(colors.green(`${icons.ok} Created UI at ${result.path}`));
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
create
|
|
583
|
+
.command("api")
|
|
584
|
+
.description("Scaffold a new API remote")
|
|
585
|
+
.option("-t, --template <url>", "Template URL")
|
|
586
|
+
.action(async (options: { template?: string }) => {
|
|
587
|
+
const result = await client.create({
|
|
588
|
+
type: "api",
|
|
589
|
+
template: options.template,
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
if (result.status === "created") {
|
|
593
|
+
console.log(colors.green(`${icons.ok} Created API at ${result.path}`));
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
create
|
|
598
|
+
.command("cli")
|
|
599
|
+
.description("Scaffold a new CLI")
|
|
600
|
+
.option("-t, --template <url>", "Template URL")
|
|
601
|
+
.action(async (options: { template?: string }) => {
|
|
602
|
+
const result = await client.create({
|
|
603
|
+
type: "cli",
|
|
604
|
+
template: options.template,
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
if (result.status === "created") {
|
|
608
|
+
console.log(colors.green(`${icons.ok} Created CLI at ${result.path}`));
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
create
|
|
613
|
+
.command("gateway")
|
|
614
|
+
.description("Scaffold a new gateway")
|
|
615
|
+
.option("-t, --template <url>", "Template URL")
|
|
616
|
+
.action(async (options: { template?: string }) => {
|
|
617
|
+
const result = await client.create({
|
|
618
|
+
type: "gateway",
|
|
619
|
+
template: options.template,
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
if (result.status === "created") {
|
|
623
|
+
console.log(
|
|
624
|
+
colors.green(`${icons.ok} Created gateway at ${result.path}`),
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
const gateway = program
|
|
630
|
+
.command("gateway")
|
|
631
|
+
.description("Manage gateway deployment");
|
|
632
|
+
|
|
633
|
+
gateway
|
|
634
|
+
.command("dev")
|
|
635
|
+
.description("Run gateway locally (wrangler dev)")
|
|
636
|
+
.action(async () => {
|
|
637
|
+
console.log();
|
|
638
|
+
console.log(` ${icons.run} Starting gateway dev server...`);
|
|
639
|
+
|
|
640
|
+
const result = await client.gatewayDev({});
|
|
641
|
+
|
|
642
|
+
if (result.status === "error") {
|
|
643
|
+
console.error(
|
|
644
|
+
colors.error(
|
|
645
|
+
`${icons.err} ${result.error || "Failed to start gateway"}`,
|
|
646
|
+
),
|
|
647
|
+
);
|
|
648
|
+
process.exit(1);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
console.log();
|
|
652
|
+
console.log(colors.green(`${icons.ok} Gateway running at ${result.url}`));
|
|
653
|
+
console.log();
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
gateway
|
|
657
|
+
.command("deploy")
|
|
658
|
+
.description("Deploy gateway to Cloudflare")
|
|
659
|
+
.option("-e, --env <env>", "Environment (production | staging)")
|
|
660
|
+
.action(async (options: { env?: string }) => {
|
|
661
|
+
console.log();
|
|
662
|
+
console.log(` ${icons.pkg} Deploying gateway...`);
|
|
663
|
+
if (options.env) {
|
|
664
|
+
console.log(colors.dim(` Environment: ${options.env}`));
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const result = await client.gatewayDeploy({
|
|
668
|
+
env: options.env as "production" | "staging" | undefined,
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
if (result.status === "error") {
|
|
672
|
+
console.error(
|
|
673
|
+
colors.error(`${icons.err} ${result.error || "Deploy failed"}`),
|
|
674
|
+
);
|
|
675
|
+
process.exit(1);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
console.log();
|
|
679
|
+
console.log(colors.green(`${icons.ok} Deployed!`));
|
|
680
|
+
console.log(` ${colors.dim("URL:")} ${result.url}`);
|
|
681
|
+
console.log();
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
gateway
|
|
685
|
+
.command("sync")
|
|
686
|
+
.description("Sync wrangler.toml vars from bos.config.json")
|
|
687
|
+
.action(async () => {
|
|
688
|
+
console.log();
|
|
689
|
+
console.log(` ${icons.pkg} Syncing gateway config...`);
|
|
690
|
+
|
|
691
|
+
const result = await client.gatewaySync({});
|
|
692
|
+
|
|
693
|
+
if (result.status === "error") {
|
|
694
|
+
console.error(
|
|
695
|
+
colors.error(`${icons.err} ${result.error || "Sync failed"}`),
|
|
696
|
+
);
|
|
697
|
+
process.exit(1);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
console.log();
|
|
701
|
+
console.log(colors.green(`${icons.ok} Synced!`));
|
|
702
|
+
console.log(` ${colors.dim("GATEWAY_DOMAIN:")} ${result.gatewayDomain}`);
|
|
703
|
+
console.log(
|
|
704
|
+
` ${colors.dim("GATEWAY_ACCOUNT:")} ${result.gatewayAccount}`,
|
|
705
|
+
);
|
|
706
|
+
console.log();
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
program
|
|
710
|
+
.command("register")
|
|
711
|
+
.description("Register a new tenant on the gateway")
|
|
712
|
+
.argument("<name>", `Account name (will create <name>.${config?.account})`)
|
|
713
|
+
.option("--network <network>", "Network: mainnet | testnet", "mainnet")
|
|
714
|
+
.action(async (name: string, options: { network: string }) => {
|
|
715
|
+
console.log();
|
|
716
|
+
console.log(` ${icons.pkg} Registering ${name}...`);
|
|
717
|
+
|
|
718
|
+
const result = await client.register({
|
|
719
|
+
name,
|
|
720
|
+
network: options.network as "mainnet" | "testnet",
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
if (result.status === "error") {
|
|
724
|
+
console.error(
|
|
725
|
+
colors.error(
|
|
726
|
+
`${icons.err} Registration failed: ${result.error || "Unknown error"}`,
|
|
727
|
+
),
|
|
728
|
+
);
|
|
729
|
+
process.exit(1);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
console.log();
|
|
733
|
+
console.log(colors.green(`${icons.ok} Registered!`));
|
|
734
|
+
console.log(` ${colors.dim("Account:")} ${result.account}`);
|
|
735
|
+
if (result.novaGroup) {
|
|
736
|
+
console.log(` ${colors.dim("NOVA Group:")} ${result.novaGroup}`);
|
|
737
|
+
}
|
|
738
|
+
console.log();
|
|
739
|
+
console.log(colors.dim(" Next steps:"));
|
|
740
|
+
console.log(
|
|
741
|
+
` ${colors.dim("1.")} Update bos.config.json with account: "${result.account}"`,
|
|
742
|
+
);
|
|
743
|
+
console.log(` ${colors.dim("2.")} bos secrets sync --env .env.local`);
|
|
744
|
+
console.log(` ${colors.dim("3.")} bos publish`);
|
|
745
|
+
console.log();
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
const secrets = program
|
|
749
|
+
.command("secrets")
|
|
750
|
+
.description("Manage encrypted secrets via NOVA");
|
|
751
|
+
|
|
752
|
+
secrets
|
|
753
|
+
.command("sync")
|
|
754
|
+
.description("Sync secrets from .env file to NOVA")
|
|
755
|
+
.option("--env <path>", "Path to .env file", ".env.local")
|
|
756
|
+
.action(async (options: { env: string }) => {
|
|
757
|
+
console.log();
|
|
758
|
+
console.log(` ${icons.pkg} Syncing secrets from ${options.env}...`);
|
|
759
|
+
|
|
760
|
+
const result = await client.secretsSync({
|
|
761
|
+
envPath: options.env,
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
if (result.status === "error") {
|
|
765
|
+
console.error(
|
|
766
|
+
colors.error(
|
|
767
|
+
`${icons.err} Sync failed: ${result.error || "Unknown error"}`,
|
|
768
|
+
),
|
|
769
|
+
);
|
|
770
|
+
process.exit(1);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
console.log();
|
|
774
|
+
console.log(colors.green(`${icons.ok} Synced ${result.count} secrets`));
|
|
775
|
+
if (result.cid) {
|
|
776
|
+
console.log(` ${colors.dim("CID:")} ${result.cid}`);
|
|
777
|
+
}
|
|
778
|
+
console.log();
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
secrets
|
|
782
|
+
.command("set")
|
|
783
|
+
.description("Set a single secret")
|
|
784
|
+
.argument("<key=value>", "Secret key=value pair")
|
|
785
|
+
.action(async (keyValue: string) => {
|
|
786
|
+
const eqIndex = keyValue.indexOf("=");
|
|
787
|
+
if (eqIndex === -1) {
|
|
788
|
+
console.error(
|
|
789
|
+
colors.error(
|
|
790
|
+
`${icons.err} Invalid format. Use: bos secrets set KEY=value`,
|
|
791
|
+
),
|
|
792
|
+
);
|
|
793
|
+
process.exit(1);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const key = keyValue.slice(0, eqIndex);
|
|
797
|
+
const value = keyValue.slice(eqIndex + 1);
|
|
798
|
+
|
|
799
|
+
console.log();
|
|
800
|
+
console.log(` ${icons.pkg} Setting secret ${key}...`);
|
|
801
|
+
|
|
802
|
+
const result = await client.secretsSet({ key, value });
|
|
803
|
+
|
|
804
|
+
if (result.status === "error") {
|
|
805
|
+
console.error(
|
|
806
|
+
colors.error(
|
|
807
|
+
`${icons.err} Failed: ${result.error || "Unknown error"}`,
|
|
808
|
+
),
|
|
809
|
+
);
|
|
810
|
+
process.exit(1);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
console.log();
|
|
814
|
+
console.log(colors.green(`${icons.ok} Secret set`));
|
|
815
|
+
if (result.cid) {
|
|
816
|
+
console.log(` ${colors.dim("CID:")} ${result.cid}`);
|
|
817
|
+
}
|
|
818
|
+
console.log();
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
secrets
|
|
822
|
+
.command("list")
|
|
823
|
+
.description("List secret keys (not values)")
|
|
824
|
+
.action(async () => {
|
|
825
|
+
const result = await client.secretsList({});
|
|
826
|
+
|
|
827
|
+
if (result.status === "error") {
|
|
828
|
+
console.error(
|
|
829
|
+
colors.error(
|
|
830
|
+
`${icons.err} Failed: ${result.error || "Unknown error"}`,
|
|
831
|
+
),
|
|
832
|
+
);
|
|
833
|
+
process.exit(1);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
console.log();
|
|
837
|
+
console.log(colors.cyan(frames.top(48)));
|
|
838
|
+
console.log(` ${icons.config} ${gradients.cyber("SECRETS")}`);
|
|
839
|
+
console.log(colors.cyan(frames.bottom(48)));
|
|
840
|
+
console.log();
|
|
841
|
+
|
|
842
|
+
if (result.keys.length === 0) {
|
|
843
|
+
console.log(colors.dim(" No secrets configured"));
|
|
844
|
+
} else {
|
|
845
|
+
for (const key of result.keys) {
|
|
846
|
+
console.log(` ${colors.dim("•")} ${key}`);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
console.log();
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
secrets
|
|
853
|
+
.command("delete")
|
|
854
|
+
.description("Delete a secret")
|
|
855
|
+
.argument("<key>", "Secret key to delete")
|
|
856
|
+
.action(async (key: string) => {
|
|
857
|
+
console.log();
|
|
858
|
+
console.log(` ${icons.pkg} Deleting secret ${key}...`);
|
|
859
|
+
|
|
860
|
+
const result = await client.secretsDelete({ key });
|
|
861
|
+
|
|
862
|
+
if (result.status === "error") {
|
|
863
|
+
console.error(
|
|
864
|
+
colors.error(
|
|
865
|
+
`${icons.err} Failed: ${result.error || "Unknown error"}`,
|
|
866
|
+
),
|
|
867
|
+
);
|
|
868
|
+
process.exit(1);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
console.log();
|
|
872
|
+
console.log(colors.green(`${icons.ok} Secret deleted`));
|
|
873
|
+
console.log();
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
program
|
|
877
|
+
.command("update")
|
|
878
|
+
.description(
|
|
879
|
+
"Update from published config (host prod, secrets, shared deps, UI files)",
|
|
880
|
+
)
|
|
881
|
+
.option(
|
|
882
|
+
"--account <account>",
|
|
883
|
+
"NEAR account to update from (default: from config)",
|
|
884
|
+
)
|
|
885
|
+
.option("--gateway <gateway>", "Gateway domain (default: from config)")
|
|
886
|
+
.option("--network <network>", "Network: mainnet | testnet", "mainnet")
|
|
887
|
+
.option("--force", "Force update even if versions match")
|
|
888
|
+
.action(
|
|
889
|
+
async (options: {
|
|
890
|
+
account?: string;
|
|
891
|
+
gateway?: string;
|
|
892
|
+
network?: string;
|
|
893
|
+
force?: boolean;
|
|
894
|
+
}) => {
|
|
895
|
+
console.log();
|
|
896
|
+
const gateway = config?.gateway as { production?: string } | undefined;
|
|
897
|
+
const gatewayDomain =
|
|
898
|
+
gateway?.production?.replace(/^https?:\/\//, "") || "everything.dev";
|
|
899
|
+
const source = `${options.account || config?.account || "every.near"}/${options.gateway || gatewayDomain}`;
|
|
900
|
+
|
|
901
|
+
const s = spinner();
|
|
902
|
+
s.start(`Updating from ${source}...`);
|
|
903
|
+
|
|
904
|
+
const result = await client.update({
|
|
905
|
+
account: options.account,
|
|
906
|
+
gateway: options.gateway,
|
|
907
|
+
network: (options.network as "mainnet" | "testnet") || "mainnet",
|
|
908
|
+
force: options.force || false,
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
if (result.status === "error") {
|
|
912
|
+
s.stop(
|
|
913
|
+
colors.error(
|
|
914
|
+
`${icons.err} Update failed: ${result.error || "Unknown error"}`,
|
|
915
|
+
),
|
|
916
|
+
);
|
|
917
|
+
process.exit(1);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
s.stop(colors.green(`${icons.ok} Updated from ${source}`));
|
|
921
|
+
|
|
922
|
+
console.log();
|
|
923
|
+
console.log(colors.cyan(frames.top(52)));
|
|
924
|
+
console.log(` ${icons.ok} ${gradients.cyber("UPDATED")}`);
|
|
925
|
+
console.log(colors.cyan(frames.bottom(52)));
|
|
926
|
+
console.log();
|
|
927
|
+
console.log(
|
|
928
|
+
` ${colors.dim("Source:")} ${colors.cyan(`${result.account}/${result.gateway}`)}`,
|
|
929
|
+
);
|
|
930
|
+
console.log(
|
|
931
|
+
` ${colors.dim("URL:")} ${colors.cyan(result.socialUrl)}`,
|
|
932
|
+
);
|
|
933
|
+
console.log(
|
|
934
|
+
` ${colors.dim("Host URL:")} ${colors.cyan(result.hostUrl)}`,
|
|
935
|
+
);
|
|
936
|
+
console.log();
|
|
937
|
+
|
|
938
|
+
if (result.catalogUpdated) {
|
|
939
|
+
console.log(
|
|
940
|
+
colors.green(` ${icons.ok} Updated root package.json catalog`),
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
if (result.packagesUpdated.length > 0) {
|
|
945
|
+
console.log(
|
|
946
|
+
colors.green(
|
|
947
|
+
` ${icons.ok} Updated packages: ${result.packagesUpdated.join(", ")}`,
|
|
948
|
+
),
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
if (result.filesSynced && result.filesSynced.length > 0) {
|
|
953
|
+
const totalFiles = result.filesSynced.reduce(
|
|
954
|
+
(sum, pkg) => sum + pkg.files.length,
|
|
955
|
+
0,
|
|
956
|
+
);
|
|
957
|
+
console.log(
|
|
958
|
+
colors.green(` ${icons.ok} Synced ${totalFiles} UI files`),
|
|
959
|
+
);
|
|
960
|
+
for (const pkg of result.filesSynced) {
|
|
961
|
+
console.log(
|
|
962
|
+
colors.dim(` ${pkg.package}: ${pkg.files.join(", ")}`),
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
if (
|
|
968
|
+
!result.catalogUpdated &&
|
|
969
|
+
result.packagesUpdated.length === 0 &&
|
|
970
|
+
(!result.filesSynced || result.filesSynced.length === 0)
|
|
971
|
+
) {
|
|
972
|
+
console.log(colors.dim(` ${icons.ok} Already up to date`));
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
console.log();
|
|
976
|
+
console.log(colors.dim(" Run 'bun install' to update lockfile"));
|
|
977
|
+
console.log();
|
|
978
|
+
},
|
|
979
|
+
);
|
|
980
|
+
|
|
981
|
+
const depsCmd = program
|
|
982
|
+
.command("deps")
|
|
983
|
+
.description("Manage shared dependencies");
|
|
984
|
+
|
|
985
|
+
depsCmd
|
|
986
|
+
.command("update")
|
|
987
|
+
.description(
|
|
988
|
+
"Interactive update of shared dependencies (bun update -i style)",
|
|
989
|
+
)
|
|
990
|
+
.argument("[category]", "Dependency category (ui | api)", "ui")
|
|
991
|
+
.action(async (category: string) => {
|
|
992
|
+
console.log();
|
|
993
|
+
console.log(` ${icons.pkg} Updating shared.${category} dependencies...`);
|
|
994
|
+
|
|
995
|
+
const result = await client.depsUpdate({
|
|
996
|
+
category: category as "ui" | "api",
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
if (result.status === "error") {
|
|
1000
|
+
console.error(
|
|
1001
|
+
colors.error(`${icons.err} ${result.error || "Update failed"}`),
|
|
1002
|
+
);
|
|
1003
|
+
process.exit(1);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
if (result.status === "cancelled") {
|
|
1007
|
+
console.log();
|
|
1008
|
+
console.log(colors.dim(" No updates selected"));
|
|
1009
|
+
console.log();
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
console.log();
|
|
1014
|
+
console.log(colors.cyan(frames.top(52)));
|
|
1015
|
+
console.log(` ${icons.ok} ${gradients.cyber("DEPENDENCIES UPDATED")}`);
|
|
1016
|
+
console.log(colors.cyan(frames.bottom(52)));
|
|
1017
|
+
console.log();
|
|
1018
|
+
|
|
1019
|
+
for (const { name, from, to } of result.updated) {
|
|
1020
|
+
console.log(` ${colors.dim("•")} ${colors.white(name)}`);
|
|
1021
|
+
console.log(` ${colors.dim(from)} → ${colors.green(to)}`);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
if (result.syncStatus === "synced") {
|
|
1025
|
+
console.log();
|
|
1026
|
+
console.log(
|
|
1027
|
+
colors.green(` ${icons.ok} Catalog synced & bun install complete`),
|
|
1028
|
+
);
|
|
1029
|
+
}
|
|
1030
|
+
console.log();
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
program
|
|
1034
|
+
.command("monitor")
|
|
1035
|
+
.description("Monitor system resources (ports, processes, memory)")
|
|
1036
|
+
.option("--json", "Output as JSON")
|
|
1037
|
+
.option("-w, --watch", "Watch mode with live updates")
|
|
1038
|
+
.option("-p, --ports <ports>", "Ports to monitor (comma-separated)")
|
|
1039
|
+
.action(async (options) => {
|
|
1040
|
+
const result = await client.monitor({
|
|
1041
|
+
json: options.json || false,
|
|
1042
|
+
watch: options.watch || false,
|
|
1043
|
+
ports: options.ports ? options.ports.split(",").map(Number) : undefined,
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1046
|
+
if (result.status === "error") {
|
|
1047
|
+
console.error(colors.error(`${icons.err} ${result.error}`));
|
|
1048
|
+
process.exit(1);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
if (result.status === "snapshot" && options.json) {
|
|
1052
|
+
console.log(JSON.stringify(result.snapshot, null, 2));
|
|
1053
|
+
}
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
program
|
|
1057
|
+
.command("kill")
|
|
1058
|
+
.description("Kill all tracked BOS processes")
|
|
1059
|
+
.option("--force", "Force kill with SIGKILL immediately")
|
|
1060
|
+
.action(async (options: { force?: boolean }) => {
|
|
1061
|
+
const result = await client.kill({ force: options.force ?? false });
|
|
1062
|
+
|
|
1063
|
+
console.log();
|
|
1064
|
+
if (result.status === "error") {
|
|
1065
|
+
console.error(
|
|
1066
|
+
colors.error(
|
|
1067
|
+
`${icons.err} ${result.error || "Failed to kill processes"}`,
|
|
1068
|
+
),
|
|
1069
|
+
);
|
|
1070
|
+
process.exit(1);
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
if (result.killed.length > 0) {
|
|
1074
|
+
console.log(
|
|
1075
|
+
colors.green(`${icons.ok} Killed ${result.killed.length} processes`),
|
|
1076
|
+
);
|
|
1077
|
+
for (const pid of result.killed) {
|
|
1078
|
+
console.log(colors.dim(` PID ${pid}`));
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
if (result.failed.length > 0) {
|
|
1082
|
+
console.log(
|
|
1083
|
+
colors.error(
|
|
1084
|
+
`${icons.err} Failed to kill ${result.failed.length} processes`,
|
|
1085
|
+
),
|
|
1086
|
+
);
|
|
1087
|
+
for (const pid of result.failed) {
|
|
1088
|
+
console.log(colors.dim(` PID ${pid}`));
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
if (result.killed.length === 0 && result.failed.length === 0) {
|
|
1092
|
+
console.log(colors.dim(" No tracked processes found"));
|
|
1093
|
+
}
|
|
1094
|
+
console.log();
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
program
|
|
1098
|
+
.command("ps")
|
|
1099
|
+
.description("List tracked BOS processes")
|
|
1100
|
+
.action(async () => {
|
|
1101
|
+
const result = await client.ps({});
|
|
1102
|
+
|
|
1103
|
+
console.log();
|
|
1104
|
+
console.log(colors.cyan(frames.top(52)));
|
|
1105
|
+
console.log(` ${icons.run} ${gradients.cyber("PROCESSES")}`);
|
|
1106
|
+
console.log(colors.cyan(frames.bottom(52)));
|
|
1107
|
+
console.log();
|
|
1108
|
+
|
|
1109
|
+
if (result.processes.length === 0) {
|
|
1110
|
+
console.log(colors.dim(" No tracked processes"));
|
|
1111
|
+
} else {
|
|
1112
|
+
for (const proc of result.processes) {
|
|
1113
|
+
const age = Math.round((Date.now() - proc.startedAt) / 1000);
|
|
1114
|
+
console.log(
|
|
1115
|
+
` ${colors.white(proc.name)} ${colors.dim(`(PID ${proc.pid})`)}`,
|
|
1116
|
+
);
|
|
1117
|
+
console.log(
|
|
1118
|
+
` ${colors.dim("Port:")} ${colors.cyan(String(proc.port))}`,
|
|
1119
|
+
);
|
|
1120
|
+
console.log(` ${colors.dim("Age:")} ${colors.cyan(`${age}s`)}`);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
console.log();
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
const docker = program
|
|
1127
|
+
.command("docker")
|
|
1128
|
+
.description("Docker container management");
|
|
1129
|
+
|
|
1130
|
+
docker
|
|
1131
|
+
.command("build")
|
|
1132
|
+
.description("Build Docker image")
|
|
1133
|
+
.option(
|
|
1134
|
+
"-t, --target <target>",
|
|
1135
|
+
"Build target: production | development",
|
|
1136
|
+
"production",
|
|
1137
|
+
)
|
|
1138
|
+
.option("--tag <tag>", "Custom image tag")
|
|
1139
|
+
.option("--no-cache", "Build without cache")
|
|
1140
|
+
.action(
|
|
1141
|
+
async (options: { target: string; tag?: string; noCache?: boolean }) => {
|
|
1142
|
+
console.log();
|
|
1143
|
+
console.log(
|
|
1144
|
+
` ${icons.pkg} Building Docker image (${options.target})...`,
|
|
1145
|
+
);
|
|
1146
|
+
|
|
1147
|
+
const result = await client.dockerBuild({
|
|
1148
|
+
target: options.target as "production" | "development",
|
|
1149
|
+
tag: options.tag,
|
|
1150
|
+
noCache: options.noCache ?? false,
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
if (result.status === "error") {
|
|
1154
|
+
console.error(
|
|
1155
|
+
colors.error(`${icons.err} ${result.error || "Build failed"}`),
|
|
1156
|
+
);
|
|
1157
|
+
process.exit(1);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
console.log();
|
|
1161
|
+
console.log(colors.green(`${icons.ok} Built ${result.tag}`));
|
|
1162
|
+
console.log();
|
|
1163
|
+
},
|
|
1164
|
+
);
|
|
1165
|
+
|
|
1166
|
+
docker
|
|
1167
|
+
.command("run")
|
|
1168
|
+
.description("Run Docker container")
|
|
1169
|
+
.option(
|
|
1170
|
+
"-t, --target <target>",
|
|
1171
|
+
"Image target: production | development",
|
|
1172
|
+
"production",
|
|
1173
|
+
)
|
|
1174
|
+
.option("-m, --mode <mode>", "Run mode: start | serve | dev", "start")
|
|
1175
|
+
.option("-p, --port <port>", "Port to expose")
|
|
1176
|
+
.option("-d, --detach", "Run in background")
|
|
1177
|
+
.option("-e, --env <env...>", "Environment variables (KEY=value)")
|
|
1178
|
+
.action(
|
|
1179
|
+
async (options: {
|
|
1180
|
+
target: string;
|
|
1181
|
+
mode: string;
|
|
1182
|
+
port?: string;
|
|
1183
|
+
detach?: boolean;
|
|
1184
|
+
env?: string[];
|
|
1185
|
+
}) => {
|
|
1186
|
+
console.log();
|
|
1187
|
+
console.log(` ${icons.run} Starting Docker container...`);
|
|
1188
|
+
|
|
1189
|
+
const envVars: Record<string, string> = {};
|
|
1190
|
+
if (options.env) {
|
|
1191
|
+
for (const e of options.env) {
|
|
1192
|
+
const [key, ...rest] = e.split("=");
|
|
1193
|
+
if (key) envVars[key] = rest.join("=");
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
const result = await client.dockerRun({
|
|
1198
|
+
target: options.target as "production" | "development",
|
|
1199
|
+
mode: options.mode as "start" | "serve" | "dev",
|
|
1200
|
+
port: options.port ? parseInt(options.port, 10) : undefined,
|
|
1201
|
+
detach: options.detach ?? false,
|
|
1202
|
+
env: Object.keys(envVars).length > 0 ? envVars : undefined,
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
if (result.status === "error") {
|
|
1206
|
+
console.error(
|
|
1207
|
+
colors.error(`${icons.err} ${result.error || "Run failed"}`),
|
|
1208
|
+
);
|
|
1209
|
+
process.exit(1);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
console.log();
|
|
1213
|
+
console.log(colors.green(`${icons.ok} Container running`));
|
|
1214
|
+
console.log(` ${colors.dim("URL:")} ${colors.cyan(result.url)}`);
|
|
1215
|
+
if (result.containerId !== "attached") {
|
|
1216
|
+
console.log(
|
|
1217
|
+
` ${colors.dim("Container:")} ${colors.cyan(result.containerId)}`,
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1220
|
+
console.log();
|
|
1221
|
+
},
|
|
1222
|
+
);
|
|
1223
|
+
|
|
1224
|
+
docker
|
|
1225
|
+
.command("stop")
|
|
1226
|
+
.description("Stop Docker container(s)")
|
|
1227
|
+
.option("-c, --container <id>", "Container ID to stop")
|
|
1228
|
+
.option("-a, --all", "Stop all containers for this app")
|
|
1229
|
+
.action(async (options: { container?: string; all?: boolean }) => {
|
|
1230
|
+
console.log();
|
|
1231
|
+
console.log(` ${icons.pkg} Stopping containers...`);
|
|
1232
|
+
|
|
1233
|
+
const result = await client.dockerStop({
|
|
1234
|
+
containerId: options.container,
|
|
1235
|
+
all: options.all ?? false,
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
if (result.status === "error") {
|
|
1239
|
+
console.error(
|
|
1240
|
+
colors.error(`${icons.err} ${result.error || "Stop failed"}`),
|
|
1241
|
+
);
|
|
1242
|
+
process.exit(1);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
console.log();
|
|
1246
|
+
if (result.stopped.length > 0) {
|
|
1247
|
+
console.log(
|
|
1248
|
+
colors.green(
|
|
1249
|
+
`${icons.ok} Stopped ${result.stopped.length} container(s)`,
|
|
1250
|
+
),
|
|
1251
|
+
);
|
|
1252
|
+
for (const id of result.stopped) {
|
|
1253
|
+
console.log(colors.dim(` ${id}`));
|
|
1254
|
+
}
|
|
1255
|
+
} else {
|
|
1256
|
+
console.log(colors.dim(" No containers stopped"));
|
|
1257
|
+
}
|
|
1258
|
+
console.log();
|
|
1259
|
+
});
|
|
1260
|
+
|
|
1261
|
+
program
|
|
1262
|
+
.command("login")
|
|
1263
|
+
.description("Login to NOVA for encrypted secrets management")
|
|
1264
|
+
.action(async () => {
|
|
1265
|
+
const { default: open } = await import("open");
|
|
1266
|
+
const { password, input } = await import("@inquirer/prompts");
|
|
1267
|
+
|
|
1268
|
+
console.log();
|
|
1269
|
+
console.log(colors.cyan(frames.top(52)));
|
|
1270
|
+
console.log(` ${icons.config} ${gradients.cyber("NOVA LOGIN")}`);
|
|
1271
|
+
console.log(colors.cyan(frames.bottom(52)));
|
|
1272
|
+
console.log();
|
|
1273
|
+
console.log(
|
|
1274
|
+
colors.dim(
|
|
1275
|
+
" NOVA provides encrypted secrets storage for your plugins.",
|
|
1276
|
+
),
|
|
1277
|
+
);
|
|
1278
|
+
console.log();
|
|
1279
|
+
console.log(colors.white(" To get your credentials:"));
|
|
1280
|
+
console.log(colors.dim(" 1. Login at nova-sdk.com"));
|
|
1281
|
+
console.log(
|
|
1282
|
+
colors.dim(
|
|
1283
|
+
" 2. Copy your account ID and session token from your profile",
|
|
1284
|
+
),
|
|
1285
|
+
);
|
|
1286
|
+
console.log();
|
|
1287
|
+
|
|
1288
|
+
try {
|
|
1289
|
+
const shouldOpen = await input({
|
|
1290
|
+
message: "Press Enter to open nova-sdk.com (or 'skip')",
|
|
1291
|
+
default: "",
|
|
1292
|
+
});
|
|
1293
|
+
|
|
1294
|
+
if (shouldOpen !== "skip") {
|
|
1295
|
+
await open("https://nova-sdk.com");
|
|
1296
|
+
console.log();
|
|
1297
|
+
console.log(
|
|
1298
|
+
colors.dim(" Browser opened. Login and copy your credentials..."),
|
|
1299
|
+
);
|
|
1300
|
+
console.log();
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
const accountId = await input({
|
|
1304
|
+
message: "Account ID (e.g., alice.nova-sdk.near):",
|
|
1305
|
+
validate: (value: string) => {
|
|
1306
|
+
if (!value.trim()) return "Account ID is required";
|
|
1307
|
+
if (!value.includes(".")) return "Invalid account ID format";
|
|
1308
|
+
return true;
|
|
1309
|
+
},
|
|
1310
|
+
});
|
|
1311
|
+
|
|
1312
|
+
const sessionToken = await input({
|
|
1313
|
+
message: "Session Token (paste the full token):",
|
|
1314
|
+
validate: (value: string) => {
|
|
1315
|
+
if (!value.trim()) return "Session token is required";
|
|
1316
|
+
if (value.length < 50) return "Token seems too short";
|
|
1317
|
+
return true;
|
|
1318
|
+
},
|
|
1319
|
+
});
|
|
1320
|
+
|
|
1321
|
+
console.log();
|
|
1322
|
+
console.log(` ${icons.pkg} Verifying credentials...`);
|
|
1323
|
+
console.log(
|
|
1324
|
+
colors.dim(` Token length: ${sessionToken.length} characters`),
|
|
1325
|
+
);
|
|
1326
|
+
|
|
1327
|
+
const result = await client.login({
|
|
1328
|
+
accountId: accountId.trim(),
|
|
1329
|
+
token: sessionToken.trim(),
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
if (result.status === "error") {
|
|
1333
|
+
console.error(
|
|
1334
|
+
colors.error(
|
|
1335
|
+
`${icons.err} Login failed: ${result.error || "Unknown error"}`,
|
|
1336
|
+
),
|
|
1337
|
+
);
|
|
1338
|
+
process.exit(1);
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
console.log();
|
|
1342
|
+
console.log(colors.green(`${icons.ok} Logged in!`));
|
|
1343
|
+
console.log(` ${colors.dim("Account:")} ${result.accountId}`);
|
|
1344
|
+
console.log(` ${colors.dim("Saved to:")} .env.bos`);
|
|
1345
|
+
console.log();
|
|
1346
|
+
console.log(
|
|
1347
|
+
colors.dim(
|
|
1348
|
+
" You can now use 'bos register' and 'bos secrets' commands.",
|
|
1349
|
+
),
|
|
1350
|
+
);
|
|
1351
|
+
console.log();
|
|
1352
|
+
} catch (error) {
|
|
1353
|
+
if (error instanceof Error && error.name === "ExitPromptError") {
|
|
1354
|
+
console.log();
|
|
1355
|
+
console.log(colors.dim(" Login cancelled."));
|
|
1356
|
+
console.log();
|
|
1357
|
+
process.exit(0);
|
|
1358
|
+
}
|
|
1359
|
+
throw error;
|
|
1360
|
+
}
|
|
1361
|
+
});
|
|
1362
|
+
|
|
1363
|
+
program
|
|
1364
|
+
.command("logout")
|
|
1365
|
+
.description("Logout from NOVA (removes credentials from .env.bos)")
|
|
1366
|
+
.action(async () => {
|
|
1367
|
+
console.log();
|
|
1368
|
+
console.log(` ${icons.pkg} Logging out...`);
|
|
1369
|
+
|
|
1370
|
+
const result = await client.logout({});
|
|
1371
|
+
|
|
1372
|
+
if (result.status === "error") {
|
|
1373
|
+
console.error(
|
|
1374
|
+
colors.error(
|
|
1375
|
+
`${icons.err} Logout failed: ${result.error || "Unknown error"}`,
|
|
1376
|
+
),
|
|
1377
|
+
);
|
|
1378
|
+
process.exit(1);
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
console.log();
|
|
1382
|
+
console.log(colors.green(`${icons.ok} Logged out`));
|
|
1383
|
+
console.log(colors.dim(" NOVA credentials removed from .env.bos"));
|
|
1384
|
+
console.log();
|
|
1385
|
+
});
|
|
1386
|
+
|
|
1387
|
+
program
|
|
1388
|
+
.command("session")
|
|
1389
|
+
.description("Record a performance analysis session with Playwright")
|
|
1390
|
+
.option("--headless", "Run browser in headless mode (default: true)", true)
|
|
1391
|
+
.option("--no-headless", "Run browser with UI visible")
|
|
1392
|
+
.option("-t, --timeout <ms>", "Session timeout in milliseconds", "120000")
|
|
1393
|
+
.option(
|
|
1394
|
+
"-o, --output <path>",
|
|
1395
|
+
"Output report path",
|
|
1396
|
+
"./session-report.json",
|
|
1397
|
+
)
|
|
1398
|
+
.option("-f, --format <format>", "Report format: json | html", "json")
|
|
1399
|
+
.option(
|
|
1400
|
+
"--flow <flow>",
|
|
1401
|
+
"Flow to run: login | navigation | custom",
|
|
1402
|
+
"login",
|
|
1403
|
+
)
|
|
1404
|
+
.option("--routes <routes>", "Routes for navigation flow (comma-separated)")
|
|
1405
|
+
.option("--interval <ms>", "Snapshot interval in milliseconds", "2000")
|
|
1406
|
+
.action(async (options) => {
|
|
1407
|
+
console.log();
|
|
1408
|
+
console.log(colors.cyan(frames.top(52)));
|
|
1409
|
+
console.log(` ${icons.scan} ${gradients.cyber("SESSION RECORDER")}`);
|
|
1410
|
+
console.log(colors.cyan(frames.bottom(52)));
|
|
1411
|
+
console.log();
|
|
1412
|
+
console.log(` ${colors.dim("Flow:")} ${colors.white(options.flow)}`);
|
|
1413
|
+
console.log(
|
|
1414
|
+
` ${colors.dim("Headless:")} ${colors.white(String(options.headless))}`,
|
|
1415
|
+
);
|
|
1416
|
+
console.log(
|
|
1417
|
+
` ${colors.dim("Output:")} ${colors.white(options.output)}`,
|
|
1418
|
+
);
|
|
1419
|
+
console.log(
|
|
1420
|
+
` ${colors.dim("Timeout:")} ${colors.white(options.timeout)}ms`,
|
|
1421
|
+
);
|
|
1422
|
+
console.log();
|
|
1423
|
+
|
|
1424
|
+
const s = spinner();
|
|
1425
|
+
s.start("Starting session recording...");
|
|
1426
|
+
|
|
1427
|
+
const result = await client.session({
|
|
1428
|
+
headless: options.headless,
|
|
1429
|
+
timeout: parseInt(options.timeout, 10),
|
|
1430
|
+
output: options.output,
|
|
1431
|
+
format: options.format as "json" | "html",
|
|
1432
|
+
flow: options.flow as "login" | "navigation" | "custom",
|
|
1433
|
+
routes: options.routes ? options.routes.split(",") : undefined,
|
|
1434
|
+
snapshotInterval: parseInt(options.interval, 10),
|
|
1435
|
+
});
|
|
1436
|
+
|
|
1437
|
+
if (result.status === "error") {
|
|
1438
|
+
s.stop(colors.error(`${icons.err} Session failed: ${result.error}`));
|
|
1439
|
+
process.exit(1);
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
if (result.status === "timeout") {
|
|
1443
|
+
s.stop(colors.error(`${icons.err} Session timed out`));
|
|
1444
|
+
process.exit(1);
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
s.stop(colors.green(`${icons.ok} Session completed`));
|
|
1448
|
+
|
|
1449
|
+
console.log();
|
|
1450
|
+
console.log(colors.cyan(frames.top(52)));
|
|
1451
|
+
console.log(` ${icons.ok} ${gradients.cyber("SESSION SUMMARY")}`);
|
|
1452
|
+
console.log(colors.cyan(frames.bottom(52)));
|
|
1453
|
+
console.log();
|
|
1454
|
+
|
|
1455
|
+
if (result.summary) {
|
|
1456
|
+
console.log(
|
|
1457
|
+
` ${colors.dim("Session ID:")} ${colors.white(result.sessionId || "")}`,
|
|
1458
|
+
);
|
|
1459
|
+
console.log(
|
|
1460
|
+
` ${colors.dim("Duration:")} ${colors.white(`${(result.summary.duration / 1000).toFixed(1)}s`)}`,
|
|
1461
|
+
);
|
|
1462
|
+
console.log(
|
|
1463
|
+
` ${colors.dim("Events:")} ${colors.white(String(result.summary.eventCount))}`,
|
|
1464
|
+
);
|
|
1465
|
+
console.log();
|
|
1466
|
+
console.log(
|
|
1467
|
+
` ${colors.dim("Peak Memory:")} ${colors.cyan(`${result.summary.peakMemoryMb.toFixed(1)} MB`)}`,
|
|
1468
|
+
);
|
|
1469
|
+
console.log(
|
|
1470
|
+
` ${colors.dim("Avg Memory:")} ${colors.cyan(`${result.summary.averageMemoryMb.toFixed(1)} MB`)}`,
|
|
1471
|
+
);
|
|
1472
|
+
console.log(
|
|
1473
|
+
` ${colors.dim("Delta:")} ${result.summary.totalMemoryDeltaMb >= 0 ? colors.cyan("+") : ""}${colors.cyan(`${result.summary.totalMemoryDeltaMb.toFixed(1)} MB`)}`,
|
|
1474
|
+
);
|
|
1475
|
+
console.log();
|
|
1476
|
+
console.log(
|
|
1477
|
+
` ${colors.dim("Processes:")} ${colors.white(`${result.summary.processesSpawned} spawned, ${result.summary.processesKilled} killed`)}`,
|
|
1478
|
+
);
|
|
1479
|
+
console.log(
|
|
1480
|
+
` ${colors.dim("Ports:")} ${colors.white(result.summary.portsUsed.join(", "))}`,
|
|
1481
|
+
);
|
|
1482
|
+
console.log();
|
|
1483
|
+
|
|
1484
|
+
if (result.status === "leaks_detected") {
|
|
1485
|
+
console.log(colors.error(` ${icons.err} RESOURCE LEAKS DETECTED`));
|
|
1486
|
+
if (result.summary.orphanedProcesses > 0) {
|
|
1487
|
+
console.log(
|
|
1488
|
+
colors.error(
|
|
1489
|
+
` - ${result.summary.orphanedProcesses} orphaned process(es)`,
|
|
1490
|
+
),
|
|
1491
|
+
);
|
|
1492
|
+
}
|
|
1493
|
+
if (result.summary.portsLeaked > 0) {
|
|
1494
|
+
console.log(
|
|
1495
|
+
colors.error(
|
|
1496
|
+
` - ${result.summary.portsLeaked} port(s) still bound`,
|
|
1497
|
+
),
|
|
1498
|
+
);
|
|
1499
|
+
}
|
|
1500
|
+
} else {
|
|
1501
|
+
console.log(colors.green(` ${icons.ok} No resource leaks detected`));
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
console.log();
|
|
1506
|
+
console.log(
|
|
1507
|
+
` ${colors.dim("Report:")} ${colors.cyan(result.reportPath || options.output)}`,
|
|
1508
|
+
);
|
|
1509
|
+
console.log();
|
|
1510
|
+
});
|
|
1511
|
+
|
|
1512
|
+
program.parse();
|
|
1220
1513
|
}
|
|
1221
1514
|
|
|
1222
1515
|
main().catch((error) => {
|
|
1223
|
-
|
|
1224
|
-
|
|
1516
|
+
console.error(colors.error(`${icons.err} Fatal error:`), error);
|
|
1517
|
+
process.exit(1);
|
|
1225
1518
|
});
|