@tinycloud/cli 0.1.1 → 0.3.0
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/README.md +128 -0
- package/dist/index.js +1047 -57
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -23,13 +23,49 @@ var ExitCode = {
|
|
|
23
23
|
|
|
24
24
|
// src/output/formatter.ts
|
|
25
25
|
import ora from "ora";
|
|
26
|
+
|
|
27
|
+
// src/output/theme.ts
|
|
28
|
+
import chalk from "chalk";
|
|
29
|
+
var TC_PALETTE = {
|
|
30
|
+
primary: "#4473b9",
|
|
31
|
+
accent: "#5b9bd5",
|
|
32
|
+
success: "#2fba6a",
|
|
33
|
+
warn: "#e8a838",
|
|
34
|
+
error: "#d94040",
|
|
35
|
+
muted: "#808080",
|
|
36
|
+
dim: "#5a5a5a"
|
|
37
|
+
};
|
|
38
|
+
var theme = {
|
|
39
|
+
primary: chalk.hex(TC_PALETTE.primary),
|
|
40
|
+
accent: chalk.hex(TC_PALETTE.accent),
|
|
41
|
+
success: chalk.hex(TC_PALETTE.success),
|
|
42
|
+
warn: chalk.hex(TC_PALETTE.warn),
|
|
43
|
+
error: chalk.hex(TC_PALETTE.error),
|
|
44
|
+
muted: chalk.hex(TC_PALETTE.muted),
|
|
45
|
+
dim: chalk.hex(TC_PALETTE.dim),
|
|
46
|
+
heading: chalk.bold.hex(TC_PALETTE.primary),
|
|
47
|
+
command: chalk.hex(TC_PALETTE.accent),
|
|
48
|
+
brand: chalk.bold.hex(TC_PALETTE.primary),
|
|
49
|
+
label: chalk.bold,
|
|
50
|
+
value: chalk.white,
|
|
51
|
+
hint: chalk.italic.hex(TC_PALETTE.muted)
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// src/output/formatter.ts
|
|
26
55
|
function outputJson(data) {
|
|
27
56
|
process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
28
57
|
}
|
|
29
58
|
function outputError(code, message) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
59
|
+
if (isInteractive()) {
|
|
60
|
+
process.stderr.write(
|
|
61
|
+
`${theme.error("\u2717")} ${theme.label(code)}: ${message}
|
|
62
|
+
`
|
|
63
|
+
);
|
|
64
|
+
} else {
|
|
65
|
+
process.stderr.write(
|
|
66
|
+
JSON.stringify({ error: { code, message } }, null, 2) + "\n"
|
|
67
|
+
);
|
|
68
|
+
}
|
|
33
69
|
}
|
|
34
70
|
function isInteractive() {
|
|
35
71
|
return Boolean(process.stdout.isTTY);
|
|
@@ -41,13 +77,56 @@ async function withSpinner(label, fn) {
|
|
|
41
77
|
const spinner = ora(label).start();
|
|
42
78
|
try {
|
|
43
79
|
const result = await fn();
|
|
44
|
-
spinner.succeed();
|
|
80
|
+
spinner.succeed(label);
|
|
45
81
|
return result;
|
|
46
82
|
} catch (error) {
|
|
47
|
-
spinner.fail();
|
|
83
|
+
spinner.fail(label);
|
|
48
84
|
throw error;
|
|
49
85
|
}
|
|
50
86
|
}
|
|
87
|
+
function shouldOutputJson() {
|
|
88
|
+
return !isInteractive() || process.argv.includes("--json");
|
|
89
|
+
}
|
|
90
|
+
function formatField(label, value) {
|
|
91
|
+
if (value === null || value === void 0) return ` ${theme.label(label + ":")} ${theme.muted("\u2014")}`;
|
|
92
|
+
if (typeof value === "boolean") {
|
|
93
|
+
return ` ${theme.label(label + ":")} ${value ? theme.success("yes") : theme.muted("no")}`;
|
|
94
|
+
}
|
|
95
|
+
return ` ${theme.label(label + ":")} ${theme.value(String(value))}`;
|
|
96
|
+
}
|
|
97
|
+
function formatTable(headers, rows) {
|
|
98
|
+
const widths = headers.map(
|
|
99
|
+
(h, i) => Math.max(h.length, ...rows.map((r) => (r[i] || "").length))
|
|
100
|
+
);
|
|
101
|
+
const headerLine = headers.map((h, i) => theme.label(h.padEnd(widths[i]))).join(" ");
|
|
102
|
+
const separator = widths.map((w) => theme.dim("\u2500".repeat(w))).join(" ");
|
|
103
|
+
const dataLines = rows.map(
|
|
104
|
+
(row) => row.map((cell, i) => (cell || "").padEnd(widths[i])).join(" ")
|
|
105
|
+
);
|
|
106
|
+
return [headerLine, separator, ...dataLines].join("\n");
|
|
107
|
+
}
|
|
108
|
+
function formatCheck(ok, label, detail) {
|
|
109
|
+
const icon = ok === "warn" ? theme.warn("\u26A0") : ok ? theme.success("\u2713") : theme.error("\u2717");
|
|
110
|
+
const detailStr = detail ? ` ${theme.muted(`(${detail})`)}` : "";
|
|
111
|
+
return `${icon} ${label}${detailStr}`;
|
|
112
|
+
}
|
|
113
|
+
function formatSection(title) {
|
|
114
|
+
return `
|
|
115
|
+
${theme.heading(title)}`;
|
|
116
|
+
}
|
|
117
|
+
function formatBytes(bytes) {
|
|
118
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
119
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
120
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
121
|
+
}
|
|
122
|
+
function formatTimeAgo(date) {
|
|
123
|
+
const d = typeof date === "string" ? new Date(date) : date;
|
|
124
|
+
const seconds = Math.floor((Date.now() - d.getTime()) / 1e3);
|
|
125
|
+
if (seconds < 60) return "just now";
|
|
126
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
|
|
127
|
+
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
|
|
128
|
+
return `${Math.floor(seconds / 86400)}d ago`;
|
|
129
|
+
}
|
|
51
130
|
|
|
52
131
|
// src/output/errors.ts
|
|
53
132
|
var CLIError = class extends Error {
|
|
@@ -81,6 +160,97 @@ function handleError(error) {
|
|
|
81
160
|
process.exit(cliError.exitCode);
|
|
82
161
|
}
|
|
83
162
|
|
|
163
|
+
// src/output/taglines.ts
|
|
164
|
+
var HOLIDAY_TAGLINES = [
|
|
165
|
+
{ month: 1, day: 1, range: 1, tagline: "New year, new keys, same cloud." },
|
|
166
|
+
{ month: 2, day: 14, tagline: "We love your data as much as you do." },
|
|
167
|
+
{ month: 3, day: 14, tagline: "3.14159 reasons to encrypt everything." },
|
|
168
|
+
{ month: 5, day: 4, tagline: "May the fourth be with your keys." },
|
|
169
|
+
{ month: 10, day: 31, tagline: "Nothing scarier than plaintext secrets." },
|
|
170
|
+
{ month: 12, day: 25, range: 2, tagline: "Unwrap your data, not your keys." },
|
|
171
|
+
{ month: 12, day: 31, tagline: "Encrypt your resolutions." }
|
|
172
|
+
];
|
|
173
|
+
var TAGLINES = [
|
|
174
|
+
// Professional
|
|
175
|
+
"Your data, your keys, your cloud.",
|
|
176
|
+
"Self-sovereign storage for the modern web.",
|
|
177
|
+
"The cloud you actually own.",
|
|
178
|
+
"Encrypted by default, decentralized by design.",
|
|
179
|
+
"Where your data answers only to you.",
|
|
180
|
+
"End-to-end encrypted. No exceptions.",
|
|
181
|
+
"Like S3 but you hold the keys.",
|
|
182
|
+
"Privacy isn't a feature. It's the architecture.",
|
|
183
|
+
"Sovereign storage, zero knowledge.",
|
|
184
|
+
"Your .env is safe here \u2014 we use real cryptography.",
|
|
185
|
+
// Playful / nerdy
|
|
186
|
+
"UCAN do anything.",
|
|
187
|
+
"Keys generated, delegations granted, data liberated.",
|
|
188
|
+
"Decentralized storage, centralized vibes.",
|
|
189
|
+
"Trust nobody, delegate everything.",
|
|
190
|
+
"sudo make me a sandwich, encrypted.",
|
|
191
|
+
"Have you tried turning your keys off and on again?",
|
|
192
|
+
"All your base are belong to you.",
|
|
193
|
+
"In UCAN we trust.",
|
|
194
|
+
"0 knowledge, 100% confidence.",
|
|
195
|
+
"Keeping secrets since 2024."
|
|
196
|
+
];
|
|
197
|
+
function getHolidayTagline() {
|
|
198
|
+
const now = /* @__PURE__ */ new Date();
|
|
199
|
+
const month = now.getMonth() + 1;
|
|
200
|
+
const day = now.getDate();
|
|
201
|
+
for (const h of HOLIDAY_TAGLINES) {
|
|
202
|
+
const range = h.range ?? 0;
|
|
203
|
+
if (h.month === month && Math.abs(day - h.day) <= range) {
|
|
204
|
+
return h.tagline;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
function pickTagline() {
|
|
210
|
+
const holiday = getHolidayTagline();
|
|
211
|
+
if (holiday) return holiday;
|
|
212
|
+
return TAGLINES[Math.floor(Math.random() * TAGLINES.length)];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/output/banner.ts
|
|
216
|
+
import { execSync } from "child_process";
|
|
217
|
+
var bannerEmitted = false;
|
|
218
|
+
function resolveCommitHash() {
|
|
219
|
+
try {
|
|
220
|
+
return execSync("git rev-parse --short HEAD", {
|
|
221
|
+
encoding: "utf-8",
|
|
222
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
223
|
+
}).trim() || null;
|
|
224
|
+
} catch {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function formatBannerLine(version) {
|
|
229
|
+
const commit = resolveCommitHash();
|
|
230
|
+
const tagline = pickTagline();
|
|
231
|
+
const versionPart = `tc v${version}`;
|
|
232
|
+
const commitPart = commit ? ` (${commit})` : "";
|
|
233
|
+
const separator = " \u2014 ";
|
|
234
|
+
if (!isInteractive()) {
|
|
235
|
+
return `${versionPart}${commitPart}${separator}${tagline}`;
|
|
236
|
+
}
|
|
237
|
+
return [
|
|
238
|
+
theme.brand("\u2601\uFE0F tc"),
|
|
239
|
+
" ",
|
|
240
|
+
theme.muted(`v${version}`),
|
|
241
|
+
commit ? theme.dim(` (${commit})`) : "",
|
|
242
|
+
theme.dim(separator),
|
|
243
|
+
theme.primary(tagline)
|
|
244
|
+
].join("");
|
|
245
|
+
}
|
|
246
|
+
function emitBanner(version) {
|
|
247
|
+
if (bannerEmitted) return;
|
|
248
|
+
if (!isInteractive()) return;
|
|
249
|
+
if (process.env.TC_HIDE_BANNER === "1") return;
|
|
250
|
+
bannerEmitted = true;
|
|
251
|
+
process.stderr.write(formatBannerLine(version) + "\n\n");
|
|
252
|
+
}
|
|
253
|
+
|
|
84
254
|
// src/config/profiles.ts
|
|
85
255
|
import { join as join2 } from "path";
|
|
86
256
|
import { rm as rm2 } from "fs/promises";
|
|
@@ -344,8 +514,33 @@ function buildAuthUrl(did, options = {}) {
|
|
|
344
514
|
return `${OPENKEY_BASE}/delegate?${params.toString()}`;
|
|
345
515
|
}
|
|
346
516
|
async function callbackFlow(did, options = {}) {
|
|
347
|
-
return new Promise((
|
|
517
|
+
return new Promise((resolve3, reject) => {
|
|
348
518
|
let timeout;
|
|
519
|
+
let settled = false;
|
|
520
|
+
let rl;
|
|
521
|
+
function settle(result) {
|
|
522
|
+
if (settled) return;
|
|
523
|
+
settled = true;
|
|
524
|
+
clearTimeout(timeout);
|
|
525
|
+
server.close();
|
|
526
|
+
if (rl) {
|
|
527
|
+
rl.close();
|
|
528
|
+
}
|
|
529
|
+
if (result.data) {
|
|
530
|
+
resolve3(result.data);
|
|
531
|
+
} else {
|
|
532
|
+
reject(result.error);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
function parsePasteInput(input) {
|
|
536
|
+
const trimmed = input.trim();
|
|
537
|
+
try {
|
|
538
|
+
return JSON.parse(trimmed);
|
|
539
|
+
} catch {
|
|
540
|
+
const decoded = Buffer.from(trimmed, "base64").toString("utf-8");
|
|
541
|
+
return JSON.parse(decoded);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
349
544
|
const server = createServer((req, res) => {
|
|
350
545
|
if (req.method === "POST" && req.url === "/callback") {
|
|
351
546
|
let body = "";
|
|
@@ -360,13 +555,11 @@ async function callbackFlow(did, options = {}) {
|
|
|
360
555
|
"Access-Control-Allow-Origin": "*"
|
|
361
556
|
});
|
|
362
557
|
res.end(JSON.stringify({ success: true }));
|
|
363
|
-
|
|
364
|
-
server.close();
|
|
365
|
-
resolve(data);
|
|
558
|
+
settle({ data });
|
|
366
559
|
} catch (err) {
|
|
367
560
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
368
561
|
res.end(JSON.stringify({ error: "Invalid JSON" }));
|
|
369
|
-
|
|
562
|
+
settle({ error: new Error("Invalid delegation data received") });
|
|
370
563
|
}
|
|
371
564
|
});
|
|
372
565
|
} else if (req.method === "OPTIONS") {
|
|
@@ -384,7 +577,7 @@ async function callbackFlow(did, options = {}) {
|
|
|
384
577
|
server.listen(0, "127.0.0.1", async () => {
|
|
385
578
|
const addr = server.address();
|
|
386
579
|
if (!addr || typeof addr === "string") {
|
|
387
|
-
|
|
580
|
+
settle({ error: new Error("Failed to start callback server") });
|
|
388
581
|
return;
|
|
389
582
|
}
|
|
390
583
|
const port = addr.port;
|
|
@@ -401,10 +594,26 @@ async function callbackFlow(did, options = {}) {
|
|
|
401
594
|
server.close();
|
|
402
595
|
throw new Error("Failed to open browser");
|
|
403
596
|
}
|
|
597
|
+
if (isInteractive()) {
|
|
598
|
+
console.error(`
|
|
599
|
+
If the browser can't connect back, paste the delegation code here:`);
|
|
600
|
+
rl = createInterface({
|
|
601
|
+
input: process.stdin,
|
|
602
|
+
output: process.stderr
|
|
603
|
+
});
|
|
604
|
+
rl.on("line", (input) => {
|
|
605
|
+
if (settled) return;
|
|
606
|
+
try {
|
|
607
|
+
const data = parsePasteInput(input);
|
|
608
|
+
settle({ data });
|
|
609
|
+
} catch {
|
|
610
|
+
console.error("Invalid delegation code. Expected JSON or base64-encoded JSON. Try again:");
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
}
|
|
404
614
|
});
|
|
405
615
|
timeout = setTimeout(() => {
|
|
406
|
-
|
|
407
|
-
reject(new Error("Authentication timed out after 5 minutes"));
|
|
616
|
+
settle({ error: new Error("Authentication timed out after 5 minutes") });
|
|
408
617
|
}, 5 * 60 * 1e3);
|
|
409
618
|
});
|
|
410
619
|
}
|
|
@@ -419,17 +628,17 @@ Open this URL in a browser to authenticate:
|
|
|
419
628
|
input: process.stdin,
|
|
420
629
|
output: process.stderr
|
|
421
630
|
});
|
|
422
|
-
return new Promise((
|
|
631
|
+
return new Promise((resolve3, reject) => {
|
|
423
632
|
rl.question("Paste delegation code: ", (input) => {
|
|
424
633
|
rl.close();
|
|
425
634
|
try {
|
|
426
635
|
const data = JSON.parse(input.trim());
|
|
427
|
-
|
|
636
|
+
resolve3(data);
|
|
428
637
|
} catch {
|
|
429
638
|
try {
|
|
430
639
|
const decoded = Buffer.from(input.trim(), "base64").toString("utf-8");
|
|
431
640
|
const data = JSON.parse(decoded);
|
|
432
|
-
|
|
641
|
+
resolve3(data);
|
|
433
642
|
} catch {
|
|
434
643
|
reject(new Error("Invalid delegation code. Expected JSON or base64-encoded JSON."));
|
|
435
644
|
}
|
|
@@ -564,15 +773,27 @@ function registerAuthCommand(program2) {
|
|
|
564
773
|
} catch {
|
|
565
774
|
profile = null;
|
|
566
775
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
776
|
+
const authenticated = session !== null;
|
|
777
|
+
if (shouldOutputJson()) {
|
|
778
|
+
outputJson({
|
|
779
|
+
authenticated,
|
|
780
|
+
did: profile?.did ?? null,
|
|
781
|
+
primaryDid: profile?.primaryDid ?? null,
|
|
782
|
+
spaceId: profile?.spaceId ?? null,
|
|
783
|
+
host: ctx.host,
|
|
784
|
+
profile: ctx.profile,
|
|
785
|
+
hasKey: hasKey !== null
|
|
786
|
+
});
|
|
787
|
+
} else {
|
|
788
|
+
process.stdout.write(theme.heading("Authentication Status") + "\n");
|
|
789
|
+
process.stdout.write(formatField("Profile", ctx.profile) + "\n");
|
|
790
|
+
process.stdout.write(formatField("Authenticated", authenticated) + "\n");
|
|
791
|
+
process.stdout.write(formatField("Host", ctx.host) + "\n");
|
|
792
|
+
process.stdout.write(formatField("DID", profile?.did ?? null) + "\n");
|
|
793
|
+
process.stdout.write(formatField("Primary DID", profile?.primaryDid ?? null) + "\n");
|
|
794
|
+
process.stdout.write(formatField("Space ID", profile?.spaceId ?? null) + "\n");
|
|
795
|
+
process.stdout.write(formatField("Has Key", hasKey !== null) + "\n");
|
|
796
|
+
}
|
|
576
797
|
} catch (error) {
|
|
577
798
|
handleError(error);
|
|
578
799
|
}
|
|
@@ -583,14 +804,25 @@ function registerAuthCommand(program2) {
|
|
|
583
804
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
584
805
|
const profile = await ProfileManager.getProfile(ctx.profile);
|
|
585
806
|
const session = await ProfileManager.getSession(ctx.profile);
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
807
|
+
const authenticated = session !== null;
|
|
808
|
+
if (shouldOutputJson()) {
|
|
809
|
+
outputJson({
|
|
810
|
+
profile: ctx.profile,
|
|
811
|
+
did: profile.did,
|
|
812
|
+
primaryDid: profile.primaryDid ?? null,
|
|
813
|
+
spaceId: profile.spaceId ?? null,
|
|
814
|
+
host: profile.host,
|
|
815
|
+
authenticated
|
|
816
|
+
});
|
|
817
|
+
} else {
|
|
818
|
+
process.stdout.write(theme.heading("Identity") + "\n");
|
|
819
|
+
process.stdout.write(formatField("Profile", ctx.profile) + "\n");
|
|
820
|
+
process.stdout.write(formatField("DID", profile.did) + "\n");
|
|
821
|
+
process.stdout.write(formatField("Primary DID", profile.primaryDid ?? null) + "\n");
|
|
822
|
+
process.stdout.write(formatField("Space ID", profile.spaceId ?? null) + "\n");
|
|
823
|
+
process.stdout.write(formatField("Host", profile.host) + "\n");
|
|
824
|
+
process.stdout.write(formatField("Authenticated", authenticated) + "\n");
|
|
825
|
+
}
|
|
594
826
|
} catch (error) {
|
|
595
827
|
handleError(error);
|
|
596
828
|
}
|
|
@@ -680,11 +912,16 @@ function registerKvCommand(program2) {
|
|
|
680
912
|
process.stdout.write(content);
|
|
681
913
|
return;
|
|
682
914
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
915
|
+
if (shouldOutputJson()) {
|
|
916
|
+
outputJson({
|
|
917
|
+
key,
|
|
918
|
+
data,
|
|
919
|
+
metadata
|
|
920
|
+
});
|
|
921
|
+
} else {
|
|
922
|
+
const content = typeof data === "string" ? data : JSON.stringify(data);
|
|
923
|
+
process.stdout.write(content + "\n");
|
|
924
|
+
}
|
|
688
925
|
} catch (error) {
|
|
689
926
|
handleError(error);
|
|
690
927
|
}
|
|
@@ -748,11 +985,24 @@ function registerKvCommand(program2) {
|
|
|
748
985
|
}
|
|
749
986
|
const rawData = result.data.data ?? result.data;
|
|
750
987
|
const keyList = Array.isArray(rawData) ? rawData : rawData?.keys ?? [];
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
988
|
+
if (shouldOutputJson()) {
|
|
989
|
+
outputJson({
|
|
990
|
+
keys: keyList,
|
|
991
|
+
count: keyList.length,
|
|
992
|
+
prefix: options.prefix ?? null
|
|
993
|
+
});
|
|
994
|
+
} else {
|
|
995
|
+
if (keyList.length === 0) {
|
|
996
|
+
process.stdout.write(theme.muted("No keys found.") + "\n");
|
|
997
|
+
} else {
|
|
998
|
+
const rows = keyList.map((e) => [
|
|
999
|
+
e.key || e,
|
|
1000
|
+
e.contentLength ? formatBytes(e.contentLength) : "\u2014",
|
|
1001
|
+
e.updatedAt ? formatTimeAgo(e.updatedAt) : "\u2014"
|
|
1002
|
+
]);
|
|
1003
|
+
process.stdout.write(formatTable(["Key", "Size", "Updated"], rows) + "\n");
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
756
1006
|
} catch (error) {
|
|
757
1007
|
handleError(error);
|
|
758
1008
|
}
|
|
@@ -793,7 +1043,20 @@ function registerSpaceCommand(program2) {
|
|
|
793
1043
|
if (!result.ok) {
|
|
794
1044
|
throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
|
|
795
1045
|
}
|
|
796
|
-
|
|
1046
|
+
if (shouldOutputJson()) {
|
|
1047
|
+
outputJson({ spaces: result.data, count: result.data.length });
|
|
1048
|
+
} else {
|
|
1049
|
+
if (result.data.length === 0) {
|
|
1050
|
+
process.stdout.write(theme.muted("No spaces found.") + "\n");
|
|
1051
|
+
} else {
|
|
1052
|
+
const rows = result.data.map((s) => [
|
|
1053
|
+
s.id || s.spaceId || "\u2014",
|
|
1054
|
+
s.name || "\u2014",
|
|
1055
|
+
s.owner || "\u2014"
|
|
1056
|
+
]);
|
|
1057
|
+
process.stdout.write(formatTable(["Space ID", "Name", "Owner"], rows) + "\n");
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
797
1060
|
} catch (error) {
|
|
798
1061
|
handleError(error);
|
|
799
1062
|
}
|
|
@@ -1154,10 +1417,20 @@ function registerProfileCommand(program2) {
|
|
|
1154
1417
|
}
|
|
1155
1418
|
})
|
|
1156
1419
|
);
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1420
|
+
if (shouldOutputJson()) {
|
|
1421
|
+
outputJson({
|
|
1422
|
+
profiles,
|
|
1423
|
+
defaultProfile: config.defaultProfile
|
|
1424
|
+
});
|
|
1425
|
+
} else {
|
|
1426
|
+
for (const p of profiles) {
|
|
1427
|
+
const marker = p.active ? theme.success("\u25CF ") : " ";
|
|
1428
|
+
const name = p.active ? theme.brand(p.name) : p.name;
|
|
1429
|
+
const host = theme.muted(p.host || "no host");
|
|
1430
|
+
process.stdout.write(`${marker}${name} ${host}
|
|
1431
|
+
`);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1161
1434
|
} catch (error) {
|
|
1162
1435
|
handleError(error);
|
|
1163
1436
|
}
|
|
@@ -1194,12 +1467,24 @@ function registerProfileCommand(program2) {
|
|
|
1194
1467
|
const hasKey = await ProfileManager.getKey(profileName) !== null;
|
|
1195
1468
|
const hasSession = await ProfileManager.getSession(profileName) !== null;
|
|
1196
1469
|
const config = await ProfileManager.getConfig();
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1470
|
+
const isDefault = profileName === config.defaultProfile;
|
|
1471
|
+
if (shouldOutputJson()) {
|
|
1472
|
+
outputJson({
|
|
1473
|
+
...p,
|
|
1474
|
+
hasKey,
|
|
1475
|
+
hasSession,
|
|
1476
|
+
isDefault
|
|
1477
|
+
});
|
|
1478
|
+
} else {
|
|
1479
|
+
process.stdout.write(`${theme.heading(p.name)}${isDefault ? theme.success(" (default)") : ""}
|
|
1480
|
+
`);
|
|
1481
|
+
process.stdout.write(formatField("Host", p.host) + "\n");
|
|
1482
|
+
process.stdout.write(formatField("DID", p.did) + "\n");
|
|
1483
|
+
process.stdout.write(formatField("Space", p.spaceId || null) + "\n");
|
|
1484
|
+
process.stdout.write(formatField("Key", hasKey) + "\n");
|
|
1485
|
+
process.stdout.write(formatField("Session", hasSession) + "\n");
|
|
1486
|
+
process.stdout.write(formatField("Created", p.createdAt) + "\n");
|
|
1487
|
+
}
|
|
1203
1488
|
} catch (error) {
|
|
1204
1489
|
handleError(error);
|
|
1205
1490
|
}
|
|
@@ -1220,8 +1505,8 @@ function registerProfileCommand(program2) {
|
|
|
1220
1505
|
try {
|
|
1221
1506
|
if (isInteractive()) {
|
|
1222
1507
|
const rl = createInterface2({ input: process.stdin, output: process.stderr });
|
|
1223
|
-
const answer = await new Promise((
|
|
1224
|
-
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);
|
|
1225
1510
|
});
|
|
1226
1511
|
rl.close();
|
|
1227
1512
|
if (answer.toLowerCase() !== "y") {
|
|
@@ -1540,9 +1825,694 @@ function registerVaultCommand(program2) {
|
|
|
1540
1825
|
});
|
|
1541
1826
|
}
|
|
1542
1827
|
|
|
1828
|
+
// src/commands/secrets.ts
|
|
1829
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
1830
|
+
import { writeFile as writeFile4 } from "fs/promises";
|
|
1831
|
+
import { PrivateKeySigner as PrivateKeySigner2 } from "@tinycloud/node-sdk";
|
|
1832
|
+
var SECRETS_PREFIX = "secrets/";
|
|
1833
|
+
async function readStdin3() {
|
|
1834
|
+
const chunks = [];
|
|
1835
|
+
for await (const chunk of process.stdin) {
|
|
1836
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
1837
|
+
}
|
|
1838
|
+
return Buffer.concat(chunks);
|
|
1839
|
+
}
|
|
1840
|
+
function resolvePrivateKey2(options) {
|
|
1841
|
+
const key = options.privateKey || process.env.TC_PRIVATE_KEY;
|
|
1842
|
+
if (!key) {
|
|
1843
|
+
throw new CLIError(
|
|
1844
|
+
"AUTH_REQUIRED",
|
|
1845
|
+
"Private key required. Use --private-key <hex> or set TC_PRIVATE_KEY env var.",
|
|
1846
|
+
ExitCode.AUTH_REQUIRED
|
|
1847
|
+
);
|
|
1848
|
+
}
|
|
1849
|
+
return key;
|
|
1850
|
+
}
|
|
1851
|
+
async function unlockVault2(node, privateKey) {
|
|
1852
|
+
const signer = new PrivateKeySigner2(privateKey);
|
|
1853
|
+
const result = await node.vault.unlock(signer);
|
|
1854
|
+
if (result && !result.ok) {
|
|
1855
|
+
throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
function registerSecretsCommand(program2) {
|
|
1859
|
+
const secrets = program2.command("secrets").description("Encrypted secrets management");
|
|
1860
|
+
secrets.command("list").description("List secrets").option("--space <spaceId>", "Space to list secrets from (for delegated access)").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (options, cmd) => {
|
|
1861
|
+
try {
|
|
1862
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1863
|
+
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
1864
|
+
const privateKey = resolvePrivateKey2(options);
|
|
1865
|
+
const node = await ensureAuthenticated(ctx, { privateKey });
|
|
1866
|
+
await withSpinner("Unlocking vault...", () => unlockVault2(node, privateKey));
|
|
1867
|
+
if (options.space) {
|
|
1868
|
+
throw new CLIError(
|
|
1869
|
+
"NOT_IMPLEMENTED",
|
|
1870
|
+
`Listing secrets from a delegated space (${options.space}) is not yet supported at the SDK level. The vault service currently operates on the space bound to the active session. SDK support for cross-space vault operations is planned.`,
|
|
1871
|
+
ExitCode.ERROR
|
|
1872
|
+
);
|
|
1873
|
+
}
|
|
1874
|
+
const result = await withSpinner("Listing secrets...", () => node.vault.list({ prefix: SECRETS_PREFIX }));
|
|
1875
|
+
if (!result.ok) {
|
|
1876
|
+
throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
|
|
1877
|
+
}
|
|
1878
|
+
const keys = result.data.data ?? result.data;
|
|
1879
|
+
const keyList = Array.isArray(keys) ? keys : [];
|
|
1880
|
+
const secretNames = keyList.map(
|
|
1881
|
+
(k) => typeof k === "string" && k.startsWith(SECRETS_PREFIX) ? k.slice(SECRETS_PREFIX.length) : k
|
|
1882
|
+
);
|
|
1883
|
+
outputJson({
|
|
1884
|
+
secrets: secretNames,
|
|
1885
|
+
count: secretNames.length,
|
|
1886
|
+
...options.space ? { space: options.space } : {}
|
|
1887
|
+
});
|
|
1888
|
+
} catch (error) {
|
|
1889
|
+
handleError(error);
|
|
1890
|
+
}
|
|
1891
|
+
});
|
|
1892
|
+
secrets.command("get <name>").description("Get a secret value").option("--raw", "Output raw value (no JSON wrapping)").option("-o, --output <file>", "Write value to file").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (name, options, cmd) => {
|
|
1893
|
+
try {
|
|
1894
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1895
|
+
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
1896
|
+
const privateKey = resolvePrivateKey2(options);
|
|
1897
|
+
const node = await ensureAuthenticated(ctx, { privateKey });
|
|
1898
|
+
await withSpinner("Unlocking vault...", () => unlockVault2(node, privateKey));
|
|
1899
|
+
const vaultKey = `${SECRETS_PREFIX}${name}`;
|
|
1900
|
+
const result = await withSpinner(`Getting secret ${name}...`, () => node.vault.get(vaultKey));
|
|
1901
|
+
if (!result.ok) {
|
|
1902
|
+
if (result.error.code === "NOT_FOUND") {
|
|
1903
|
+
throw new CLIError("NOT_FOUND", `Secret "${name}" not found`, ExitCode.NOT_FOUND);
|
|
1904
|
+
}
|
|
1905
|
+
throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
|
|
1906
|
+
}
|
|
1907
|
+
const data = result.data.data ?? result.data;
|
|
1908
|
+
let value;
|
|
1909
|
+
if (typeof data === "string") {
|
|
1910
|
+
try {
|
|
1911
|
+
const parsed = JSON.parse(data);
|
|
1912
|
+
value = parsed.value;
|
|
1913
|
+
} catch {
|
|
1914
|
+
value = data;
|
|
1915
|
+
}
|
|
1916
|
+
} else if (data instanceof Uint8Array) {
|
|
1917
|
+
try {
|
|
1918
|
+
const parsed = JSON.parse(Buffer.from(data).toString("utf-8"));
|
|
1919
|
+
value = parsed.value;
|
|
1920
|
+
} catch {
|
|
1921
|
+
value = Buffer.from(data).toString("utf-8");
|
|
1922
|
+
}
|
|
1923
|
+
} else {
|
|
1924
|
+
value = data.value ?? data;
|
|
1925
|
+
}
|
|
1926
|
+
if (options.output) {
|
|
1927
|
+
await writeFile4(options.output, value);
|
|
1928
|
+
outputJson({ name, written: options.output });
|
|
1929
|
+
return;
|
|
1930
|
+
}
|
|
1931
|
+
if (options.raw) {
|
|
1932
|
+
process.stdout.write(value);
|
|
1933
|
+
return;
|
|
1934
|
+
}
|
|
1935
|
+
outputJson({ name, value });
|
|
1936
|
+
} catch (error) {
|
|
1937
|
+
handleError(error);
|
|
1938
|
+
}
|
|
1939
|
+
});
|
|
1940
|
+
secrets.command("put <name> [value]").description("Store a secret").option("--file <path>", "Read value from file").option("--stdin", "Read value from stdin").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (name, value, options, cmd) => {
|
|
1941
|
+
try {
|
|
1942
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1943
|
+
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
1944
|
+
const privateKey = resolvePrivateKey2(options);
|
|
1945
|
+
const node = await ensureAuthenticated(ctx, { privateKey });
|
|
1946
|
+
await withSpinner("Unlocking vault...", () => unlockVault2(node, privateKey));
|
|
1947
|
+
let secretValue;
|
|
1948
|
+
const sources = [value !== void 0, !!options.file, !!options.stdin].filter(Boolean);
|
|
1949
|
+
if (sources.length === 0) {
|
|
1950
|
+
throw new CLIError("USAGE_ERROR", "Must provide a value, --file, or --stdin", ExitCode.USAGE_ERROR);
|
|
1951
|
+
}
|
|
1952
|
+
if (sources.length > 1) {
|
|
1953
|
+
throw new CLIError("USAGE_ERROR", "Provide only one of: value argument, --file, or --stdin", ExitCode.USAGE_ERROR);
|
|
1954
|
+
}
|
|
1955
|
+
if (options.file) {
|
|
1956
|
+
secretValue = await readFile4(options.file, "utf-8");
|
|
1957
|
+
} else if (options.stdin) {
|
|
1958
|
+
secretValue = (await readStdin3()).toString("utf-8");
|
|
1959
|
+
} else {
|
|
1960
|
+
secretValue = value;
|
|
1961
|
+
}
|
|
1962
|
+
const payload = JSON.stringify({
|
|
1963
|
+
value: secretValue,
|
|
1964
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1965
|
+
});
|
|
1966
|
+
const vaultKey = `${SECRETS_PREFIX}${name}`;
|
|
1967
|
+
const result = await withSpinner(`Storing secret ${name}...`, () => node.vault.put(vaultKey, payload));
|
|
1968
|
+
if (!result.ok) {
|
|
1969
|
+
throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
|
|
1970
|
+
}
|
|
1971
|
+
outputJson({ name, written: true });
|
|
1972
|
+
} catch (error) {
|
|
1973
|
+
handleError(error);
|
|
1974
|
+
}
|
|
1975
|
+
});
|
|
1976
|
+
secrets.command("delete <name>").description("Delete a secret").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (name, options, cmd) => {
|
|
1977
|
+
try {
|
|
1978
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1979
|
+
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
1980
|
+
const privateKey = resolvePrivateKey2(options);
|
|
1981
|
+
const node = await ensureAuthenticated(ctx, { privateKey });
|
|
1982
|
+
await withSpinner("Unlocking vault...", () => unlockVault2(node, privateKey));
|
|
1983
|
+
const vaultKey = `${SECRETS_PREFIX}${name}`;
|
|
1984
|
+
const result = await withSpinner(`Deleting secret ${name}...`, () => node.vault.delete(vaultKey));
|
|
1985
|
+
if (!result.ok) {
|
|
1986
|
+
throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
|
|
1987
|
+
}
|
|
1988
|
+
outputJson({ name, deleted: true });
|
|
1989
|
+
} catch (error) {
|
|
1990
|
+
handleError(error);
|
|
1991
|
+
}
|
|
1992
|
+
});
|
|
1993
|
+
secrets.command("manage").description("Open the TinyCloud Secrets Manager in your browser").action(async () => {
|
|
1994
|
+
try {
|
|
1995
|
+
const open = (await import("open")).default;
|
|
1996
|
+
await open("https://secrets.tinycloud.xyz");
|
|
1997
|
+
outputJson({ opened: "https://secrets.tinycloud.xyz" });
|
|
1998
|
+
} catch (error) {
|
|
1999
|
+
handleError(error);
|
|
2000
|
+
}
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
// src/commands/vars.ts
|
|
2005
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
2006
|
+
import { writeFile as writeFile5 } from "fs/promises";
|
|
2007
|
+
var VARIABLES_PREFIX = "variables/";
|
|
2008
|
+
async function readStdin4() {
|
|
2009
|
+
const chunks = [];
|
|
2010
|
+
for await (const chunk of process.stdin) {
|
|
2011
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
2012
|
+
}
|
|
2013
|
+
return Buffer.concat(chunks);
|
|
2014
|
+
}
|
|
2015
|
+
function resolvePrivateKey3(options) {
|
|
2016
|
+
const key = options.privateKey || process.env.TC_PRIVATE_KEY;
|
|
2017
|
+
if (!key) {
|
|
2018
|
+
throw new CLIError(
|
|
2019
|
+
"AUTH_REQUIRED",
|
|
2020
|
+
"Private key required. Use --private-key <hex> or set TC_PRIVATE_KEY env var.",
|
|
2021
|
+
ExitCode.AUTH_REQUIRED
|
|
2022
|
+
);
|
|
2023
|
+
}
|
|
2024
|
+
return key;
|
|
2025
|
+
}
|
|
2026
|
+
function registerVarsCommand(program2) {
|
|
2027
|
+
const vars = program2.command("vars").description("Plaintext variable management");
|
|
2028
|
+
vars.command("list").description("List variables").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (options, cmd) => {
|
|
2029
|
+
try {
|
|
2030
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2031
|
+
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
2032
|
+
const privateKey = resolvePrivateKey3(options);
|
|
2033
|
+
const node = await ensureAuthenticated(ctx, { privateKey });
|
|
2034
|
+
const prefixedKv = node.kv.withPrefix(VARIABLES_PREFIX);
|
|
2035
|
+
const result = await withSpinner("Listing variables...", () => prefixedKv.list());
|
|
2036
|
+
if (!result.ok) {
|
|
2037
|
+
throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
|
|
2038
|
+
}
|
|
2039
|
+
const rawData = result.data.data ?? result.data;
|
|
2040
|
+
const keyList = Array.isArray(rawData) ? rawData : rawData?.keys ?? [];
|
|
2041
|
+
outputJson({
|
|
2042
|
+
variables: keyList,
|
|
2043
|
+
count: keyList.length
|
|
2044
|
+
});
|
|
2045
|
+
} catch (error) {
|
|
2046
|
+
handleError(error);
|
|
2047
|
+
}
|
|
2048
|
+
});
|
|
2049
|
+
vars.command("get <name>").description("Get a variable value").option("--raw", "Output raw value (no JSON wrapping)").option("-o, --output <file>", "Write value to file").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (name, options, cmd) => {
|
|
2050
|
+
try {
|
|
2051
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2052
|
+
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
2053
|
+
const privateKey = resolvePrivateKey3(options);
|
|
2054
|
+
const node = await ensureAuthenticated(ctx, { privateKey });
|
|
2055
|
+
const prefixedKv = node.kv.withPrefix(VARIABLES_PREFIX);
|
|
2056
|
+
const result = await withSpinner(`Getting variable ${name}...`, () => prefixedKv.get(name));
|
|
2057
|
+
if (!result.ok) {
|
|
2058
|
+
if (result.error.code === "KV_NOT_FOUND" || result.error.code === "NOT_FOUND") {
|
|
2059
|
+
throw new CLIError("NOT_FOUND", `Variable "${name}" not found`, ExitCode.NOT_FOUND);
|
|
2060
|
+
}
|
|
2061
|
+
throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
|
|
2062
|
+
}
|
|
2063
|
+
const data = result.data.data;
|
|
2064
|
+
let value;
|
|
2065
|
+
if (typeof data === "string") {
|
|
2066
|
+
try {
|
|
2067
|
+
const parsed = JSON.parse(data);
|
|
2068
|
+
value = parsed.value;
|
|
2069
|
+
} catch {
|
|
2070
|
+
value = data;
|
|
2071
|
+
}
|
|
2072
|
+
} else if (data && typeof data === "object" && "value" in data) {
|
|
2073
|
+
value = data.value;
|
|
2074
|
+
} else {
|
|
2075
|
+
value = typeof data === "string" ? data : JSON.stringify(data);
|
|
2076
|
+
}
|
|
2077
|
+
if (options.output) {
|
|
2078
|
+
await writeFile5(options.output, value);
|
|
2079
|
+
outputJson({ name, written: options.output });
|
|
2080
|
+
return;
|
|
2081
|
+
}
|
|
2082
|
+
if (options.raw) {
|
|
2083
|
+
process.stdout.write(value);
|
|
2084
|
+
return;
|
|
2085
|
+
}
|
|
2086
|
+
outputJson({ name, value });
|
|
2087
|
+
} catch (error) {
|
|
2088
|
+
handleError(error);
|
|
2089
|
+
}
|
|
2090
|
+
});
|
|
2091
|
+
vars.command("put <name> [value]").description("Set a variable").option("--file <path>", "Read value from file").option("--stdin", "Read value from stdin").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (name, value, options, cmd) => {
|
|
2092
|
+
try {
|
|
2093
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2094
|
+
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
2095
|
+
const privateKey = resolvePrivateKey3(options);
|
|
2096
|
+
const node = await ensureAuthenticated(ctx, { privateKey });
|
|
2097
|
+
let varValue;
|
|
2098
|
+
const sources = [value !== void 0, !!options.file, !!options.stdin].filter(Boolean);
|
|
2099
|
+
if (sources.length === 0) {
|
|
2100
|
+
throw new CLIError("USAGE_ERROR", "Must provide a value, --file, or --stdin", ExitCode.USAGE_ERROR);
|
|
2101
|
+
}
|
|
2102
|
+
if (sources.length > 1) {
|
|
2103
|
+
throw new CLIError("USAGE_ERROR", "Provide only one of: value argument, --file, or --stdin", ExitCode.USAGE_ERROR);
|
|
2104
|
+
}
|
|
2105
|
+
if (options.file) {
|
|
2106
|
+
varValue = await readFile5(options.file, "utf-8");
|
|
2107
|
+
} else if (options.stdin) {
|
|
2108
|
+
varValue = (await readStdin4()).toString("utf-8");
|
|
2109
|
+
} else {
|
|
2110
|
+
varValue = value;
|
|
2111
|
+
}
|
|
2112
|
+
const payload = {
|
|
2113
|
+
value: varValue,
|
|
2114
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2115
|
+
};
|
|
2116
|
+
const prefixedKv = node.kv.withPrefix(VARIABLES_PREFIX);
|
|
2117
|
+
const result = await withSpinner(`Setting variable ${name}...`, () => prefixedKv.put(name, payload));
|
|
2118
|
+
if (!result.ok) {
|
|
2119
|
+
throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
|
|
2120
|
+
}
|
|
2121
|
+
outputJson({ name, written: true });
|
|
2122
|
+
} catch (error) {
|
|
2123
|
+
handleError(error);
|
|
2124
|
+
}
|
|
2125
|
+
});
|
|
2126
|
+
vars.command("delete <name>").description("Delete a variable").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (name, options, cmd) => {
|
|
2127
|
+
try {
|
|
2128
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2129
|
+
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
2130
|
+
const privateKey = resolvePrivateKey3(options);
|
|
2131
|
+
const node = await ensureAuthenticated(ctx, { privateKey });
|
|
2132
|
+
const prefixedKv = node.kv.withPrefix(VARIABLES_PREFIX);
|
|
2133
|
+
const result = await withSpinner(`Deleting variable ${name}...`, () => prefixedKv.delete(name));
|
|
2134
|
+
if (!result.ok) {
|
|
2135
|
+
throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
|
|
2136
|
+
}
|
|
2137
|
+
outputJson({ name, deleted: true });
|
|
2138
|
+
} catch (error) {
|
|
2139
|
+
handleError(error);
|
|
2140
|
+
}
|
|
2141
|
+
});
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
// src/commands/doctor.ts
|
|
2145
|
+
function registerDoctorCommand(program2) {
|
|
2146
|
+
program2.command("doctor").description("Run diagnostic checks").action(async (_options, cmd) => {
|
|
2147
|
+
try {
|
|
2148
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
2149
|
+
const checks = [];
|
|
2150
|
+
const nodeVersion = process.version;
|
|
2151
|
+
const nodeOk = parseInt(nodeVersion.slice(1)) >= 18;
|
|
2152
|
+
checks.push({ name: "Node.js", ok: nodeOk, detail: nodeVersion });
|
|
2153
|
+
let profileName = globalOpts.profile;
|
|
2154
|
+
let profileOk = false;
|
|
2155
|
+
let profileDetail = "";
|
|
2156
|
+
try {
|
|
2157
|
+
const config = await ProfileManager.getConfig();
|
|
2158
|
+
profileName = profileName || config.defaultProfile;
|
|
2159
|
+
const profile = await ProfileManager.getProfile(profileName);
|
|
2160
|
+
profileOk = true;
|
|
2161
|
+
profileDetail = `"${profileName}" at ${profile.host}`;
|
|
2162
|
+
} catch {
|
|
2163
|
+
profileDetail = profileName ? `"${profileName}" not found` : "no profiles configured";
|
|
2164
|
+
}
|
|
2165
|
+
checks.push({ name: "Profile", ok: profileOk, detail: profileDetail });
|
|
2166
|
+
let keyOk = false;
|
|
2167
|
+
let keyDetail = "";
|
|
2168
|
+
if (profileOk && profileName) {
|
|
2169
|
+
try {
|
|
2170
|
+
const key = await ProfileManager.getKey(profileName);
|
|
2171
|
+
keyOk = key !== null;
|
|
2172
|
+
if (keyOk) {
|
|
2173
|
+
const profile = await ProfileManager.getProfile(profileName);
|
|
2174
|
+
keyDetail = profile.did ? `${profile.did.slice(0, 20)}...` : "key found";
|
|
2175
|
+
} else {
|
|
2176
|
+
keyDetail = "no key \u2014 run tc init";
|
|
2177
|
+
}
|
|
2178
|
+
} catch {
|
|
2179
|
+
keyDetail = "error reading key";
|
|
2180
|
+
}
|
|
2181
|
+
} else {
|
|
2182
|
+
keyDetail = "skipped (no profile)";
|
|
2183
|
+
}
|
|
2184
|
+
checks.push({ name: "Key", ok: keyOk, detail: keyDetail });
|
|
2185
|
+
let sessionOk = false;
|
|
2186
|
+
let sessionDetail = "";
|
|
2187
|
+
if (profileOk && profileName) {
|
|
2188
|
+
try {
|
|
2189
|
+
const session = await ProfileManager.getSession(profileName);
|
|
2190
|
+
sessionOk = session !== null;
|
|
2191
|
+
sessionDetail = sessionOk ? "active" : "no session \u2014 run tc auth login";
|
|
2192
|
+
} catch {
|
|
2193
|
+
sessionDetail = "error reading session";
|
|
2194
|
+
}
|
|
2195
|
+
} else {
|
|
2196
|
+
sessionDetail = "skipped (no profile)";
|
|
2197
|
+
}
|
|
2198
|
+
checks.push({ name: "Session", ok: sessionOk, detail: sessionDetail });
|
|
2199
|
+
let nodeReachable = false;
|
|
2200
|
+
let nodeDetail = "";
|
|
2201
|
+
try {
|
|
2202
|
+
const host = profileOk && profileName ? (await ProfileManager.getProfile(profileName)).host : globalOpts.host || DEFAULT_HOST;
|
|
2203
|
+
const start = Date.now();
|
|
2204
|
+
const response = await fetch(`${host}/health`);
|
|
2205
|
+
const latency = Date.now() - start;
|
|
2206
|
+
nodeReachable = response.ok;
|
|
2207
|
+
nodeDetail = nodeReachable ? `${host} (${latency}ms)` : `${host} returned ${response.status}`;
|
|
2208
|
+
} catch (e) {
|
|
2209
|
+
nodeDetail = `unreachable \u2014 ${e instanceof Error ? e.message : "connection failed"}`;
|
|
2210
|
+
}
|
|
2211
|
+
checks.push({ name: "Node", ok: nodeReachable, detail: nodeDetail });
|
|
2212
|
+
let spaceOk = false;
|
|
2213
|
+
let spaceDetail = "";
|
|
2214
|
+
if (sessionOk && profileName) {
|
|
2215
|
+
try {
|
|
2216
|
+
const profile = await ProfileManager.getProfile(profileName);
|
|
2217
|
+
spaceOk = Boolean(profile.spaceId);
|
|
2218
|
+
spaceDetail = spaceOk ? `${profile.spaceId.slice(0, 16)}...` : "no space \u2014 run tc space create";
|
|
2219
|
+
} catch {
|
|
2220
|
+
spaceDetail = "error checking space";
|
|
2221
|
+
}
|
|
2222
|
+
} else {
|
|
2223
|
+
spaceDetail = "skipped (no session)";
|
|
2224
|
+
}
|
|
2225
|
+
checks.push({ name: "Space", ok: spaceOk, detail: spaceDetail });
|
|
2226
|
+
const result = {
|
|
2227
|
+
checks,
|
|
2228
|
+
healthy: checks.every((c) => c.ok)
|
|
2229
|
+
};
|
|
2230
|
+
if (shouldOutputJson()) {
|
|
2231
|
+
outputJson(result);
|
|
2232
|
+
} else {
|
|
2233
|
+
process.stderr.write(formatSection("Diagnostics") + "\n");
|
|
2234
|
+
for (const check of checks) {
|
|
2235
|
+
process.stdout.write(formatCheck(check.ok, check.name, check.detail) + "\n");
|
|
2236
|
+
}
|
|
2237
|
+
process.stdout.write("\n");
|
|
2238
|
+
if (result.healthy) {
|
|
2239
|
+
process.stdout.write(theme.success("All checks passed.") + "\n");
|
|
2240
|
+
} else {
|
|
2241
|
+
const failed = checks.filter((c) => !c.ok).length;
|
|
2242
|
+
process.stdout.write(theme.warn(`${failed} check${failed > 1 ? "s" : ""} need attention.`) + "\n");
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
} catch (error) {
|
|
2246
|
+
handleError(error);
|
|
2247
|
+
}
|
|
2248
|
+
});
|
|
2249
|
+
}
|
|
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
|
+
|
|
1543
2487
|
// src/index.ts
|
|
1544
2488
|
var program = new Command();
|
|
1545
|
-
program.name("tc").description("TinyCloud CLI").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");
|
|
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");
|
|
2490
|
+
program.hook("preAction", async (thisCommand) => {
|
|
2491
|
+
const opts = thisCommand.optsWithGlobals();
|
|
2492
|
+
if (!opts.quiet) {
|
|
2493
|
+
emitBanner("0.1.1");
|
|
2494
|
+
}
|
|
2495
|
+
const commandName = thisCommand.name();
|
|
2496
|
+
const parentName = thisCommand.parent?.name();
|
|
2497
|
+
const fullCommand = parentName && parentName !== "tc" ? `${parentName} ${commandName}` : commandName;
|
|
2498
|
+
const skipGuard = ["tc", "init", "doctor", "completion", "help"].includes(commandName) || fullCommand === "profile create";
|
|
2499
|
+
if (!skipGuard && !opts.quiet && isInteractive()) {
|
|
2500
|
+
try {
|
|
2501
|
+
const config = await ProfileManager.getConfig();
|
|
2502
|
+
const profileName = opts.profile || config.defaultProfile;
|
|
2503
|
+
const hasProfile = await ProfileManager.profileExists(profileName);
|
|
2504
|
+
if (!hasProfile) {
|
|
2505
|
+
process.stderr.write(theme.warn("\u26A0 No profile configured.") + " " + theme.muted("Run: tc init") + "\n\n");
|
|
2506
|
+
} else {
|
|
2507
|
+
const key = await ProfileManager.getKey(profileName);
|
|
2508
|
+
if (!key) {
|
|
2509
|
+
process.stderr.write(theme.warn("\u26A0 No key found.") + " " + theme.muted("Run: tc init") + "\n\n");
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
} catch {
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
});
|
|
1546
2516
|
registerInitCommand(program);
|
|
1547
2517
|
registerAuthCommand(program);
|
|
1548
2518
|
registerKvCommand(program);
|
|
@@ -1553,6 +2523,26 @@ registerNodeCommand(program);
|
|
|
1553
2523
|
registerProfileCommand(program);
|
|
1554
2524
|
registerCompletionCommand(program);
|
|
1555
2525
|
registerVaultCommand(program);
|
|
2526
|
+
registerSecretsCommand(program);
|
|
2527
|
+
registerVarsCommand(program);
|
|
2528
|
+
registerDoctorCommand(program);
|
|
2529
|
+
registerSqlCommand(program);
|
|
2530
|
+
registerDuckdbCommand(program);
|
|
2531
|
+
program.addHelpText("afterAll", () => {
|
|
2532
|
+
if (!process.stdout.isTTY) return "";
|
|
2533
|
+
return `
|
|
2534
|
+
${theme.heading("Examples:")}
|
|
2535
|
+
${theme.command("tc init")} ${theme.muted("Set up a profile and generate keys")}
|
|
2536
|
+
${theme.command("tc auth login")} ${theme.muted("Authenticate via browser")}
|
|
2537
|
+
${theme.command('tc kv put greeting "Hello"')} ${theme.muted("Store a value")}
|
|
2538
|
+
${theme.command("tc kv list")} ${theme.muted("List all keys")}
|
|
2539
|
+
${theme.command("tc delegation create --to did:pkh:...")} ${theme.muted("Grant access to another user")}
|
|
2540
|
+
${theme.command("tc space list")} ${theme.muted("Show your spaces")}
|
|
2541
|
+
|
|
2542
|
+
${theme.muted("Docs:")} ${theme.accent("https://docs.tinycloud.xyz/cli")}
|
|
2543
|
+
${theme.muted("Repo:")} ${theme.accent("https://github.com/tinycloudlabs/web-sdk")}
|
|
2544
|
+
`;
|
|
2545
|
+
});
|
|
1556
2546
|
try {
|
|
1557
2547
|
await program.parseAsync(process.argv);
|
|
1558
2548
|
} catch (error) {
|