@tinycloud/cli 1.5.0-beta.19 → 1.5.0-beta.20
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/legacy-entry.js +241 -122
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +1 -1
- package/skills/tc-cli/SKILL.md +2 -0
package/dist/legacy-entry.js
CHANGED
|
@@ -5578,8 +5578,8 @@ function fetched(key, result, request) {
|
|
|
5578
5578
|
}
|
|
5579
5579
|
return { bytes: result.data.data, etag: result.data.headers.etag ?? "" };
|
|
5580
5580
|
}
|
|
5581
|
-
function kvSyncTransport(
|
|
5582
|
-
const host = options.host ??
|
|
5581
|
+
function kvSyncTransport(kv2, options = {}) {
|
|
5582
|
+
const host = options.host ?? kv2.host;
|
|
5583
5583
|
const fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
5584
5584
|
return {
|
|
5585
5585
|
async syncPage({ prefix, cursor, limit, retentionGrant, signal }) {
|
|
@@ -5598,7 +5598,7 @@ function kvSyncTransport(kv, options = {}) {
|
|
|
5598
5598
|
);
|
|
5599
5599
|
}
|
|
5600
5600
|
}
|
|
5601
|
-
const result = await
|
|
5601
|
+
const result = await kv2.changes({
|
|
5602
5602
|
prefix,
|
|
5603
5603
|
limit,
|
|
5604
5604
|
...cursor === void 0 ? {} : { cursor },
|
|
@@ -5613,7 +5613,7 @@ function kvSyncTransport(kv, options = {}) {
|
|
|
5613
5613
|
if (keys.length === 0) return contents;
|
|
5614
5614
|
const signal = options2?.signal;
|
|
5615
5615
|
const ordered = [...keys].sort(compareKeyBytes);
|
|
5616
|
-
const batch = await
|
|
5616
|
+
const batch = await kv2.batchGet(ordered, { binary: true, ...signal === void 0 ? {} : { signal } });
|
|
5617
5617
|
if (batch.ok) {
|
|
5618
5618
|
for (const item of batch.data.results) {
|
|
5619
5619
|
const result = item.result;
|
|
@@ -5626,7 +5626,7 @@ function kvSyncTransport(kv, options = {}) {
|
|
|
5626
5626
|
for (const key of keys) {
|
|
5627
5627
|
if (contents.has(key)) continue;
|
|
5628
5628
|
const request = `Failed to get key ${JSON.stringify(key)}`;
|
|
5629
|
-
contents.set(key, fetched(key, await
|
|
5629
|
+
contents.set(key, fetched(key, await kv2.get(key, { binary: true, ...signal === void 0 ? {} : { signal } }), request));
|
|
5630
5630
|
}
|
|
5631
5631
|
return contents;
|
|
5632
5632
|
}
|
|
@@ -29144,12 +29144,12 @@ async function verifyEnvelopeV3(envelope, options) {
|
|
|
29144
29144
|
const nativeProjection2 = sortedCapabilities.map((capability) => capability.kind === "encryption" ? { service: "tinycloud.encryption", space: capability.resource, path: capability.resource, actions: [capability.action] } : { service: "tinycloud.kv", space: capability.resource.slice(0, capability.resource.indexOf("/kv/")), path: capability.resource.split("/kv/")[1], actions: [...capability.actions], caveat: { type: "xyz.tinycloud.resource/selector", kind: capability.selector, value: capability.resource } }).sort((left, right) => canonicalize2(left).localeCompare(canonicalize2(right)));
|
|
29145
29145
|
const nativeProjectionHashHex = hex(sha2562(new TextEncoder().encode(`${NATIVE_PROJECTION_V1_DOMAIN}${canonicalize2(nativeProjection2)}`)));
|
|
29146
29146
|
const expectedAttenuation = Object.fromEntries(sortedCapabilities.map((capability) => capability.kind === "encryption" ? [capability.resource, { [capability.action]: [{}] }] : [capability.resource, Object.fromEntries(capability.actions.map((action) => [action, [{ kind: capability.selector, type: "xyz.tinycloud.resource/selector", value: capability.resource }]]))]));
|
|
29147
|
-
const
|
|
29147
|
+
const kv2 = policy.capabilityCeiling.find((capability) => capability.kind === "kv");
|
|
29148
29148
|
const expectedKvActions = parsed.actions.flatMap((action) => action === "read" ? ["tinycloud.kv/get", "tinycloud.kv/metadata"] : action === "list" ? ["tinycloud.kv/list"] : ["tinycloud.kv/put"]);
|
|
29149
29149
|
const marker = parsed.contentSource.kvResource.indexOf("/kv/");
|
|
29150
29150
|
const resourceSpace = marker < 1 ? "" : parsed.contentSource.kvResource.slice(0, marker);
|
|
29151
29151
|
const resourcePath = marker < 1 ? "" : parsed.contentSource.kvResource.slice(marker + 4);
|
|
29152
|
-
if (parsed.shareId !== parsed.contentSource.shareId || resourceSpace !== parsed.target.spaceId || resourcePath !== parsed.resource.path.replace(/\/$/, "") || parsed.resource.kind !== parsed.contentSource.selector ||
|
|
29152
|
+
if (parsed.shareId !== parsed.contentSource.shareId || resourceSpace !== parsed.target.spaceId || resourcePath !== parsed.resource.path.replace(/\/$/, "") || parsed.resource.kind !== parsed.contentSource.selector || kv2?.kind !== "kv" || expectedKvActions.some((action) => !kv2.actions.includes(action)) || parsed.encryptionNetwork !== parsed.contentSource.encryptionNetwork || parsed.contentSource.keyVersion <= 0 || policy.expiresAt !== void 0 && Date.parse(parsed.expiry) > Date.parse(policy.expiresAt)) return false;
|
|
29153
29153
|
const binding = parsed.attestedEnforcerBinding;
|
|
29154
29154
|
const { signature: bindingSignature, ...unsignedBinding } = binding;
|
|
29155
29155
|
const expectedBindingDigestHex = hex(sha2562(new TextEncoder().encode(canonicalize2({ enforcerDid: binding.enforcerDid, nodeAudience: binding.nodeAudience }))));
|
|
@@ -32525,10 +32525,10 @@ function hasKvAction(permission, action) {
|
|
|
32525
32525
|
return permission.actions.some((entry) => entry === action || entry === `tinycloud.kv/${action}`);
|
|
32526
32526
|
}
|
|
32527
32527
|
function readTarget(permissions) {
|
|
32528
|
-
const
|
|
32529
|
-
const list =
|
|
32528
|
+
const kv2 = (permissions ?? []).filter((permission) => permission.service === "tinycloud.kv" || permission.service === "kv");
|
|
32529
|
+
const list = kv2.find((permission) => hasKvAction(permission, "list"));
|
|
32530
32530
|
if (list) return { op: "list", prefix: list.path };
|
|
32531
|
-
const get =
|
|
32531
|
+
const get = kv2.find((permission) => hasKvAction(permission, "get"));
|
|
32532
32532
|
if (!get) return void 0;
|
|
32533
32533
|
return { op: "get", key: get.path === "" || get.path.endsWith("/") ? `${get.path}.tinycloud-space-probe` : get.path };
|
|
32534
32534
|
}
|
|
@@ -32540,8 +32540,8 @@ function kvSpaceProbe(ctx, permissions, restore2 = restoreProfileSessionNode) {
|
|
|
32540
32540
|
if (!target) return "unreadable";
|
|
32541
32541
|
restored ??= await restore2(ctx);
|
|
32542
32542
|
if ("unavailable" in restored) return "unreadable";
|
|
32543
|
-
const
|
|
32544
|
-
const result = target.op === "list" ? await
|
|
32543
|
+
const kv2 = restored.node.kv;
|
|
32544
|
+
const result = target.op === "list" ? await kv2.list({ prefix: target.prefix, limit: 1 }) : await kv2.get(target.key);
|
|
32545
32545
|
if (result.ok) return "hosted";
|
|
32546
32546
|
const status = result.error.meta?.status;
|
|
32547
32547
|
if (status === 404 && UNHOSTED.test(result.error.message)) return "unhosted";
|
|
@@ -36071,7 +36071,7 @@ async function refreshOpenKeySession(profileName2, host, options = {}) {
|
|
|
36071
36071
|
// owner is verified below.
|
|
36072
36072
|
permissions: permissions ?? ownerLoginPermissions(expectedOwner),
|
|
36073
36073
|
expiry: openKeyExpiry,
|
|
36074
|
-
...permissions ? { reason: "Allow this TinyCloud CLI profile to use the permissions in the requested manifest." } : {}
|
|
36074
|
+
...permissions ? { reason: options.reason ?? "Allow this TinyCloud CLI profile to use the permissions in the requested manifest." } : {}
|
|
36075
36075
|
});
|
|
36076
36076
|
let sanitizedSession;
|
|
36077
36077
|
let verifiedOwner;
|
|
@@ -37154,7 +37154,7 @@ Examples:
|
|
|
37154
37154
|
}
|
|
37155
37155
|
});
|
|
37156
37156
|
}
|
|
37157
|
-
function appsInstallIo(
|
|
37157
|
+
function appsInstallIo(kv2, db) {
|
|
37158
37158
|
return {
|
|
37159
37159
|
async fetchBytes(url) {
|
|
37160
37160
|
const response = await fetch(url, { redirect: "error" });
|
|
@@ -37162,7 +37162,7 @@ function appsInstallIo(kv, db) {
|
|
|
37162
37162
|
return new Uint8Array(await response.arrayBuffer());
|
|
37163
37163
|
},
|
|
37164
37164
|
async kvGet(key) {
|
|
37165
|
-
const result = await
|
|
37165
|
+
const result = await kv2.get(key, { binary: true });
|
|
37166
37166
|
if (result.ok) {
|
|
37167
37167
|
const bytes = result.data.data;
|
|
37168
37168
|
if (bytes instanceof Uint8Array) return bytes;
|
|
@@ -37172,7 +37172,7 @@ function appsInstallIo(kv, db) {
|
|
|
37172
37172
|
throw new Error(result.error.code ?? "KV_GET_FAILED");
|
|
37173
37173
|
},
|
|
37174
37174
|
async kvCreate(key, bytes) {
|
|
37175
|
-
const result = await
|
|
37175
|
+
const result = await kv2.put(key, bytes, { ifNoneMatch: "*" });
|
|
37176
37176
|
if (result.ok) return "created";
|
|
37177
37177
|
if (result.error.code === "KV_PRECONDITION_FAILED") return "exists";
|
|
37178
37178
|
throw new Error(result.error.code ?? "KV_PUT_FAILED");
|
|
@@ -37225,6 +37225,25 @@ init_formatter();
|
|
|
37225
37225
|
init_errors();
|
|
37226
37226
|
init_constants();
|
|
37227
37227
|
import { CLI_NAME as CLI_NAME25 } from "@tinycloud/operations/cli-name";
|
|
37228
|
+
async function createKeyOnlyProfile(profileName2, host, defaultSpace) {
|
|
37229
|
+
await ProfileManager.ensureConfigDir();
|
|
37230
|
+
const { jwk, did } = await generateKey2();
|
|
37231
|
+
await ProfileManager.setKey(profileName2, jwk);
|
|
37232
|
+
await ProfileManager.setProfile(profileName2, {
|
|
37233
|
+
name: profileName2,
|
|
37234
|
+
host,
|
|
37235
|
+
chainId: DEFAULT_CHAIN_ID,
|
|
37236
|
+
spaceName: "default",
|
|
37237
|
+
did,
|
|
37238
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
37239
|
+
...defaultSpace ? { defaultSpace } : {}
|
|
37240
|
+
});
|
|
37241
|
+
const config = await ProfileManager.getConfig();
|
|
37242
|
+
if (profileName2 === "default" || !await ProfileManager.profileExists(config.defaultProfile)) {
|
|
37243
|
+
await ProfileManager.setConfig({ ...config, defaultProfile: profileName2 });
|
|
37244
|
+
}
|
|
37245
|
+
return { did };
|
|
37246
|
+
}
|
|
37228
37247
|
function registerInitCommand(program) {
|
|
37229
37248
|
program.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) => {
|
|
37230
37249
|
try {
|
|
@@ -37246,25 +37265,7 @@ function registerInitCommand(program) {
|
|
|
37246
37265
|
ExitCode.ERROR
|
|
37247
37266
|
);
|
|
37248
37267
|
}
|
|
37249
|
-
await
|
|
37250
|
-
const { jwk, did } = await withSpinner("Generating key...", async () => {
|
|
37251
|
-
return generateKey2();
|
|
37252
|
-
});
|
|
37253
|
-
await ProfileManager.setKey(profileName2, jwk);
|
|
37254
|
-
const profileConfig = {
|
|
37255
|
-
name: profileName2,
|
|
37256
|
-
host,
|
|
37257
|
-
chainId: DEFAULT_CHAIN_ID,
|
|
37258
|
-
spaceName: "default",
|
|
37259
|
-
did,
|
|
37260
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
37261
|
-
...defaultSpace ? { defaultSpace } : {}
|
|
37262
|
-
};
|
|
37263
|
-
await ProfileManager.setProfile(profileName2, profileConfig);
|
|
37264
|
-
const config = await ProfileManager.getConfig();
|
|
37265
|
-
if (profileName2 === "default" || !await ProfileManager.profileExists(config.defaultProfile)) {
|
|
37266
|
-
await ProfileManager.setConfig({ ...config, defaultProfile: profileName2 });
|
|
37267
|
-
}
|
|
37268
|
+
const { did } = await withSpinner("Generating key...", () => createKeyOnlyProfile(profileName2, host, defaultSpace));
|
|
37268
37269
|
if (options.keyOnly) {
|
|
37269
37270
|
outputJson({
|
|
37270
37271
|
profile: profileName2,
|
|
@@ -37539,25 +37540,25 @@ async function readStdin2() {
|
|
|
37539
37540
|
}
|
|
37540
37541
|
async function kvHandle(node, spaceInput, profileName2) {
|
|
37541
37542
|
const spaceUri = await resolveSpaceUri(spaceInput, profileName2);
|
|
37542
|
-
const
|
|
37543
|
-
return { kv, spaceUri };
|
|
37543
|
+
const kv2 = spaceUri ? node.kvForSpace(spaceUri) : node.kv;
|
|
37544
|
+
return { kv: kv2, spaceUri };
|
|
37544
37545
|
}
|
|
37545
37546
|
function retryFields(retry) {
|
|
37546
37547
|
return retry ? { attempts: retry.attempts, backoffMs: retry.backoffMs } : {};
|
|
37547
37548
|
}
|
|
37548
37549
|
function registerKvCommand(program) {
|
|
37549
|
-
const
|
|
37550
|
-
|
|
37550
|
+
const kv2 = program.command("kv").description("Key-value store operations");
|
|
37551
|
+
kv2.command("get <key>").description("Get a value by key").option("--raw", "Output raw value (no JSON wrapping)").option("-o, --output <file>", "Write value to file").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").action(async (key, options, cmd) => {
|
|
37551
37552
|
try {
|
|
37552
37553
|
assertAddressableKey(key);
|
|
37553
37554
|
const globalOpts = cmd.optsWithGlobals();
|
|
37554
37555
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
37555
37556
|
const node = await ensureAuthenticated(ctx);
|
|
37556
|
-
const { kv:
|
|
37557
|
+
const { kv: kv3, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
37557
37558
|
const wantBytes = !!options.output || !!options.raw;
|
|
37558
37559
|
const result = await withSpinner(
|
|
37559
37560
|
`Getting ${key}...`,
|
|
37560
|
-
() =>
|
|
37561
|
+
() => kv3.get(key, wantBytes ? { binary: true } : void 0)
|
|
37561
37562
|
);
|
|
37562
37563
|
if (!result.ok) {
|
|
37563
37564
|
const hosted = await unhostedSpaceError(result.error, spaceUri, ctx.profile);
|
|
@@ -37593,7 +37594,7 @@ function registerKvCommand(program) {
|
|
|
37593
37594
|
return handleError(error);
|
|
37594
37595
|
}
|
|
37595
37596
|
});
|
|
37596
|
-
|
|
37597
|
+
kv2.command("put <key> [value]").description("Set a value").option("--file <path>", "Read value from file").option("--stdin", "Read value from stdin").option("--if-none-match <value>", "`--if-none-match '*'`: Write only if the key doesn't exist yet; never overwrite").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").action(async (key, value, options, cmd) => {
|
|
37597
37598
|
try {
|
|
37598
37599
|
assertAddressableKey(key);
|
|
37599
37600
|
if (options.ifNoneMatch !== void 0 && options.ifNoneMatch !== "*") {
|
|
@@ -37621,8 +37622,8 @@ function registerKvCommand(program) {
|
|
|
37621
37622
|
putValue = value;
|
|
37622
37623
|
}
|
|
37623
37624
|
}
|
|
37624
|
-
const { kv:
|
|
37625
|
-
const result = await withSpinner(`Writing ${key}...`, () =>
|
|
37625
|
+
const { kv: kv3, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
37626
|
+
const result = await withSpinner(`Writing ${key}...`, () => kv3.put(
|
|
37626
37627
|
key,
|
|
37627
37628
|
putValue,
|
|
37628
37629
|
options.ifNoneMatch === "*" ? { ifNoneMatch: "*" } : void 0
|
|
@@ -37638,14 +37639,14 @@ function registerKvCommand(program) {
|
|
|
37638
37639
|
return handleError(error);
|
|
37639
37640
|
}
|
|
37640
37641
|
});
|
|
37641
|
-
|
|
37642
|
+
kv2.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) => {
|
|
37642
37643
|
try {
|
|
37643
37644
|
assertAddressableKey(key);
|
|
37644
37645
|
const globalOpts = cmd.optsWithGlobals();
|
|
37645
37646
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
37646
37647
|
const node = await ensureAuthenticated(ctx);
|
|
37647
|
-
const { kv:
|
|
37648
|
-
const result = await withSpinner(`Deleting ${key}...`, () =>
|
|
37648
|
+
const { kv: kv3, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
37649
|
+
const result = await withSpinner(`Deleting ${key}...`, () => kv3.delete(key));
|
|
37649
37650
|
if (!result.ok) {
|
|
37650
37651
|
await throwKvError(result.error, spaceUri, ctx.profile);
|
|
37651
37652
|
}
|
|
@@ -37654,7 +37655,7 @@ function registerKvCommand(program) {
|
|
|
37654
37655
|
return handleError(error);
|
|
37655
37656
|
}
|
|
37656
37657
|
});
|
|
37657
|
-
|
|
37658
|
+
kv2.command("list").description("List keys").option("--prefix <prefix>", "Filter by key prefix").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").option("--limit <n>", "Keys per page, 1-1000 (the node's default otherwise)").option("--cursor <cursor>", "Continue from a previous page's nextCursor").option("--all", "Follow nextCursor until the listing is complete").action(async (options, cmd) => {
|
|
37658
37659
|
try {
|
|
37659
37660
|
if (options.prefix) assertAddressableKey(options.prefix);
|
|
37660
37661
|
const limit = options.limit === void 0 ? void 0 : Number(options.limit);
|
|
@@ -37664,13 +37665,13 @@ function registerKvCommand(program) {
|
|
|
37664
37665
|
const globalOpts = cmd.optsWithGlobals();
|
|
37665
37666
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
37666
37667
|
const node = await ensureAuthenticated(ctx);
|
|
37667
|
-
const { kv:
|
|
37668
|
+
const { kv: kv3, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
37668
37669
|
const keyList = [];
|
|
37669
37670
|
let cursor = options.cursor;
|
|
37670
37671
|
let truncated;
|
|
37671
37672
|
const seenCursors = /* @__PURE__ */ new Set();
|
|
37672
37673
|
for (; ; ) {
|
|
37673
|
-
const result = await withSpinner("Listing keys...", () =>
|
|
37674
|
+
const result = await withSpinner("Listing keys...", () => kv3.list({
|
|
37674
37675
|
...options.prefix ? { prefix: options.prefix } : {},
|
|
37675
37676
|
...limit === void 0 ? {} : { limit },
|
|
37676
37677
|
...cursor === void 0 ? {} : { cursor }
|
|
@@ -37725,14 +37726,14 @@ function registerKvCommand(program) {
|
|
|
37725
37726
|
return handleError(error);
|
|
37726
37727
|
}
|
|
37727
37728
|
});
|
|
37728
|
-
|
|
37729
|
+
kv2.command("head <key>").description("Get metadata for a key (no body)").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").action(async (key, options, cmd) => {
|
|
37729
37730
|
try {
|
|
37730
37731
|
assertAddressableKey(key);
|
|
37731
37732
|
const globalOpts = cmd.optsWithGlobals();
|
|
37732
37733
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
37733
37734
|
const node = await ensureAuthenticated(ctx);
|
|
37734
|
-
const { kv:
|
|
37735
|
-
const result = await withSpinner(`Checking ${key}...`, () =>
|
|
37735
|
+
const { kv: kv3, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
37736
|
+
const result = await withSpinner(`Checking ${key}...`, () => kv3.head(key));
|
|
37736
37737
|
if (!result.ok) {
|
|
37737
37738
|
const hosted = await unhostedSpaceError(result.error, spaceUri, ctx.profile);
|
|
37738
37739
|
if (hosted) throw hosted;
|
|
@@ -38134,12 +38135,129 @@ function registerNodeCommand(program) {
|
|
|
38134
38135
|
});
|
|
38135
38136
|
}
|
|
38136
38137
|
|
|
38138
|
+
// src/commands/onboard.ts
|
|
38139
|
+
import { CLI_NAME as CLI_NAME29 } from "@tinycloud/operations/cli-name";
|
|
38140
|
+
init_constants();
|
|
38141
|
+
init_profiles();
|
|
38142
|
+
init_types();
|
|
38143
|
+
init_permissions();
|
|
38144
|
+
init_sdk();
|
|
38145
|
+
init_errors();
|
|
38146
|
+
init_formatter();
|
|
38147
|
+
var ONBOARD_EXPIRY = "30d";
|
|
38148
|
+
var kv = (actions) => actions.map((action) => `tinycloud.kv/${action}`);
|
|
38149
|
+
var ONBOARD_STEPS = [
|
|
38150
|
+
{
|
|
38151
|
+
space: "applications",
|
|
38152
|
+
asks: "Read and change your apps' data for 30 days.",
|
|
38153
|
+
permissions: [{ service: "tinycloud.kv", space: "applications", path: "", actions: kv(["get", "list", "put", "del", "metadata"]) }]
|
|
38154
|
+
},
|
|
38155
|
+
{
|
|
38156
|
+
space: "account",
|
|
38157
|
+
asks: "See which spaces and apps you have, read-only, for 30 days.",
|
|
38158
|
+
permissions: [
|
|
38159
|
+
{ service: "tinycloud.kv", space: "account", path: "spaces/", actions: kv(["get", "list"]) },
|
|
38160
|
+
{ service: "tinycloud.kv", space: "account", path: "applications/", actions: kv(["get", "list"]) }
|
|
38161
|
+
]
|
|
38162
|
+
}
|
|
38163
|
+
];
|
|
38164
|
+
var ApprovalNeeded = class extends Error {
|
|
38165
|
+
constructor(approvalUrl) {
|
|
38166
|
+
super("approval needed");
|
|
38167
|
+
this.approvalUrl = approvalUrl;
|
|
38168
|
+
}
|
|
38169
|
+
};
|
|
38170
|
+
function registerOnboardCommand(program) {
|
|
38171
|
+
program.command("onboard").description(`Set up this computer to act for its owner: a profile, the owner's OpenKey sign-in (or new account), and a starting grant for 30 days (read and change the applications space; read which spaces and apps exist). Each run does the next approval: it prints the approval link and exits ${ExitCode.PENDING}; run it again with the code OpenKey shows. Exits 0 when onboarding is complete.`).option("--code <code>", "The code OpenKey showed after the owner approved the last link").action(async (options, cmd) => {
|
|
38172
|
+
try {
|
|
38173
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
38174
|
+
const ctx = await ProfileManager.resolveContext(globalOpts, { storedSession: "unused" });
|
|
38175
|
+
if (!await ProfileManager.profileExists(ctx.profile)) await createKeyOnlyProfile(ctx.profile, ctx.host);
|
|
38176
|
+
let profile = await ProfileManager.getProfile(ctx.profile);
|
|
38177
|
+
if (resolveProfilePosture(profile) !== "owner-openkey") {
|
|
38178
|
+
throw new CLIError(
|
|
38179
|
+
"PROFILE_NOT_ONBOARDABLE",
|
|
38180
|
+
`Profile "${ctx.profile}" doesn't sign in with OpenKey, so onboarding can't use it. Run \`${CLI_NAME29} --profile <new name> onboard\` instead.`,
|
|
38181
|
+
ExitCode.USAGE_ERROR
|
|
38182
|
+
);
|
|
38183
|
+
}
|
|
38184
|
+
let code5 = typeof options.code === "string" && options.code.trim() !== "" ? options.code.trim() : void 0;
|
|
38185
|
+
const acquire = async (did, flow = {}) => {
|
|
38186
|
+
if (code5 === void 0) throw new ApprovalNeeded(buildAuthUrl(did, flow));
|
|
38187
|
+
const pasted = code5;
|
|
38188
|
+
code5 = void 0;
|
|
38189
|
+
return delegationFromInput(pasted, did, flow);
|
|
38190
|
+
};
|
|
38191
|
+
for (const [index, step] of ONBOARD_STEPS.entries()) {
|
|
38192
|
+
const reason = `TinyCloud onboarding, step ${index + 1} of ${ONBOARD_STEPS.length}: ${step.asks}`;
|
|
38193
|
+
try {
|
|
38194
|
+
const session2 = await ProfileManager.getSession(ctx.profile);
|
|
38195
|
+
if (!sessionStateIsUsable(storedSessionState(session2, profile).state)) {
|
|
38196
|
+
const { delegationData } = await refreshOpenKeySession(ctx.profile, ctx.host, {
|
|
38197
|
+
permissions: step.permissions,
|
|
38198
|
+
expiry: ONBOARD_EXPIRY,
|
|
38199
|
+
reason,
|
|
38200
|
+
persistHost: globalOpts.host !== void 0,
|
|
38201
|
+
openKeyAcquisition: acquire
|
|
38202
|
+
});
|
|
38203
|
+
await ensureSpaceSetup({ profile: ctx.profile, host: ctx.host }, delegationData, delegationData.permissions);
|
|
38204
|
+
profile = await ProfileManager.getProfile(ctx.profile);
|
|
38205
|
+
continue;
|
|
38206
|
+
}
|
|
38207
|
+
await ensureDelegationAuthority({
|
|
38208
|
+
ctx,
|
|
38209
|
+
profile,
|
|
38210
|
+
node: await ensureAuthenticated(ctx),
|
|
38211
|
+
requested: await resolvePermissionSpaces(step.permissions, ctx.profile),
|
|
38212
|
+
expiryOption: ONBOARD_EXPIRY,
|
|
38213
|
+
reason,
|
|
38214
|
+
yes: true,
|
|
38215
|
+
openKeyAcquisition: acquire
|
|
38216
|
+
});
|
|
38217
|
+
} catch (error) {
|
|
38218
|
+
if (!(error instanceof ApprovalNeeded)) throw error;
|
|
38219
|
+
const next = `${CLI_NAME29} --profile ${ctx.profile} onboard --code <code>`;
|
|
38220
|
+
output({
|
|
38221
|
+
onboarded: false,
|
|
38222
|
+
profile: ctx.profile,
|
|
38223
|
+
step: index + 1,
|
|
38224
|
+
steps: ONBOARD_STEPS.length,
|
|
38225
|
+
asks: step.asks,
|
|
38226
|
+
approvalUrl: error.approvalUrl,
|
|
38227
|
+
next
|
|
38228
|
+
}, () => [
|
|
38229
|
+
`Step ${index + 1} of ${ONBOARD_STEPS.length}: ${step.asks}`,
|
|
38230
|
+
`Open this link, sign in or create an account, and approve:`,
|
|
38231
|
+
` ${error.approvalUrl}`,
|
|
38232
|
+
`Then run: ${next}`
|
|
38233
|
+
].join("\n"));
|
|
38234
|
+
process.exitCode = ExitCode.PENDING;
|
|
38235
|
+
return;
|
|
38236
|
+
}
|
|
38237
|
+
}
|
|
38238
|
+
profile = await ProfileManager.getProfile(ctx.profile);
|
|
38239
|
+
const session = await ProfileManager.getSession(ctx.profile);
|
|
38240
|
+
output({
|
|
38241
|
+
onboarded: true,
|
|
38242
|
+
profile: ctx.profile,
|
|
38243
|
+
ownerDid: profile.ownerDid ?? null,
|
|
38244
|
+
host: ctx.host,
|
|
38245
|
+
granted: ONBOARD_STEPS.map(({ space, asks }) => ({ space, asks })),
|
|
38246
|
+
expiresAt: storedSessionState(session, profile).expiresAt
|
|
38247
|
+
}, () => `Onboarded: profile ${ctx.profile} acts for ${profile.ownerDid ?? "the owner"}.
|
|
38248
|
+
${ONBOARD_STEPS.map(({ asks }) => ` ${asks}`).join("\n")}`);
|
|
38249
|
+
} catch (error) {
|
|
38250
|
+
return handleError(error);
|
|
38251
|
+
}
|
|
38252
|
+
});
|
|
38253
|
+
}
|
|
38254
|
+
|
|
38137
38255
|
// src/commands/profile.ts
|
|
38138
38256
|
init_profiles();
|
|
38139
38257
|
init_formatter();
|
|
38140
38258
|
init_errors();
|
|
38141
38259
|
init_constants();
|
|
38142
|
-
import { CLI_NAME as
|
|
38260
|
+
import { CLI_NAME as CLI_NAME30 } from "@tinycloud/operations/cli-name";
|
|
38143
38261
|
import { createInterface as createInterface5 } from "readline";
|
|
38144
38262
|
init_theme();
|
|
38145
38263
|
init_types();
|
|
@@ -38285,9 +38403,9 @@ profile at command time. Precedence for every kv/sql command:
|
|
|
38285
38403
|
explicit --space flag > profile defaultSpace > primary space.
|
|
38286
38404
|
|
|
38287
38405
|
Examples:
|
|
38288
|
-
$ ${
|
|
38289
|
-
$ ${
|
|
38290
|
-
$ ${
|
|
38406
|
+
$ ${CLI_NAME30} profile set-default-space applications
|
|
38407
|
+
$ ${CLI_NAME30} profile set-default-space applications --profile cli-test
|
|
38408
|
+
$ ${CLI_NAME30} profile set-default-space --unset
|
|
38291
38409
|
`).action(async (name, options, cmd) => {
|
|
38292
38410
|
try {
|
|
38293
38411
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -38299,7 +38417,7 @@ Examples:
|
|
|
38299
38417
|
if (!options.unset && (name === void 0 || name === "")) {
|
|
38300
38418
|
throw new CLIError(
|
|
38301
38419
|
"USAGE_ERROR",
|
|
38302
|
-
`Provide a space name (e.g. \`${
|
|
38420
|
+
`Provide a space name (e.g. \`${CLI_NAME30} profile set-default-space applications\`) or pass --unset.`,
|
|
38303
38421
|
ExitCode.USAGE_ERROR
|
|
38304
38422
|
);
|
|
38305
38423
|
}
|
|
@@ -38358,7 +38476,7 @@ function parseOperatorType(raw) {
|
|
|
38358
38476
|
|
|
38359
38477
|
// src/commands/replica.ts
|
|
38360
38478
|
init_profile_replicas();
|
|
38361
|
-
import { CLI_NAME as
|
|
38479
|
+
import { CLI_NAME as CLI_NAME31 } from "@tinycloud/operations/cli-name";
|
|
38362
38480
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
38363
38481
|
import { constants as fsConstants5 } from "fs";
|
|
38364
38482
|
import { mkdir as mkdir5, open as open11, readdir as readdir6, rename as rename4, unlink as unlink5, lstat as lstat9 } from "fs/promises";
|
|
@@ -38499,11 +38617,11 @@ var EXIT_BY_CODE = {
|
|
|
38499
38617
|
var READ_FAILURE = {
|
|
38500
38618
|
absent: { code: "KEY_ABSENT", exit: ExitCode.NOT_FOUND, what: "is not in the replica (the replica covers its whole prefix)" },
|
|
38501
38619
|
deleted: { code: "KEY_DELETED", exit: ExitCode.NOT_FOUND, what: "was deleted at the source" },
|
|
38502
|
-
content_missing: { code: "CONTENT_MISSING", exit: ExitCode.NOT_FOUND, what: `is known but its content has not been fetched yet; run ${
|
|
38620
|
+
content_missing: { code: "CONTENT_MISSING", exit: ExitCode.NOT_FOUND, what: `is known but its content has not been fetched yet; run ${CLI_NAME31} replica sync` },
|
|
38503
38621
|
coverage_incomplete: {
|
|
38504
38622
|
code: "COVERAGE_INCOMPLETE",
|
|
38505
38623
|
exit: ExitCode.NOT_FOUND,
|
|
38506
|
-
what: `is not in the replica, which has not finished its first sync; run ${
|
|
38624
|
+
what: `is not in the replica, which has not finished its first sync; run ${CLI_NAME31} replica sync`
|
|
38507
38625
|
},
|
|
38508
38626
|
not_covered: { code: "NOT_COVERED", exit: ExitCode.USAGE_ERROR, what: "is outside the replica's prefix" }
|
|
38509
38627
|
};
|
|
@@ -38621,7 +38739,7 @@ async function existingReplicaName(profile, option) {
|
|
|
38621
38739
|
const names = await replicaNames(profile);
|
|
38622
38740
|
if (names.length === 1) return names[0];
|
|
38623
38741
|
if (names.length === 0) {
|
|
38624
|
-
throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, `Profile "${profile}" has no replicas. Create one with: ${
|
|
38742
|
+
throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, `Profile "${profile}" has no replicas. Create one with: ${CLI_NAME31} replica sync --prefix <prefix>`);
|
|
38625
38743
|
}
|
|
38626
38744
|
throw new CLIError("USAGE_ERROR", `Profile "${profile}" has several replicas (${names.join(", ")}); pass --replica <name>.`, ExitCode.USAGE_ERROR);
|
|
38627
38745
|
}
|
|
@@ -38729,9 +38847,9 @@ async function storedGrants(profile) {
|
|
|
38729
38847
|
function grantHint(space, prefix) {
|
|
38730
38848
|
return [
|
|
38731
38849
|
"A replica needs a device grant with get and sync on its prefix:",
|
|
38732
|
-
` device: ${
|
|
38733
|
-
` owner: ${
|
|
38734
|
-
` device: ${
|
|
38850
|
+
` device: ${CLI_NAME31} auth request --cap tinycloud.kv:${space}:${prefix}:get,list,metadata,sync --expiry 30d --emit req.json`,
|
|
38851
|
+
` owner: ${CLI_NAME31} auth grant req.json > grant.json`,
|
|
38852
|
+
` device: ${CLI_NAME31} auth import grant.json`
|
|
38735
38853
|
].join("\n");
|
|
38736
38854
|
}
|
|
38737
38855
|
function resolveSpace(input, prefix, grants) {
|
|
@@ -38787,9 +38905,9 @@ async function grantTransports(input) {
|
|
|
38787
38905
|
fetch: globalThis.fetch.bind(globalThis),
|
|
38788
38906
|
hosts: [input.host]
|
|
38789
38907
|
});
|
|
38790
|
-
const
|
|
38791
|
-
|
|
38792
|
-
context.registerService("kv",
|
|
38908
|
+
const kv2 = new KVService({});
|
|
38909
|
+
kv2.initialize(context);
|
|
38910
|
+
context.registerService("kv", kv2);
|
|
38793
38911
|
context.setSession({
|
|
38794
38912
|
delegationHeader: { Authorization: new TextDecoder().decode(grant.bytes) },
|
|
38795
38913
|
delegationCid: grant.cid,
|
|
@@ -38797,7 +38915,7 @@ async function grantTransports(input) {
|
|
|
38797
38915
|
verificationMethod: input.deviceDid,
|
|
38798
38916
|
jwk: input.jwk
|
|
38799
38917
|
});
|
|
38800
|
-
const client =
|
|
38918
|
+
const client = kv2;
|
|
38801
38919
|
return kvSyncTransport(client);
|
|
38802
38920
|
};
|
|
38803
38921
|
}
|
|
@@ -38822,7 +38940,7 @@ function describeStatus(status) {
|
|
|
38822
38940
|
formatField("Size", formatBytes(status.bytes)),
|
|
38823
38941
|
formatField("Grant", status.device.delegationCid),
|
|
38824
38942
|
formatField("Last error", status.lastError ? `${status.lastError.code}: ${status.lastError.message}` : null),
|
|
38825
|
-
formatField("Purge", status.purgePending ? `incomplete: content files remain; the next ${
|
|
38943
|
+
formatField("Purge", status.purgePending ? `incomplete: content files remain; the next ${CLI_NAME31} replica command retries` : null)
|
|
38826
38944
|
].join("\n");
|
|
38827
38945
|
}
|
|
38828
38946
|
async function writeFileAtomic(path, bytes) {
|
|
@@ -38986,7 +39104,7 @@ or coverage incomplete; 5 grant expired, revoked or not yet valid; 6 network;
|
|
|
38986
39104
|
(options, cmd) => run(async () => {
|
|
38987
39105
|
const profile = await profileName(cmd);
|
|
38988
39106
|
if (options.purge) {
|
|
38989
|
-
throw new CLIError("REPORT_READ_ONLY", `Replica report is read-only. To remove a named local replica, run ${
|
|
39107
|
+
throw new CLIError("REPORT_READ_ONLY", `Replica report is read-only. To remove a named local replica, run ${CLI_NAME31} replica reset --replica <name> --purge; flag-owned purge has no equivalent yet (TC-1309).`, ExitCode.USAGE_ERROR);
|
|
38990
39108
|
}
|
|
38991
39109
|
if (options.clearPending) {
|
|
38992
39110
|
throw new CLIError("REPORT_READ_ONLY", "Clearing pending replica writes has no equivalent yet; see TC-1309 for a safe replacement command.", ExitCode.USAGE_ERROR);
|
|
@@ -39088,7 +39206,7 @@ or coverage incomplete; 5 grant expired, revoked or not yet valid; 6 network;
|
|
|
39088
39206
|
if (differs !== void 0) {
|
|
39089
39207
|
throw new ReplicaError(
|
|
39090
39208
|
ReplicaErrorCode.CONFIG_MISMATCH,
|
|
39091
|
-
`Replica ${name} already follows ${differs}. Use another --replica name, or ${
|
|
39209
|
+
`Replica ${name} already follows ${differs}. Use another --replica name, or ${CLI_NAME31} replica reset --replica ${name} --purge first.`
|
|
39092
39210
|
);
|
|
39093
39211
|
}
|
|
39094
39212
|
}
|
|
@@ -39253,7 +39371,7 @@ init_theme();
|
|
|
39253
39371
|
init_errors();
|
|
39254
39372
|
init_warnings();
|
|
39255
39373
|
init_constants();
|
|
39256
|
-
import { CLI_NAME as
|
|
39374
|
+
import { CLI_NAME as CLI_NAME33 } from "@tinycloud/operations/cli-name";
|
|
39257
39375
|
import { readFile as readFile14 } from "fs/promises";
|
|
39258
39376
|
import { join as join17 } from "path";
|
|
39259
39377
|
import { constants as osConstants, homedir, hostname, userInfo } from "os";
|
|
@@ -39534,7 +39652,7 @@ init_safe_text();
|
|
|
39534
39652
|
// src/lib/secrets-wait.ts
|
|
39535
39653
|
init_constants();
|
|
39536
39654
|
init_errors();
|
|
39537
|
-
import { CLI_NAME as
|
|
39655
|
+
import { CLI_NAME as CLI_NAME32 } from "@tinycloud/operations/cli-name";
|
|
39538
39656
|
var WAIT_BACKOFF_MS = [3e3, 4500, 6750, 10125];
|
|
39539
39657
|
var WAIT_STEADY_INTERVAL_MS = 15e3;
|
|
39540
39658
|
var DEFAULT_WAIT_TIMEOUT = "5m";
|
|
@@ -39542,7 +39660,7 @@ var MAX_WAIT_TIMEOUT_MS = 9 * 6e4;
|
|
|
39542
39660
|
var MAX_CONSECUTIVE_FAILURES = 5;
|
|
39543
39661
|
var TIMEOUT_RE = /^(0|\d+s|\d+m)$/;
|
|
39544
39662
|
var RETRYABLE_STATUS_ERRORS = { NODE_UNREACHABLE: true, NODE_ERROR: true };
|
|
39545
|
-
var EXPLICIT_KEY_HINT = `${
|
|
39663
|
+
var EXPLICIT_KEY_HINT = `${CLI_NAME32} secrets wait never signs in, and an explicit private key has no stored session. Use a local profile signed in once with ${CLI_NAME32} --profile <profile> auth login --method local, without TC_PRIVATE_KEY.`;
|
|
39546
39664
|
function abortableSleep(ms2, signal) {
|
|
39547
39665
|
return new Promise((resolve9) => {
|
|
39548
39666
|
if (signal.aborted) {
|
|
@@ -39578,7 +39696,7 @@ async function runSecretsWait(request, deps) {
|
|
|
39578
39696
|
let step = 0;
|
|
39579
39697
|
const cancelled = () => new CLIError(
|
|
39580
39698
|
"CANCELLED",
|
|
39581
|
-
`Interrupted: ${
|
|
39699
|
+
`Interrupted: ${CLI_NAME32} secrets wait stopped before the secrets were ready.`,
|
|
39582
39700
|
ExitCode.CANCELLED,
|
|
39583
39701
|
progress === void 0 ? {} : { progress }
|
|
39584
39702
|
);
|
|
@@ -39722,13 +39840,13 @@ function statusError(error, profile, warnings) {
|
|
|
39722
39840
|
if (error.code === "AUTH_REQUIRED" || error.code === "SESSION_NOT_FOUND") {
|
|
39723
39841
|
const recovery = recoveryOf(error, "sign_in");
|
|
39724
39842
|
if (recovery !== void 0) {
|
|
39725
|
-
const hint = recovery.command !== void 0 ? `Sign in once with: ${recovery.command}, then retry.` : `Sign in to profile "${profile}" once with ${
|
|
39843
|
+
const hint = recovery.command !== void 0 ? `Sign in once with: ${recovery.command}, then retry.` : `Sign in to profile "${profile}" once with ${CLI_NAME32} auth login, then retry.`;
|
|
39726
39844
|
return new CLIError("AUTH_REQUIRED", error.message, ExitCode.AUTH_REQUIRED, { hint, recovery, ...extra });
|
|
39727
39845
|
}
|
|
39728
39846
|
}
|
|
39729
39847
|
if (error.code === "PROFILE_MIGRATION_REQUIRED") {
|
|
39730
39848
|
const recovery = recoveryOf(error, "complete_migration");
|
|
39731
|
-
const hint = recovery?.command !== void 0 ? `Run ${recovery.command} once to finish this profile's one-time upgrade, then retry wait.` : `Run ${
|
|
39849
|
+
const hint = recovery?.command !== void 0 ? `Run ${recovery.command} once to finish this profile's one-time upgrade, then retry wait.` : `Run ${CLI_NAME32} auth caps once with profile "${profile}" to finish its one-time upgrade, then retry wait.`;
|
|
39732
39850
|
return new CLIError("PROFILE_MIGRATION_REQUIRED", error.message, ExitCode.ERROR, {
|
|
39733
39851
|
hint,
|
|
39734
39852
|
...recovery === void 0 ? {} : { recovery },
|
|
@@ -39920,11 +40038,11 @@ async function runSecretOperation(params) {
|
|
|
39920
40038
|
}
|
|
39921
40039
|
function secretPermissionReason(action, name) {
|
|
39922
40040
|
const target = name ? ` secret "${name}"` : " secrets";
|
|
39923
|
-
return `Allow \`${
|
|
40041
|
+
return `Allow \`${CLI_NAME33} secrets ${action}${name ? ` ${name}` : ""}\` to access${target} with the required TinyCloud permissions.`;
|
|
39924
40042
|
}
|
|
39925
40043
|
function assertOwnerApprovalPossible(profileName2, profile, action, name, missing = []) {
|
|
39926
40044
|
if (profile.authMethod !== "openkey" || process.stdin.isTTY || process.stderr.isTTY) return;
|
|
39927
|
-
const command = `${
|
|
40045
|
+
const command = `${CLI_NAME33} secrets ${action === "del" ? "delete" : action}${name ? ` ${name}` : ""}`;
|
|
39928
40046
|
throw new CLIError(
|
|
39929
40047
|
"PERMISSION_DENIED",
|
|
39930
40048
|
action === "get" ? missing.length > 0 && missing.every((permission) => permission.service === "tinycloud.encryption" && Array.isArray(permission.actions) && permission.actions.length > 0 && permission.actions.every((capability) => capability === SECRET_DECRYPT_CAPABILITY)) ? `Profile "${profileName2}" lacks the scoped decrypt authority (${SECRET_DECRYPT_CAPABILITY}) needed to read secret "${name}", and requesting it needs an interactive browser approval.` : `Profile "${profileName2}" lacks a scoped read or decrypt grant needed to read secret "${name}", and requesting it needs an interactive browser approval.` : `Profile "${profileName2}" holds no grant for \`${command}\`, and requesting one needs an interactive browser approval.`,
|
|
@@ -39935,7 +40053,7 @@ function assertOwnerApprovalPossible(profileName2, profile, action, name, missin
|
|
|
39935
40053
|
);
|
|
39936
40054
|
}
|
|
39937
40055
|
function scopedSecretLoginHint(profileName2) {
|
|
39938
|
-
return `Have the owner approve a scoped login whose manifest names the secret: ${
|
|
40056
|
+
return `Have the owner approve a scoped login whose manifest names the secret: ${CLI_NAME33} --profile ${profileName2} auth login --method openkey --paste --manifest <manifest.json>. Pass the owner's code on stdin, newline-terminated.`;
|
|
39939
40057
|
}
|
|
39940
40058
|
async function runSecretOperationAttempt(label, operation) {
|
|
39941
40059
|
try {
|
|
@@ -40069,7 +40187,7 @@ ${message}`,
|
|
|
40069
40187
|
"AUTH_REQUIRED",
|
|
40070
40188
|
"Not signed in to TinyCloud.",
|
|
40071
40189
|
ExitCode.AUTH_REQUIRED,
|
|
40072
|
-
{ hint: `Sign in with: ${
|
|
40190
|
+
{ hint: `Sign in with: ${CLI_NAME33} --profile ${result.context.profile} auth login` }
|
|
40073
40191
|
);
|
|
40074
40192
|
}
|
|
40075
40193
|
if (result.error.code === "NODE_UNREACHABLE") {
|
|
@@ -40568,7 +40686,7 @@ function argvAfterSeparator(cmd) {
|
|
|
40568
40686
|
return void 0;
|
|
40569
40687
|
}
|
|
40570
40688
|
function secretRunPlan(cmd, operands, maps) {
|
|
40571
|
-
const usage3 = `${
|
|
40689
|
+
const usage3 = `${CLI_NAME33} secrets run NAME... [--map NAME=ENV_VAR] -- <command> [args...]`;
|
|
40572
40690
|
const after = argvAfterSeparator(cmd);
|
|
40573
40691
|
if (after === void 0 || after.length > operands.length) {
|
|
40574
40692
|
throw new CLIError("USAGE_ERROR", `Put the command after \`--\`: ${usage3}`, ExitCode.USAGE_ERROR);
|
|
@@ -40628,7 +40746,7 @@ function exitAsCommand(outcome, program) {
|
|
|
40628
40746
|
return process.exit(128 + (osConstants.signals[outcome.signal] ?? 0));
|
|
40629
40747
|
}
|
|
40630
40748
|
const reason = outcome.errno === "ENOENT" ? "command not found" : outcome.errno === "EACCES" ? "permission denied" : `cannot run (${outcome.errno ?? "unknown error"})`;
|
|
40631
|
-
process.stderr.write(`${
|
|
40749
|
+
process.stderr.write(`${CLI_NAME33} secrets run: ${safeInline(program)}: ${reason}
|
|
40632
40750
|
`);
|
|
40633
40751
|
return process.exit(outcome.errno === "ENOENT" ? 127 : 126);
|
|
40634
40752
|
}
|
|
@@ -40693,7 +40811,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
40693
40811
|
name: "Encryption network",
|
|
40694
40812
|
ok: false,
|
|
40695
40813
|
detail: `${networkName} not found`,
|
|
40696
|
-
hint: `${
|
|
40814
|
+
hint: `${CLI_NAME33} secrets network init ${networkName}`
|
|
40697
40815
|
}
|
|
40698
40816
|
];
|
|
40699
40817
|
let secret;
|
|
@@ -40739,7 +40857,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
40739
40857
|
name: "Secret access",
|
|
40740
40858
|
ok: false,
|
|
40741
40859
|
detail: notFound ? `${resolved.permissionPaths.vault} not found` : result.error.message,
|
|
40742
|
-
hint: notFound ? local ? `${
|
|
40860
|
+
hint: notFound ? local ? `${CLI_NAME33} secrets put ${resolved.name}${formatSecretScopeFlag(scopeOptions)} --stdin` : `${CLI_NAME33} secrets request ${resolved.name}${formatSecretScopeFlag(scopeOptions)}` : `Ask the owner profile to grant ${secretCapabilityAction("get")} and ${SECRET_DECRYPT_CAPABILITY}.`
|
|
40743
40861
|
});
|
|
40744
40862
|
}
|
|
40745
40863
|
} else {
|
|
@@ -40978,7 +41096,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
40978
41096
|
if (effectiveLocal(profile, options)) {
|
|
40979
41097
|
throw new CLIError(
|
|
40980
41098
|
"LOCAL_OWNER_PROFILE",
|
|
40981
|
-
`Profile "${ctx.profile}" uses a local key, which owns its secrets: store them with ${
|
|
41099
|
+
`Profile "${ctx.profile}" uses a local key, which owns its secrets: store them with ${CLI_NAME33} secrets put NAME --stdin instead of requesting them.`,
|
|
40982
41100
|
ExitCode.USAGE_ERROR
|
|
40983
41101
|
);
|
|
40984
41102
|
}
|
|
@@ -41101,7 +41219,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
41101
41219
|
|
|
41102
41220
|
// src/commands/share.ts
|
|
41103
41221
|
import { Option } from "commander";
|
|
41104
|
-
import { CLI_NAME as
|
|
41222
|
+
import { CLI_NAME as CLI_NAME34 } from "@tinycloud/operations/cli-name";
|
|
41105
41223
|
import { ProfileLockTimeoutError as ProfileLockTimeoutError3 } from "@tinycloud/operations/state";
|
|
41106
41224
|
init_errors();
|
|
41107
41225
|
|
|
@@ -42562,7 +42680,7 @@ function shareCliError(error, operation = "publish") {
|
|
|
42562
42680
|
"SHARE_HISTORY_UNREADABLE",
|
|
42563
42681
|
"sender history cannot be read with the current session key",
|
|
42564
42682
|
1,
|
|
42565
|
-
{ hint: `Run \`${
|
|
42683
|
+
{ hint: `Run \`${CLI_NAME34} share history recover\` to move the unreadable file aside. The old file is kept, not deleted.` }
|
|
42566
42684
|
);
|
|
42567
42685
|
}
|
|
42568
42686
|
if (error instanceof SharePublishAuthorityError) {
|
|
@@ -42570,12 +42688,12 @@ function shareCliError(error, operation = "publish") {
|
|
|
42570
42688
|
const profileName2 = "profileName" in failure ? failure.profileName : void 0;
|
|
42571
42689
|
const localKey = "localKey" in failure && failure.localKey === true;
|
|
42572
42690
|
const profileHint = profileName2 === void 0 ? "" : `--profile ${profileName2} `;
|
|
42573
|
-
const loginHint = localKey ? `\`${
|
|
42691
|
+
const loginHint = localKey ? `\`${CLI_NAME34} ${profileHint}auth login --method local\`` : `\`${CLI_NAME34} ${profileHint}auth login --device --manifest builtin:share-publishing\` (or \`${CLI_NAME34} ${profileHint}enable share\`)`;
|
|
42574
42692
|
if (failure.kind === "caveated-session") {
|
|
42575
42693
|
const holder = profileName2 === void 0 ? "this session" : `profile ${profileName2}'s session`;
|
|
42576
42694
|
return new CLIError(
|
|
42577
42695
|
"PERMISSION_DENIED",
|
|
42578
|
-
`${holder} carries signed restrictions (caveats) on the authority an anyone-with-link share needs, so it cannot create the share link; nothing was shared. Approve Share publishing without restrictions on a new, dedicated profile (any unused name): \`${
|
|
42696
|
+
`${holder} carries signed restrictions (caveats) on the authority an anyone-with-link share needs, so it cannot create the share link; nothing was shared. Approve Share publishing without restrictions on a new, dedicated profile (any unused name): \`${CLI_NAME34} init --name publisher --key-only && ${CLI_NAME34} --profile publisher enable share\``,
|
|
42579
42697
|
5
|
|
42580
42698
|
);
|
|
42581
42699
|
}
|
|
@@ -42676,7 +42794,7 @@ function shareCliError(error, operation = "publish") {
|
|
|
42676
42794
|
if (nodeCode === "ENOENT") return new CLIError("INVALID_ARGUMENT", "share input was not found", 2);
|
|
42677
42795
|
if (nodeCode === "EISDIR") return new CLIError("INVALID_ARGUMENT", "share input must be a Markdown file", 2);
|
|
42678
42796
|
const mapped = typeof nodeCode === "string" && known[nodeCode] !== void 0 ? known[nodeCode] : known[message];
|
|
42679
|
-
const classified = mapped === void 0 ? classifyShareFailure(error, `\`${
|
|
42797
|
+
const classified = mapped === void 0 ? classifyShareFailure(error, `\`${CLI_NAME34} auth login\``) : void 0;
|
|
42680
42798
|
if (classified !== void 0) return Object.assign(new CLIError(classified.code, classified.reason, classified.exitCode), { cause: error });
|
|
42681
42799
|
if (error instanceof TypeError) return new CLIError("INVALID_ARGUMENT", "share input is invalid", 2);
|
|
42682
42800
|
return new CLIError(mapped?.code ?? "INVALID_ARGUMENT", mapped ? mapped.code : "share operation failed", mapped?.exit ?? 2);
|
|
@@ -43086,7 +43204,7 @@ init_constants();
|
|
|
43086
43204
|
init_sdk();
|
|
43087
43205
|
init_host();
|
|
43088
43206
|
init_theme();
|
|
43089
|
-
import { CLI_NAME as
|
|
43207
|
+
import { CLI_NAME as CLI_NAME35 } from "@tinycloud/operations/cli-name";
|
|
43090
43208
|
import { ACCOUNT_REGISTRY_SPACE } from "@tinycloud/node-sdk";
|
|
43091
43209
|
import { randomBytes as randomBytes9 } from "crypto";
|
|
43092
43210
|
import { mkdir as mkdir7, writeFile as writeFile4 } from "fs/promises";
|
|
@@ -43133,7 +43251,7 @@ function registerSpaceCommand(program) {
|
|
|
43133
43251
|
} else {
|
|
43134
43252
|
const profile = await ProfileManager.getProfile(ctx.profile);
|
|
43135
43253
|
const cap = "tinycloud.kv:account:spaces/:get,list";
|
|
43136
|
-
const hint = resolveProfilePosture(profile) === "delegate-session" ? `Ask the space owner to grant it. Create the request with: ${
|
|
43254
|
+
const hint = resolveProfilePosture(profile) === "delegate-session" ? `Ask the space owner to grant it. Create the request with: ${CLI_NAME35} --profile ${ctx.profile} auth request --cap ${cap} --emit` : `Grant it to this session with: ${CLI_NAME35} --profile ${ctx.profile} auth request --grant --cap ${cap}. The default OpenKey owner sign-in covers only the default space, so it includes neither.`;
|
|
43137
43255
|
throw new CLIError(
|
|
43138
43256
|
"PERMISSION_DENIED",
|
|
43139
43257
|
`Listing spaces needs tinycloud.kv/get and tinycloud.kv/list on the account space's spaces/ registry (${accountSpaceId ?? ACCOUNT_REGISTRY_SPACE}), or tinycloud.space/list. This session holds neither.`,
|
|
@@ -43170,10 +43288,10 @@ function registerSpaceCommand(program) {
|
|
|
43170
43288
|
|
|
43171
43289
|
A delegate cannot host a space \u2014 only its owner (root authority) can. This
|
|
43172
43290
|
emits a tinycloud.host.request artifact naming the space and its owner so you
|
|
43173
|
-
can hand it to the owner; they run \`${
|
|
43291
|
+
can hand it to the owner; they run \`${CLI_NAME35} space host <name>\` and confirm.
|
|
43174
43292
|
|
|
43175
43293
|
If you ARE the owner of the resolved space, this refuses and tells you to host
|
|
43176
|
-
it directly with \`${
|
|
43294
|
+
it directly with \`${CLI_NAME35} space host <name>\` (no request needed).
|
|
43177
43295
|
`).action(async (name, options, cmd) => {
|
|
43178
43296
|
try {
|
|
43179
43297
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -43184,7 +43302,7 @@ it directly with \`${CLI_NAME34} space host <name>\` (no request needed).
|
|
|
43184
43302
|
if (await isRootAuthority(spaceId, ctx.profile)) {
|
|
43185
43303
|
throw new CLIError(
|
|
43186
43304
|
"ALREADY_ROOT_AUTHORITY",
|
|
43187
|
-
`You are the owner of ${spaceId}. Host it directly: ${
|
|
43305
|
+
`You are the owner of ${spaceId}. Host it directly: ${CLI_NAME35} space host ${spaceName}`,
|
|
43188
43306
|
ExitCode.USAGE_ERROR
|
|
43189
43307
|
);
|
|
43190
43308
|
}
|
|
@@ -43270,7 +43388,7 @@ init_sdk();
|
|
|
43270
43388
|
init_space();
|
|
43271
43389
|
init_host();
|
|
43272
43390
|
init_theme();
|
|
43273
|
-
import { CLI_NAME as
|
|
43391
|
+
import { CLI_NAME as CLI_NAME36 } from "@tinycloud/operations/cli-name";
|
|
43274
43392
|
import { writeFile as writeFile5 } from "fs/promises";
|
|
43275
43393
|
import { resolve as resolve8 } from "path";
|
|
43276
43394
|
async function dbHandle(node, dbName, spaceInput, profileName2) {
|
|
@@ -43294,12 +43412,12 @@ database for simple apps, or pass --db to target a named database. Pass
|
|
|
43294
43412
|
--space to target a non-primary space (e.g. the manifest "applications" space).
|
|
43295
43413
|
|
|
43296
43414
|
Common workflows:
|
|
43297
|
-
$ ${
|
|
43298
|
-
$ ${
|
|
43299
|
-
$ ${
|
|
43300
|
-
$ ${
|
|
43301
|
-
$ ${
|
|
43302
|
-
$ ${
|
|
43415
|
+
$ ${CLI_NAME36} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
|
|
43416
|
+
$ ${CLI_NAME36} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["ship docs"]'
|
|
43417
|
+
$ ${CLI_NAME36} sql query "SELECT id, body FROM notes ORDER BY id"
|
|
43418
|
+
$ ${CLI_NAME36} sql query "SELECT * FROM events WHERE type = ?" --db analytics --params '["signup"]'
|
|
43419
|
+
$ ${CLI_NAME36} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
|
|
43420
|
+
$ ${CLI_NAME36} sql export --db analytics --output analytics.db
|
|
43303
43421
|
|
|
43304
43422
|
Commands:
|
|
43305
43423
|
query Read rows with SELECT statements
|
|
@@ -43316,10 +43434,10 @@ Tips:
|
|
|
43316
43434
|
sql.command("query <sql>").description("Run a read-only SELECT query").option("--db <name>", "SQLite database name within the current space", "default").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").option("--params <json>", "Bind parameters as a JSON array for ? placeholders").addHelpText("after", `
|
|
43317
43435
|
|
|
43318
43436
|
Examples:
|
|
43319
|
-
$ ${
|
|
43320
|
-
$ ${
|
|
43321
|
-
$ ${
|
|
43322
|
-
$ ${
|
|
43437
|
+
$ ${CLI_NAME36} sql query "SELECT * FROM notes ORDER BY id"
|
|
43438
|
+
$ ${CLI_NAME36} sql query "SELECT * FROM notes WHERE id = ?" --params '[42]'
|
|
43439
|
+
$ ${CLI_NAME36} sql query "SELECT count(*) AS total FROM events" --db analytics --json
|
|
43440
|
+
$ ${CLI_NAME36} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
|
|
43323
43441
|
|
|
43324
43442
|
Output:
|
|
43325
43443
|
Human output is formatted as a table. Piped output or --json returns
|
|
@@ -43360,11 +43478,11 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
|
|
|
43360
43478
|
sql.command("execute <sql>").description("Run a write or schema statement").option("--db <name>", "SQLite database name within the current space", "default").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").option("--params <json>", "Bind parameters as a JSON array for ? placeholders").addHelpText("after", `
|
|
43361
43479
|
|
|
43362
43480
|
Examples:
|
|
43363
|
-
$ ${
|
|
43364
|
-
$ ${
|
|
43365
|
-
$ ${
|
|
43366
|
-
$ ${
|
|
43367
|
-
$ ${
|
|
43481
|
+
$ ${CLI_NAME36} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
|
|
43482
|
+
$ ${CLI_NAME36} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["first note"]'
|
|
43483
|
+
$ ${CLI_NAME36} sql execute "UPDATE notes SET body = ? WHERE id = ?" --params '["edited", 1]'
|
|
43484
|
+
$ ${CLI_NAME36} sql execute "DROP TABLE old_notes" --db archive
|
|
43485
|
+
$ ${CLI_NAME36} sql execute "DELETE FROM conversation WHERE id = ?" --space applications --db xyz.tinycloud.listen/conversations --params '["abc"]'
|
|
43368
43486
|
|
|
43369
43487
|
Output:
|
|
43370
43488
|
Returns JSON with the changed row count and last inserted row id when available.
|
|
@@ -43393,9 +43511,9 @@ Output:
|
|
|
43393
43511
|
sql.command("export").description("Export a SQLite database as a binary .db file").option("--db <name>", "SQLite database name within the current space", "default").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").option("-o, --output <file>", "Output file path", "export.db").addHelpText("after", `
|
|
43394
43512
|
|
|
43395
43513
|
Examples:
|
|
43396
|
-
$ ${
|
|
43397
|
-
$ ${
|
|
43398
|
-
$ ${
|
|
43514
|
+
$ ${CLI_NAME36} sql export
|
|
43515
|
+
$ ${CLI_NAME36} sql export --db analytics --output analytics.db
|
|
43516
|
+
$ ${CLI_NAME36} sql export --space applications --db xyz.tinycloud.listen/conversations --output listen.db
|
|
43399
43517
|
|
|
43400
43518
|
Output:
|
|
43401
43519
|
Writes the database file locally and returns JSON with the path and size.
|
|
@@ -43428,19 +43546,19 @@ Output:
|
|
|
43428
43546
|
sql.command("copy").description("Copy rows between SQL databases (optionally across spaces)").requiredOption("--from-db <name>", "Source database name").requiredOption("--to-db <name>", "Destination database name").option("--from-space <name|uri>", "Source space (defaults to primary)").option("--to-space <name|uri>", "Destination space (defaults to primary)").option("--table <name...>", "Restrict copy to specific tables (repeat or comma-separated)").option("--dry-run", "Print the plan without writing", false).addHelpText("after", `
|
|
43429
43547
|
|
|
43430
43548
|
Examples:
|
|
43431
|
-
$ ${
|
|
43549
|
+
$ ${CLI_NAME36} sql copy --from-db com.tinycloud.conversation-sync/conversations \\
|
|
43432
43550
|
--to-db xyz.tinycloud.listen/conversations \\
|
|
43433
43551
|
--space applications --dry-run
|
|
43434
|
-
$ ${
|
|
43552
|
+
$ ${CLI_NAME36} sql copy --from-space applications --from-db com.foo/data \\
|
|
43435
43553
|
--to-space applications --to-db com.bar/data \\
|
|
43436
43554
|
--table conversation --table participant
|
|
43437
43555
|
|
|
43438
43556
|
Notes:
|
|
43439
43557
|
- Refuses to run when (resolved space, db) is identical for source and destination.
|
|
43440
|
-
- Does NOT create destination tables. Run the target app once (or use \`${
|
|
43558
|
+
- Does NOT create destination tables. Run the target app once (or use \`${CLI_NAME36} sql execute\`)
|
|
43441
43559
|
to materialize the schema before copying.
|
|
43442
43560
|
- One row at a time; suitable for small/medium datasets. Large copies should
|
|
43443
|
-
use \`${
|
|
43561
|
+
use \`${CLI_NAME36} sql export\` + bulk import.
|
|
43444
43562
|
- Authorization: the active session/delegation must cover sql/read on source
|
|
43445
43563
|
AND sql/write on destination. Otherwise the relevant operation will fail.
|
|
43446
43564
|
`).action(async (options, cmd) => {
|
|
@@ -43540,7 +43658,7 @@ init_errors();
|
|
|
43540
43658
|
init_formatter();
|
|
43541
43659
|
init_theme();
|
|
43542
43660
|
init_permissions();
|
|
43543
|
-
import { CLI_NAME as
|
|
43661
|
+
import { CLI_NAME as CLI_NAME37 } from "@tinycloud/operations/cli-name";
|
|
43544
43662
|
import {
|
|
43545
43663
|
NodeWasmBindings as NodeWasmBindings2
|
|
43546
43664
|
} from "@tinycloud/node-sdk";
|
|
@@ -43814,7 +43932,7 @@ function formatStatus(summary) {
|
|
|
43814
43932
|
lines.push(`Default profile: ${theme.value(summary.defaultProfile)}`);
|
|
43815
43933
|
lines.push("");
|
|
43816
43934
|
if (summary.profiles.length === 0) {
|
|
43817
|
-
lines.push(theme.muted(`No profiles configured. Run: ${
|
|
43935
|
+
lines.push(theme.muted(`No profiles configured. Run: ${CLI_NAME37} init`));
|
|
43818
43936
|
return `${lines.join("\n")}
|
|
43819
43937
|
`;
|
|
43820
43938
|
}
|
|
@@ -44293,6 +44411,7 @@ function registerVarsCommand(program) {
|
|
|
44293
44411
|
// src/command-registry.ts
|
|
44294
44412
|
function registerTinyCloudCommands(program) {
|
|
44295
44413
|
registerInitCommand(program);
|
|
44414
|
+
registerOnboardCommand(program);
|
|
44296
44415
|
registerContextCommand(program);
|
|
44297
44416
|
registerAuthCommand(program);
|
|
44298
44417
|
registerEnableCommand(program);
|