@tinycloud/cli 0.2.0 → 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/dist/index.js CHANGED
@@ -514,7 +514,7 @@ function buildAuthUrl(did, options = {}) {
514
514
  return `${OPENKEY_BASE}/delegate?${params.toString()}`;
515
515
  }
516
516
  async function callbackFlow(did, options = {}) {
517
- return new Promise((resolve, reject) => {
517
+ return new Promise((resolve3, reject) => {
518
518
  let timeout;
519
519
  let settled = false;
520
520
  let rl;
@@ -527,7 +527,7 @@ async function callbackFlow(did, options = {}) {
527
527
  rl.close();
528
528
  }
529
529
  if (result.data) {
530
- resolve(result.data);
530
+ resolve3(result.data);
531
531
  } else {
532
532
  reject(result.error);
533
533
  }
@@ -628,17 +628,17 @@ Open this URL in a browser to authenticate:
628
628
  input: process.stdin,
629
629
  output: process.stderr
630
630
  });
631
- return new Promise((resolve, reject) => {
631
+ return new Promise((resolve3, reject) => {
632
632
  rl.question("Paste delegation code: ", (input) => {
633
633
  rl.close();
634
634
  try {
635
635
  const data = JSON.parse(input.trim());
636
- resolve(data);
636
+ resolve3(data);
637
637
  } catch {
638
638
  try {
639
639
  const decoded = Buffer.from(input.trim(), "base64").toString("utf-8");
640
640
  const data = JSON.parse(decoded);
641
- resolve(data);
641
+ resolve3(data);
642
642
  } catch {
643
643
  reject(new Error("Invalid delegation code. Expected JSON or base64-encoded JSON."));
644
644
  }
@@ -1356,7 +1356,7 @@ function registerNodeCommand(program2) {
1356
1356
  try {
1357
1357
  const globalOpts = cmd.optsWithGlobals();
1358
1358
  const ctx = await ProfileManager.resolveContext(globalOpts);
1359
- const response = await fetch(`${ctx.host}/version`);
1359
+ const response = await fetch(`${ctx.host}/info`);
1360
1360
  if (!response.ok) {
1361
1361
  throw new CLIError("NODE_ERROR", `Node returned ${response.status}`, ExitCode.NODE_ERROR);
1362
1362
  }
@@ -1373,7 +1373,7 @@ function registerNodeCommand(program2) {
1373
1373
  const start = Date.now();
1374
1374
  const [healthRes, versionRes] = await Promise.allSettled([
1375
1375
  fetch(`${ctx.host}/healthz`),
1376
- fetch(`${ctx.host}/version`)
1376
+ fetch(`${ctx.host}/info`)
1377
1377
  ]);
1378
1378
  const latencyMs = Date.now() - start;
1379
1379
  const healthy = healthRes.status === "fulfilled" && healthRes.value.ok;
@@ -1505,8 +1505,8 @@ function registerProfileCommand(program2) {
1505
1505
  try {
1506
1506
  if (isInteractive()) {
1507
1507
  const rl = createInterface2({ input: process.stdin, output: process.stderr });
1508
- const answer = await new Promise((resolve) => {
1509
- rl.question(`Delete profile "${name}"? This cannot be undone. [y/N] `, resolve);
1508
+ const answer = await new Promise((resolve3) => {
1509
+ rl.question(`Delete profile "${name}"? This cannot be undone. [y/N] `, resolve3);
1510
1510
  });
1511
1511
  rl.close();
1512
1512
  if (answer.toLowerCase() !== "y") {
@@ -2248,6 +2248,242 @@ function registerDoctorCommand(program2) {
2248
2248
  });
2249
2249
  }
2250
2250
 
2251
+ // src/commands/sql.ts
2252
+ import { writeFile as writeFile6 } from "fs/promises";
2253
+ import { resolve } from "path";
2254
+ function registerSqlCommand(program2) {
2255
+ const sql = program2.command("sql").description("SQL database operations");
2256
+ sql.command("query <sql>").description("Run a SELECT query").option("--db <name>", "Database name", "default").option("--params <json>", "Bind parameters as JSON array").action(async (sqlStr, options, cmd) => {
2257
+ try {
2258
+ const globalOpts = cmd.optsWithGlobals();
2259
+ const ctx = await ProfileManager.resolveContext(globalOpts);
2260
+ const node = await ensureAuthenticated(ctx);
2261
+ const params = options.params ? JSON.parse(options.params) : void 0;
2262
+ const result = await withSpinner(
2263
+ "Running query...",
2264
+ () => node.sql.db(options.db).query(sqlStr, params)
2265
+ );
2266
+ if (!result.ok) {
2267
+ throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
2268
+ }
2269
+ const { columns, rows, rowCount } = result.data;
2270
+ if (shouldOutputJson()) {
2271
+ outputJson({ columns, rows, rowCount });
2272
+ } else {
2273
+ if (rows.length === 0) {
2274
+ process.stdout.write(theme.muted("No rows returned.") + "\n");
2275
+ } else {
2276
+ const stringRows = rows.map(
2277
+ (row) => row.map((v) => v === null ? "NULL" : String(v))
2278
+ );
2279
+ process.stdout.write(formatTable(columns, stringRows) + "\n");
2280
+ process.stdout.write(theme.muted(`
2281
+ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
2282
+ }
2283
+ }
2284
+ } catch (error) {
2285
+ handleError(error);
2286
+ }
2287
+ });
2288
+ sql.command("execute <sql>").description("Run INSERT/UPDATE/DELETE/DDL statement").option("--db <name>", "Database name", "default").option("--params <json>", "Bind parameters as JSON array").action(async (sqlStr, options, cmd) => {
2289
+ try {
2290
+ const globalOpts = cmd.optsWithGlobals();
2291
+ const ctx = await ProfileManager.resolveContext(globalOpts);
2292
+ const node = await ensureAuthenticated(ctx);
2293
+ const params = options.params ? JSON.parse(options.params) : void 0;
2294
+ const result = await withSpinner(
2295
+ "Executing statement...",
2296
+ () => node.sql.db(options.db).execute(sqlStr, params)
2297
+ );
2298
+ if (!result.ok) {
2299
+ throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
2300
+ }
2301
+ outputJson({
2302
+ changes: result.data.changes,
2303
+ lastInsertRowId: result.data.lastInsertRowId
2304
+ });
2305
+ } catch (error) {
2306
+ handleError(error);
2307
+ }
2308
+ });
2309
+ sql.command("export").description("Export database as binary file").option("--db <name>", "Database name", "default").option("-o, --output <file>", "Output file path", "export.db").action(async (options, cmd) => {
2310
+ try {
2311
+ const globalOpts = cmd.optsWithGlobals();
2312
+ const ctx = await ProfileManager.resolveContext(globalOpts);
2313
+ const node = await ensureAuthenticated(ctx);
2314
+ const result = await withSpinner(
2315
+ "Exporting database...",
2316
+ () => node.sql.db(options.db).export()
2317
+ );
2318
+ if (!result.ok) {
2319
+ throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
2320
+ }
2321
+ const blob = result.data;
2322
+ const buffer = Buffer.from(await blob.arrayBuffer());
2323
+ const outputPath = resolve(options.output);
2324
+ await writeFile6(outputPath, buffer);
2325
+ outputJson({
2326
+ file: outputPath,
2327
+ size: blob.size,
2328
+ sizeHuman: formatBytes(blob.size)
2329
+ });
2330
+ } catch (error) {
2331
+ handleError(error);
2332
+ }
2333
+ });
2334
+ }
2335
+
2336
+ // src/commands/duckdb.ts
2337
+ import { readFile as readFile6, writeFile as writeFile7 } from "fs/promises";
2338
+ import { resolve as resolve2 } from "path";
2339
+ function registerDuckdbCommand(program2) {
2340
+ const duckdb = program2.command("duckdb").description("DuckDB database operations");
2341
+ duckdb.command("query <sql>").description("Run a SELECT query").option("--db <name>", "Database name", "default").option("--params <json>", "Bind parameters as JSON array").action(async (sqlStr, options, cmd) => {
2342
+ try {
2343
+ const globalOpts = cmd.optsWithGlobals();
2344
+ const ctx = await ProfileManager.resolveContext(globalOpts);
2345
+ const node = await ensureAuthenticated(ctx);
2346
+ const params = options.params ? JSON.parse(options.params) : void 0;
2347
+ const result = await withSpinner(
2348
+ "Running query...",
2349
+ () => node.duckdb.db(options.db).query(sqlStr, params)
2350
+ );
2351
+ if (!result.ok) {
2352
+ throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
2353
+ }
2354
+ const { columns, rows, rowCount } = result.data;
2355
+ if (shouldOutputJson()) {
2356
+ outputJson({ columns, rows, rowCount });
2357
+ } else {
2358
+ if (rows.length === 0) {
2359
+ process.stdout.write(theme.muted("No rows returned.") + "\n");
2360
+ } else {
2361
+ const stringRows = rows.map(
2362
+ (row) => row.map((v) => v === null ? "NULL" : String(v))
2363
+ );
2364
+ process.stdout.write(formatTable(columns, stringRows) + "\n");
2365
+ process.stdout.write(theme.muted(`
2366
+ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
2367
+ }
2368
+ }
2369
+ } catch (error) {
2370
+ handleError(error);
2371
+ }
2372
+ });
2373
+ duckdb.command("execute <sql>").description("Run INSERT/UPDATE/DELETE/DDL statement").option("--db <name>", "Database name", "default").option("--params <json>", "Bind parameters as JSON array").action(async (sqlStr, options, cmd) => {
2374
+ try {
2375
+ const globalOpts = cmd.optsWithGlobals();
2376
+ const ctx = await ProfileManager.resolveContext(globalOpts);
2377
+ const node = await ensureAuthenticated(ctx);
2378
+ const params = options.params ? JSON.parse(options.params) : void 0;
2379
+ const result = await withSpinner(
2380
+ "Executing statement...",
2381
+ () => node.duckdb.db(options.db).execute(sqlStr, params)
2382
+ );
2383
+ if (!result.ok) {
2384
+ throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
2385
+ }
2386
+ outputJson({ changes: result.data.changes });
2387
+ } catch (error) {
2388
+ handleError(error);
2389
+ }
2390
+ });
2391
+ duckdb.command("describe").description("Show database schema (tables, columns, views)").option("--db <name>", "Database name", "default").action(async (options, cmd) => {
2392
+ try {
2393
+ const globalOpts = cmd.optsWithGlobals();
2394
+ const ctx = await ProfileManager.resolveContext(globalOpts);
2395
+ const node = await ensureAuthenticated(ctx);
2396
+ const result = await withSpinner(
2397
+ "Describing schema...",
2398
+ () => node.duckdb.db(options.db).describe()
2399
+ );
2400
+ if (!result.ok) {
2401
+ throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
2402
+ }
2403
+ const schema = result.data;
2404
+ if (shouldOutputJson()) {
2405
+ outputJson(schema);
2406
+ } else {
2407
+ const { tables, views } = schema;
2408
+ if (tables.length === 0 && views.length === 0) {
2409
+ process.stdout.write(theme.muted("No tables or views found.") + "\n");
2410
+ return;
2411
+ }
2412
+ if (tables.length > 0) {
2413
+ process.stdout.write(theme.label("Tables:") + "\n\n");
2414
+ for (const table of tables) {
2415
+ process.stdout.write(` ${theme.value(table.name)}
2416
+ `);
2417
+ const colRows = table.columns.map((col) => [
2418
+ col.name,
2419
+ col.type,
2420
+ col.nullable ? "YES" : "NO"
2421
+ ]);
2422
+ const colTable = formatTable(["Column", "Type", "Nullable"], colRows);
2423
+ process.stdout.write(colTable.split("\n").map((l) => " " + l).join("\n") + "\n\n");
2424
+ }
2425
+ }
2426
+ if (views.length > 0) {
2427
+ process.stdout.write(theme.label("Views:") + "\n\n");
2428
+ const viewRows = views.map((v) => [v.name, v.sql]);
2429
+ process.stdout.write(formatTable(["View", "SQL"], viewRows) + "\n");
2430
+ }
2431
+ }
2432
+ } catch (error) {
2433
+ handleError(error);
2434
+ }
2435
+ });
2436
+ duckdb.command("export").description("Export database as binary file").option("--db <name>", "Database name", "default").option("-o, --output <file>", "Output file path", "export.duckdb").action(async (options, cmd) => {
2437
+ try {
2438
+ const globalOpts = cmd.optsWithGlobals();
2439
+ const ctx = await ProfileManager.resolveContext(globalOpts);
2440
+ const node = await ensureAuthenticated(ctx);
2441
+ const result = await withSpinner(
2442
+ "Exporting database...",
2443
+ () => node.duckdb.db(options.db).export()
2444
+ );
2445
+ if (!result.ok) {
2446
+ throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
2447
+ }
2448
+ const blob = result.data;
2449
+ const buffer = Buffer.from(await blob.arrayBuffer());
2450
+ const outputPath = resolve2(options.output);
2451
+ await writeFile7(outputPath, buffer);
2452
+ outputJson({
2453
+ file: outputPath,
2454
+ size: blob.size,
2455
+ sizeHuman: formatBytes(blob.size)
2456
+ });
2457
+ } catch (error) {
2458
+ handleError(error);
2459
+ }
2460
+ });
2461
+ duckdb.command("import <file>").description("Import a DuckDB database file").option("--db <name>", "Database name", "default").action(async (file, options, cmd) => {
2462
+ try {
2463
+ const globalOpts = cmd.optsWithGlobals();
2464
+ const ctx = await ProfileManager.resolveContext(globalOpts);
2465
+ const node = await ensureAuthenticated(ctx);
2466
+ const filePath = resolve2(file);
2467
+ const bytes = new Uint8Array(await readFile6(filePath));
2468
+ const result = await withSpinner(
2469
+ "Importing database...",
2470
+ () => node.duckdb.db(options.db).import(bytes)
2471
+ );
2472
+ if (!result.ok) {
2473
+ throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
2474
+ }
2475
+ outputJson({
2476
+ file: filePath,
2477
+ size: bytes.byteLength,
2478
+ sizeHuman: formatBytes(bytes.byteLength),
2479
+ imported: true
2480
+ });
2481
+ } catch (error) {
2482
+ handleError(error);
2483
+ }
2484
+ });
2485
+ }
2486
+
2251
2487
  // src/index.ts
2252
2488
  var program = new Command();
2253
2489
  program.name("tc").description("TinyCloud CLI \u2014 self-sovereign storage from the terminal").version("0.1.0").option("-p, --profile <name>", "Profile to use").option("-H, --host <url>", "TinyCloud node URL").option("-v, --verbose", "Enable verbose output").option("--no-cache", "Disable caching").option("-q, --quiet", "Suppress non-essential output").option("--json", "Force JSON output");
@@ -2290,6 +2526,8 @@ registerVaultCommand(program);
2290
2526
  registerSecretsCommand(program);
2291
2527
  registerVarsCommand(program);
2292
2528
  registerDoctorCommand(program);
2529
+ registerSqlCommand(program);
2530
+ registerDuckdbCommand(program);
2293
2531
  program.addHelpText("afterAll", () => {
2294
2532
  if (!process.stdout.isTTY) return "";
2295
2533
  return `