@jango-blockchained/hoox-cli 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +403 -0
  2. package/bin/hoox.js +12 -0
  3. package/package.json +60 -0
  4. package/src/commands/check/check-command.test.ts +468 -0
  5. package/src/commands/check/check-command.ts +1144 -0
  6. package/src/commands/check/index.ts +10 -0
  7. package/src/commands/check/prerequisites-command.test.ts +19 -0
  8. package/src/commands/check/prerequisites-command.ts +92 -0
  9. package/src/commands/check/types.ts +103 -0
  10. package/src/commands/clone/clone-command.test.ts +442 -0
  11. package/src/commands/clone/clone-command.ts +440 -0
  12. package/src/commands/clone/index.ts +1 -0
  13. package/src/commands/config/config-command.test.ts +583 -0
  14. package/src/commands/config/config-command.ts +901 -0
  15. package/src/commands/config/env-command.test.ts +43 -0
  16. package/src/commands/config/env-command.ts +314 -0
  17. package/src/commands/config/index.ts +3 -0
  18. package/src/commands/config/kv-command.test.ts +14 -0
  19. package/src/commands/config/kv-command.ts +329 -0
  20. package/src/commands/dashboard/dashboard-command.test.ts +47 -0
  21. package/src/commands/dashboard/dashboard-command.ts +127 -0
  22. package/src/commands/dashboard/index.ts +1 -0
  23. package/src/commands/db/db-command.test.ts +21 -0
  24. package/src/commands/db/db-command.ts +314 -0
  25. package/src/commands/db/index.ts +1 -0
  26. package/src/commands/deploy/deploy-command.test.ts +304 -0
  27. package/src/commands/deploy/deploy-command.ts +1053 -0
  28. package/src/commands/deploy/index.ts +2 -0
  29. package/src/commands/deploy/telegram-service.ts +61 -0
  30. package/src/commands/deploy/types.ts +34 -0
  31. package/src/commands/dev/dev-command.test.ts +383 -0
  32. package/src/commands/dev/dev-command.ts +407 -0
  33. package/src/commands/dev/index.ts +1 -0
  34. package/src/commands/infra/index.ts +5 -0
  35. package/src/commands/infra/infra-command.test.ts +719 -0
  36. package/src/commands/infra/infra-command.ts +940 -0
  37. package/src/commands/infra/types.ts +23 -0
  38. package/src/commands/init/index.ts +1 -0
  39. package/src/commands/init/init-command.test.ts +827 -0
  40. package/src/commands/init/init-command.ts +627 -0
  41. package/src/commands/init/types.ts +185 -0
  42. package/src/commands/monitor/index.ts +2 -0
  43. package/src/commands/monitor/monitor-command.test.ts +235 -0
  44. package/src/commands/monitor/monitor-command.ts +245 -0
  45. package/src/commands/monitor/monitor-service.ts +50 -0
  46. package/src/commands/monitor/types.ts +13 -0
  47. package/src/commands/repair/index.ts +2 -0
  48. package/src/commands/repair/repair-command.test.ts +204 -0
  49. package/src/commands/repair/repair-command.ts +199 -0
  50. package/src/commands/repair/repair-service.ts +102 -0
  51. package/src/commands/repair/types.ts +13 -0
  52. package/src/commands/test/index.ts +2 -0
  53. package/src/commands/test/test-command.test.ts +319 -0
  54. package/src/commands/test/test-command.ts +412 -0
  55. package/src/commands/waf/index.ts +2 -0
  56. package/src/commands/waf/types.ts +48 -0
  57. package/src/commands/waf/waf-command.test.ts +506 -0
  58. package/src/commands/waf/waf-command.ts +548 -0
  59. package/src/index.ts +198 -0
  60. package/src/services/cloudflare/cloudflare-service.test.ts +654 -0
  61. package/src/services/cloudflare/cloudflare-service.ts +435 -0
  62. package/src/services/cloudflare/index.ts +2 -0
  63. package/src/services/cloudflare/types.ts +29 -0
  64. package/src/services/config/config-service.test.ts +395 -0
  65. package/src/services/config/config-service.ts +207 -0
  66. package/src/services/config/index.ts +9 -0
  67. package/src/services/config/types.ts +66 -0
  68. package/src/services/db/db-service.test.ts +51 -0
  69. package/src/services/db/db-service.ts +140 -0
  70. package/src/services/db/index.ts +1 -0
  71. package/src/services/docker/docker-service.ts +155 -0
  72. package/src/services/docker/index.ts +1 -0
  73. package/src/services/env/env-service.test.ts +210 -0
  74. package/src/services/env/env-service.ts +156 -0
  75. package/src/services/env/index.ts +1 -0
  76. package/src/services/kv/index.ts +1 -0
  77. package/src/services/kv/kv-sync-service.test.ts +38 -0
  78. package/src/services/kv/kv-sync-service.ts +151 -0
  79. package/src/services/prerequisites/index.ts +1 -0
  80. package/src/services/prerequisites/prerequisites-service.test.ts +89 -0
  81. package/src/services/prerequisites/prerequisites-service.ts +269 -0
  82. package/src/services/prerequisites/types.ts +48 -0
  83. package/src/services/secrets/index.ts +12 -0
  84. package/src/services/secrets/secrets-service.test.ts +486 -0
  85. package/src/services/secrets/secrets-service.ts +293 -0
  86. package/src/services/secrets/types.ts +57 -0
  87. package/src/ui/banner.ts +52 -0
  88. package/src/ui/index.ts +8 -0
  89. package/src/ui/menu.ts +473 -0
  90. package/src/utils/errors.test.ts +69 -0
  91. package/src/utils/errors.ts +23 -0
  92. package/src/utils/formatters.test.ts +180 -0
  93. package/src/utils/formatters.ts +252 -0
  94. package/src/utils/theme.ts +94 -0
