@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 +247 -9
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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}/
|
|
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}/
|
|
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((
|
|
1509
|
-
rl.question(`Delete profile "${name}"? This cannot be undone. [y/N] `,
|
|
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 `
|