@tinycloud/cli 0.7.0-beta.1 → 0.7.0-beta.10
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 +878 -131
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -322,7 +322,8 @@ function wrapError(error) {
|
|
|
322
322
|
}
|
|
323
323
|
function handleError(error) {
|
|
324
324
|
const cliError = wrapError(error);
|
|
325
|
-
const
|
|
325
|
+
const prebuilt = typeof cliError.metadata?.hint === "string" ? cliError.metadata.hint : void 0;
|
|
326
|
+
const hint = prebuilt ?? buildAuthHint(cliError) ?? (cliError.code === "NETWORK_ERROR" ? buildNetworkHint() : void 0);
|
|
326
327
|
outputError(cliError.code, cliError.message, hint);
|
|
327
328
|
process.exit(cliError.exitCode);
|
|
328
329
|
}
|
|
@@ -815,12 +816,19 @@ function buildAuthUrl(did, options = {}) {
|
|
|
815
816
|
if (options.host) {
|
|
816
817
|
params.set("host", options.host);
|
|
817
818
|
}
|
|
819
|
+
const reason = typeof options.reason === "string" ? options.reason.trim() : "";
|
|
818
820
|
if (options.permissions?.length) {
|
|
819
821
|
params.set(
|
|
820
822
|
"permissions",
|
|
821
|
-
Buffer.from(JSON.stringify({
|
|
823
|
+
Buffer.from(JSON.stringify({
|
|
824
|
+
permissions: options.permissions,
|
|
825
|
+
...reason ? { reason } : {}
|
|
826
|
+
})).toString("base64url")
|
|
822
827
|
);
|
|
823
828
|
}
|
|
829
|
+
if (reason) {
|
|
830
|
+
params.set("reason", reason);
|
|
831
|
+
}
|
|
824
832
|
if (options.expiry !== void 0) {
|
|
825
833
|
params.set("expiry", String(options.expiry));
|
|
826
834
|
}
|
|
@@ -911,8 +919,8 @@ async function callbackFlow(did, options = {}) {
|
|
|
911
919
|
}
|
|
912
920
|
if (openBrowser) {
|
|
913
921
|
try {
|
|
914
|
-
const
|
|
915
|
-
await
|
|
922
|
+
const open2 = (await import("open")).default;
|
|
923
|
+
await open2(authUrl);
|
|
916
924
|
} catch {
|
|
917
925
|
server.close();
|
|
918
926
|
throw new Error("Failed to open browser");
|
|
@@ -973,11 +981,19 @@ Open this URL in a browser to authenticate:
|
|
|
973
981
|
|
|
974
982
|
// src/commands/init.ts
|
|
975
983
|
function registerInitCommand(program2) {
|
|
976
|
-
program2.command("init").description("Initialize a new TinyCloud profile").option("--name <profile>", "Profile name", "default").option("--key-only", "Only generate key, skip authentication").option("--host <url>", "TinyCloud node URL").option("--paste", "Use manual paste mode for authentication").option("--no-popup", "Print the OpenKey URL without opening a browser").action(async (options, cmd) => {
|
|
984
|
+
program2.command("init").description("Initialize a new TinyCloud profile").option("--name <profile>", "Profile name", "default").option("--key-only", "Only generate key, skip authentication").option("--host <url>", "TinyCloud node URL").option("--paste", "Use manual paste mode for authentication").option("--no-popup", "Print the OpenKey URL without opening a browser").option("--default-space <name>", "Default space used when --space is omitted (e.g. applications)").action(async (options, cmd) => {
|
|
977
985
|
try {
|
|
978
986
|
const globalOpts = cmd.optsWithGlobals();
|
|
979
987
|
const profileName = options.name;
|
|
980
988
|
const host = options.host ?? globalOpts.host ?? DEFAULT_HOST;
|
|
989
|
+
const defaultSpace = options.defaultSpace;
|
|
990
|
+
if (defaultSpace !== void 0 && !/^[A-Za-z0-9_-]+$/.test(defaultSpace)) {
|
|
991
|
+
throw new CLIError(
|
|
992
|
+
"INVALID_SPACE",
|
|
993
|
+
`Invalid --default-space "${defaultSpace}". Use a short name ([A-Za-z0-9_-]).`,
|
|
994
|
+
ExitCode.USAGE_ERROR
|
|
995
|
+
);
|
|
996
|
+
}
|
|
981
997
|
if (await ProfileManager.profileExists(profileName)) {
|
|
982
998
|
throw new CLIError(
|
|
983
999
|
"PROFILE_EXISTS",
|
|
@@ -996,7 +1012,8 @@ function registerInitCommand(program2) {
|
|
|
996
1012
|
chainId: DEFAULT_CHAIN_ID,
|
|
997
1013
|
spaceName: "default",
|
|
998
1014
|
did,
|
|
999
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1015
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1016
|
+
...defaultSpace ? { defaultSpace } : {}
|
|
1000
1017
|
};
|
|
1001
1018
|
await ProfileManager.setProfile(profileName, profileConfig);
|
|
1002
1019
|
const config = await ProfileManager.getConfig();
|
|
@@ -1044,7 +1061,7 @@ import { spawn } from "child_process";
|
|
|
1044
1061
|
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
1045
1062
|
import { dirname as dirname2 } from "path";
|
|
1046
1063
|
import { createInterface as createInterface2 } from "readline";
|
|
1047
|
-
import { principalDidEquals } from "@tinycloud/node-sdk";
|
|
1064
|
+
import { grantAuthRequest, principalDidEquals } from "@tinycloud/node-sdk";
|
|
1048
1065
|
|
|
1049
1066
|
// src/config/types.ts
|
|
1050
1067
|
var CLI_PROFILE_POSTURES = [
|
|
@@ -5731,6 +5748,31 @@ var KVService = class extends BaseService {
|
|
|
5731
5748
|
}
|
|
5732
5749
|
return void 0;
|
|
5733
5750
|
}
|
|
5751
|
+
/**
|
|
5752
|
+
* Classify a KV 404 by reading the response body once.
|
|
5753
|
+
*
|
|
5754
|
+
* The server returns 404 both for a genuinely missing key AND for an
|
|
5755
|
+
* un-hosted space (body "Space not found"). Previously get/head/delete
|
|
5756
|
+
* collapsed every 404 to KV_NOT_FOUND before reading the body, so an
|
|
5757
|
+
* un-hosted-space read was indistinguishable from a missing key. We now
|
|
5758
|
+
* preserve status + the "Space not found" body for the un-hosted case (so the
|
|
5759
|
+
* CLI/SDK can normalize it to SPACE_NOT_HOSTED, matching put/list/sql), and
|
|
5760
|
+
* fall through to KV_NOT_FOUND for a real missing key.
|
|
5761
|
+
*/
|
|
5762
|
+
async classifyNotFound(response, key) {
|
|
5763
|
+
const errorText = await response.text();
|
|
5764
|
+
if (/space not found/i.test(errorText)) {
|
|
5765
|
+
return err(
|
|
5766
|
+
serviceError(
|
|
5767
|
+
ErrorCodes.KV_NOT_FOUND,
|
|
5768
|
+
`KV ${response.status} - ${errorText}`,
|
|
5769
|
+
"kv",
|
|
5770
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
5771
|
+
)
|
|
5772
|
+
);
|
|
5773
|
+
}
|
|
5774
|
+
return err(serviceError(ErrorCodes.KV_NOT_FOUND, `Key not found: ${key}`, "kv"));
|
|
5775
|
+
}
|
|
5734
5776
|
/**
|
|
5735
5777
|
* Get the full path with optional prefix.
|
|
5736
5778
|
*
|
|
@@ -5781,6 +5823,38 @@ var KVService = class extends BaseService {
|
|
|
5781
5823
|
signal: this.combineSignals(signal)
|
|
5782
5824
|
});
|
|
5783
5825
|
}
|
|
5826
|
+
/**
|
|
5827
|
+
* Serialize a single put value into a fetch body.
|
|
5828
|
+
*
|
|
5829
|
+
* Binary values (Blob/ArrayBuffer/typed-array, incl. Node Buffer) are sent as
|
|
5830
|
+
* raw bytes (as a Blob) so they round-trip byte-identically — without this a
|
|
5831
|
+
* Buffer would be JSON.stringify'd into `{"type":"Buffer","data":[...]}`.
|
|
5832
|
+
* Strings are returned unchanged (preserving prior behavior); other values are
|
|
5833
|
+
* JSON-encoded. `contentType` overrides the inferred type for binary values.
|
|
5834
|
+
*/
|
|
5835
|
+
serializePutValue(value, contentType) {
|
|
5836
|
+
if (value instanceof Blob) {
|
|
5837
|
+
if (!contentType || value.type === contentType) {
|
|
5838
|
+
return value;
|
|
5839
|
+
}
|
|
5840
|
+
return new Blob([value], { type: contentType });
|
|
5841
|
+
}
|
|
5842
|
+
if (value instanceof ArrayBuffer) {
|
|
5843
|
+
return new Blob([value], {
|
|
5844
|
+
type: contentType ?? "application/octet-stream"
|
|
5845
|
+
});
|
|
5846
|
+
}
|
|
5847
|
+
if (ArrayBuffer.isView(value)) {
|
|
5848
|
+
const view = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
5849
|
+
return new Blob([view], {
|
|
5850
|
+
type: contentType ?? "application/octet-stream"
|
|
5851
|
+
});
|
|
5852
|
+
}
|
|
5853
|
+
if (typeof value === "string") {
|
|
5854
|
+
return contentType ? new Blob([value], { type: contentType }) : value;
|
|
5855
|
+
}
|
|
5856
|
+
return JSON.stringify(value);
|
|
5857
|
+
}
|
|
5784
5858
|
serializeBatchPutValue(item) {
|
|
5785
5859
|
const contentType = item.contentType;
|
|
5786
5860
|
if (item.value instanceof Blob) {
|
|
@@ -5850,10 +5924,13 @@ var KVService = class extends BaseService {
|
|
|
5850
5924
|
* @param raw - Whether to return raw text
|
|
5851
5925
|
* @returns Parsed data
|
|
5852
5926
|
*/
|
|
5853
|
-
async parseResponse(response, raw = false) {
|
|
5927
|
+
async parseResponse(response, raw = false, binary = false) {
|
|
5854
5928
|
if (!response.ok) {
|
|
5855
5929
|
return void 0;
|
|
5856
5930
|
}
|
|
5931
|
+
if (binary) {
|
|
5932
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
5933
|
+
}
|
|
5857
5934
|
if (raw) {
|
|
5858
5935
|
return await response.text();
|
|
5859
5936
|
}
|
|
@@ -5942,13 +6019,7 @@ var KVService = class extends BaseService {
|
|
|
5942
6019
|
}));
|
|
5943
6020
|
}
|
|
5944
6021
|
if (response.status === 404) {
|
|
5945
|
-
return
|
|
5946
|
-
serviceError(
|
|
5947
|
-
ErrorCodes.KV_NOT_FOUND,
|
|
5948
|
-
`Key not found: ${key}`,
|
|
5949
|
-
"kv"
|
|
5950
|
-
)
|
|
5951
|
-
);
|
|
6022
|
+
return this.classifyNotFound(response, key);
|
|
5952
6023
|
}
|
|
5953
6024
|
const errorText = await response.text();
|
|
5954
6025
|
return err(
|
|
@@ -5960,7 +6031,11 @@ var KVService = class extends BaseService {
|
|
|
5960
6031
|
)
|
|
5961
6032
|
);
|
|
5962
6033
|
}
|
|
5963
|
-
const data = await this.parseResponse(
|
|
6034
|
+
const data = await this.parseResponse(
|
|
6035
|
+
response,
|
|
6036
|
+
options?.raw,
|
|
6037
|
+
options?.binary
|
|
6038
|
+
);
|
|
5964
6039
|
return ok({
|
|
5965
6040
|
data,
|
|
5966
6041
|
headers: this.createResponseHeaders(response.headers)
|
|
@@ -5979,12 +6054,7 @@ var KVService = class extends BaseService {
|
|
|
5979
6054
|
return err(authRequiredError("kv"));
|
|
5980
6055
|
}
|
|
5981
6056
|
const path = this.getFullPath(key, options?.prefix);
|
|
5982
|
-
|
|
5983
|
-
if (typeof value === "string") {
|
|
5984
|
-
body = value;
|
|
5985
|
-
} else {
|
|
5986
|
-
body = JSON.stringify(value);
|
|
5987
|
-
}
|
|
6057
|
+
const body = this.serializePutValue(value, options?.contentType);
|
|
5988
6058
|
try {
|
|
5989
6059
|
const response = await this.invokeOperation(
|
|
5990
6060
|
path,
|
|
@@ -6210,13 +6280,7 @@ var KVService = class extends BaseService {
|
|
|
6210
6280
|
}));
|
|
6211
6281
|
}
|
|
6212
6282
|
if (response.status === 404) {
|
|
6213
|
-
return
|
|
6214
|
-
serviceError(
|
|
6215
|
-
ErrorCodes.KV_NOT_FOUND,
|
|
6216
|
-
`Key not found: ${key}`,
|
|
6217
|
-
"kv"
|
|
6218
|
-
)
|
|
6219
|
-
);
|
|
6283
|
+
return this.classifyNotFound(response, key);
|
|
6220
6284
|
}
|
|
6221
6285
|
const errorText = await response.text();
|
|
6222
6286
|
return err(
|
|
@@ -6261,13 +6325,7 @@ var KVService = class extends BaseService {
|
|
|
6261
6325
|
}));
|
|
6262
6326
|
}
|
|
6263
6327
|
if (response.status === 404) {
|
|
6264
|
-
return
|
|
6265
|
-
serviceError(
|
|
6266
|
-
ErrorCodes.KV_NOT_FOUND,
|
|
6267
|
-
`Key not found: ${key}`,
|
|
6268
|
-
"kv"
|
|
6269
|
-
)
|
|
6270
|
-
);
|
|
6328
|
+
return this.classifyNotFound(response, key);
|
|
6271
6329
|
}
|
|
6272
6330
|
const errorText = await response.text();
|
|
6273
6331
|
return err(
|
|
@@ -6414,6 +6472,7 @@ var DatabaseHandle = class {
|
|
|
6414
6472
|
var SQLAction = {
|
|
6415
6473
|
READ: "tinycloud.sql/read",
|
|
6416
6474
|
WRITE: "tinycloud.sql/write",
|
|
6475
|
+
DDL: "tinycloud.sql/ddl",
|
|
6417
6476
|
ADMIN: "tinycloud.sql/admin",
|
|
6418
6477
|
SELECT: "tinycloud.sql/select",
|
|
6419
6478
|
INSERT: "tinycloud.sql/insert",
|
|
@@ -6423,6 +6482,7 @@ var SQLAction = {
|
|
|
6423
6482
|
EXPORT: "tinycloud.sql/export",
|
|
6424
6483
|
ALL: "tinycloud.sql/*"
|
|
6425
6484
|
};
|
|
6485
|
+
var DDL_TOKENS = /* @__PURE__ */ new Set(["alter", "create", "drop"]);
|
|
6426
6486
|
var SQLService = class extends BaseService {
|
|
6427
6487
|
constructor(config = {}) {
|
|
6428
6488
|
super();
|
|
@@ -6498,9 +6558,15 @@ var SQLService = class extends BaseService {
|
|
|
6498
6558
|
if (options?.schema) {
|
|
6499
6559
|
body.schema = options.schema;
|
|
6500
6560
|
}
|
|
6561
|
+
const actions = [
|
|
6562
|
+
this.actionForSql(sql, SQLAction.WRITE),
|
|
6563
|
+
...(options?.schema ?? []).map(
|
|
6564
|
+
(statement) => this.actionForSql(statement, SQLAction.DDL)
|
|
6565
|
+
)
|
|
6566
|
+
];
|
|
6501
6567
|
const response = await this.invokeSQL(
|
|
6502
6568
|
dbName,
|
|
6503
|
-
this.
|
|
6569
|
+
this.dedupeActions(actions),
|
|
6504
6570
|
body,
|
|
6505
6571
|
options?.signal
|
|
6506
6572
|
);
|
|
@@ -6522,7 +6588,7 @@ var SQLService = class extends BaseService {
|
|
|
6522
6588
|
try {
|
|
6523
6589
|
const response = await this.invokeSQL(
|
|
6524
6590
|
dbName,
|
|
6525
|
-
this.
|
|
6591
|
+
this.actionsForSqlBatch(statements),
|
|
6526
6592
|
{ action: "batch", statements },
|
|
6527
6593
|
options?.signal
|
|
6528
6594
|
);
|
|
@@ -6586,9 +6652,10 @@ var SQLService = class extends BaseService {
|
|
|
6586
6652
|
});
|
|
6587
6653
|
}
|
|
6588
6654
|
// === Private helpers ===
|
|
6589
|
-
async invokeSQL(dbName,
|
|
6655
|
+
async invokeSQL(dbName, actions, body, signal) {
|
|
6590
6656
|
const session = this.context.session;
|
|
6591
|
-
const
|
|
6657
|
+
const actionList = Array.isArray(actions) ? actions : [actions];
|
|
6658
|
+
const headers = actionList.length === 1 ? this.context.invoke(session, "sql", dbName, actionList[0]) : this.invokeSQLAny(session, dbName, actionList);
|
|
6592
6659
|
return this.context.fetch(`${this.host}/invoke`, {
|
|
6593
6660
|
method: "POST",
|
|
6594
6661
|
headers: {
|
|
@@ -6600,12 +6667,34 @@ var SQLService = class extends BaseService {
|
|
|
6600
6667
|
});
|
|
6601
6668
|
}
|
|
6602
6669
|
actionForSql(sql, fallback) {
|
|
6603
|
-
|
|
6670
|
+
const token = firstSqlToken(sql);
|
|
6671
|
+
if (token === "pragma") return SQLAction.ADMIN;
|
|
6672
|
+
if (token !== void 0 && DDL_TOKENS.has(token)) return SQLAction.DDL;
|
|
6673
|
+
return fallback;
|
|
6674
|
+
}
|
|
6675
|
+
actionsForSqlBatch(statements) {
|
|
6676
|
+
return this.dedupeActions(
|
|
6677
|
+
statements.map((statement) => this.actionForSql(statement.sql, SQLAction.WRITE))
|
|
6678
|
+
);
|
|
6604
6679
|
}
|
|
6605
|
-
|
|
6606
|
-
return
|
|
6607
|
-
|
|
6608
|
-
|
|
6680
|
+
dedupeActions(actions) {
|
|
6681
|
+
return [...new Set(actions)];
|
|
6682
|
+
}
|
|
6683
|
+
invokeSQLAny(session, dbName, actions) {
|
|
6684
|
+
if (!this.context.invokeAny) {
|
|
6685
|
+
throw new Error(
|
|
6686
|
+
`SQL operation requires multiple permissions (${actions.join(", ")}) but this SDK runtime does not support multi-resource invocations`
|
|
6687
|
+
);
|
|
6688
|
+
}
|
|
6689
|
+
return this.context.invokeAny(
|
|
6690
|
+
session,
|
|
6691
|
+
actions.map((action) => ({
|
|
6692
|
+
spaceId: session.spaceId,
|
|
6693
|
+
service: "sql",
|
|
6694
|
+
path: dbName,
|
|
6695
|
+
action
|
|
6696
|
+
}))
|
|
6697
|
+
);
|
|
6609
6698
|
}
|
|
6610
6699
|
async handleErrorResponse(response, operation) {
|
|
6611
6700
|
const errorText = await response.text();
|
|
@@ -10733,30 +10822,31 @@ function resolveChainId(profile, session) {
|
|
|
10733
10822
|
return profile.chainId;
|
|
10734
10823
|
}
|
|
10735
10824
|
async function resolveSpaceUri(input, profileName) {
|
|
10736
|
-
|
|
10737
|
-
|
|
10738
|
-
|
|
10825
|
+
const profile = await ProfileManager.getProfile(profileName);
|
|
10826
|
+
const effective = input || profile.defaultSpace;
|
|
10827
|
+
if (!effective) return void 0;
|
|
10828
|
+
if (effective.startsWith("tinycloud:")) {
|
|
10829
|
+
const parsed = parseSpaceUri(effective);
|
|
10739
10830
|
if (!parsed) {
|
|
10740
10831
|
throw new CLIError(
|
|
10741
10832
|
"INVALID_SPACE",
|
|
10742
|
-
`Invalid
|
|
10833
|
+
`Invalid space "${effective}". Use a short name ([A-Za-z0-9_-]) or a full tinycloud:... URI.`,
|
|
10743
10834
|
ExitCode.USAGE_ERROR
|
|
10744
10835
|
);
|
|
10745
10836
|
}
|
|
10746
10837
|
return buildSpaceUri(parsed.owner, parsed.name);
|
|
10747
10838
|
}
|
|
10748
|
-
if (!/^[A-Za-z0-9_-]+$/.test(
|
|
10839
|
+
if (!/^[A-Za-z0-9_-]+$/.test(effective)) {
|
|
10749
10840
|
throw new CLIError(
|
|
10750
10841
|
"INVALID_SPACE",
|
|
10751
|
-
`Invalid
|
|
10842
|
+
`Invalid space "${effective}". Use a short name ([A-Za-z0-9_-]) or a full tinycloud:... URI.`,
|
|
10752
10843
|
ExitCode.USAGE_ERROR
|
|
10753
10844
|
);
|
|
10754
10845
|
}
|
|
10755
|
-
const profile = await ProfileManager.getProfile(profileName);
|
|
10756
10846
|
const session = await ProfileManager.getSession(profileName);
|
|
10757
10847
|
const address = resolveAddress(profile, session);
|
|
10758
10848
|
const chainId = resolveChainId(profile, session);
|
|
10759
|
-
return makePkhSpaceId(address, chainId,
|
|
10849
|
+
return makePkhSpaceId(address, chainId, effective);
|
|
10760
10850
|
}
|
|
10761
10851
|
|
|
10762
10852
|
// src/lib/permissions.ts
|
|
@@ -10938,6 +11028,7 @@ async function loadManifestPermissions(source, profile) {
|
|
|
10938
11028
|
)
|
|
10939
11029
|
};
|
|
10940
11030
|
});
|
|
11031
|
+
permissions.push(...await secretPermissionsFromAppManifest(manifest, profile));
|
|
10941
11032
|
return resolvePermissionSpaces(permissions, profile);
|
|
10942
11033
|
}
|
|
10943
11034
|
throw new CLIError(
|
|
@@ -10946,6 +11037,46 @@ async function loadManifestPermissions(source, profile) {
|
|
|
10946
11037
|
ExitCode.USAGE_ERROR
|
|
10947
11038
|
);
|
|
10948
11039
|
}
|
|
11040
|
+
async function secretPermissionsFromAppManifest(manifest, profile) {
|
|
11041
|
+
if (manifest.secrets === void 0) {
|
|
11042
|
+
return [];
|
|
11043
|
+
}
|
|
11044
|
+
const resolved = resolveManifest({
|
|
11045
|
+
app_id: String(manifest.app_id),
|
|
11046
|
+
name: typeof manifest.name === "string" ? manifest.name : String(manifest.app_id),
|
|
11047
|
+
defaults: false,
|
|
11048
|
+
prefix: "",
|
|
11049
|
+
secrets: manifest.secrets
|
|
11050
|
+
});
|
|
11051
|
+
const permissions = resolved.resources.filter(
|
|
11052
|
+
(resource) => resource.service === "tinycloud.kv" && resource.space === "secrets" && resource.path.startsWith("vault/secrets/")
|
|
11053
|
+
);
|
|
11054
|
+
const needsDecrypt = permissions.some(
|
|
11055
|
+
(permission) => permission.actions.includes("tinycloud.kv/get")
|
|
11056
|
+
);
|
|
11057
|
+
if (needsDecrypt) {
|
|
11058
|
+
permissions.push({
|
|
11059
|
+
service: ENCRYPTION_PERMISSION_SERVICE,
|
|
11060
|
+
space: ENCRYPTION_MANIFEST_SPACE,
|
|
11061
|
+
path: await defaultSecretsNetworkId(profile),
|
|
11062
|
+
actions: ["tinycloud.encryption/decrypt"],
|
|
11063
|
+
skipPrefix: true
|
|
11064
|
+
});
|
|
11065
|
+
}
|
|
11066
|
+
return permissions;
|
|
11067
|
+
}
|
|
11068
|
+
async function defaultSecretsNetworkId(profileName) {
|
|
11069
|
+
const profile = await ProfileManager.getProfile(profileName);
|
|
11070
|
+
const ownerDid = (profile.ownerDid ?? profile.did)?.split("#")[0];
|
|
11071
|
+
if (!ownerDid) {
|
|
11072
|
+
throw new CLIError(
|
|
11073
|
+
"OWNER_DID_UNKNOWN",
|
|
11074
|
+
`Cannot determine owner DID for profile "${profileName}". Run \`tc auth login\` first.`,
|
|
11075
|
+
ExitCode.AUTH_REQUIRED
|
|
11076
|
+
);
|
|
11077
|
+
}
|
|
11078
|
+
return `urn:tinycloud:encryption:${ownerDid}:default`;
|
|
11079
|
+
}
|
|
10949
11080
|
function permissionsFromDelegation(delegation) {
|
|
10950
11081
|
if (delegation.resources?.length) {
|
|
10951
11082
|
return delegation.resources.map((resource) => ({
|
|
@@ -11298,6 +11429,10 @@ function registerAuthCommand(program2) {
|
|
|
11298
11429
|
jwk: key,
|
|
11299
11430
|
host: ctx.host,
|
|
11300
11431
|
permissions: group,
|
|
11432
|
+
reason: permissionGrantReason(
|
|
11433
|
+
"Grant requested TinyCloud permissions from `tc auth request --grant`.",
|
|
11434
|
+
group
|
|
11435
|
+
),
|
|
11301
11436
|
openkeyHost,
|
|
11302
11437
|
expiry: expiryOption,
|
|
11303
11438
|
noPopup: options.popup === false
|
|
@@ -11442,23 +11577,11 @@ function registerAuthCommand(program2) {
|
|
|
11442
11577
|
node,
|
|
11443
11578
|
requested: parsed.requested,
|
|
11444
11579
|
expiryOption: parsed.requestedExpiry,
|
|
11580
|
+
reason: "Grant permissions requested by a TinyCloud auth request artifact.",
|
|
11445
11581
|
yes: options.yes === true
|
|
11446
11582
|
});
|
|
11447
|
-
const
|
|
11448
|
-
|
|
11449
|
-
parsed.requested,
|
|
11450
|
-
parsed.requestedExpiry !== void 0 ? { expiry: parsed.requestedExpiry } : void 0
|
|
11451
|
-
);
|
|
11452
|
-
outputJson({
|
|
11453
|
-
kind: "tinycloud.auth.delegation",
|
|
11454
|
-
version: 1,
|
|
11455
|
-
requestId: parsed.requestId,
|
|
11456
|
-
delegationCid: result.delegation.cid,
|
|
11457
|
-
delegation: result.delegation,
|
|
11458
|
-
permissions: parsed.requested,
|
|
11459
|
-
expiry: result.delegation.expiry.toISOString(),
|
|
11460
|
-
prompted: result.prompted
|
|
11461
|
-
});
|
|
11583
|
+
const grant = await grantAuthRequest(node, parsed);
|
|
11584
|
+
outputJson(grant);
|
|
11462
11585
|
} catch (error) {
|
|
11463
11586
|
handleError(error);
|
|
11464
11587
|
}
|
|
@@ -11738,6 +11861,7 @@ async function ensureDelegationAuthority(params) {
|
|
|
11738
11861
|
jwk: key,
|
|
11739
11862
|
host: params.ctx.host,
|
|
11740
11863
|
permissions: group,
|
|
11864
|
+
reason: permissionGrantReason(params.reason, group),
|
|
11741
11865
|
openkeyHost,
|
|
11742
11866
|
expiry: params.expiryOption
|
|
11743
11867
|
});
|
|
@@ -11785,6 +11909,12 @@ async function ensureDelegationAuthority(params) {
|
|
|
11785
11909
|
});
|
|
11786
11910
|
}
|
|
11787
11911
|
}
|
|
11912
|
+
function permissionGrantReason(context, permissions) {
|
|
11913
|
+
const first = permissions[0];
|
|
11914
|
+
const summary = first ? compactPermission(first) : "no permissions";
|
|
11915
|
+
const more = permissions.length > 1 ? ` and ${permissions.length - 1} more permission${permissions.length === 2 ? "" : "s"}` : "";
|
|
11916
|
+
return `${context} Requested: ${summary}${more}.`;
|
|
11917
|
+
}
|
|
11788
11918
|
function execCapturedCommand(command) {
|
|
11789
11919
|
return new Promise((resolve3, reject) => {
|
|
11790
11920
|
const child = spawn(process.execPath, [process.argv[1], ...command.argv], {
|
|
@@ -11874,9 +12004,10 @@ function groupPermissionsBySpace(permissions) {
|
|
|
11874
12004
|
rawEntries.push(permission);
|
|
11875
12005
|
continue;
|
|
11876
12006
|
}
|
|
11877
|
-
const
|
|
12007
|
+
const key = normalizeSpaceForCompare(permission.space);
|
|
12008
|
+
const group = groups.get(key) ?? [];
|
|
11878
12009
|
group.push(permission);
|
|
11879
|
-
groups.set(
|
|
12010
|
+
groups.set(key, group);
|
|
11880
12011
|
}
|
|
11881
12012
|
const grouped = Array.from(groups.values());
|
|
11882
12013
|
if (grouped.length === 0) {
|
|
@@ -11888,8 +12019,16 @@ function groupPermissionsBySpace(permissions) {
|
|
|
11888
12019
|
function isRawPermission(permission) {
|
|
11889
12020
|
return permission.service === "tinycloud.encryption" && permission.path.startsWith("urn:tinycloud:encryption:");
|
|
11890
12021
|
}
|
|
12022
|
+
function normalizeSpaceForCompare(space) {
|
|
12023
|
+
return space.replace(
|
|
12024
|
+
/(eip155:\d+:)(0x[0-9a-fA-F]{40})/,
|
|
12025
|
+
(_match, prefix, addr) => prefix + addr.toLowerCase()
|
|
12026
|
+
);
|
|
12027
|
+
}
|
|
11891
12028
|
function returnedSpaceMatchesExpected(returnedSpace, expectedSpace) {
|
|
11892
|
-
if (returnedSpace === expectedSpace)
|
|
12029
|
+
if (normalizeSpaceForCompare(returnedSpace) === normalizeSpaceForCompare(expectedSpace)) {
|
|
12030
|
+
return true;
|
|
12031
|
+
}
|
|
11893
12032
|
if (!returnedSpace.startsWith("tinycloud:")) return false;
|
|
11894
12033
|
const returnedName = returnedSpace.slice(returnedSpace.lastIndexOf(":") + 1);
|
|
11895
12034
|
return returnedName === expectedSpace;
|
|
@@ -11898,7 +12037,7 @@ function portableFromOpenKeyDelegation(data, permissions, host) {
|
|
|
11898
12037
|
const primary = permissions.find((permission) => !isRawPermission(permission)) ?? permissions[0];
|
|
11899
12038
|
const returnedSpace = String(data.spaceId ?? primary.space ?? "encryption");
|
|
11900
12039
|
const expectedSpaces = new Set(
|
|
11901
|
-
permissions.filter((permission) => !isRawPermission(permission)).map((permission) => permission.space)
|
|
12040
|
+
permissions.filter((permission) => !isRawPermission(permission)).map((permission) => normalizeSpaceForCompare(permission.space))
|
|
11902
12041
|
);
|
|
11903
12042
|
const matchesExpectedSpace = expectedSpaces.size === 1 && returnedSpaceMatchesExpected(returnedSpace, Array.from(expectedSpaces)[0]);
|
|
11904
12043
|
if (expectedSpaces.size > 0 && !matchesExpectedSpace) {
|
|
@@ -12125,6 +12264,79 @@ async function refreshOpenKeySession(profileName, host, options = {}) {
|
|
|
12125
12264
|
// src/commands/kv.ts
|
|
12126
12265
|
import { readFile as readFile4 } from "fs/promises";
|
|
12127
12266
|
import { writeFile as writeFile3 } from "fs/promises";
|
|
12267
|
+
|
|
12268
|
+
// src/lib/host.ts
|
|
12269
|
+
function canonicalizeAddress2(address) {
|
|
12270
|
+
const trimmed = address.trim();
|
|
12271
|
+
return trimmed.startsWith("0x") ? `0x${trimmed.slice(2).toLowerCase()}` : trimmed.toLowerCase();
|
|
12272
|
+
}
|
|
12273
|
+
async function resolveLocalAddress(profile, profileName) {
|
|
12274
|
+
const session = await ProfileManager.getSession(profileName);
|
|
12275
|
+
const sessAddr = session?.address;
|
|
12276
|
+
if (typeof sessAddr === "string" && sessAddr.length > 0) {
|
|
12277
|
+
return canonicalizeAddress2(sessAddr);
|
|
12278
|
+
}
|
|
12279
|
+
if (profile.address) return canonicalizeAddress2(profile.address);
|
|
12280
|
+
if (profile.ownerDid) {
|
|
12281
|
+
const match = profile.ownerDid.match(/^did:pkh:eip155:\d+:(0x[a-fA-F0-9]{40})$/);
|
|
12282
|
+
if (match) return canonicalizeAddress2(match[1]);
|
|
12283
|
+
}
|
|
12284
|
+
return null;
|
|
12285
|
+
}
|
|
12286
|
+
function ownerAddressFromSpaceUri(spaceUri) {
|
|
12287
|
+
const match = spaceUri.match(/^tinycloud:pkh:eip155:\d+:(0x[a-fA-F0-9]{40}):/);
|
|
12288
|
+
return match ? canonicalizeAddress2(match[1]) : null;
|
|
12289
|
+
}
|
|
12290
|
+
function ownerDidFromSpaceUri(spaceUri) {
|
|
12291
|
+
const match = spaceUri.match(/^tinycloud:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40}):/);
|
|
12292
|
+
if (!match) return null;
|
|
12293
|
+
return `did:pkh:eip155:${match[1]}:${canonicalizeAddress2(match[2])}`;
|
|
12294
|
+
}
|
|
12295
|
+
async function isRootAuthority(spaceUri, profileName) {
|
|
12296
|
+
const profile = await ProfileManager.getProfile(profileName);
|
|
12297
|
+
if (resolveProfilePosture(profile) === "delegate-session") return false;
|
|
12298
|
+
const ownerAddr = ownerAddressFromSpaceUri(spaceUri);
|
|
12299
|
+
if (!ownerAddr) return false;
|
|
12300
|
+
const selfAddr = await resolveLocalAddress(profile, profileName);
|
|
12301
|
+
return selfAddr !== null && selfAddr === ownerAddr;
|
|
12302
|
+
}
|
|
12303
|
+
function spaceNameFromUri(spaceUri) {
|
|
12304
|
+
return spaceUri.slice(spaceUri.lastIndexOf(":") + 1);
|
|
12305
|
+
}
|
|
12306
|
+
async function unhostedSpaceError(error, spaceUri, profileName) {
|
|
12307
|
+
if (!spaceUri) return null;
|
|
12308
|
+
const status = error.meta?.status;
|
|
12309
|
+
const isUnhosted = status === 404 && /space not found/i.test(error.message);
|
|
12310
|
+
if (!isUnhosted) return null;
|
|
12311
|
+
const spaceName = spaceNameFromUri(spaceUri);
|
|
12312
|
+
const owner = await isRootAuthority(spaceUri, profileName);
|
|
12313
|
+
const hint = owner ? [
|
|
12314
|
+
"You are the owner. Host it once:",
|
|
12315
|
+
` tc space host ${spaceName}`,
|
|
12316
|
+
"Then retry."
|
|
12317
|
+
].join("\n") : [
|
|
12318
|
+
"You are a delegate and CANNOT host this space \u2014 only its owner can.",
|
|
12319
|
+
"Emit a host request:",
|
|
12320
|
+
` tc space host-request ${spaceName} --emit ./host-request.json`,
|
|
12321
|
+
"Send it to the owner; they run `tc space host` and confirm. Then retry."
|
|
12322
|
+
].join("\n");
|
|
12323
|
+
const message = owner ? `Space '${spaceName}' (${spaceUri}) is not hosted.` : `Space '${spaceName}' (owner ${ownerDidFromSpaceUri(spaceUri) ?? spaceUri}) is not hosted.`;
|
|
12324
|
+
return new CLIError("SPACE_NOT_HOSTED", message, ExitCode.ERROR, { hint });
|
|
12325
|
+
}
|
|
12326
|
+
async function resolveHostSpace(name, profileName) {
|
|
12327
|
+
const resolved = await resolveSpaceUri(name, profileName);
|
|
12328
|
+
if (!resolved) {
|
|
12329
|
+
throw new Error(`Could not resolve a space for "${name}".`);
|
|
12330
|
+
}
|
|
12331
|
+
return resolved;
|
|
12332
|
+
}
|
|
12333
|
+
|
|
12334
|
+
// src/commands/kv.ts
|
|
12335
|
+
async function throwKvError(error, spaceUri, profileName) {
|
|
12336
|
+
const hosted = await unhostedSpaceError(error, spaceUri, profileName);
|
|
12337
|
+
if (hosted) throw hosted;
|
|
12338
|
+
throw new CLIError(error.code, error.message, ExitCode.ERROR);
|
|
12339
|
+
}
|
|
12128
12340
|
async function readStdin2() {
|
|
12129
12341
|
const chunks = [];
|
|
12130
12342
|
for await (const chunk of process.stdin) {
|
|
@@ -12134,7 +12346,8 @@ async function readStdin2() {
|
|
|
12134
12346
|
}
|
|
12135
12347
|
async function kvHandle(node, spaceInput, profileName) {
|
|
12136
12348
|
const spaceUri = await resolveSpaceUri(spaceInput, profileName);
|
|
12137
|
-
|
|
12349
|
+
const kv = spaceUri ? node.kvForSpace(spaceUri) : node.kv;
|
|
12350
|
+
return { kv, spaceUri };
|
|
12138
12351
|
}
|
|
12139
12352
|
function registerKvCommand(program2) {
|
|
12140
12353
|
const kv = program2.command("kv").description("Key-value store operations");
|
|
@@ -12143,25 +12356,29 @@ function registerKvCommand(program2) {
|
|
|
12143
12356
|
const globalOpts = cmd.optsWithGlobals();
|
|
12144
12357
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
12145
12358
|
const node = await ensureAuthenticated(ctx);
|
|
12146
|
-
const kv2 = await kvHandle(node, options.space, ctx.profile);
|
|
12147
|
-
const
|
|
12359
|
+
const { kv: kv2, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
12360
|
+
const wantBytes = !!options.output || !!options.raw;
|
|
12361
|
+
const result = await withSpinner(
|
|
12362
|
+
`Getting ${key}...`,
|
|
12363
|
+
() => kv2.get(key, wantBytes ? { binary: true } : void 0)
|
|
12364
|
+
);
|
|
12148
12365
|
if (!result.ok) {
|
|
12366
|
+
const hosted = await unhostedSpaceError(result.error, spaceUri, ctx.profile);
|
|
12367
|
+
if (hosted) throw hosted;
|
|
12149
12368
|
if (result.error.code === "KV_NOT_FOUND" || result.error.code === "NOT_FOUND") {
|
|
12150
12369
|
throw new CLIError("NOT_FOUND", `Key "${key}" not found`, ExitCode.NOT_FOUND);
|
|
12151
12370
|
}
|
|
12152
|
-
|
|
12371
|
+
await throwKvError(result.error, spaceUri, ctx.profile);
|
|
12153
12372
|
}
|
|
12154
12373
|
const data = result.data.data;
|
|
12155
12374
|
const metadata = result.data.headers ?? {};
|
|
12156
12375
|
if (options.output) {
|
|
12157
|
-
|
|
12158
|
-
await writeFile3(options.output, content);
|
|
12376
|
+
await writeFile3(options.output, data);
|
|
12159
12377
|
outputJson({ key, written: options.output });
|
|
12160
12378
|
return;
|
|
12161
12379
|
}
|
|
12162
12380
|
if (options.raw) {
|
|
12163
|
-
|
|
12164
|
-
process.stdout.write(content);
|
|
12381
|
+
process.stdout.write(data);
|
|
12165
12382
|
return;
|
|
12166
12383
|
}
|
|
12167
12384
|
if (shouldOutputJson()) {
|
|
@@ -12178,7 +12395,7 @@ function registerKvCommand(program2) {
|
|
|
12178
12395
|
handleError(error);
|
|
12179
12396
|
}
|
|
12180
12397
|
});
|
|
12181
|
-
kv.command("put <key> [value]").description("Set a value").option("--file <path>", "Read value from file").option("--stdin", "Read value from stdin").action(async (key, value, options, cmd) => {
|
|
12398
|
+
kv.command("put <key> [value]").description("Set a value").option("--file <path>", "Read value from file").option("--stdin", "Read value from stdin").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").action(async (key, value, options, cmd) => {
|
|
12182
12399
|
try {
|
|
12183
12400
|
const globalOpts = cmd.optsWithGlobals();
|
|
12184
12401
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
@@ -12202,23 +12419,25 @@ function registerKvCommand(program2) {
|
|
|
12202
12419
|
putValue = value;
|
|
12203
12420
|
}
|
|
12204
12421
|
}
|
|
12205
|
-
const
|
|
12422
|
+
const { kv: kv2, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
12423
|
+
const result = await withSpinner(`Writing ${key}...`, () => kv2.put(key, putValue));
|
|
12206
12424
|
if (!result.ok) {
|
|
12207
|
-
|
|
12425
|
+
await throwKvError(result.error, spaceUri, ctx.profile);
|
|
12208
12426
|
}
|
|
12209
12427
|
outputJson({ key, written: true });
|
|
12210
12428
|
} catch (error) {
|
|
12211
12429
|
handleError(error);
|
|
12212
12430
|
}
|
|
12213
12431
|
});
|
|
12214
|
-
kv.command("delete <key>").description("Delete a key").action(async (key,
|
|
12432
|
+
kv.command("delete <key>").description("Delete a key").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").action(async (key, options, cmd) => {
|
|
12215
12433
|
try {
|
|
12216
12434
|
const globalOpts = cmd.optsWithGlobals();
|
|
12217
12435
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
12218
12436
|
const node = await ensureAuthenticated(ctx);
|
|
12219
|
-
const
|
|
12437
|
+
const { kv: kv2, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
12438
|
+
const result = await withSpinner(`Deleting ${key}...`, () => kv2.delete(key));
|
|
12220
12439
|
if (!result.ok) {
|
|
12221
|
-
|
|
12440
|
+
await throwKvError(result.error, spaceUri, ctx.profile);
|
|
12222
12441
|
}
|
|
12223
12442
|
outputJson({ key, deleted: true });
|
|
12224
12443
|
} catch (error) {
|
|
@@ -12230,11 +12449,11 @@ function registerKvCommand(program2) {
|
|
|
12230
12449
|
const globalOpts = cmd.optsWithGlobals();
|
|
12231
12450
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
12232
12451
|
const node = await ensureAuthenticated(ctx);
|
|
12233
|
-
const kv2 = await kvHandle(node, options.space, ctx.profile);
|
|
12452
|
+
const { kv: kv2, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
12234
12453
|
const listOptions = options.prefix ? { prefix: options.prefix } : void 0;
|
|
12235
12454
|
const result = await withSpinner("Listing keys...", () => kv2.list(listOptions));
|
|
12236
12455
|
if (!result.ok) {
|
|
12237
|
-
|
|
12456
|
+
await throwKvError(result.error, spaceUri, ctx.profile);
|
|
12238
12457
|
}
|
|
12239
12458
|
const rawData = result.data.data ?? result.data;
|
|
12240
12459
|
const keyList = Array.isArray(rawData) ? rawData : rawData?.keys ?? [];
|
|
@@ -12265,14 +12484,16 @@ function registerKvCommand(program2) {
|
|
|
12265
12484
|
const globalOpts = cmd.optsWithGlobals();
|
|
12266
12485
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
12267
12486
|
const node = await ensureAuthenticated(ctx);
|
|
12268
|
-
const kv2 = await kvHandle(node, options.space, ctx.profile);
|
|
12487
|
+
const { kv: kv2, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
12269
12488
|
const result = await withSpinner(`Checking ${key}...`, () => kv2.head(key));
|
|
12270
12489
|
if (!result.ok) {
|
|
12490
|
+
const hosted = await unhostedSpaceError(result.error, spaceUri, ctx.profile);
|
|
12491
|
+
if (hosted) throw hosted;
|
|
12271
12492
|
if (result.error.code === "KV_NOT_FOUND" || result.error.code === "NOT_FOUND") {
|
|
12272
12493
|
outputJson({ key, exists: false, metadata: {} });
|
|
12273
12494
|
return;
|
|
12274
12495
|
}
|
|
12275
|
-
|
|
12496
|
+
await throwKvError(result.error, spaceUri, ctx.profile);
|
|
12276
12497
|
}
|
|
12277
12498
|
outputJson({
|
|
12278
12499
|
key,
|
|
@@ -12286,6 +12507,13 @@ function registerKvCommand(program2) {
|
|
|
12286
12507
|
}
|
|
12287
12508
|
|
|
12288
12509
|
// src/commands/space.ts
|
|
12510
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
12511
|
+
import { mkdir as mkdir3, writeFile as writeFile4 } from "fs/promises";
|
|
12512
|
+
import { dirname as dirname3 } from "path";
|
|
12513
|
+
function didWithoutFragment2(did) {
|
|
12514
|
+
const fragment = did.indexOf("#");
|
|
12515
|
+
return fragment === -1 ? did : did.slice(0, fragment);
|
|
12516
|
+
}
|
|
12289
12517
|
function registerSpaceCommand(program2) {
|
|
12290
12518
|
const space = program2.command("space").description("Space management");
|
|
12291
12519
|
space.command("list").description("List spaces").action(async (_options, cmd) => {
|
|
@@ -12315,16 +12543,60 @@ function registerSpaceCommand(program2) {
|
|
|
12315
12543
|
handleError(error);
|
|
12316
12544
|
}
|
|
12317
12545
|
});
|
|
12318
|
-
space.command("create <name>").description("Create
|
|
12546
|
+
space.command("create <name>").alias("host").description("Create (host) one of your owned spaces by name").action(async (name, _options, cmd) => {
|
|
12319
12547
|
try {
|
|
12320
12548
|
const globalOpts = cmd.optsWithGlobals();
|
|
12321
12549
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
12322
12550
|
const node = await ensureAuthenticated(ctx);
|
|
12323
|
-
const
|
|
12324
|
-
|
|
12325
|
-
|
|
12551
|
+
const spaceId = await node.hostOwnedSpace(name);
|
|
12552
|
+
outputJson({ spaceId, name, hosted: true });
|
|
12553
|
+
} catch (error) {
|
|
12554
|
+
handleError(error);
|
|
12555
|
+
}
|
|
12556
|
+
});
|
|
12557
|
+
space.command("host-request <name>").description("Emit a request asking the space owner to host it (delegate-only)").option("--emit [file]", "Write the request artifact to file (or stdout when no path)").addHelpText("after", `
|
|
12558
|
+
|
|
12559
|
+
A delegate cannot host a space \u2014 only its owner (root authority) can. This
|
|
12560
|
+
emits a tinycloud.host.request artifact naming the space and its owner so you
|
|
12561
|
+
can hand it to the owner; they run \`tc space host <name>\` and confirm.
|
|
12562
|
+
|
|
12563
|
+
If you ARE the owner of the resolved space, this refuses and tells you to host
|
|
12564
|
+
it directly with \`tc space host <name>\` (no request needed).
|
|
12565
|
+
`).action(async (name, options, cmd) => {
|
|
12566
|
+
try {
|
|
12567
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
12568
|
+
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
12569
|
+
const profile = await ProfileManager.getProfile(ctx.profile);
|
|
12570
|
+
const spaceId = await resolveHostSpace(name, ctx.profile);
|
|
12571
|
+
const spaceName = spaceNameFromUri(spaceId);
|
|
12572
|
+
if (await isRootAuthority(spaceId, ctx.profile)) {
|
|
12573
|
+
throw new CLIError(
|
|
12574
|
+
"ALREADY_ROOT_AUTHORITY",
|
|
12575
|
+
`You are the owner of ${spaceId}. Host it directly: tc space host ${spaceName}`,
|
|
12576
|
+
ExitCode.USAGE_ERROR
|
|
12577
|
+
);
|
|
12578
|
+
}
|
|
12579
|
+
const requesterDid = didWithoutFragment2(profile.sessionDid ?? profile.did);
|
|
12580
|
+
const ownerDid = ownerDidFromSpaceUri(spaceId);
|
|
12581
|
+
if (!ownerDid) {
|
|
12582
|
+
throw new CLIError(
|
|
12583
|
+
"UNRESOLVABLE_OWNER",
|
|
12584
|
+
`Cannot determine the owner of ${spaceId}; host-request needs a pkh space URI.`,
|
|
12585
|
+
ExitCode.USAGE_ERROR
|
|
12586
|
+
);
|
|
12326
12587
|
}
|
|
12327
|
-
|
|
12588
|
+
const artifact = {
|
|
12589
|
+
kind: "tinycloud.host.request",
|
|
12590
|
+
version: 1,
|
|
12591
|
+
requestId: `hostreq_${Date.now().toString(36)}_${randomBytes3(4).toString("hex")}`,
|
|
12592
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12593
|
+
spaceName,
|
|
12594
|
+
spaceId,
|
|
12595
|
+
ownerDid,
|
|
12596
|
+
requesterDid,
|
|
12597
|
+
host: ctx.host
|
|
12598
|
+
};
|
|
12599
|
+
await emitHostRequestArtifact(artifact, options.emit);
|
|
12328
12600
|
} catch (error) {
|
|
12329
12601
|
handleError(error);
|
|
12330
12602
|
}
|
|
@@ -12361,6 +12633,22 @@ function registerSpaceCommand(program2) {
|
|
|
12361
12633
|
}
|
|
12362
12634
|
});
|
|
12363
12635
|
}
|
|
12636
|
+
async function emitHostRequestArtifact(artifact, emitOption) {
|
|
12637
|
+
if (typeof emitOption === "string" && emitOption.length > 0) {
|
|
12638
|
+
await mkdir3(dirname3(emitOption), { recursive: true });
|
|
12639
|
+
await writeFile4(emitOption, JSON.stringify(artifact, null, 2) + "\n", "utf8");
|
|
12640
|
+
outputJson({
|
|
12641
|
+
emitted: true,
|
|
12642
|
+
path: emitOption,
|
|
12643
|
+
requestId: artifact.requestId,
|
|
12644
|
+
spaceName: artifact.spaceName,
|
|
12645
|
+
spaceId: artifact.spaceId,
|
|
12646
|
+
ownerDid: artifact.ownerDid
|
|
12647
|
+
});
|
|
12648
|
+
return;
|
|
12649
|
+
}
|
|
12650
|
+
outputJson(artifact);
|
|
12651
|
+
}
|
|
12364
12652
|
|
|
12365
12653
|
// src/lib/duration.ts
|
|
12366
12654
|
function parseDuration(input) {
|
|
@@ -12776,6 +13064,7 @@ function registerProfileCommand(program2) {
|
|
|
12776
13064
|
process.stdout.write(formatField("Posture", posture) + "\n");
|
|
12777
13065
|
process.stdout.write(formatField("Operator", operatorType) + "\n");
|
|
12778
13066
|
process.stdout.write(formatField("Space", p.spaceId || null) + "\n");
|
|
13067
|
+
process.stdout.write(formatField("Default Space", p.defaultSpace || null) + "\n");
|
|
12779
13068
|
process.stdout.write(formatField("Key", hasKey) + "\n");
|
|
12780
13069
|
process.stdout.write(formatField("Session", hasSession) + "\n");
|
|
12781
13070
|
process.stdout.write(formatField("Created", p.createdAt) + "\n");
|
|
@@ -12796,6 +13085,46 @@ function registerProfileCommand(program2) {
|
|
|
12796
13085
|
handleError(error);
|
|
12797
13086
|
}
|
|
12798
13087
|
});
|
|
13088
|
+
profile.command("set-default-space [name]").description("Set (or clear) the default space used when --space is omitted").option("--profile <name>", "Profile to modify (defaults to the active profile)").option("--unset", "Clear the default space so commands fall back to the primary space").addHelpText("after", `
|
|
13089
|
+
|
|
13090
|
+
The default space is a short space NAME (e.g. "applications"), resolved per
|
|
13091
|
+
profile at command time. Precedence for every kv/sql command:
|
|
13092
|
+
explicit --space flag > profile defaultSpace > primary space.
|
|
13093
|
+
|
|
13094
|
+
Examples:
|
|
13095
|
+
$ tc profile set-default-space applications
|
|
13096
|
+
$ tc profile set-default-space applications --profile cli-test
|
|
13097
|
+
$ tc profile set-default-space --unset
|
|
13098
|
+
`).action(async (name, options, cmd) => {
|
|
13099
|
+
try {
|
|
13100
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
13101
|
+
const ctx = await ProfileManager.resolveContext({
|
|
13102
|
+
...globalOpts,
|
|
13103
|
+
profile: options.profile ?? globalOpts.profile
|
|
13104
|
+
});
|
|
13105
|
+
const profileName = ctx.profile;
|
|
13106
|
+
if (!options.unset && (name === void 0 || name === "")) {
|
|
13107
|
+
throw new CLIError(
|
|
13108
|
+
"USAGE_ERROR",
|
|
13109
|
+
"Provide a space name (e.g. `tc profile set-default-space applications`) or pass --unset.",
|
|
13110
|
+
ExitCode.USAGE_ERROR
|
|
13111
|
+
);
|
|
13112
|
+
}
|
|
13113
|
+
if (!options.unset && !/^[A-Za-z0-9_-]+$/.test(name)) {
|
|
13114
|
+
throw new CLIError(
|
|
13115
|
+
"INVALID_SPACE",
|
|
13116
|
+
`Invalid space name "${name}". Use a short name ([A-Za-z0-9_-]).`,
|
|
13117
|
+
ExitCode.USAGE_ERROR
|
|
13118
|
+
);
|
|
13119
|
+
}
|
|
13120
|
+
const p = await ProfileManager.getProfile(profileName);
|
|
13121
|
+
const defaultSpace = options.unset ? void 0 : name;
|
|
13122
|
+
await ProfileManager.setProfile(profileName, { ...p, defaultSpace });
|
|
13123
|
+
outputJson({ profile: profileName, defaultSpace: defaultSpace ?? null, updated: true });
|
|
13124
|
+
} catch (error) {
|
|
13125
|
+
handleError(error);
|
|
13126
|
+
}
|
|
13127
|
+
});
|
|
12799
13128
|
profile.command("delete <name>").description("Delete a profile").action(async (name, _options, cmd) => {
|
|
12800
13129
|
try {
|
|
12801
13130
|
if (isInteractive()) {
|
|
@@ -12967,7 +13296,7 @@ complete -c tc -l quiet -s q -d "Suppress non-essential output"
|
|
|
12967
13296
|
|
|
12968
13297
|
// src/commands/vault.ts
|
|
12969
13298
|
import { readFile as readFile5 } from "fs/promises";
|
|
12970
|
-
import { writeFile as
|
|
13299
|
+
import { writeFile as writeFile5 } from "fs/promises";
|
|
12971
13300
|
import { PrivateKeySigner as PrivateKeySigner2 } from "@tinycloud/node-sdk";
|
|
12972
13301
|
async function readStdin3() {
|
|
12973
13302
|
const chunks = [];
|
|
@@ -13056,7 +13385,7 @@ function registerVaultCommand(program2) {
|
|
|
13056
13385
|
const data = result.data.data ?? result.data;
|
|
13057
13386
|
if (options.output) {
|
|
13058
13387
|
const content = data instanceof Uint8Array ? Buffer.from(data) : typeof data === "string" ? data : JSON.stringify(data);
|
|
13059
|
-
await
|
|
13388
|
+
await writeFile5(options.output, content);
|
|
13060
13389
|
outputJson({ key, written: options.output });
|
|
13061
13390
|
return;
|
|
13062
13391
|
}
|
|
@@ -13140,7 +13469,7 @@ function registerVaultCommand(program2) {
|
|
|
13140
13469
|
|
|
13141
13470
|
// src/commands/secrets.ts
|
|
13142
13471
|
import { readFile as readFile6 } from "fs/promises";
|
|
13143
|
-
import { writeFile as
|
|
13472
|
+
import { writeFile as writeFile6 } from "fs/promises";
|
|
13144
13473
|
import { join as join5 } from "path";
|
|
13145
13474
|
import { homedir as homedir2 } from "os";
|
|
13146
13475
|
var SECRETS_SPACE2 = "secrets";
|
|
@@ -13262,12 +13591,17 @@ async function runSecretOperation(params) {
|
|
|
13262
13591
|
node: params.node,
|
|
13263
13592
|
requested,
|
|
13264
13593
|
expiryOption: void 0,
|
|
13594
|
+
reason: secretPermissionReason(params.action, params.name),
|
|
13265
13595
|
yes: true,
|
|
13266
13596
|
force: true
|
|
13267
13597
|
})
|
|
13268
13598
|
);
|
|
13269
13599
|
return runSecretOperationAttempt(params.label, params.operation);
|
|
13270
13600
|
}
|
|
13601
|
+
function secretPermissionReason(action, name) {
|
|
13602
|
+
const target = name ? ` secret "${name}"` : " secrets";
|
|
13603
|
+
return `Allow \`tc secrets ${action}${name ? ` ${name}` : ""}\` to access${target} with the required TinyCloud permissions.`;
|
|
13604
|
+
}
|
|
13271
13605
|
async function runSecretOperationAttempt(label, operation) {
|
|
13272
13606
|
try {
|
|
13273
13607
|
return await withSpinner(label, operation);
|
|
@@ -13639,6 +13973,30 @@ function secretPermissionEntries(params) {
|
|
|
13639
13973
|
}
|
|
13640
13974
|
return permissions;
|
|
13641
13975
|
}
|
|
13976
|
+
function formatSecretScopeFlag(options) {
|
|
13977
|
+
return options?.scope ? ` --scope ${JSON.stringify(options.scope)}` : "";
|
|
13978
|
+
}
|
|
13979
|
+
function outputSecretDoctor(result) {
|
|
13980
|
+
if (shouldOutputJson()) {
|
|
13981
|
+
outputJson(result);
|
|
13982
|
+
return;
|
|
13983
|
+
}
|
|
13984
|
+
process.stderr.write(formatSection("Secrets") + "\n");
|
|
13985
|
+
for (const check of result.checks) {
|
|
13986
|
+
process.stdout.write(formatCheck(check.ok, check.name, check.detail) + "\n");
|
|
13987
|
+
if (check.hint) {
|
|
13988
|
+
process.stdout.write(` ${theme.hint(check.hint)}
|
|
13989
|
+
`);
|
|
13990
|
+
}
|
|
13991
|
+
}
|
|
13992
|
+
process.stdout.write("\n");
|
|
13993
|
+
if (result.healthy) {
|
|
13994
|
+
process.stdout.write(theme.success("Secrets checks passed.") + "\n");
|
|
13995
|
+
} else {
|
|
13996
|
+
const failed = result.checks.filter((check) => check.ok === false).length;
|
|
13997
|
+
process.stdout.write(theme.warn(`${failed} secrets check${failed > 1 ? "s" : ""} need attention.`) + "\n");
|
|
13998
|
+
}
|
|
13999
|
+
}
|
|
13642
14000
|
function registerSecretsCommand(program2) {
|
|
13643
14001
|
const secrets = program2.command("secrets").description("Encrypted secrets management");
|
|
13644
14002
|
const network = secrets.command("network").description("Manage the default secrets encryption network");
|
|
@@ -13680,6 +14038,93 @@ function registerSecretsCommand(program2) {
|
|
|
13680
14038
|
handleError(error);
|
|
13681
14039
|
}
|
|
13682
14040
|
});
|
|
14041
|
+
secrets.command("doctor [name]").description("Check secrets setup and optional secret access").option("--scope <scope>", "Logical secret scope").option("--space <scope>", "Deprecated alias for --scope").option("--network <name>", "Encryption network name", "default").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (name, options, cmd) => {
|
|
14042
|
+
try {
|
|
14043
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
14044
|
+
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
14045
|
+
const node = await ensureSecretsNode(ctx, options);
|
|
14046
|
+
const networkName = options.network ?? "default";
|
|
14047
|
+
const networkId = networkName.startsWith("urn:tinycloud:encryption:") ? networkName : node.getDefaultEncryptionNetworkId(networkName);
|
|
14048
|
+
const descriptor = await withSpinner(
|
|
14049
|
+
"Checking secrets encryption network...",
|
|
14050
|
+
() => node.getEncryptionNetwork(networkName)
|
|
14051
|
+
);
|
|
14052
|
+
const checks = [
|
|
14053
|
+
descriptor ? {
|
|
14054
|
+
name: "Encryption network",
|
|
14055
|
+
ok: descriptor.state === "active" ? true : "warn",
|
|
14056
|
+
detail: `${networkName} (${descriptor.state})`
|
|
14057
|
+
} : {
|
|
14058
|
+
name: "Encryption network",
|
|
14059
|
+
ok: false,
|
|
14060
|
+
detail: `${networkName} not found`,
|
|
14061
|
+
hint: `tc secrets network init ${networkName}`
|
|
14062
|
+
}
|
|
14063
|
+
];
|
|
14064
|
+
let secret;
|
|
14065
|
+
if (name) {
|
|
14066
|
+
const scopeOptions = resolveSecretScope(options);
|
|
14067
|
+
const resolved = resolveSecretPath2(name, scopeOptions);
|
|
14068
|
+
const result = await runSecretOperation({
|
|
14069
|
+
ctx,
|
|
14070
|
+
node,
|
|
14071
|
+
action: "get",
|
|
14072
|
+
name,
|
|
14073
|
+
scopeOptions,
|
|
14074
|
+
label: `Checking secret ${name}...`,
|
|
14075
|
+
operation: () => node.secrets.get(name, scopeOptions)
|
|
14076
|
+
});
|
|
14077
|
+
if (result.ok) {
|
|
14078
|
+
secret = {
|
|
14079
|
+
name: resolved.name,
|
|
14080
|
+
path: resolved.permissionPaths.vault,
|
|
14081
|
+
...resolved.scope ? { scope: resolved.scope } : {},
|
|
14082
|
+
exists: true,
|
|
14083
|
+
readable: true
|
|
14084
|
+
};
|
|
14085
|
+
checks.push({
|
|
14086
|
+
name: "Secret access",
|
|
14087
|
+
ok: true,
|
|
14088
|
+
detail: `${resolved.permissionPaths.vault} readable`
|
|
14089
|
+
});
|
|
14090
|
+
} else {
|
|
14091
|
+
const notFound = result.error.code === "NOT_FOUND" || result.error.code === "KEY_NOT_FOUND";
|
|
14092
|
+
secret = {
|
|
14093
|
+
name: resolved.name,
|
|
14094
|
+
path: resolved.permissionPaths.vault,
|
|
14095
|
+
...resolved.scope ? { scope: resolved.scope } : {},
|
|
14096
|
+
exists: !notFound,
|
|
14097
|
+
readable: false
|
|
14098
|
+
};
|
|
14099
|
+
checks.push({
|
|
14100
|
+
name: "Secret access",
|
|
14101
|
+
ok: false,
|
|
14102
|
+
detail: notFound ? `${resolved.permissionPaths.vault} not found` : result.error.message,
|
|
14103
|
+
hint: notFound ? `tc secrets put ${resolved.name}${formatSecretScopeFlag(scopeOptions)} <value>` : "Ask the owner profile to grant tinycloud.kv/get and tinycloud.encryption/decrypt."
|
|
14104
|
+
});
|
|
14105
|
+
}
|
|
14106
|
+
} else {
|
|
14107
|
+
checks.push({
|
|
14108
|
+
name: "Secret access",
|
|
14109
|
+
ok: "warn",
|
|
14110
|
+
detail: "skipped; pass a secret name to verify read access"
|
|
14111
|
+
});
|
|
14112
|
+
}
|
|
14113
|
+
outputSecretDoctor({
|
|
14114
|
+
healthy: checks.every((check) => check.ok !== false),
|
|
14115
|
+
network: {
|
|
14116
|
+
name: networkName,
|
|
14117
|
+
networkId,
|
|
14118
|
+
exists: descriptor !== null,
|
|
14119
|
+
...descriptor?.state ? { state: descriptor.state } : {}
|
|
14120
|
+
},
|
|
14121
|
+
...secret ? { secret } : {},
|
|
14122
|
+
checks
|
|
14123
|
+
});
|
|
14124
|
+
} catch (error) {
|
|
14125
|
+
handleError(error);
|
|
14126
|
+
}
|
|
14127
|
+
});
|
|
13683
14128
|
secrets.command("list").description("List secrets").option("--scope <scope>", "Logical secret scope").option("--space <scope>", "Deprecated alias for --scope").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (options, cmd) => {
|
|
13684
14129
|
try {
|
|
13685
14130
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -13731,7 +14176,7 @@ function registerSecretsCommand(program2) {
|
|
|
13731
14176
|
})
|
|
13732
14177
|
);
|
|
13733
14178
|
if (options.output) {
|
|
13734
|
-
await
|
|
14179
|
+
await writeFile6(options.output, value2);
|
|
13735
14180
|
outputJson({ name, written: options.output });
|
|
13736
14181
|
return;
|
|
13737
14182
|
}
|
|
@@ -13760,7 +14205,7 @@ function registerSecretsCommand(program2) {
|
|
|
13760
14205
|
}
|
|
13761
14206
|
const value = String(result.data);
|
|
13762
14207
|
if (options.output) {
|
|
13763
|
-
await
|
|
14208
|
+
await writeFile6(options.output, value);
|
|
13764
14209
|
outputJson({ name, written: options.output });
|
|
13765
14210
|
return;
|
|
13766
14211
|
}
|
|
@@ -13867,8 +14312,8 @@ function registerSecretsCommand(program2) {
|
|
|
13867
14312
|
});
|
|
13868
14313
|
secrets.command("manage").description("Open the TinyCloud Secrets Manager in your browser").action(async () => {
|
|
13869
14314
|
try {
|
|
13870
|
-
const
|
|
13871
|
-
await
|
|
14315
|
+
const open2 = (await import("open")).default;
|
|
14316
|
+
await open2("https://secrets.tinycloud.xyz");
|
|
13872
14317
|
outputJson({ opened: "https://secrets.tinycloud.xyz" });
|
|
13873
14318
|
} catch (error) {
|
|
13874
14319
|
handleError(error);
|
|
@@ -13878,7 +14323,7 @@ function registerSecretsCommand(program2) {
|
|
|
13878
14323
|
|
|
13879
14324
|
// src/commands/vars.ts
|
|
13880
14325
|
import { readFile as readFile7 } from "fs/promises";
|
|
13881
|
-
import { writeFile as
|
|
14326
|
+
import { writeFile as writeFile7 } from "fs/promises";
|
|
13882
14327
|
var VARIABLES_PREFIX = "variables/";
|
|
13883
14328
|
async function readStdin5() {
|
|
13884
14329
|
const chunks = [];
|
|
@@ -13950,7 +14395,7 @@ function registerVarsCommand(program2) {
|
|
|
13950
14395
|
value = typeof data === "string" ? data : JSON.stringify(data);
|
|
13951
14396
|
}
|
|
13952
14397
|
if (options.output) {
|
|
13953
|
-
await
|
|
14398
|
+
await writeFile7(options.output, value);
|
|
13954
14399
|
outputJson({ name, written: options.output });
|
|
13955
14400
|
return;
|
|
13956
14401
|
}
|
|
@@ -14124,12 +14569,18 @@ function registerDoctorCommand(program2) {
|
|
|
14124
14569
|
}
|
|
14125
14570
|
|
|
14126
14571
|
// src/commands/sql.ts
|
|
14127
|
-
import { writeFile as
|
|
14572
|
+
import { writeFile as writeFile8 } from "fs/promises";
|
|
14128
14573
|
import { resolve } from "path";
|
|
14129
14574
|
async function dbHandle(node, dbName, spaceInput, profileName) {
|
|
14130
14575
|
const spaceUri = await resolveSpaceUri(spaceInput, profileName);
|
|
14131
14576
|
const sql = spaceUri ? node.sqlForSpace(spaceUri) : node.sql;
|
|
14132
|
-
return sql.db(dbName);
|
|
14577
|
+
return { handle: sql.db(dbName), spaceUri };
|
|
14578
|
+
}
|
|
14579
|
+
async function throwSqlError(error, spaceUri, profileName, prefix) {
|
|
14580
|
+
const hosted = await unhostedSpaceError(error, spaceUri, profileName);
|
|
14581
|
+
if (hosted) throw hosted;
|
|
14582
|
+
const message = prefix ? `${prefix}${error.message}` : error.message;
|
|
14583
|
+
throw new CLIError(error.code, message, ExitCode.ERROR, error.meta);
|
|
14133
14584
|
}
|
|
14134
14585
|
function registerSqlCommand(program2) {
|
|
14135
14586
|
const sql = program2.command("sql").description("SQLite database operations for your TinyCloud space").addHelpText("after", `
|
|
@@ -14175,13 +14626,13 @@ Output:
|
|
|
14175
14626
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
14176
14627
|
const node = await ensureAuthenticated(ctx);
|
|
14177
14628
|
const params = options.params ? JSON.parse(options.params) : void 0;
|
|
14178
|
-
const handle = await dbHandle(node, options.db, options.space, ctx.profile);
|
|
14629
|
+
const { handle, spaceUri } = await dbHandle(node, options.db, options.space, ctx.profile);
|
|
14179
14630
|
const result = await withSpinner(
|
|
14180
14631
|
"Running query...",
|
|
14181
14632
|
() => handle.query(sqlStr, params)
|
|
14182
14633
|
);
|
|
14183
14634
|
if (!result.ok) {
|
|
14184
|
-
|
|
14635
|
+
await throwSqlError(result.error, spaceUri, ctx.profile);
|
|
14185
14636
|
}
|
|
14186
14637
|
const { columns, rows, rowCount } = result.data;
|
|
14187
14638
|
if (shouldOutputJson()) {
|
|
@@ -14219,13 +14670,13 @@ Output:
|
|
|
14219
14670
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
14220
14671
|
const node = await ensureAuthenticated(ctx);
|
|
14221
14672
|
const params = options.params ? JSON.parse(options.params) : void 0;
|
|
14222
|
-
const handle = await dbHandle(node, options.db, options.space, ctx.profile);
|
|
14673
|
+
const { handle, spaceUri } = await dbHandle(node, options.db, options.space, ctx.profile);
|
|
14223
14674
|
const result = await withSpinner(
|
|
14224
14675
|
"Executing statement...",
|
|
14225
14676
|
() => handle.execute(sqlStr, params)
|
|
14226
14677
|
);
|
|
14227
14678
|
if (!result.ok) {
|
|
14228
|
-
|
|
14679
|
+
await throwSqlError(result.error, spaceUri, ctx.profile);
|
|
14229
14680
|
}
|
|
14230
14681
|
outputJson({
|
|
14231
14682
|
changes: result.data.changes,
|
|
@@ -14249,18 +14700,18 @@ Output:
|
|
|
14249
14700
|
const globalOpts = cmd.optsWithGlobals();
|
|
14250
14701
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
14251
14702
|
const node = await ensureAuthenticated(ctx);
|
|
14252
|
-
const handle = await dbHandle(node, options.db, options.space, ctx.profile);
|
|
14703
|
+
const { handle, spaceUri } = await dbHandle(node, options.db, options.space, ctx.profile);
|
|
14253
14704
|
const result = await withSpinner(
|
|
14254
14705
|
"Exporting database...",
|
|
14255
14706
|
() => handle.export()
|
|
14256
14707
|
);
|
|
14257
14708
|
if (!result.ok) {
|
|
14258
|
-
|
|
14709
|
+
await throwSqlError(result.error, spaceUri, ctx.profile);
|
|
14259
14710
|
}
|
|
14260
14711
|
const blob = result.data;
|
|
14261
14712
|
const buffer = Buffer.from(await blob.arrayBuffer());
|
|
14262
14713
|
const outputPath = resolve(options.output);
|
|
14263
|
-
await
|
|
14714
|
+
await writeFile8(outputPath, buffer);
|
|
14264
14715
|
outputJson({
|
|
14265
14716
|
file: outputPath,
|
|
14266
14717
|
size: blob.size,
|
|
@@ -14304,8 +14755,8 @@ Notes:
|
|
|
14304
14755
|
ExitCode.USAGE_ERROR
|
|
14305
14756
|
);
|
|
14306
14757
|
}
|
|
14307
|
-
const fromHandle = await dbHandle(node, options.fromDb, fromSpaceInput, ctx.profile);
|
|
14308
|
-
const toHandle = await dbHandle(node, options.toDb, toSpaceInput, ctx.profile);
|
|
14758
|
+
const { handle: fromHandle, spaceUri: fromSpaceUriResolved } = await dbHandle(node, options.fromDb, fromSpaceInput, ctx.profile);
|
|
14759
|
+
const { handle: toHandle, spaceUri: toSpaceUriResolved } = await dbHandle(node, options.toDb, toSpaceInput, ctx.profile);
|
|
14309
14760
|
let tables;
|
|
14310
14761
|
if (options.table && options.table.length > 0) {
|
|
14311
14762
|
tables = options.table.flatMap((t) => t.split(",").map((s) => s.trim()).filter(Boolean));
|
|
@@ -14314,7 +14765,7 @@ Notes:
|
|
|
14314
14765
|
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name"
|
|
14315
14766
|
);
|
|
14316
14767
|
if (!listing.ok) {
|
|
14317
|
-
|
|
14768
|
+
await throwSqlError(listing.error, fromSpaceUriResolved, ctx.profile, "Cannot list source tables: ");
|
|
14318
14769
|
}
|
|
14319
14770
|
tables = listing.data.rows.map((r) => String(r[0]));
|
|
14320
14771
|
}
|
|
@@ -14330,12 +14781,7 @@ Notes:
|
|
|
14330
14781
|
const safe = quoteIdent(table);
|
|
14331
14782
|
const countResult = await fromHandle.query(`SELECT count(*) AS n FROM ${safe}`);
|
|
14332
14783
|
if (!countResult.ok) {
|
|
14333
|
-
|
|
14334
|
-
countResult.error.code,
|
|
14335
|
-
`Cannot count rows in source table "${table}": ${countResult.error.message}`,
|
|
14336
|
-
ExitCode.ERROR,
|
|
14337
|
-
countResult.error.meta
|
|
14338
|
-
);
|
|
14784
|
+
await throwSqlError(countResult.error, fromSpaceUriResolved, ctx.profile, `Cannot count rows in source table "${table}": `);
|
|
14339
14785
|
}
|
|
14340
14786
|
const rows = Number(countResult.data.rows[0]?.[0] ?? 0);
|
|
14341
14787
|
plan.push({ table, rows, copied: 0, skipped: 0 });
|
|
@@ -14353,7 +14799,7 @@ Notes:
|
|
|
14353
14799
|
const safe = quoteIdent(entry.table);
|
|
14354
14800
|
const fetched = await fromHandle.query(`SELECT * FROM ${safe}`);
|
|
14355
14801
|
if (!fetched.ok) {
|
|
14356
|
-
|
|
14802
|
+
await throwSqlError(fetched.error, fromSpaceUriResolved, ctx.profile, `Failed to read "${entry.table}": `);
|
|
14357
14803
|
}
|
|
14358
14804
|
const columns = fetched.data.columns;
|
|
14359
14805
|
const rows = fetched.data.rows;
|
|
@@ -14364,12 +14810,7 @@ Notes:
|
|
|
14364
14810
|
for (const row of rows) {
|
|
14365
14811
|
const writeResult = await toHandle.execute(insertSql, row);
|
|
14366
14812
|
if (!writeResult.ok) {
|
|
14367
|
-
|
|
14368
|
-
writeResult.error.code,
|
|
14369
|
-
`Insert into "${entry.table}" failed after ${entry.copied} row(s): ${writeResult.error.message}`,
|
|
14370
|
-
ExitCode.ERROR,
|
|
14371
|
-
writeResult.error.meta
|
|
14372
|
-
);
|
|
14813
|
+
await throwSqlError(writeResult.error, toSpaceUriResolved, ctx.profile, `Insert into "${entry.table}" failed after ${entry.copied} row(s): `);
|
|
14373
14814
|
}
|
|
14374
14815
|
entry.copied += writeResult.data.changes ?? 1;
|
|
14375
14816
|
}
|
|
@@ -14389,7 +14830,7 @@ function quoteIdent(name) {
|
|
|
14389
14830
|
}
|
|
14390
14831
|
|
|
14391
14832
|
// src/commands/duckdb.ts
|
|
14392
|
-
import { readFile as readFile8, writeFile as
|
|
14833
|
+
import { readFile as readFile8, writeFile as writeFile9 } from "fs/promises";
|
|
14393
14834
|
import { resolve as resolve2 } from "path";
|
|
14394
14835
|
function registerDuckdbCommand(program2) {
|
|
14395
14836
|
const duckdb = program2.command("duckdb").description("DuckDB database operations");
|
|
@@ -14503,7 +14944,7 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
|
|
|
14503
14944
|
const blob = result.data;
|
|
14504
14945
|
const buffer = Buffer.from(await blob.arrayBuffer());
|
|
14505
14946
|
const outputPath = resolve2(options.output);
|
|
14506
|
-
await
|
|
14947
|
+
await writeFile9(outputPath, buffer);
|
|
14507
14948
|
outputJson({
|
|
14508
14949
|
file: outputPath,
|
|
14509
14950
|
size: blob.size,
|
|
@@ -15088,6 +15529,310 @@ function plural(count, label) {
|
|
|
15088
15529
|
return `${count} ${label}${count === 1 ? "" : "s"}`;
|
|
15089
15530
|
}
|
|
15090
15531
|
|
|
15532
|
+
// src/commands/account.ts
|
|
15533
|
+
import open from "open";
|
|
15534
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
15535
|
+
var ACCOUNT_BILLING_URL = "https://account.tinycloud.xyz/billing";
|
|
15536
|
+
function registerAccountCommand(program2) {
|
|
15537
|
+
const account = program2.command("account").description("Account applications, spaces, delegations, and billing");
|
|
15538
|
+
account.command("status").description("Show account status").action(async (_options, cmd) => {
|
|
15539
|
+
try {
|
|
15540
|
+
const node = await authenticatedNode(cmd);
|
|
15541
|
+
const status = await node.account.status();
|
|
15542
|
+
assertOk(status);
|
|
15543
|
+
outputJson(status.data);
|
|
15544
|
+
} catch (error) {
|
|
15545
|
+
handleError(error);
|
|
15546
|
+
}
|
|
15547
|
+
});
|
|
15548
|
+
const apps = account.command("apps").description("Manage account application registry");
|
|
15549
|
+
apps.command("list").description("List applications registered under account/applications").option("--live", "Read canonical account KV records instead of the SQLite index").action(async (_options, cmd) => {
|
|
15550
|
+
try {
|
|
15551
|
+
const options = _options;
|
|
15552
|
+
const node = await authenticatedNode(cmd);
|
|
15553
|
+
const result = options.live ? await node.account.applications.list() : await node.account.index.applications.list();
|
|
15554
|
+
assertOk(result);
|
|
15555
|
+
const payload = { applications: result.data, count: result.data.length };
|
|
15556
|
+
if (shouldOutputJson()) {
|
|
15557
|
+
outputJson(payload);
|
|
15558
|
+
return;
|
|
15559
|
+
}
|
|
15560
|
+
if (result.data.length === 0) {
|
|
15561
|
+
process.stdout.write(theme.muted("No account applications registered.") + "\n");
|
|
15562
|
+
return;
|
|
15563
|
+
}
|
|
15564
|
+
process.stdout.write(
|
|
15565
|
+
formatTable(
|
|
15566
|
+
["App ID", "Name", "Manifests", "Updated"],
|
|
15567
|
+
result.data.map((app) => [
|
|
15568
|
+
app.appId,
|
|
15569
|
+
app.name ?? "\u2014",
|
|
15570
|
+
String(app.manifests.length),
|
|
15571
|
+
app.updatedAt ?? "\u2014"
|
|
15572
|
+
])
|
|
15573
|
+
) + "\n"
|
|
15574
|
+
);
|
|
15575
|
+
} catch (error) {
|
|
15576
|
+
handleError(error);
|
|
15577
|
+
}
|
|
15578
|
+
});
|
|
15579
|
+
const spaces = account.command("spaces").description("Manage account space registry");
|
|
15580
|
+
spaces.command("list").description("List spaces registered under account/spaces").option("--live", "Read canonical account KV records instead of the SQLite index").action(async (options, cmd) => {
|
|
15581
|
+
try {
|
|
15582
|
+
const node = await authenticatedNode(cmd);
|
|
15583
|
+
const result = options.live ? await node.account.spaces.list() : await node.account.index.spaces.list();
|
|
15584
|
+
assertOk(result);
|
|
15585
|
+
const payload = { spaces: result.data.map(formatSpace), count: result.data.length };
|
|
15586
|
+
if (shouldOutputJson()) {
|
|
15587
|
+
outputJson(payload);
|
|
15588
|
+
return;
|
|
15589
|
+
}
|
|
15590
|
+
if (result.data.length === 0) {
|
|
15591
|
+
process.stdout.write(theme.muted("No account spaces registered.") + "\n");
|
|
15592
|
+
return;
|
|
15593
|
+
}
|
|
15594
|
+
process.stdout.write(
|
|
15595
|
+
formatTable(
|
|
15596
|
+
["Space", "Type", "Owner", "Status", "Updated"],
|
|
15597
|
+
result.data.map((space) => [
|
|
15598
|
+
space.name,
|
|
15599
|
+
space.type,
|
|
15600
|
+
space.ownerDid,
|
|
15601
|
+
space.status,
|
|
15602
|
+
space.updatedAt ?? "\u2014"
|
|
15603
|
+
])
|
|
15604
|
+
) + "\n"
|
|
15605
|
+
);
|
|
15606
|
+
} catch (error) {
|
|
15607
|
+
handleError(error);
|
|
15608
|
+
}
|
|
15609
|
+
});
|
|
15610
|
+
spaces.command("info <space-id>").description("Show a registered account space").action(async (spaceId, _options, cmd) => {
|
|
15611
|
+
try {
|
|
15612
|
+
const node = await authenticatedNode(cmd);
|
|
15613
|
+
const result = await node.account.spaces.get(spaceId);
|
|
15614
|
+
assertOk(result);
|
|
15615
|
+
outputJson(formatSpace(result.data));
|
|
15616
|
+
} catch (error) {
|
|
15617
|
+
handleError(error);
|
|
15618
|
+
}
|
|
15619
|
+
});
|
|
15620
|
+
spaces.command("register <space-id>").description("Register a space in account/spaces").requiredOption("--name <name>", "Display name for the space").requiredOption("--owner <did>", "Owner DID for the space").option("--type <type>", "Space type: owned, delegated, or discovered", "discovered").option("--permission <permission...>", "Permission strings for the space").action(async (spaceId, options, cmd) => {
|
|
15621
|
+
try {
|
|
15622
|
+
const node = await authenticatedNode(cmd);
|
|
15623
|
+
const result = await node.account.spaces.register({
|
|
15624
|
+
spaceId,
|
|
15625
|
+
name: options.name,
|
|
15626
|
+
ownerDid: options.owner,
|
|
15627
|
+
type: options.type,
|
|
15628
|
+
permissions: options.permission ?? [],
|
|
15629
|
+
status: "active"
|
|
15630
|
+
});
|
|
15631
|
+
assertOk(result);
|
|
15632
|
+
outputJson({ space: formatSpace(result.data), registered: true });
|
|
15633
|
+
} catch (error) {
|
|
15634
|
+
handleError(error);
|
|
15635
|
+
}
|
|
15636
|
+
});
|
|
15637
|
+
spaces.command("sync").description("Register currently accessible spaces into account/spaces").action(async (_options, cmd) => {
|
|
15638
|
+
try {
|
|
15639
|
+
const node = await authenticatedNode(cmd);
|
|
15640
|
+
const result = await node.account.spaces.syncAccessible();
|
|
15641
|
+
assertOk(result);
|
|
15642
|
+
outputJson({ spaces: result.data.map(formatSpace), count: result.data.length, synced: true });
|
|
15643
|
+
} catch (error) {
|
|
15644
|
+
handleError(error);
|
|
15645
|
+
}
|
|
15646
|
+
});
|
|
15647
|
+
spaces.command("remove <space-id>").alias("delete").description("Remove a space registry entry").action(async (spaceId, _options, cmd) => {
|
|
15648
|
+
try {
|
|
15649
|
+
const node = await authenticatedNode(cmd);
|
|
15650
|
+
const result = await node.account.spaces.remove(spaceId);
|
|
15651
|
+
assertOk(result);
|
|
15652
|
+
outputJson({ spaceId, removed: true });
|
|
15653
|
+
} catch (error) {
|
|
15654
|
+
handleError(error);
|
|
15655
|
+
}
|
|
15656
|
+
});
|
|
15657
|
+
apps.command("info <app-id>").description("Show a registered account application").action(async (appId, _options, cmd) => {
|
|
15658
|
+
try {
|
|
15659
|
+
const node = await authenticatedNode(cmd);
|
|
15660
|
+
const result = await node.account.applications.get(appId);
|
|
15661
|
+
assertOk(result);
|
|
15662
|
+
outputJson(result.data);
|
|
15663
|
+
} catch (error) {
|
|
15664
|
+
handleError(error);
|
|
15665
|
+
}
|
|
15666
|
+
});
|
|
15667
|
+
apps.command("register <manifest>").description("Register an app manifest in account/applications").action(async (manifestSource, _options, cmd) => {
|
|
15668
|
+
try {
|
|
15669
|
+
const node = await authenticatedNode(cmd);
|
|
15670
|
+
const manifest = await loadManifestSource2(manifestSource);
|
|
15671
|
+
const result = await node.account.applications.register(manifest);
|
|
15672
|
+
assertOk(result);
|
|
15673
|
+
outputJson({ application: result.data, registered: true });
|
|
15674
|
+
} catch (error) {
|
|
15675
|
+
handleError(error);
|
|
15676
|
+
}
|
|
15677
|
+
});
|
|
15678
|
+
apps.command("remove <app-id>").alias("delete").description("Remove an application registry entry").action(async (appId, _options, cmd) => {
|
|
15679
|
+
try {
|
|
15680
|
+
const node = await authenticatedNode(cmd);
|
|
15681
|
+
const result = await node.account.applications.remove(appId);
|
|
15682
|
+
assertOk(result);
|
|
15683
|
+
outputJson({ appId, removed: true });
|
|
15684
|
+
} catch (error) {
|
|
15685
|
+
handleError(error);
|
|
15686
|
+
}
|
|
15687
|
+
});
|
|
15688
|
+
const delegations = account.command("delegations").description("View and revoke account delegations");
|
|
15689
|
+
delegations.command("list").description("List delegations granted by or to this account").option("--granted", "Show only delegations granted by this account").option("--received", "Show only delegations granted to this account").option("--space <space>", "Filter by space name or ID").option("--live", "Read live delegation services instead of the SQLite index").action(async (options, cmd) => {
|
|
15690
|
+
try {
|
|
15691
|
+
if (options.granted && options.received) {
|
|
15692
|
+
throw new CLIError("USAGE_ERROR", "Use only one of --granted or --received.", ExitCode.USAGE_ERROR);
|
|
15693
|
+
}
|
|
15694
|
+
const node = await authenticatedNode(cmd);
|
|
15695
|
+
const direction = options.granted ? "granted" : options.received ? "received" : "all";
|
|
15696
|
+
const result = options.live ? await node.account.delegations.list({ direction, space: options.space }) : await node.account.index.delegations.list({ direction, space: options.space });
|
|
15697
|
+
assertOk(result);
|
|
15698
|
+
const payload = { delegations: result.data.map(formatDelegation2), count: result.data.length };
|
|
15699
|
+
if (shouldOutputJson()) {
|
|
15700
|
+
outputJson(payload);
|
|
15701
|
+
return;
|
|
15702
|
+
}
|
|
15703
|
+
if (result.data.length === 0) {
|
|
15704
|
+
process.stdout.write(theme.muted("No delegations found.") + "\n");
|
|
15705
|
+
return;
|
|
15706
|
+
}
|
|
15707
|
+
process.stdout.write(
|
|
15708
|
+
formatTable(
|
|
15709
|
+
["CID", "Direction", "Space", "Counterparty", "Status", "Expiry"],
|
|
15710
|
+
result.data.map((delegation) => [
|
|
15711
|
+
delegation.cid,
|
|
15712
|
+
delegation.direction,
|
|
15713
|
+
delegation.spaceName ?? delegation.spaceId,
|
|
15714
|
+
delegation.counterpartyDid,
|
|
15715
|
+
delegation.status,
|
|
15716
|
+
delegation.expiry.toISOString()
|
|
15717
|
+
])
|
|
15718
|
+
) + "\n"
|
|
15719
|
+
);
|
|
15720
|
+
} catch (error) {
|
|
15721
|
+
handleError(error);
|
|
15722
|
+
}
|
|
15723
|
+
});
|
|
15724
|
+
delegations.command("revoke <cid>").description("Revoke an active delegation granted by this account").requiredOption("--space <space>", "Space name or ID containing the delegation").action(async (cid, options, cmd) => {
|
|
15725
|
+
try {
|
|
15726
|
+
const node = await authenticatedNode(cmd);
|
|
15727
|
+
const result = await node.account.delegations.revoke({ cid, space: options.space });
|
|
15728
|
+
assertOk(result);
|
|
15729
|
+
outputJson({ cid, space: options.space, revoked: true });
|
|
15730
|
+
} catch (error) {
|
|
15731
|
+
handleError(error);
|
|
15732
|
+
}
|
|
15733
|
+
});
|
|
15734
|
+
const index = account.command("index").description("Manage the materialized account SQLite index");
|
|
15735
|
+
index.command("status").description("Show account SQLite index sync status").action(async (_options, cmd) => {
|
|
15736
|
+
try {
|
|
15737
|
+
const node = await authenticatedNode(cmd);
|
|
15738
|
+
const result = await node.account.index.status();
|
|
15739
|
+
assertOk(result);
|
|
15740
|
+
outputJson(result.data);
|
|
15741
|
+
} catch (error) {
|
|
15742
|
+
handleError(error);
|
|
15743
|
+
}
|
|
15744
|
+
});
|
|
15745
|
+
index.command("rebuild").description("Rebuild account SQLite index from canonical account data").action(async (_options, cmd) => {
|
|
15746
|
+
try {
|
|
15747
|
+
const node = await authenticatedNode(cmd);
|
|
15748
|
+
const result = await node.account.index.rebuild();
|
|
15749
|
+
assertOk(result);
|
|
15750
|
+
outputJson(result.data);
|
|
15751
|
+
} catch (error) {
|
|
15752
|
+
handleError(error);
|
|
15753
|
+
}
|
|
15754
|
+
});
|
|
15755
|
+
index.command("query <sql>").description("Query the materialized account SQLite index").option("--params <json>", "Bind parameters as a JSON array for ? placeholders").action(async (sql, options, cmd) => {
|
|
15756
|
+
try {
|
|
15757
|
+
const node = await authenticatedNode(cmd);
|
|
15758
|
+
const params = parseParams(options.params);
|
|
15759
|
+
const result = await node.account.index.query(sql, params);
|
|
15760
|
+
assertOk(result);
|
|
15761
|
+
outputJson(result.data);
|
|
15762
|
+
} catch (error) {
|
|
15763
|
+
handleError(error);
|
|
15764
|
+
}
|
|
15765
|
+
});
|
|
15766
|
+
const billing = account.command("billing").description("Open account billing");
|
|
15767
|
+
for (const name of ["status", "checkout", "portal"]) {
|
|
15768
|
+
billing.command(name).description(`${name === "status" ? "Show" : "Open"} account billing page`).option("--open", "Open account.tinycloud.xyz in your browser").action(async (options) => {
|
|
15769
|
+
try {
|
|
15770
|
+
if (options.open) {
|
|
15771
|
+
await open(ACCOUNT_BILLING_URL);
|
|
15772
|
+
}
|
|
15773
|
+
outputJson({ url: ACCOUNT_BILLING_URL, opened: Boolean(options.open) });
|
|
15774
|
+
} catch (error) {
|
|
15775
|
+
handleError(error);
|
|
15776
|
+
}
|
|
15777
|
+
});
|
|
15778
|
+
}
|
|
15779
|
+
}
|
|
15780
|
+
async function authenticatedNode(cmd) {
|
|
15781
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
15782
|
+
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
15783
|
+
return ensureAuthenticated(ctx);
|
|
15784
|
+
}
|
|
15785
|
+
function assertOk(result) {
|
|
15786
|
+
if (!result.ok) {
|
|
15787
|
+
throw new CLIError(result.error.code, result.error.message, ExitCode.ERROR);
|
|
15788
|
+
}
|
|
15789
|
+
}
|
|
15790
|
+
async function loadManifestSource2(source) {
|
|
15791
|
+
const raw = /^https?:\/\//i.test(source) ? await fetchManifest(source) : await readFile10(source, "utf8");
|
|
15792
|
+
return JSON.parse(raw);
|
|
15793
|
+
}
|
|
15794
|
+
async function fetchManifest(source) {
|
|
15795
|
+
const response = await fetch(source);
|
|
15796
|
+
if (!response.ok) {
|
|
15797
|
+
throw new CLIError(
|
|
15798
|
+
"MANIFEST_FETCH_FAILED",
|
|
15799
|
+
`Failed to fetch manifest from ${source}: ${response.status} ${response.statusText}`,
|
|
15800
|
+
ExitCode.NETWORK_ERROR
|
|
15801
|
+
);
|
|
15802
|
+
}
|
|
15803
|
+
return response.text();
|
|
15804
|
+
}
|
|
15805
|
+
function parseParams(input) {
|
|
15806
|
+
if (!input) return void 0;
|
|
15807
|
+
const parsed = JSON.parse(input);
|
|
15808
|
+
if (!Array.isArray(parsed)) {
|
|
15809
|
+
throw new CLIError("INVALID_PARAMS", "--params must be a JSON array.", ExitCode.USAGE_ERROR);
|
|
15810
|
+
}
|
|
15811
|
+
return parsed;
|
|
15812
|
+
}
|
|
15813
|
+
function formatDelegation2(delegation) {
|
|
15814
|
+
return {
|
|
15815
|
+
cid: delegation.cid,
|
|
15816
|
+
direction: delegation.direction,
|
|
15817
|
+
spaceId: delegation.spaceId,
|
|
15818
|
+
spaceName: delegation.spaceName,
|
|
15819
|
+
counterpartyDid: delegation.counterpartyDid,
|
|
15820
|
+
delegateDid: delegation.delegateDid,
|
|
15821
|
+
delegatorDid: delegation.delegatorDid,
|
|
15822
|
+
path: delegation.path,
|
|
15823
|
+
actions: delegation.actions,
|
|
15824
|
+
expiry: delegation.expiry.toISOString(),
|
|
15825
|
+
status: delegation.status,
|
|
15826
|
+
createdAt: delegation.createdAt?.toISOString()
|
|
15827
|
+
};
|
|
15828
|
+
}
|
|
15829
|
+
function formatSpace(space) {
|
|
15830
|
+
return {
|
|
15831
|
+
...space,
|
|
15832
|
+
expiresAt: space.expiresAt?.toISOString()
|
|
15833
|
+
};
|
|
15834
|
+
}
|
|
15835
|
+
|
|
15091
15836
|
// src/index.ts
|
|
15092
15837
|
var { version } = JSON.parse(
|
|
15093
15838
|
readFileSync3(new URL("../package.json", import.meta.url), "utf-8")
|
|
@@ -15138,6 +15883,7 @@ registerDuckdbCommand(program);
|
|
|
15138
15883
|
registerManifestCommand(program);
|
|
15139
15884
|
registerUpgradeCommand(program);
|
|
15140
15885
|
registerStatusCommand(program);
|
|
15886
|
+
registerAccountCommand(program);
|
|
15141
15887
|
program.addHelpText("before", () => `${theme.label("Version:")} ${theme.value(version)}
|
|
15142
15888
|
`);
|
|
15143
15889
|
program.addHelpText("afterAll", () => {
|
|
@@ -15149,6 +15895,7 @@ ${theme.heading("Examples:")}
|
|
|
15149
15895
|
${theme.command('tc kv put greeting "Hello"')} ${theme.muted("Store a value")}
|
|
15150
15896
|
${theme.command("tc kv list")} ${theme.muted("List all keys")}
|
|
15151
15897
|
${theme.command("tc secrets network init")} ${theme.muted("Create the default secrets network")}
|
|
15898
|
+
${theme.command("tc account apps list")} ${theme.muted("List registered account apps")}
|
|
15152
15899
|
${theme.command("tc delegation create --to did:pkh:...")} ${theme.muted("Grant access to another user")}
|
|
15153
15900
|
${theme.command("tc space list")} ${theme.muted("Show your spaces")}
|
|
15154
15901
|
|