@@ -0,0 +1,47 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import type { Command } from "commander";
3
+
4
+ const { registerDashboardCommand } = await import("./dashboard-command.js");
5
+
6
+ describe("registerDashboardCommand", () => {
7
+ it("registers dashboard command group on program", () => {
8
+ const commands: { name: string; description: string }[] = [];
9
+ const subcommands: { parent: string; name: string; description: string }[] =
10
+ [];
11
+
12
+ const mockProgram = {
13
+ command: (name: string) => {
14
+ commands.push({ name, description: "" });
15
+ const desc = (d: string) => {
16
+ commands[commands.length - 1].description = d;
17
+ return {
18
+ command: (subName: string) => {
19
+ subcommands.push({
20
+ parent: name,
21
+ name: subName,
22
+ description: "",
23
+ });
24
+ return {
25
+ description: (d2: string) => {
26
+ subcommands[subcommands.length - 1].description = d2;
27
+ return {
28
+ option: () => ({ action: () => {} }),
29
+ };
30
+ },
31
+ };
32
+ },
33
+ };
34
+ };
35
+ return { description: desc };
36
+ },
37
+ opts: () => ({ json: false, quiet: false }),
38
+ } as unknown as Command;
39
+
40
+ registerDashboardCommand(mockProgram);
41
+
42
+ expect(commands.length).toBe(1);
43
+ expect(commands[0].name).toBe("dashboard");
44
+ expect(subcommands.length).toBe(1);
45
+ expect(subcommands[0].name).toBe("update-urls");
46
+ });
47
+ });
@@ -0,0 +1,127 @@
1
+ import type { Command } from "commander";
2
+ import { ConfigService } from "../../services/config/index.js";
3
+ import { CLIError, ExitCode } from "../../utils/errors.js";
4
+ import {
5
+ formatSuccess,
6
+ formatError,
7
+ formatTable,
8
+ } from "../../utils/formatters.js";
9
+ import type { FormatOptions } from "../../utils/formatters.js";
10
+ import { theme, icons } from "../../utils/theme.js";
11
+ import * as jsonc from "jsonc-parser";
12
+ import { readFileSync, writeFileSync, existsSync } from "node:fs";
13
+ import { resolve } from "node:path";
14
+
15
+ interface ServiceUrl {
16
+ worker: string;
17
+ url: string;
18
+ }
19
+
20
+ function getServiceUrls(config: ConfigService): ServiceUrl[] {
21
+ const global = config.getGlobal();
22
+ const prefix = global.subdomain_prefix ?? "hoox";
23
+ const workers = config.listEnabledWorkers();
24
+
25
+ return workers.map((name) => ({
26
+ worker: name,
27
+ url: `https://${name}.${prefix}.workers.dev`,
28
+ }));
29
+ }
30
+
31
+ function updateWranglerVars(
32
+ filePath: string,
33
+ urls: ServiceUrl[],
34
+ dryRun: boolean,
35
+ opts: FormatOptions
36
+ ): void {
37
+ if (!existsSync(filePath)) {
38
+ throw new CLIError(
39
+ `Dashboard wrangler.jsonc not found at ${filePath}`,
40
+ ExitCode.ERROR
41
+ );
42
+ }
43
+
44
+ const content = readFileSync(filePath, "utf-8");
45
+ const errors: jsonc.ParseError[] = [];
46
+ const parsed = jsonc.parse(content, errors) as Record<string, unknown>;
47
+
48
+ if (errors.length > 0) {
49
+ throw new CLIError(`Invalid JSONC in ${filePath}`, ExitCode.ERROR);
50
+ }
51
+
52
+ const vars = (parsed.vars as Record<string, string>) ?? {};
53
+ const changes: { key: string; oldValue: string; newValue: string }[] = [];
54
+
55
+ for (const { worker, url } of urls) {
56
+ const key = `${worker.toUpperCase().replace(/-/g, "_")}_URL`;
57
+ const oldValue = vars[key] ?? "(not set)";
58
+ changes.push({ key, oldValue, newValue: url });
59
+ vars[key] = url;
60
+ }
61
+
62
+ if (changes.length === 0) {
63
+ formatSuccess("No service URLs to update.", opts);
64
+ return;
65
+ }
66
+
67
+ if (!opts.quiet) {
68
+ const rows = changes.map((c) => ({
69
+ Key: c.key,
70
+ Old: c.oldValue,
71
+ New: theme.success(c.newValue),
72
+ }));
73
+ console.log(theme.heading("\nService URL changes:"));
74
+ formatTable(rows, opts);
75
+ }
76
+
77
+ if (dryRun) {
78
+ formatSuccess("Dry run — no changes written.", opts);
79
+ return;
80
+ }
81
+
82
+ const edits = jsonc.modify(content, ["vars"], vars, {
83
+ formattingOptions: { tabSize: 2, insertSpaces: true },
84
+ });
85
+ const updated = jsonc.applyEdits(content, edits);
86
+ writeFileSync(filePath, updated, "utf-8");
87
+
88
+ formatSuccess(
89
+ `Updated ${changes.length} service URL(s) in dashboard wrangler.jsonc`,
90
+ opts
91
+ );
92
+ }
93
+
94
+ export function registerDashboardCommand(program: Command): void {
95
+ const dashboardCmd = program
96
+ .command("dashboard")
97
+ .description("Dashboard-specific operations");
98
+
99
+ dashboardCmd
100
+ .command("update-urls")
101
+ .description("Update dashboard wrangler.jsonc with current service URLs")
102
+ .option("--dry-run", "Show changes without applying")
103
+ .action(async (options: { dryRun?: boolean }) => {
104
+ const opts: FormatOptions = {
105
+ json: program.opts<{ json?: boolean }>().json,
106
+ quiet: program.opts<{ quiet?: boolean }>().quiet,
107
+ };
108
+
109
+ try {
110
+ const config = new ConfigService();
111
+ await config.load();
112
+
113
+ const urls = getServiceUrls(config);
114
+ const dashboardPath = resolve(
115
+ process.cwd(),
116
+ "pages",
117
+ "dashboard",
118
+ "wrangler.jsonc"
119
+ );
120
+
121
+ updateWranglerVars(dashboardPath, urls, options.dryRun === true, opts);
122
+ } catch (err) {
123
+ formatError(err instanceof Error ? err : String(err), opts);
124
+ process.exit(err instanceof CLIError ? err.code : ExitCode.ERROR);
125
+ }
126
+ });
127
+ }
@@ -0,0 +1 @@
1
+ export { registerDashboardCommand } from "./dashboard-command.js";
@@ -0,0 +1,21 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { DbService } from "../../services/db/index.js";
3
+
4
+ describe("db command", () => {
5
+ describe("DbService integration", () => {
6
+ it("resolves database name from explicit arg", async () => {
7
+ const svc = new DbService();
8
+ const name = await svc.resolveDbName("my-test-db");
9
+ expect(name).toBe("my-test-db");
10
+ });
11
+
12
+ it("parses table names from wrangler output", () => {
13
+ const output = JSON.stringify([
14
+ { results: [{ name: "trades" }, { name: "signals" }] },
15
+ ]);
16
+ const tables = DbService.parseTableNames(output);
17
+ expect(tables).toContain("trades");
18
+ expect(tables).toContain("signals");
19
+ });
20
+ });
21
+ });
@@ -0,0 +1,314 @@
1
+ /**
2
+ * `hoox db` command group — database management.
3
+ *
4
+ * Subcommands:
5
+ * apply [--remote] [--file <path>] — Apply schema.sql to D1
6
+ * migrate [--remote] — Run tracking migrations
7
+ * list [--remote] — List database tables
8
+ * query <sql> [--remote] — Execute a SQL query
9
+ * export [--output <path>] — Export database
10
+ * reset [--confirm] — Drop and recreate (DESTRUCTIVE)
11
+ */
12
+
13
+ import { Command } from "commander";
14
+ import * as p from "@clack/prompts";
15
+ import { DbService } from "../../services/db/index.js";
16
+ import {
17
+ formatSuccess,
18
+ formatError,
19
+ formatTable,
20
+ formatJson,
21
+ } from "../../utils/formatters.js";
22
+ import { CLIError, ExitCode } from "../../utils/errors.js";
23
+ import type { FormatOptions } from "../../utils/formatters.js";
24
+
25
+ function getFormatOptions(cmd: Command): FormatOptions {
26
+ const rootCmd = cmd.parent?.parent as Command | undefined;
27
+ return {
28
+ json: Boolean(rootCmd?.optsWithGlobals()?.json),
29
+ quiet: Boolean(rootCmd?.optsWithGlobals()?.quiet),
30
+ };
31
+ }
32
+
33
+ /**
34
+ * Resolve database name from config or --database flag.
35
+ */
36
+ async function resolveDb(
37
+ cmd: Command,
38
+ svc: DbService,
39
+ ): Promise<string> {
40
+ const opts = cmd.optsWithGlobals<{ database?: string }>();
41
+ return await svc.resolveDbName(opts.database);
42
+ }
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // db apply
46
+ // ---------------------------------------------------------------------------
47
+
48
+ async function handleApply(
49
+ opts: FormatOptions,
50
+ dbName: string,
51
+ remote: boolean,
52
+ file?: string,
53
+ ): Promise<void> {
54
+ const svc = new DbService();
55
+ const output = await svc.apply(dbName, remote, file);
56
+ formatSuccess(
57
+ `Schema applied to ${dbName}${remote ? " (remote)" : " (local)"}`,
58
+ opts,
59
+ );
60
+ if (!opts.quiet && output) {
61
+ process.stdout.write(`${output}\n`);
62
+ }
63
+ }
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // db migrate
67
+ // ---------------------------------------------------------------------------
68
+
69
+ async function handleMigrate(
70
+ opts: FormatOptions,
71
+ dbName: string,
72
+ remote: boolean,
73
+ ): Promise<void> {
74
+ const svc = new DbService();
75
+ const output = await svc.migrate(dbName, remote);
76
+ formatSuccess(
77
+ `Migrations applied to ${dbName}${remote ? " (remote)" : " (local)"}`,
78
+ opts,
79
+ );
80
+ if (!opts.quiet && output) {
81
+ process.stdout.write(`${output}\n`);
82
+ }
83
+ }
84
+
85
+ // ---------------------------------------------------------------------------
86
+ // db list
87
+ // ---------------------------------------------------------------------------
88
+
89
+ async function handleList(
90
+ opts: FormatOptions,
91
+ dbName: string,
92
+ remote: boolean,
93
+ ): Promise<void> {
94
+ const svc = new DbService();
95
+ const tables = await svc.listTables(dbName, remote);
96
+
97
+ if (opts.json) {
98
+ formatJson(tables, opts);
99
+ } else if (!opts.quiet) {
100
+ if (tables.length === 0) {
101
+ process.stdout.write("No tables found.\n");
102
+ return;
103
+ }
104
+ const rows = tables.map((t) => ({ Table: t }));
105
+ formatTable(rows, opts);
106
+ }
107
+ }
108
+
109
+ // ---------------------------------------------------------------------------
110
+ // db query
111
+ // ---------------------------------------------------------------------------
112
+
113
+ async function handleQuery(
114
+ opts: FormatOptions,
115
+ dbName: string,
116
+ sql: string,
117
+ remote: boolean,
118
+ ): Promise<void> {
119
+ const svc = new DbService();
120
+ const output = await svc.query(dbName, sql, remote);
121
+
122
+ if (opts.json) {
123
+ // Already JSON from wrangler — try to parse for cleaner output
124
+ try {
125
+ const parsed = JSON.parse(output);
126
+ formatJson(parsed, opts);
127
+ } catch {
128
+ process.stdout.write(`${output}\n`);
129
+ }
130
+ } else {
131
+ process.stdout.write(`${output}\n`);
132
+ }
133
+ }
134
+
135
+ // ---------------------------------------------------------------------------
136
+ // db export
137
+ // ---------------------------------------------------------------------------
138
+
139
+ async function handleExport(
140
+ opts: FormatOptions,
141
+ dbName: string,
142
+ outputPath?: string,
143
+ ): Promise<void> {
144
+ const svc = new DbService();
145
+ const path = await svc.export(dbName, outputPath);
146
+ formatSuccess(`Database exported to ${path}`, opts);
147
+ }
148
+
149
+ // ---------------------------------------------------------------------------
150
+ // db reset
151
+ // ---------------------------------------------------------------------------
152
+
153
+ async function handleReset(
154
+ opts: FormatOptions,
155
+ dbName: string,
156
+ confirmed: boolean,
157
+ ): Promise<void> {
158
+ if (!confirmed) {
159
+ const answer = await p.confirm({
160
+ message: `WARNING: This will DELETE and recreate the "${dbName}" database. ALL DATA WILL BE LOST. Continue?`,
161
+ initialValue: false,
162
+ });
163
+ if (p.isCancel(answer) || !answer) {
164
+ p.cancel("Reset cancelled.");
165
+ return;
166
+ }
167
+ }
168
+
169
+ const svc = new DbService();
170
+ const output = await svc.reset(dbName);
171
+ formatSuccess(`Database "${dbName}" has been recreated`, opts);
172
+ if (!opts.quiet && output) {
173
+ process.stdout.write(`${output}\n`);
174
+ }
175
+ }
176
+
177
+ // ---------------------------------------------------------------------------
178
+ // Registration
179
+ // ---------------------------------------------------------------------------
180
+
181
+ export function registerDbCommand(program: Command): void {
182
+ const dbCmd = program
183
+ .command("db")
184
+ .summary("Manage D1 databases")
185
+ .description(
186
+ `Manage Cloudflare D1 databases — schema, queries, migrations, backup.
187
+
188
+ SUBCOMMANDS:
189
+ apply Apply schema.sql to D1
190
+ migrate Run tracking migrations
191
+ list List database tables
192
+ query <sql> Execute a SQL query
193
+ export Export database to .sql file
194
+ reset Drop and recreate database (DESTRUCTIVE)
195
+
196
+ OPTIONS:
197
+ --database <name> Database name (auto-detected from config if omitted)
198
+ --remote Operate on production (remote) database
199
+
200
+ EXAMPLES:
201
+ hoox db apply
202
+ hoox db apply --remote
203
+ hoox db migrate
204
+ hoox db list
205
+ hoox db query "SELECT * FROM trade_signals LIMIT 5"
206
+ hoox db export
207
+ hoox db reset`,
208
+ )
209
+ .option("--database <name>", "Database name (auto-detected if omitted)")
210
+ .option("--remote", "Operate on production (remote) database");
211
+
212
+ // -- apply
213
+ dbCmd
214
+ .command("apply")
215
+ .description("Apply schema.sql to the database")
216
+ .option("--file <path>", "Path to schema.sql file")
217
+ .action(async (options: { file?: string }, cmd: Command) => {
218
+ const opts = getFormatOptions(cmd);
219
+ try {
220
+ const svc = new DbService();
221
+ const dbName = await resolveDb(cmd, svc);
222
+ const remote = Boolean(cmd.optsWithGlobals<{ remote?: boolean }>().remote);
223
+ await handleApply(opts, dbName, remote, options.file);
224
+ } catch (err) {
225
+ formatError(err instanceof Error ? err : String(err), opts);
226
+ process.exit(err instanceof CLIError ? err.code : ExitCode.ERROR);
227
+ }
228
+ });
229
+
230
+ // -- migrate
231
+ dbCmd
232
+ .command("migrate")
233
+ .description("Run tracking migrations")
234
+ .action(async (_, cmd: Command) => {
235
+ const opts = getFormatOptions(cmd);
236
+ try {
237
+ const svc = new DbService();
238
+ const dbName = await resolveDb(cmd, svc);
239
+ const remote = Boolean(cmd.optsWithGlobals<{ remote?: boolean }>().remote);
240
+ await handleMigrate(opts, dbName, remote);
241
+ } catch (err) {
242
+ formatError(err instanceof Error ? err : String(err), opts);
243
+ process.exit(err instanceof CLIError ? err.code : ExitCode.ERROR);
244
+ }
245
+ });
246
+
247
+ // -- list
248
+ dbCmd
249
+ .command("list")
250
+ .description("List database tables")
251
+ .action(async (_, cmd: Command) => {
252
+ const opts = getFormatOptions(cmd);
253
+ try {
254
+ const svc = new DbService();
255
+ const dbName = await resolveDb(cmd, svc);
256
+ const remote = Boolean(cmd.optsWithGlobals<{ remote?: boolean }>().remote);
257
+ await handleList(opts, dbName, remote);
258
+ } catch (err) {
259
+ formatError(err instanceof Error ? err : String(err), opts);
260
+ process.exit(err instanceof CLIError ? err.code : ExitCode.ERROR);
261
+ }
262
+ });
263
+
264
+ // -- query
265
+ dbCmd
266
+ .command("query <sql>")
267
+ .description("Execute a SQL query")
268
+ .action(async (sql: string, _, cmd: Command) => {
269
+ const opts = getFormatOptions(cmd);
270
+ try {
271
+ const svc = new DbService();
272
+ const dbName = await resolveDb(cmd, svc);
273
+ const remote = Boolean(cmd.optsWithGlobals<{ remote?: boolean }>().remote);
274
+ await handleQuery(opts, dbName, sql, remote);
275
+ } catch (err) {
276
+ formatError(err instanceof Error ? err : String(err), opts);
277
+ process.exit(err instanceof CLIError ? err.code : ExitCode.ERROR);
278
+ }
279
+ });
280
+
281
+ // -- export
282
+ dbCmd
283
+ .command("export")
284
+ .description("Export database to .sql file")
285
+ .option("--output <path>", "Output file path")
286
+ .action(async (options: { output?: string }, cmd: Command) => {
287
+ const opts = getFormatOptions(cmd);
288
+ try {
289
+ const svc = new DbService();
290
+ const dbName = await resolveDb(cmd, svc);
291
+ await handleExport(opts, dbName, options.output);
292
+ } catch (err) {
293
+ formatError(err instanceof Error ? err : String(err), opts);
294
+ process.exit(err instanceof CLIError ? err.code : ExitCode.ERROR);
295
+ }
296
+ });
297
+
298
+ // -- reset
299
+ dbCmd
300
+ .command("reset")
301
+ .description("Drop and recreate the database (DESTRUCTIVE)")
302
+ .option("--confirm", "Skip confirmation prompt")
303
+ .action(async (options: { confirm?: boolean }, cmd: Command) => {
304
+ const opts = getFormatOptions(cmd);
305
+ try {
306
+ const svc = new DbService();
307
+ const dbName = await resolveDb(cmd, svc);
308
+ await handleReset(opts, dbName, Boolean(options.confirm));
309
+ } catch (err) {
310
+ formatError(err instanceof Error ? err : String(err), opts);
311
+ process.exit(err instanceof CLIError ? err.code : ExitCode.ERROR);
312
+ }
313
+ });
314
+ }
@@ -0,0 +1 @@
1
+ export { registerDbCommand } from "./db-command.js";