@tinycloud/cli 0.7.0-beta.2 → 0.7.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +235 -87
- 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
|
}
|
|
@@ -1053,7 +1054,7 @@ import { spawn } from "child_process";
|
|
|
1053
1054
|
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
1054
1055
|
import { dirname as dirname2 } from "path";
|
|
1055
1056
|
import { createInterface as createInterface2 } from "readline";
|
|
1056
|
-
import { principalDidEquals } from "@tinycloud/node-sdk";
|
|
1057
|
+
import { grantAuthRequest, principalDidEquals } from "@tinycloud/node-sdk";
|
|
1057
1058
|
|
|
1058
1059
|
// src/config/types.ts
|
|
1059
1060
|
var CLI_PROFILE_POSTURES = [
|
|
@@ -5740,6 +5741,31 @@ var KVService = class extends BaseService {
|
|
|
5740
5741
|
}
|
|
5741
5742
|
return void 0;
|
|
5742
5743
|
}
|
|
5744
|
+
/**
|
|
5745
|
+
* Classify a KV 404 by reading the response body once.
|
|
5746
|
+
*
|
|
5747
|
+
* The server returns 404 both for a genuinely missing key AND for an
|
|
5748
|
+
* un-hosted space (body "Space not found"). Previously get/head/delete
|
|
5749
|
+
* collapsed every 404 to KV_NOT_FOUND before reading the body, so an
|
|
5750
|
+
* un-hosted-space read was indistinguishable from a missing key. We now
|
|
5751
|
+
* preserve status + the "Space not found" body for the un-hosted case (so the
|
|
5752
|
+
* CLI/SDK can normalize it to SPACE_NOT_HOSTED, matching put/list/sql), and
|
|
5753
|
+
* fall through to KV_NOT_FOUND for a real missing key.
|
|
5754
|
+
*/
|
|
5755
|
+
async classifyNotFound(response, key) {
|
|
5756
|
+
const errorText = await response.text();
|
|
5757
|
+
if (/space not found/i.test(errorText)) {
|
|
5758
|
+
return err(
|
|
5759
|
+
serviceError(
|
|
5760
|
+
ErrorCodes.KV_NOT_FOUND,
|
|
5761
|
+
`KV ${response.status} - ${errorText}`,
|
|
5762
|
+
"kv",
|
|
5763
|
+
{ meta: { status: response.status, statusText: response.statusText } }
|
|
5764
|
+
)
|
|
5765
|
+
);
|
|
5766
|
+
}
|
|
5767
|
+
return err(serviceError(ErrorCodes.KV_NOT_FOUND, `Key not found: ${key}`, "kv"));
|
|
5768
|
+
}
|
|
5743
5769
|
/**
|
|
5744
5770
|
* Get the full path with optional prefix.
|
|
5745
5771
|
*
|
|
@@ -5986,13 +6012,7 @@ var KVService = class extends BaseService {
|
|
|
5986
6012
|
}));
|
|
5987
6013
|
}
|
|
5988
6014
|
if (response.status === 404) {
|
|
5989
|
-
return
|
|
5990
|
-
serviceError(
|
|
5991
|
-
ErrorCodes.KV_NOT_FOUND,
|
|
5992
|
-
`Key not found: ${key}`,
|
|
5993
|
-
"kv"
|
|
5994
|
-
)
|
|
5995
|
-
);
|
|
6015
|
+
return this.classifyNotFound(response, key);
|
|
5996
6016
|
}
|
|
5997
6017
|
const errorText = await response.text();
|
|
5998
6018
|
return err(
|
|
@@ -6253,13 +6273,7 @@ var KVService = class extends BaseService {
|
|
|
6253
6273
|
}));
|
|
6254
6274
|
}
|
|
6255
6275
|
if (response.status === 404) {
|
|
6256
|
-
return
|
|
6257
|
-
serviceError(
|
|
6258
|
-
ErrorCodes.KV_NOT_FOUND,
|
|
6259
|
-
`Key not found: ${key}`,
|
|
6260
|
-
"kv"
|
|
6261
|
-
)
|
|
6262
|
-
);
|
|
6276
|
+
return this.classifyNotFound(response, key);
|
|
6263
6277
|
}
|
|
6264
6278
|
const errorText = await response.text();
|
|
6265
6279
|
return err(
|
|
@@ -6304,13 +6318,7 @@ var KVService = class extends BaseService {
|
|
|
6304
6318
|
}));
|
|
6305
6319
|
}
|
|
6306
6320
|
if (response.status === 404) {
|
|
6307
|
-
return
|
|
6308
|
-
serviceError(
|
|
6309
|
-
ErrorCodes.KV_NOT_FOUND,
|
|
6310
|
-
`Key not found: ${key}`,
|
|
6311
|
-
"kv"
|
|
6312
|
-
)
|
|
6313
|
-
);
|
|
6321
|
+
return this.classifyNotFound(response, key);
|
|
6314
6322
|
}
|
|
6315
6323
|
const errorText = await response.text();
|
|
6316
6324
|
return err(
|
|
@@ -11488,21 +11496,8 @@ function registerAuthCommand(program2) {
|
|
|
11488
11496
|
expiryOption: parsed.requestedExpiry,
|
|
11489
11497
|
yes: options.yes === true
|
|
11490
11498
|
});
|
|
11491
|
-
const
|
|
11492
|
-
|
|
11493
|
-
parsed.requested,
|
|
11494
|
-
parsed.requestedExpiry !== void 0 ? { expiry: parsed.requestedExpiry } : void 0
|
|
11495
|
-
);
|
|
11496
|
-
outputJson({
|
|
11497
|
-
kind: "tinycloud.auth.delegation",
|
|
11498
|
-
version: 1,
|
|
11499
|
-
requestId: parsed.requestId,
|
|
11500
|
-
delegationCid: result.delegation.cid,
|
|
11501
|
-
delegation: result.delegation,
|
|
11502
|
-
permissions: parsed.requested,
|
|
11503
|
-
expiry: result.delegation.expiry.toISOString(),
|
|
11504
|
-
prompted: result.prompted
|
|
11505
|
-
});
|
|
11499
|
+
const grant = await grantAuthRequest(node, parsed);
|
|
11500
|
+
outputJson(grant);
|
|
11506
11501
|
} catch (error) {
|
|
11507
11502
|
handleError(error);
|
|
11508
11503
|
}
|
|
@@ -11918,9 +11913,10 @@ function groupPermissionsBySpace(permissions) {
|
|
|
11918
11913
|
rawEntries.push(permission);
|
|
11919
11914
|
continue;
|
|
11920
11915
|
}
|
|
11921
|
-
const
|
|
11916
|
+
const key = normalizeSpaceForCompare(permission.space);
|
|
11917
|
+
const group = groups.get(key) ?? [];
|
|
11922
11918
|
group.push(permission);
|
|
11923
|
-
groups.set(
|
|
11919
|
+
groups.set(key, group);
|
|
11924
11920
|
}
|
|
11925
11921
|
const grouped = Array.from(groups.values());
|
|
11926
11922
|
if (grouped.length === 0) {
|
|
@@ -11932,8 +11928,16 @@ function groupPermissionsBySpace(permissions) {
|
|
|
11932
11928
|
function isRawPermission(permission) {
|
|
11933
11929
|
return permission.service === "tinycloud.encryption" && permission.path.startsWith("urn:tinycloud:encryption:");
|
|
11934
11930
|
}
|
|
11931
|
+
function normalizeSpaceForCompare(space) {
|
|
11932
|
+
return space.replace(
|
|
11933
|
+
/(eip155:\d+:)(0x[0-9a-fA-F]{40})/,
|
|
11934
|
+
(_match, prefix, addr) => prefix + addr.toLowerCase()
|
|
11935
|
+
);
|
|
11936
|
+
}
|
|
11935
11937
|
function returnedSpaceMatchesExpected(returnedSpace, expectedSpace) {
|
|
11936
|
-
if (returnedSpace === expectedSpace)
|
|
11938
|
+
if (normalizeSpaceForCompare(returnedSpace) === normalizeSpaceForCompare(expectedSpace)) {
|
|
11939
|
+
return true;
|
|
11940
|
+
}
|
|
11937
11941
|
if (!returnedSpace.startsWith("tinycloud:")) return false;
|
|
11938
11942
|
const returnedName = returnedSpace.slice(returnedSpace.lastIndexOf(":") + 1);
|
|
11939
11943
|
return returnedName === expectedSpace;
|
|
@@ -11942,7 +11946,7 @@ function portableFromOpenKeyDelegation(data, permissions, host) {
|
|
|
11942
11946
|
const primary = permissions.find((permission) => !isRawPermission(permission)) ?? permissions[0];
|
|
11943
11947
|
const returnedSpace = String(data.spaceId ?? primary.space ?? "encryption");
|
|
11944
11948
|
const expectedSpaces = new Set(
|
|
11945
|
-
permissions.filter((permission) => !isRawPermission(permission)).map((permission) => permission.space)
|
|
11949
|
+
permissions.filter((permission) => !isRawPermission(permission)).map((permission) => normalizeSpaceForCompare(permission.space))
|
|
11946
11950
|
);
|
|
11947
11951
|
const matchesExpectedSpace = expectedSpaces.size === 1 && returnedSpaceMatchesExpected(returnedSpace, Array.from(expectedSpaces)[0]);
|
|
11948
11952
|
if (expectedSpaces.size > 0 && !matchesExpectedSpace) {
|
|
@@ -12169,6 +12173,79 @@ async function refreshOpenKeySession(profileName, host, options = {}) {
|
|
|
12169
12173
|
// src/commands/kv.ts
|
|
12170
12174
|
import { readFile as readFile4 } from "fs/promises";
|
|
12171
12175
|
import { writeFile as writeFile3 } from "fs/promises";
|
|
12176
|
+
|
|
12177
|
+
// src/lib/host.ts
|
|
12178
|
+
function canonicalizeAddress2(address) {
|
|
12179
|
+
const trimmed = address.trim();
|
|
12180
|
+
return trimmed.startsWith("0x") ? `0x${trimmed.slice(2).toLowerCase()}` : trimmed.toLowerCase();
|
|
12181
|
+
}
|
|
12182
|
+
async function resolveLocalAddress(profile, profileName) {
|
|
12183
|
+
const session = await ProfileManager.getSession(profileName);
|
|
12184
|
+
const sessAddr = session?.address;
|
|
12185
|
+
if (typeof sessAddr === "string" && sessAddr.length > 0) {
|
|
12186
|
+
return canonicalizeAddress2(sessAddr);
|
|
12187
|
+
}
|
|
12188
|
+
if (profile.address) return canonicalizeAddress2(profile.address);
|
|
12189
|
+
if (profile.ownerDid) {
|
|
12190
|
+
const match = profile.ownerDid.match(/^did:pkh:eip155:\d+:(0x[a-fA-F0-9]{40})$/);
|
|
12191
|
+
if (match) return canonicalizeAddress2(match[1]);
|
|
12192
|
+
}
|
|
12193
|
+
return null;
|
|
12194
|
+
}
|
|
12195
|
+
function ownerAddressFromSpaceUri(spaceUri) {
|
|
12196
|
+
const match = spaceUri.match(/^tinycloud:pkh:eip155:\d+:(0x[a-fA-F0-9]{40}):/);
|
|
12197
|
+
return match ? canonicalizeAddress2(match[1]) : null;
|
|
12198
|
+
}
|
|
12199
|
+
function ownerDidFromSpaceUri(spaceUri) {
|
|
12200
|
+
const match = spaceUri.match(/^tinycloud:pkh:eip155:(\d+):(0x[a-fA-F0-9]{40}):/);
|
|
12201
|
+
if (!match) return null;
|
|
12202
|
+
return `did:pkh:eip155:${match[1]}:${canonicalizeAddress2(match[2])}`;
|
|
12203
|
+
}
|
|
12204
|
+
async function isRootAuthority(spaceUri, profileName) {
|
|
12205
|
+
const profile = await ProfileManager.getProfile(profileName);
|
|
12206
|
+
if (resolveProfilePosture(profile) === "delegate-session") return false;
|
|
12207
|
+
const ownerAddr = ownerAddressFromSpaceUri(spaceUri);
|
|
12208
|
+
if (!ownerAddr) return false;
|
|
12209
|
+
const selfAddr = await resolveLocalAddress(profile, profileName);
|
|
12210
|
+
return selfAddr !== null && selfAddr === ownerAddr;
|
|
12211
|
+
}
|
|
12212
|
+
function spaceNameFromUri(spaceUri) {
|
|
12213
|
+
return spaceUri.slice(spaceUri.lastIndexOf(":") + 1);
|
|
12214
|
+
}
|
|
12215
|
+
async function unhostedSpaceError(error, spaceUri, profileName) {
|
|
12216
|
+
if (!spaceUri) return null;
|
|
12217
|
+
const status = error.meta?.status;
|
|
12218
|
+
const isUnhosted = status === 404 && /space not found/i.test(error.message);
|
|
12219
|
+
if (!isUnhosted) return null;
|
|
12220
|
+
const spaceName = spaceNameFromUri(spaceUri);
|
|
12221
|
+
const owner = await isRootAuthority(spaceUri, profileName);
|
|
12222
|
+
const hint = owner ? [
|
|
12223
|
+
"You are the owner. Host it once:",
|
|
12224
|
+
` tc space host ${spaceName}`,
|
|
12225
|
+
"Then retry."
|
|
12226
|
+
].join("\n") : [
|
|
12227
|
+
"You are a delegate and CANNOT host this space \u2014 only its owner can.",
|
|
12228
|
+
"Emit a host request:",
|
|
12229
|
+
` tc space host-request ${spaceName} --emit ./host-request.json`,
|
|
12230
|
+
"Send it to the owner; they run `tc space host` and confirm. Then retry."
|
|
12231
|
+
].join("\n");
|
|
12232
|
+
const message = owner ? `Space '${spaceName}' (${spaceUri}) is not hosted.` : `Space '${spaceName}' (owner ${ownerDidFromSpaceUri(spaceUri) ?? spaceUri}) is not hosted.`;
|
|
12233
|
+
return new CLIError("SPACE_NOT_HOSTED", message, ExitCode.ERROR, { hint });
|
|
12234
|
+
}
|
|
12235
|
+
async function resolveHostSpace(name, profileName) {
|
|
12236
|
+
const resolved = await resolveSpaceUri(name, profileName);
|
|
12237
|
+
if (!resolved) {
|
|
12238
|
+
throw new Error(`Could not resolve a space for "${name}".`);
|
|
12239
|
+
}
|
|
12240
|
+
return resolved;
|
|
12241
|
+
}
|
|
12242
|
+
|
|
12243
|
+
// src/commands/kv.ts
|
|
12244
|
+
async function throwKvError(error, spaceUri, profileName) {
|
|
12245
|
+
const hosted = await unhostedSpaceError(error, spaceUri, profileName);
|
|
12246
|
+
if (hosted) throw hosted;
|
|
12247
|
+
throw new CLIError(error.code, error.message, ExitCode.ERROR);
|
|
12248
|
+
}
|
|
12172
12249
|
async function readStdin2() {
|
|
12173
12250
|
const chunks = [];
|
|
12174
12251
|
for await (const chunk of process.stdin) {
|
|
@@ -12178,7 +12255,8 @@ async function readStdin2() {
|
|
|
12178
12255
|
}
|
|
12179
12256
|
async function kvHandle(node, spaceInput, profileName) {
|
|
12180
12257
|
const spaceUri = await resolveSpaceUri(spaceInput, profileName);
|
|
12181
|
-
|
|
12258
|
+
const kv = spaceUri ? node.kvForSpace(spaceUri) : node.kv;
|
|
12259
|
+
return { kv, spaceUri };
|
|
12182
12260
|
}
|
|
12183
12261
|
function registerKvCommand(program2) {
|
|
12184
12262
|
const kv = program2.command("kv").description("Key-value store operations");
|
|
@@ -12187,17 +12265,19 @@ function registerKvCommand(program2) {
|
|
|
12187
12265
|
const globalOpts = cmd.optsWithGlobals();
|
|
12188
12266
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
12189
12267
|
const node = await ensureAuthenticated(ctx);
|
|
12190
|
-
const kv2 = await kvHandle(node, options.space, ctx.profile);
|
|
12268
|
+
const { kv: kv2, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
12191
12269
|
const wantBytes = !!options.output || !!options.raw;
|
|
12192
12270
|
const result = await withSpinner(
|
|
12193
12271
|
`Getting ${key}...`,
|
|
12194
12272
|
() => kv2.get(key, wantBytes ? { binary: true } : void 0)
|
|
12195
12273
|
);
|
|
12196
12274
|
if (!result.ok) {
|
|
12275
|
+
const hosted = await unhostedSpaceError(result.error, spaceUri, ctx.profile);
|
|
12276
|
+
if (hosted) throw hosted;
|
|
12197
12277
|
if (result.error.code === "KV_NOT_FOUND" || result.error.code === "NOT_FOUND") {
|
|
12198
12278
|
throw new CLIError("NOT_FOUND", `Key "${key}" not found`, ExitCode.NOT_FOUND);
|
|
12199
12279
|
}
|
|
12200
|
-
|
|
12280
|
+
await throwKvError(result.error, spaceUri, ctx.profile);
|
|
12201
12281
|
}
|
|
12202
12282
|
const data = result.data.data;
|
|
12203
12283
|
const metadata = result.data.headers ?? {};
|
|
@@ -12248,10 +12328,10 @@ function registerKvCommand(program2) {
|
|
|
12248
12328
|
putValue = value;
|
|
12249
12329
|
}
|
|
12250
12330
|
}
|
|
12251
|
-
const kv2 = await kvHandle(node, options.space, ctx.profile);
|
|
12331
|
+
const { kv: kv2, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
12252
12332
|
const result = await withSpinner(`Writing ${key}...`, () => kv2.put(key, putValue));
|
|
12253
12333
|
if (!result.ok) {
|
|
12254
|
-
|
|
12334
|
+
await throwKvError(result.error, spaceUri, ctx.profile);
|
|
12255
12335
|
}
|
|
12256
12336
|
outputJson({ key, written: true });
|
|
12257
12337
|
} catch (error) {
|
|
@@ -12263,10 +12343,10 @@ function registerKvCommand(program2) {
|
|
|
12263
12343
|
const globalOpts = cmd.optsWithGlobals();
|
|
12264
12344
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
12265
12345
|
const node = await ensureAuthenticated(ctx);
|
|
12266
|
-
const kv2 = await kvHandle(node, options.space, ctx.profile);
|
|
12346
|
+
const { kv: kv2, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
12267
12347
|
const result = await withSpinner(`Deleting ${key}...`, () => kv2.delete(key));
|
|
12268
12348
|
if (!result.ok) {
|
|
12269
|
-
|
|
12349
|
+
await throwKvError(result.error, spaceUri, ctx.profile);
|
|
12270
12350
|
}
|
|
12271
12351
|
outputJson({ key, deleted: true });
|
|
12272
12352
|
} catch (error) {
|
|
@@ -12278,11 +12358,11 @@ function registerKvCommand(program2) {
|
|
|
12278
12358
|
const globalOpts = cmd.optsWithGlobals();
|
|
12279
12359
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
12280
12360
|
const node = await ensureAuthenticated(ctx);
|
|
12281
|
-
const kv2 = await kvHandle(node, options.space, ctx.profile);
|
|
12361
|
+
const { kv: kv2, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
12282
12362
|
const listOptions = options.prefix ? { prefix: options.prefix } : void 0;
|
|
12283
12363
|
const result = await withSpinner("Listing keys...", () => kv2.list(listOptions));
|
|
12284
12364
|
if (!result.ok) {
|
|
12285
|
-
|
|
12365
|
+
await throwKvError(result.error, spaceUri, ctx.profile);
|
|
12286
12366
|
}
|
|
12287
12367
|
const rawData = result.data.data ?? result.data;
|
|
12288
12368
|
const keyList = Array.isArray(rawData) ? rawData : rawData?.keys ?? [];
|
|
@@ -12313,14 +12393,16 @@ function registerKvCommand(program2) {
|
|
|
12313
12393
|
const globalOpts = cmd.optsWithGlobals();
|
|
12314
12394
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
12315
12395
|
const node = await ensureAuthenticated(ctx);
|
|
12316
|
-
const kv2 = await kvHandle(node, options.space, ctx.profile);
|
|
12396
|
+
const { kv: kv2, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
12317
12397
|
const result = await withSpinner(`Checking ${key}...`, () => kv2.head(key));
|
|
12318
12398
|
if (!result.ok) {
|
|
12399
|
+
const hosted = await unhostedSpaceError(result.error, spaceUri, ctx.profile);
|
|
12400
|
+
if (hosted) throw hosted;
|
|
12319
12401
|
if (result.error.code === "KV_NOT_FOUND" || result.error.code === "NOT_FOUND") {
|
|
12320
12402
|
outputJson({ key, exists: false, metadata: {} });
|
|
12321
12403
|
return;
|
|
12322
12404
|
}
|
|
12323
|
-
|
|
12405
|
+
await throwKvError(result.error, spaceUri, ctx.profile);
|
|
12324
12406
|
}
|
|
12325
12407
|
outputJson({
|
|
12326
12408
|
key,
|
|
@@ -12334,6 +12416,13 @@ function registerKvCommand(program2) {
|
|
|
12334
12416
|
}
|
|
12335
12417
|
|
|
12336
12418
|
// src/commands/space.ts
|
|
12419
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
12420
|
+
import { mkdir as mkdir3, writeFile as writeFile4 } from "fs/promises";
|
|
12421
|
+
import { dirname as dirname3 } from "path";
|
|
12422
|
+
function didWithoutFragment2(did) {
|
|
12423
|
+
const fragment = did.indexOf("#");
|
|
12424
|
+
return fragment === -1 ? did : did.slice(0, fragment);
|
|
12425
|
+
}
|
|
12337
12426
|
function registerSpaceCommand(program2) {
|
|
12338
12427
|
const space = program2.command("space").description("Space management");
|
|
12339
12428
|
space.command("list").description("List spaces").action(async (_options, cmd) => {
|
|
@@ -12374,6 +12463,53 @@ function registerSpaceCommand(program2) {
|
|
|
12374
12463
|
handleError(error);
|
|
12375
12464
|
}
|
|
12376
12465
|
});
|
|
12466
|
+
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", `
|
|
12467
|
+
|
|
12468
|
+
A delegate cannot host a space \u2014 only its owner (root authority) can. This
|
|
12469
|
+
emits a tinycloud.host.request artifact naming the space and its owner so you
|
|
12470
|
+
can hand it to the owner; they run \`tc space host <name>\` and confirm.
|
|
12471
|
+
|
|
12472
|
+
If you ARE the owner of the resolved space, this refuses and tells you to host
|
|
12473
|
+
it directly with \`tc space host <name>\` (no request needed).
|
|
12474
|
+
`).action(async (name, options, cmd) => {
|
|
12475
|
+
try {
|
|
12476
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
12477
|
+
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
12478
|
+
const profile = await ProfileManager.getProfile(ctx.profile);
|
|
12479
|
+
const spaceId = await resolveHostSpace(name, ctx.profile);
|
|
12480
|
+
const spaceName = spaceNameFromUri(spaceId);
|
|
12481
|
+
if (await isRootAuthority(spaceId, ctx.profile)) {
|
|
12482
|
+
throw new CLIError(
|
|
12483
|
+
"ALREADY_ROOT_AUTHORITY",
|
|
12484
|
+
`You are the owner of ${spaceId}. Host it directly: tc space host ${spaceName}`,
|
|
12485
|
+
ExitCode.USAGE_ERROR
|
|
12486
|
+
);
|
|
12487
|
+
}
|
|
12488
|
+
const requesterDid = didWithoutFragment2(profile.sessionDid ?? profile.did);
|
|
12489
|
+
const ownerDid = ownerDidFromSpaceUri(spaceId);
|
|
12490
|
+
if (!ownerDid) {
|
|
12491
|
+
throw new CLIError(
|
|
12492
|
+
"UNRESOLVABLE_OWNER",
|
|
12493
|
+
`Cannot determine the owner of ${spaceId}; host-request needs a pkh space URI.`,
|
|
12494
|
+
ExitCode.USAGE_ERROR
|
|
12495
|
+
);
|
|
12496
|
+
}
|
|
12497
|
+
const artifact = {
|
|
12498
|
+
kind: "tinycloud.host.request",
|
|
12499
|
+
version: 1,
|
|
12500
|
+
requestId: `hostreq_${Date.now().toString(36)}_${randomBytes3(4).toString("hex")}`,
|
|
12501
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12502
|
+
spaceName,
|
|
12503
|
+
spaceId,
|
|
12504
|
+
ownerDid,
|
|
12505
|
+
requesterDid,
|
|
12506
|
+
host: ctx.host
|
|
12507
|
+
};
|
|
12508
|
+
await emitHostRequestArtifact(artifact, options.emit);
|
|
12509
|
+
} catch (error) {
|
|
12510
|
+
handleError(error);
|
|
12511
|
+
}
|
|
12512
|
+
});
|
|
12377
12513
|
space.command("info [space-id]").description("Get space info").action(async (spaceId, _options, cmd) => {
|
|
12378
12514
|
try {
|
|
12379
12515
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -12406,6 +12542,22 @@ function registerSpaceCommand(program2) {
|
|
|
12406
12542
|
}
|
|
12407
12543
|
});
|
|
12408
12544
|
}
|
|
12545
|
+
async function emitHostRequestArtifact(artifact, emitOption) {
|
|
12546
|
+
if (typeof emitOption === "string" && emitOption.length > 0) {
|
|
12547
|
+
await mkdir3(dirname3(emitOption), { recursive: true });
|
|
12548
|
+
await writeFile4(emitOption, JSON.stringify(artifact, null, 2) + "\n", "utf8");
|
|
12549
|
+
outputJson({
|
|
12550
|
+
emitted: true,
|
|
12551
|
+
path: emitOption,
|
|
12552
|
+
requestId: artifact.requestId,
|
|
12553
|
+
spaceName: artifact.spaceName,
|
|
12554
|
+
spaceId: artifact.spaceId,
|
|
12555
|
+
ownerDid: artifact.ownerDid
|
|
12556
|
+
});
|
|
12557
|
+
return;
|
|
12558
|
+
}
|
|
12559
|
+
outputJson(artifact);
|
|
12560
|
+
}
|
|
12409
12561
|
|
|
12410
12562
|
// src/lib/duration.ts
|
|
12411
12563
|
function parseDuration(input) {
|
|
@@ -13053,7 +13205,7 @@ complete -c tc -l quiet -s q -d "Suppress non-essential output"
|
|
|
13053
13205
|
|
|
13054
13206
|
// src/commands/vault.ts
|
|
13055
13207
|
import { readFile as readFile5 } from "fs/promises";
|
|
13056
|
-
import { writeFile as
|
|
13208
|
+
import { writeFile as writeFile5 } from "fs/promises";
|
|
13057
13209
|
import { PrivateKeySigner as PrivateKeySigner2 } from "@tinycloud/node-sdk";
|
|
13058
13210
|
async function readStdin3() {
|
|
13059
13211
|
const chunks = [];
|
|
@@ -13142,7 +13294,7 @@ function registerVaultCommand(program2) {
|
|
|
13142
13294
|
const data = result.data.data ?? result.data;
|
|
13143
13295
|
if (options.output) {
|
|
13144
13296
|
const content = data instanceof Uint8Array ? Buffer.from(data) : typeof data === "string" ? data : JSON.stringify(data);
|
|
13145
|
-
await
|
|
13297
|
+
await writeFile5(options.output, content);
|
|
13146
13298
|
outputJson({ key, written: options.output });
|
|
13147
13299
|
return;
|
|
13148
13300
|
}
|
|
@@ -13226,7 +13378,7 @@ function registerVaultCommand(program2) {
|
|
|
13226
13378
|
|
|
13227
13379
|
// src/commands/secrets.ts
|
|
13228
13380
|
import { readFile as readFile6 } from "fs/promises";
|
|
13229
|
-
import { writeFile as
|
|
13381
|
+
import { writeFile as writeFile6 } from "fs/promises";
|
|
13230
13382
|
import { join as join5 } from "path";
|
|
13231
13383
|
import { homedir as homedir2 } from "os";
|
|
13232
13384
|
var SECRETS_SPACE2 = "secrets";
|
|
@@ -13817,7 +13969,7 @@ function registerSecretsCommand(program2) {
|
|
|
13817
13969
|
})
|
|
13818
13970
|
);
|
|
13819
13971
|
if (options.output) {
|
|
13820
|
-
await
|
|
13972
|
+
await writeFile6(options.output, value2);
|
|
13821
13973
|
outputJson({ name, written: options.output });
|
|
13822
13974
|
return;
|
|
13823
13975
|
}
|
|
@@ -13846,7 +13998,7 @@ function registerSecretsCommand(program2) {
|
|
|
13846
13998
|
}
|
|
13847
13999
|
const value = String(result.data);
|
|
13848
14000
|
if (options.output) {
|
|
13849
|
-
await
|
|
14001
|
+
await writeFile6(options.output, value);
|
|
13850
14002
|
outputJson({ name, written: options.output });
|
|
13851
14003
|
return;
|
|
13852
14004
|
}
|
|
@@ -13964,7 +14116,7 @@ function registerSecretsCommand(program2) {
|
|
|
13964
14116
|
|
|
13965
14117
|
// src/commands/vars.ts
|
|
13966
14118
|
import { readFile as readFile7 } from "fs/promises";
|
|
13967
|
-
import { writeFile as
|
|
14119
|
+
import { writeFile as writeFile7 } from "fs/promises";
|
|
13968
14120
|
var VARIABLES_PREFIX = "variables/";
|
|
13969
14121
|
async function readStdin5() {
|
|
13970
14122
|
const chunks = [];
|
|
@@ -14036,7 +14188,7 @@ function registerVarsCommand(program2) {
|
|
|
14036
14188
|
value = typeof data === "string" ? data : JSON.stringify(data);
|
|
14037
14189
|
}
|
|
14038
14190
|
if (options.output) {
|
|
14039
|
-
await
|
|
14191
|
+
await writeFile7(options.output, value);
|
|
14040
14192
|
outputJson({ name, written: options.output });
|
|
14041
14193
|
return;
|
|
14042
14194
|
}
|
|
@@ -14210,12 +14362,18 @@ function registerDoctorCommand(program2) {
|
|
|
14210
14362
|
}
|
|
14211
14363
|
|
|
14212
14364
|
// src/commands/sql.ts
|
|
14213
|
-
import { writeFile as
|
|
14365
|
+
import { writeFile as writeFile8 } from "fs/promises";
|
|
14214
14366
|
import { resolve } from "path";
|
|
14215
14367
|
async function dbHandle(node, dbName, spaceInput, profileName) {
|
|
14216
14368
|
const spaceUri = await resolveSpaceUri(spaceInput, profileName);
|
|
14217
14369
|
const sql = spaceUri ? node.sqlForSpace(spaceUri) : node.sql;
|
|
14218
|
-
return sql.db(dbName);
|
|
14370
|
+
return { handle: sql.db(dbName), spaceUri };
|
|
14371
|
+
}
|
|
14372
|
+
async function throwSqlError(error, spaceUri, profileName, prefix) {
|
|
14373
|
+
const hosted = await unhostedSpaceError(error, spaceUri, profileName);
|
|
14374
|
+
if (hosted) throw hosted;
|
|
14375
|
+
const message = prefix ? `${prefix}${error.message}` : error.message;
|
|
14376
|
+
throw new CLIError(error.code, message, ExitCode.ERROR, error.meta);
|
|
14219
14377
|
}
|
|
14220
14378
|
function registerSqlCommand(program2) {
|
|
14221
14379
|
const sql = program2.command("sql").description("SQLite database operations for your TinyCloud space").addHelpText("after", `
|
|
@@ -14261,13 +14419,13 @@ Output:
|
|
|
14261
14419
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
14262
14420
|
const node = await ensureAuthenticated(ctx);
|
|
14263
14421
|
const params = options.params ? JSON.parse(options.params) : void 0;
|
|
14264
|
-
const handle = await dbHandle(node, options.db, options.space, ctx.profile);
|
|
14422
|
+
const { handle, spaceUri } = await dbHandle(node, options.db, options.space, ctx.profile);
|
|
14265
14423
|
const result = await withSpinner(
|
|
14266
14424
|
"Running query...",
|
|
14267
14425
|
() => handle.query(sqlStr, params)
|
|
14268
14426
|
);
|
|
14269
14427
|
if (!result.ok) {
|
|
14270
|
-
|
|
14428
|
+
await throwSqlError(result.error, spaceUri, ctx.profile);
|
|
14271
14429
|
}
|
|
14272
14430
|
const { columns, rows, rowCount } = result.data;
|
|
14273
14431
|
if (shouldOutputJson()) {
|
|
@@ -14305,13 +14463,13 @@ Output:
|
|
|
14305
14463
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
14306
14464
|
const node = await ensureAuthenticated(ctx);
|
|
14307
14465
|
const params = options.params ? JSON.parse(options.params) : void 0;
|
|
14308
|
-
const handle = await dbHandle(node, options.db, options.space, ctx.profile);
|
|
14466
|
+
const { handle, spaceUri } = await dbHandle(node, options.db, options.space, ctx.profile);
|
|
14309
14467
|
const result = await withSpinner(
|
|
14310
14468
|
"Executing statement...",
|
|
14311
14469
|
() => handle.execute(sqlStr, params)
|
|
14312
14470
|
);
|
|
14313
14471
|
if (!result.ok) {
|
|
14314
|
-
|
|
14472
|
+
await throwSqlError(result.error, spaceUri, ctx.profile);
|
|
14315
14473
|
}
|
|
14316
14474
|
outputJson({
|
|
14317
14475
|
changes: result.data.changes,
|
|
@@ -14335,18 +14493,18 @@ Output:
|
|
|
14335
14493
|
const globalOpts = cmd.optsWithGlobals();
|
|
14336
14494
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
14337
14495
|
const node = await ensureAuthenticated(ctx);
|
|
14338
|
-
const handle = await dbHandle(node, options.db, options.space, ctx.profile);
|
|
14496
|
+
const { handle, spaceUri } = await dbHandle(node, options.db, options.space, ctx.profile);
|
|
14339
14497
|
const result = await withSpinner(
|
|
14340
14498
|
"Exporting database...",
|
|
14341
14499
|
() => handle.export()
|
|
14342
14500
|
);
|
|
14343
14501
|
if (!result.ok) {
|
|
14344
|
-
|
|
14502
|
+
await throwSqlError(result.error, spaceUri, ctx.profile);
|
|
14345
14503
|
}
|
|
14346
14504
|
const blob = result.data;
|
|
14347
14505
|
const buffer = Buffer.from(await blob.arrayBuffer());
|
|
14348
14506
|
const outputPath = resolve(options.output);
|
|
14349
|
-
await
|
|
14507
|
+
await writeFile8(outputPath, buffer);
|
|
14350
14508
|
outputJson({
|
|
14351
14509
|
file: outputPath,
|
|
14352
14510
|
size: blob.size,
|
|
@@ -14390,8 +14548,8 @@ Notes:
|
|
|
14390
14548
|
ExitCode.USAGE_ERROR
|
|
14391
14549
|
);
|
|
14392
14550
|
}
|
|
14393
|
-
const fromHandle = await dbHandle(node, options.fromDb, fromSpaceInput, ctx.profile);
|
|
14394
|
-
const toHandle = await dbHandle(node, options.toDb, toSpaceInput, ctx.profile);
|
|
14551
|
+
const { handle: fromHandle, spaceUri: fromSpaceUriResolved } = await dbHandle(node, options.fromDb, fromSpaceInput, ctx.profile);
|
|
14552
|
+
const { handle: toHandle, spaceUri: toSpaceUriResolved } = await dbHandle(node, options.toDb, toSpaceInput, ctx.profile);
|
|
14395
14553
|
let tables;
|
|
14396
14554
|
if (options.table && options.table.length > 0) {
|
|
14397
14555
|
tables = options.table.flatMap((t) => t.split(",").map((s) => s.trim()).filter(Boolean));
|
|
@@ -14400,7 +14558,7 @@ Notes:
|
|
|
14400
14558
|
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name"
|
|
14401
14559
|
);
|
|
14402
14560
|
if (!listing.ok) {
|
|
14403
|
-
|
|
14561
|
+
await throwSqlError(listing.error, fromSpaceUriResolved, ctx.profile, "Cannot list source tables: ");
|
|
14404
14562
|
}
|
|
14405
14563
|
tables = listing.data.rows.map((r) => String(r[0]));
|
|
14406
14564
|
}
|
|
@@ -14416,12 +14574,7 @@ Notes:
|
|
|
14416
14574
|
const safe = quoteIdent(table);
|
|
14417
14575
|
const countResult = await fromHandle.query(`SELECT count(*) AS n FROM ${safe}`);
|
|
14418
14576
|
if (!countResult.ok) {
|
|
14419
|
-
|
|
14420
|
-
countResult.error.code,
|
|
14421
|
-
`Cannot count rows in source table "${table}": ${countResult.error.message}`,
|
|
14422
|
-
ExitCode.ERROR,
|
|
14423
|
-
countResult.error.meta
|
|
14424
|
-
);
|
|
14577
|
+
await throwSqlError(countResult.error, fromSpaceUriResolved, ctx.profile, `Cannot count rows in source table "${table}": `);
|
|
14425
14578
|
}
|
|
14426
14579
|
const rows = Number(countResult.data.rows[0]?.[0] ?? 0);
|
|
14427
14580
|
plan.push({ table, rows, copied: 0, skipped: 0 });
|
|
@@ -14439,7 +14592,7 @@ Notes:
|
|
|
14439
14592
|
const safe = quoteIdent(entry.table);
|
|
14440
14593
|
const fetched = await fromHandle.query(`SELECT * FROM ${safe}`);
|
|
14441
14594
|
if (!fetched.ok) {
|
|
14442
|
-
|
|
14595
|
+
await throwSqlError(fetched.error, fromSpaceUriResolved, ctx.profile, `Failed to read "${entry.table}": `);
|
|
14443
14596
|
}
|
|
14444
14597
|
const columns = fetched.data.columns;
|
|
14445
14598
|
const rows = fetched.data.rows;
|
|
@@ -14450,12 +14603,7 @@ Notes:
|
|
|
14450
14603
|
for (const row of rows) {
|
|
14451
14604
|
const writeResult = await toHandle.execute(insertSql, row);
|
|
14452
14605
|
if (!writeResult.ok) {
|
|
14453
|
-
|
|
14454
|
-
writeResult.error.code,
|
|
14455
|
-
`Insert into "${entry.table}" failed after ${entry.copied} row(s): ${writeResult.error.message}`,
|
|
14456
|
-
ExitCode.ERROR,
|
|
14457
|
-
writeResult.error.meta
|
|
14458
|
-
);
|
|
14606
|
+
await throwSqlError(writeResult.error, toSpaceUriResolved, ctx.profile, `Insert into "${entry.table}" failed after ${entry.copied} row(s): `);
|
|
14459
14607
|
}
|
|
14460
14608
|
entry.copied += writeResult.data.changes ?? 1;
|
|
14461
14609
|
}
|
|
@@ -14475,7 +14623,7 @@ function quoteIdent(name) {
|
|
|
14475
14623
|
}
|
|
14476
14624
|
|
|
14477
14625
|
// src/commands/duckdb.ts
|
|
14478
|
-
import { readFile as readFile8, writeFile as
|
|
14626
|
+
import { readFile as readFile8, writeFile as writeFile9 } from "fs/promises";
|
|
14479
14627
|
import { resolve as resolve2 } from "path";
|
|
14480
14628
|
function registerDuckdbCommand(program2) {
|
|
14481
14629
|
const duckdb = program2.command("duckdb").description("DuckDB database operations");
|
|
@@ -14589,7 +14737,7 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
|
|
|
14589
14737
|
const blob = result.data;
|
|
14590
14738
|
const buffer = Buffer.from(await blob.arrayBuffer());
|
|
14591
14739
|
const outputPath = resolve2(options.output);
|
|
14592
|
-
await
|
|
14740
|
+
await writeFile9(outputPath, buffer);
|
|
14593
14741
|
outputJson({
|
|
14594
14742
|
file: outputPath,
|
|
14595
14743
|
size: blob.size,
|