@tinycloud/cli 1.3.0-beta.23 → 1.3.0-beta.25
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 +338 -194
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +5 -5
- package/skills/tc-cli/SDK.md +8 -9
package/dist/legacy-entry.js
CHANGED
|
@@ -9786,18 +9786,89 @@ var init_profiles = __esm({
|
|
|
9786
9786
|
// src/commands/account.ts
|
|
9787
9787
|
init_profiles();
|
|
9788
9788
|
init_constants();
|
|
9789
|
+
import open6 from "open";
|
|
9790
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
9791
|
+
|
|
9792
|
+
// src/lib/account-access.ts
|
|
9793
|
+
init_constants();
|
|
9794
|
+
init_errors();
|
|
9795
|
+
init_profiles();
|
|
9796
|
+
init_permissions();
|
|
9797
|
+
import { ACCOUNT_MANIFEST_PERMISSIONS } from "@tinycloud/sdk-core";
|
|
9798
|
+
import { CLI_NAME as CLI_NAME10 } from "@tinycloud/operations/cli-name";
|
|
9799
|
+
var PRIMARY_SPACE = "default";
|
|
9800
|
+
function accountPermissionEntries(need = "registry") {
|
|
9801
|
+
const entries = ACCOUNT_MANIFEST_PERMISSIONS.map((resource) => ({
|
|
9802
|
+
service: resource.service,
|
|
9803
|
+
space: resource.space,
|
|
9804
|
+
path: resource.path,
|
|
9805
|
+
actions: [...resource.actions]
|
|
9806
|
+
}));
|
|
9807
|
+
if (need === "remove") {
|
|
9808
|
+
for (const path of ["applications/", "spaces/"]) {
|
|
9809
|
+
entries.push({ service: "tinycloud.kv", space: "account", path, actions: ["tinycloud.kv/del"] });
|
|
9810
|
+
}
|
|
9811
|
+
}
|
|
9812
|
+
if (need === "spaces" || need === "delegations") {
|
|
9813
|
+
entries.push({ service: "tinycloud.space", space: PRIMARY_SPACE, path: "", actions: ["tinycloud.space/list"] });
|
|
9814
|
+
}
|
|
9815
|
+
if (need === "delegations") {
|
|
9816
|
+
entries.push(capabilitiesRead(PRIMARY_SPACE));
|
|
9817
|
+
}
|
|
9818
|
+
return entries;
|
|
9819
|
+
}
|
|
9820
|
+
function capabilitiesRead(space) {
|
|
9821
|
+
return { service: "tinycloud.capabilities", space, path: "", actions: ["tinycloud.capabilities/read"] };
|
|
9822
|
+
}
|
|
9823
|
+
function capSpecs(entries) {
|
|
9824
|
+
return entries.map((entry) => {
|
|
9825
|
+
const actions = entry.actions.map((action) => action.slice(action.lastIndexOf("/") + 1));
|
|
9826
|
+
return `${entry.service}:${entry.space}:${entry.path}:${actions.join(",")}`;
|
|
9827
|
+
});
|
|
9828
|
+
}
|
|
9829
|
+
function shellWord(value) {
|
|
9830
|
+
return /^[A-Za-z0-9._@:+-]+$/.test(value) ? value : `'${value.replace(/'/g, `'\\''`)}'`;
|
|
9831
|
+
}
|
|
9832
|
+
function accessRequired(profile, entries, what) {
|
|
9833
|
+
const grant = `${CLI_NAME10} --profile ${shellWord(profile)} auth request --grant ` + capSpecs(entries).map((spec) => `--cap '${spec}'`).join(" ");
|
|
9834
|
+
const approvals = new Set(entries.map((entry) => entry.space)).size;
|
|
9835
|
+
return new CLIError(
|
|
9836
|
+
"ACCOUNT_ACCESS_REQUIRED",
|
|
9837
|
+
`This session lacks account permissions this command needs (${what}). A plain OpenKey login grants only the \`default\` space.`,
|
|
9838
|
+
ExitCode.PERMISSION_DENIED,
|
|
9839
|
+
{
|
|
9840
|
+
hint: `Grant account access (${approvals} OpenKey approval${approvals === 1 ? "" : "s"}), then retry: ${grant}
|
|
9841
|
+
If the browser cannot reach the CLI, re-run with --paste: ${grant} --paste`
|
|
9842
|
+
}
|
|
9843
|
+
);
|
|
9844
|
+
}
|
|
9845
|
+
async function assertAccountAccess(node, profile, need = "registry", cid) {
|
|
9846
|
+
if (need === "revoke") {
|
|
9847
|
+
if ((await ProfileManager.getProfile(profile)).authMethod === "local") return;
|
|
9848
|
+
throw new CLIError(
|
|
9849
|
+
"ACCOUNT_REVOKE_UNSUPPORTED",
|
|
9850
|
+
"This profile signs through OpenKey, which cannot sign the raw-CID revocation `account delegations revoke` needs.",
|
|
9851
|
+
ExitCode.PERMISSION_DENIED,
|
|
9852
|
+
{ hint: `Revoke it with: ${CLI_NAME10} --profile ${shellWord(profile)} delegation revoke ${cid ?? "<cid>"}` }
|
|
9853
|
+
);
|
|
9854
|
+
}
|
|
9855
|
+
const entries = accountPermissionEntries(need);
|
|
9856
|
+
const required = await resolvePermissionSpaces(entries, profile);
|
|
9857
|
+
if (node.hasRuntimePermissions(required)) return;
|
|
9858
|
+
throw accessRequired(profile, entries, need === "registry" ? "kv applications/ and spaces/, sql account, delegation list, capabilities read on `account`" : `the \`account\` registry set plus ${need === "remove" ? "kv del on applications/ and spaces/" : "space list and capabilities read on `default`"}`);
|
|
9859
|
+
}
|
|
9860
|
+
|
|
9861
|
+
// src/commands/account.ts
|
|
9789
9862
|
init_sdk();
|
|
9790
9863
|
init_errors();
|
|
9791
9864
|
init_formatter();
|
|
9792
9865
|
init_theme();
|
|
9793
|
-
import open6 from "open";
|
|
9794
|
-
import { readFile as readFile6 } from "fs/promises";
|
|
9795
9866
|
var ACCOUNT_BILLING_URL = "https://account.tinycloud.xyz/billing";
|
|
9796
9867
|
function registerAccountCommand(program) {
|
|
9797
9868
|
const account = program.command("account").description("Account applications, spaces, delegations, and billing");
|
|
9798
9869
|
account.command("status").description("Show account status").action(async (_options, cmd) => {
|
|
9799
9870
|
try {
|
|
9800
|
-
const node = await authenticatedNode(cmd);
|
|
9871
|
+
const node = await authenticatedNode(cmd, "delegations");
|
|
9801
9872
|
const status = await node.account.status();
|
|
9802
9873
|
assertOk(status);
|
|
9803
9874
|
outputJson(status.data);
|
|
@@ -9839,7 +9910,7 @@ function registerAccountCommand(program) {
|
|
|
9839
9910
|
const spaces = account.command("spaces").description("Manage account space registry");
|
|
9840
9911
|
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) => {
|
|
9841
9912
|
try {
|
|
9842
|
-
const node = await authenticatedNode(cmd);
|
|
9913
|
+
const node = await authenticatedNode(cmd, "spaces");
|
|
9843
9914
|
const result = options.live ? await node.account.spaces.list() : await node.account.spaces.list({ preferIndex: true });
|
|
9844
9915
|
assertOk(result);
|
|
9845
9916
|
const payload = { spaces: result.data.map(formatSpace), count: result.data.length };
|
|
@@ -9896,7 +9967,7 @@ function registerAccountCommand(program) {
|
|
|
9896
9967
|
});
|
|
9897
9968
|
spaces.command("sync").description("Register currently accessible spaces into account/spaces").action(async (_options, cmd) => {
|
|
9898
9969
|
try {
|
|
9899
|
-
const node = await authenticatedNode(cmd);
|
|
9970
|
+
const node = await authenticatedNode(cmd, "spaces");
|
|
9900
9971
|
const result = await node.account.spaces.syncAccessible();
|
|
9901
9972
|
assertOk(result);
|
|
9902
9973
|
outputJson({ spaces: result.data.map(formatSpace), count: result.data.length, synced: true });
|
|
@@ -9906,7 +9977,7 @@ function registerAccountCommand(program) {
|
|
|
9906
9977
|
});
|
|
9907
9978
|
spaces.command("remove <space-id>").alias("delete").description("Remove a space registry entry").action(async (spaceId, _options, cmd) => {
|
|
9908
9979
|
try {
|
|
9909
|
-
const node = await authenticatedNode(cmd);
|
|
9980
|
+
const node = await authenticatedNode(cmd, "remove");
|
|
9910
9981
|
const result = await node.account.spaces.remove(spaceId);
|
|
9911
9982
|
assertOk(result);
|
|
9912
9983
|
outputJson({ spaceId, removed: true });
|
|
@@ -9937,7 +10008,7 @@ function registerAccountCommand(program) {
|
|
|
9937
10008
|
});
|
|
9938
10009
|
apps.command("remove <app-id>").alias("delete").description("Remove an application registry entry").action(async (appId, _options, cmd) => {
|
|
9939
10010
|
try {
|
|
9940
|
-
const node = await authenticatedNode(cmd);
|
|
10011
|
+
const node = await authenticatedNode(cmd, "remove");
|
|
9941
10012
|
const result = await node.account.applications.remove(appId);
|
|
9942
10013
|
assertOk(result);
|
|
9943
10014
|
outputJson({ appId, removed: true });
|
|
@@ -9951,7 +10022,7 @@ function registerAccountCommand(program) {
|
|
|
9951
10022
|
if (options.granted && options.received) {
|
|
9952
10023
|
throw new CLIError("USAGE_ERROR", "Use only one of --granted or --received.", ExitCode.USAGE_ERROR);
|
|
9953
10024
|
}
|
|
9954
|
-
const node = await authenticatedNode(cmd);
|
|
10025
|
+
const node = await authenticatedNode(cmd, "delegations");
|
|
9955
10026
|
const direction = options.granted ? "granted" : options.received ? "received" : "all";
|
|
9956
10027
|
const result = options.live ? await node.account.delegations.list({ direction, space: options.space }) : await node.account.delegations.list({ direction, space: options.space, preferIndex: true });
|
|
9957
10028
|
assertOk(result);
|
|
@@ -9983,7 +10054,7 @@ function registerAccountCommand(program) {
|
|
|
9983
10054
|
});
|
|
9984
10055
|
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) => {
|
|
9985
10056
|
try {
|
|
9986
|
-
const node = await authenticatedNode(cmd);
|
|
10057
|
+
const node = await authenticatedNode(cmd, "revoke", cid);
|
|
9987
10058
|
const result = await node.account.delegations.revoke({ cid, space: options.space });
|
|
9988
10059
|
assertOk(result);
|
|
9989
10060
|
outputJson({ cid, space: options.space, revoked: true });
|
|
@@ -10014,7 +10085,7 @@ function registerAccountCommand(program) {
|
|
|
10014
10085
|
});
|
|
10015
10086
|
index.command("rebuild").description("Rebuild account SQLite index from canonical account data").action(async (_options, cmd) => {
|
|
10016
10087
|
try {
|
|
10017
|
-
const node = await authenticatedNode(cmd);
|
|
10088
|
+
const node = await authenticatedNode(cmd, "delegations");
|
|
10018
10089
|
const result = await node.account.index.rebuild();
|
|
10019
10090
|
assertOk(result);
|
|
10020
10091
|
outputJson(result.data);
|
|
@@ -10047,10 +10118,12 @@ function registerAccountCommand(program) {
|
|
|
10047
10118
|
});
|
|
10048
10119
|
}
|
|
10049
10120
|
}
|
|
10050
|
-
async function authenticatedNode(cmd) {
|
|
10121
|
+
async function authenticatedNode(cmd, need = "registry", cid) {
|
|
10051
10122
|
const globalOpts = cmd.optsWithGlobals();
|
|
10052
10123
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
10053
|
-
|
|
10124
|
+
const node = await ensureAuthenticated(ctx);
|
|
10125
|
+
await assertAccountAccess(node, ctx.profile, need, cid);
|
|
10126
|
+
return node;
|
|
10054
10127
|
}
|
|
10055
10128
|
function assertOk(result) {
|
|
10056
10129
|
if (!result.ok) {
|
|
@@ -10107,7 +10180,7 @@ function formatSpace(space) {
|
|
|
10107
10180
|
init_profile_gate();
|
|
10108
10181
|
init_dist();
|
|
10109
10182
|
init_profiles();
|
|
10110
|
-
import { CLI_NAME as
|
|
10183
|
+
import { CLI_NAME as CLI_NAME15 } from "@tinycloud/operations/cli-name";
|
|
10111
10184
|
import { get as httpGet } from "http";
|
|
10112
10185
|
import { get as httpsGet } from "https";
|
|
10113
10186
|
import { spawn } from "child_process";
|
|
@@ -11067,14 +11140,14 @@ init_space();
|
|
|
11067
11140
|
import { lstat as lstat6 } from "fs/promises";
|
|
11068
11141
|
import { join as join12 } from "path";
|
|
11069
11142
|
import { REPLICA_LOGOUT_PENDING_FILE as REPLICA_LOGOUT_PENDING_FILE3 } from "@tinycloud/node-sdk";
|
|
11070
|
-
import { CLI_NAME as
|
|
11143
|
+
import { CLI_NAME as CLI_NAME11 } from "@tinycloud/operations/cli-name";
|
|
11071
11144
|
import { isLocalOwnerProfile as isLocalOwnerProfile3, ownerSessionLacksProof as ownerSessionLacksProof3 } from "@tinycloud/operations/profile";
|
|
11072
11145
|
import { ProfileLockTimeoutError as ProfileLockTimeoutError2, profilePath as profilePath7 } from "@tinycloud/operations/state";
|
|
11073
11146
|
function assertNotLocalOwner(profileName2, profile, flow) {
|
|
11074
11147
|
if (profile === null || !isLocalOwnerProfile3(profile)) return;
|
|
11075
11148
|
throw new CLIError(
|
|
11076
11149
|
"LOCAL_OWNER_PROFILE",
|
|
11077
|
-
`Profile "${profileName2}" holds a local owner key. ${flow} would turn it into an OpenKey profile while keeping that key. Use a separate profile: \`${
|
|
11150
|
+
`Profile "${profileName2}" holds a local owner key. ${flow} would turn it into an OpenKey profile while keeping that key. Use a separate profile: \`${CLI_NAME11} init --name publisher --key-only\`, then \`${CLI_NAME11} --profile publisher auth login --device --manifest ...\`.`,
|
|
11078
11151
|
ExitCode.USAGE_ERROR
|
|
11079
11152
|
);
|
|
11080
11153
|
}
|
|
@@ -11109,7 +11182,7 @@ function assertSessionReplaceable(profileName2, snapshot, ownerDid, scope, newEx
|
|
|
11109
11182
|
if (problem !== void 0) {
|
|
11110
11183
|
throw new CLIError(
|
|
11111
11184
|
"PROFILE_STATE_INCONSISTENT",
|
|
11112
|
-
`Profile "${profileName2}" is inconsistent (${problem}), possibly from an interrupted write. Nothing was saved. Check \`${
|
|
11185
|
+
`Profile "${profileName2}" is inconsistent (${problem}), possibly from an interrupted write. Nothing was saved. Check \`${CLI_NAME11} --profile ${profileName2} context\`, then pass --replace-session to replace this state, or use a new profile.`,
|
|
11113
11186
|
ExitCode.ERROR
|
|
11114
11187
|
);
|
|
11115
11188
|
}
|
|
@@ -11125,7 +11198,7 @@ function assertSessionReplaceable(profileName2, snapshot, ownerDid, scope, newEx
|
|
|
11125
11198
|
const space = typeof session.spaceId === "string" ? session.spaceId : "an unknown space";
|
|
11126
11199
|
throw new CLIError(
|
|
11127
11200
|
"SESSION_IN_USE",
|
|
11128
|
-
`Profile "${profileName2}" has a live session for ${space}${expiresAt ? ` until ${expiresAt}` : ""} that this login would ${keepsScope ? "shorten" : "narrow or replace"}, dropping that authority. Nothing was saved. Keep the user's existing profiles: use a new profile name (\`${
|
|
11201
|
+
`Profile "${profileName2}" has a live session for ${space}${expiresAt ? ` until ${expiresAt}` : ""} that this login would ${keepsScope ? "shorten" : "narrow or replace"}, dropping that authority. Nothing was saved. Keep the user's existing profiles: use a new profile name (\`${CLI_NAME11} init --name publisher --key-only\`, then \`${CLI_NAME11} --profile publisher auth login --device --manifest ...\`), or pass --replace-session to replace this session.`,
|
|
11129
11202
|
ExitCode.USAGE_ERROR
|
|
11130
11203
|
);
|
|
11131
11204
|
}
|
|
@@ -11159,7 +11232,7 @@ async function commitLogin(profileName2, snapshot, commit) {
|
|
|
11159
11232
|
if (canonicalJson(current.profile) !== canonicalJson(snapshot.profile) || canonicalJson(current.key) !== canonicalJson(snapshot.key) || canonicalJson(current.session) !== canonicalJson(snapshot.session)) {
|
|
11160
11233
|
throw new CLIError(
|
|
11161
11234
|
"PROFILE_CHANGED_DURING_LOGIN",
|
|
11162
|
-
`Profile "${profileName2}" changed while this login was in progress (another login, key rotation or logout). Nothing was saved; check \`${
|
|
11235
|
+
`Profile "${profileName2}" changed while this login was in progress (another login, key rotation or logout). Nothing was saved; check \`${CLI_NAME11} --profile ${profileName2} context\` and run the login again if it is still needed.`,
|
|
11163
11236
|
ExitCode.ERROR
|
|
11164
11237
|
);
|
|
11165
11238
|
}
|
|
@@ -11191,7 +11264,7 @@ async function commitLogin(profileName2, snapshot, commit) {
|
|
|
11191
11264
|
if (failures.length === 0) throw error;
|
|
11192
11265
|
throw new CLIError(
|
|
11193
11266
|
"PROFILE_STATE_INCONSISTENT",
|
|
11194
|
-
`Saving the login for profile "${profileName2}" failed (${errorMessage(error)}), and restoring its previous state failed too (${failures.map(errorMessage).join("; ")}). The profile's key, session and settings may not match. Check \`${
|
|
11267
|
+
`Saving the login for profile "${profileName2}" failed (${errorMessage(error)}), and restoring its previous state failed too (${failures.map(errorMessage).join("; ")}). The profile's key, session and settings may not match. Check \`${CLI_NAME11} --profile ${profileName2} context\`, then run the login again with --replace-session, or use a new profile.`,
|
|
11195
11268
|
ExitCode.ERROR
|
|
11196
11269
|
);
|
|
11197
11270
|
}
|
|
@@ -11207,7 +11280,7 @@ async function commitLogin(profileName2, snapshot, commit) {
|
|
|
11207
11280
|
|
|
11208
11281
|
// src/auth/owner-key.ts
|
|
11209
11282
|
init_theme();
|
|
11210
|
-
import { CLI_NAME as
|
|
11283
|
+
import { CLI_NAME as CLI_NAME12 } from "@tinycloud/operations/cli-name";
|
|
11211
11284
|
function openKeyPrimaryFlag(delegation) {
|
|
11212
11285
|
return typeof delegation.primary === "boolean" ? delegation.primary : void 0;
|
|
11213
11286
|
}
|
|
@@ -11218,7 +11291,7 @@ function withOwnerKeyPrimary(profile, primary) {
|
|
|
11218
11291
|
function nonPrimaryKeyWarning(ownerDid) {
|
|
11219
11292
|
const address = ownerDid.split(":")[4] ?? ownerDid;
|
|
11220
11293
|
return `${theme.warn("Warning:")} you signed in with OpenKey key ${address} (${ownerDid}), which is not your account's primary OpenKey key. This key is a separate owner with its own spaces and data, so this profile does not see the data stored under your primary key.
|
|
11221
|
-
To use your primary key, log in again and choose the primary key in OpenKey, for example into a new profile: ${
|
|
11294
|
+
To use your primary key, log in again and choose the primary key in OpenKey, for example into a new profile: ${CLI_NAME12} init --name <profile>
|
|
11222
11295
|
`;
|
|
11223
11296
|
}
|
|
11224
11297
|
function warnIfNotPrimaryKey(ownerDid, primary) {
|
|
@@ -11227,15 +11300,16 @@ function warnIfNotPrimaryKey(ownerDid, primary) {
|
|
|
11227
11300
|
}
|
|
11228
11301
|
function announceFirstOwner(profileName2, expectedOwner, ownerDid) {
|
|
11229
11302
|
if (expectedOwner !== void 0 || ownerDid === void 0) return;
|
|
11230
|
-
process.stderr.write(`Profile "${profileName2}" now belongs to ${ownerDid}. If that isn't your OpenKey account, stop using this profile and create a new one with ${
|
|
11303
|
+
process.stderr.write(`Profile "${profileName2}" now belongs to ${ownerDid}. If that isn't your OpenKey account, stop using this profile and create a new one with ${CLI_NAME12} init.
|
|
11231
11304
|
`);
|
|
11232
11305
|
}
|
|
11233
11306
|
function ownerLoginPermissions(owner) {
|
|
11234
|
-
const space = ownerSpaceId("default", owner);
|
|
11307
|
+
const space = owner === void 0 ? "default" : ownerSpaceId("default", owner);
|
|
11235
11308
|
return [
|
|
11236
11309
|
{ service: "tinycloud.kv", space, path: "", actions: ["tinycloud.kv/put", "tinycloud.kv/get", "tinycloud.kv/del", "tinycloud.kv/list", "tinycloud.kv/metadata"] },
|
|
11237
11310
|
{ service: "tinycloud.sql", space, path: "", actions: ["tinycloud.sql/read", "tinycloud.sql/write", "tinycloud.sql/admin"] },
|
|
11238
|
-
{ service: "tinycloud.capabilities", space, path: "", actions: ["tinycloud.capabilities/read"] }
|
|
11311
|
+
{ service: "tinycloud.capabilities", space, path: "", actions: ["tinycloud.capabilities/read"] },
|
|
11312
|
+
{ service: "tinycloud.delegation", space, path: "", actions: ["tinycloud.delegation/list"] }
|
|
11239
11313
|
];
|
|
11240
11314
|
}
|
|
11241
11315
|
|
|
@@ -11928,7 +12002,7 @@ init_types();
|
|
|
11928
12002
|
init_constants();
|
|
11929
12003
|
init_permissions();
|
|
11930
12004
|
init_sdk();
|
|
11931
|
-
import { CLI_NAME as
|
|
12005
|
+
import { CLI_NAME as CLI_NAME14 } from "@tinycloud/operations/cli-name";
|
|
11932
12006
|
import { TinyCloudNode as TinyCloudNode2 } from "@tinycloud/node-sdk";
|
|
11933
12007
|
import { DelegationRequestBindingSchema, PermissionEntrySchema } from "@tinycloud/operations/artifacts";
|
|
11934
12008
|
import {
|
|
@@ -11943,7 +12017,7 @@ import {
|
|
|
11943
12017
|
// src/lib/revoke-delegation.ts
|
|
11944
12018
|
init_constants();
|
|
11945
12019
|
init_errors();
|
|
11946
|
-
import { CLI_NAME as
|
|
12020
|
+
import { CLI_NAME as CLI_NAME13 } from "@tinycloud/operations/cli-name";
|
|
11947
12021
|
import { parseSignedCompactUcanAttenuation as parseSignedCompactUcanAttenuation2 } from "@tinycloud/sdk-core";
|
|
11948
12022
|
import { inspectStoredDelegation, withoutBearer as withoutBearer2 } from "@tinycloud/operations/delegation-binding";
|
|
11949
12023
|
init_permissions();
|
|
@@ -12129,7 +12203,7 @@ async function revokeDelegationOnNode(params) {
|
|
|
12129
12203
|
"REVOKE_UNAUTHORIZED",
|
|
12130
12204
|
"The node rejected this revocation: Unauthorized Revoker.",
|
|
12131
12205
|
ExitCode.PERMISSION_DENIED,
|
|
12132
|
-
{ status: 403, hint: `Use a different profile belonging to the delegation's grantor, its recipient, or the owner of a space it covers; the rejected profile was "${ctx.profile}" (DID ${profileDid}). Retry with \`${
|
|
12206
|
+
{ status: 403, hint: `Use a different profile belonging to the delegation's grantor, its recipient, or the owner of a space it covers; the rejected profile was "${ctx.profile}" (DID ${profileDid}). Retry with \`${CLI_NAME13} --profile <profile-name> delegation revoke ${cid}\`.` }
|
|
12133
12207
|
);
|
|
12134
12208
|
}
|
|
12135
12209
|
throw cliErrorFromService(result.error);
|
|
@@ -12212,7 +12286,7 @@ function renderList(result) {
|
|
|
12212
12286
|
}
|
|
12213
12287
|
process.stdout.write(`Profile: ${result.profile}
|
|
12214
12288
|
`);
|
|
12215
|
-
process.stdout.write(`Session CID: ${result.session?.cid ?? "unavailable"}` + (result.session?.cid ? ` (current session; revoke with ${
|
|
12289
|
+
process.stdout.write(`Session CID: ${result.session?.cid ?? "unavailable"}` + (result.session?.cid ? ` (current session; revoke with ${CLI_NAME14} delegation revoke ${result.session.cid})
|
|
12216
12290
|
` : "\n"));
|
|
12217
12291
|
process.stdout.write(`Local verification: ${result.localVerification}; node validation and revocation are not checked.
|
|
12218
12292
|
`);
|
|
@@ -12310,7 +12384,7 @@ function registerAuthGrantsCommand(auth, options = {}) {
|
|
|
12310
12384
|
ExitCode.ERROR,
|
|
12311
12385
|
{
|
|
12312
12386
|
causeCode: cause.code,
|
|
12313
|
-
hint: `Do not use --revoke again. After fixing local profile storage, run: ${
|
|
12387
|
+
hint: `Do not use --revoke again. After fixing local profile storage, run: ${CLI_NAME14} --profile ${ctx.profile} auth grants remove ${cid}`
|
|
12314
12388
|
}
|
|
12315
12389
|
);
|
|
12316
12390
|
}
|
|
@@ -12355,6 +12429,19 @@ init_delegation_expiry();
|
|
|
12355
12429
|
init_space();
|
|
12356
12430
|
init_permissions();
|
|
12357
12431
|
|
|
12432
|
+
// src/lib/approval-notices.ts
|
|
12433
|
+
var spaceLabel = (group) => {
|
|
12434
|
+
const space = group[0]?.space ?? "";
|
|
12435
|
+
return `${space.split(":").pop() || space} space`;
|
|
12436
|
+
};
|
|
12437
|
+
function approvalNotices(groups) {
|
|
12438
|
+
if (groups.length < 2) return void 0;
|
|
12439
|
+
return {
|
|
12440
|
+
intro: `This needs ${groups.length} approvals in OpenKey (${groups.map(spaceLabel).join(", then ")}).`,
|
|
12441
|
+
prompts: groups.map((group, index) => `Approval ${index + 1} of ${groups.length}: ${spaceLabel(group)}...`)
|
|
12442
|
+
};
|
|
12443
|
+
}
|
|
12444
|
+
|
|
12358
12445
|
// src/auth/replication-login.ts
|
|
12359
12446
|
init_dist();
|
|
12360
12447
|
init_errors();
|
|
@@ -12648,7 +12735,7 @@ async function revokeForLogout(ctx, discardLocalChanges) {
|
|
|
12648
12735
|
"REVOCATION_FAILED",
|
|
12649
12736
|
`Revocation failed for ${cid}: ${result.error.message}. Session, key, and replica files are retained for retry; local reads stay denied.`,
|
|
12650
12737
|
ExitCode.ERROR,
|
|
12651
|
-
{ cid, hint: `Retry \`${
|
|
12738
|
+
{ cid, hint: `Retry \`${CLI_NAME15} auth logout --revoke\` when the node is available.` }
|
|
12652
12739
|
);
|
|
12653
12740
|
}
|
|
12654
12741
|
if (result.data.cid !== cid || result.data.revoked !== true) {
|
|
@@ -12667,7 +12754,7 @@ async function revokeForLogout(ctx, discardLocalChanges) {
|
|
|
12667
12754
|
"REVOCATION_FAILED",
|
|
12668
12755
|
`Revocation failed before local logout: ${error instanceof Error ? error.message : String(error)}. Session, key, and replica files are retained for retry; local reads stay denied.`,
|
|
12669
12756
|
ExitCode.ERROR,
|
|
12670
|
-
{ hint: `Retry \`${
|
|
12757
|
+
{ hint: `Retry \`${CLI_NAME15} auth logout --revoke\` when online authority is available.` }
|
|
12671
12758
|
);
|
|
12672
12759
|
}
|
|
12673
12760
|
}
|
|
@@ -12866,7 +12953,7 @@ async function finishProfileLogout(ctx, options = {}) {
|
|
|
12866
12953
|
function registerAuthCommand(program, options) {
|
|
12867
12954
|
const auth = program.command("auth").description("Authentication management");
|
|
12868
12955
|
registerAuthGrantsCommand(auth, { openKeyAcquisition: options?.openKeyAcquisition });
|
|
12869
|
-
auth.command("login").description("Authenticate with TinyCloud").option("--device", "Approve on another device (e.g. a phone) through OpenKey device authorization; requires a KV-scoped --manifest (SQL and secret decrypt need browser or --paste login)").option("--paste", "Wait up to 5 minutes for a signed code from OpenKey on stdin instead of a browser callback (Safari may require paste)").option("--no-popup", "Print the OpenKey URL without opening a browser").option("--method <method>", "Authentication method: local or openkey").option("--manifest <fileOrBase64>", `Request only this manifest's permissions (one space, plus raw encryption network entries such as a secrets decrypt grant); ${SHARE_PUBLISHING_MANIFEST_REF} covers \`${
|
|
12956
|
+
auth.command("login").description("Authenticate with TinyCloud").option("--device", "Approve on another device (e.g. a phone) through OpenKey device authorization; requires a KV-scoped --manifest (SQL and secret decrypt need browser or --paste login)").option("--paste", "Wait up to 5 minutes for a signed code from OpenKey on stdin instead of a browser callback (Safari may require paste)").option("--no-popup", "Print the OpenKey URL without opening a browser").option("--method <method>", "Authentication method: local or openkey").option("--manifest <fileOrBase64>", `Request only this manifest's permissions (one space, plus raw encryption network entries such as a secrets decrypt grant); ${SHARE_PUBLISHING_MANIFEST_REF} covers \`${CLI_NAME15} share publish\``).option("--expiry <duration>", "OpenKey session lifetime, e.g. 1h or 7d (device login: at most 30d, default 30d)").option("--owner <did>", "Sign in as this owner (did:pkh:eip155:CHAIN:ADDRESS): OpenKey preselects that key, e.g. one that is not the account's primary key, and an approval by any other identity is refused. With --manifest, also names the secrets owner for a manifest's `secrets` on a profile with no recorded owner").option("--replace-session", "Scoped or device login: replace this profile's live session even though the new scope would narrow, change or shorten it (prefer a new profile)").option("--replication-prefix <prefix>", "Add replication get+sync authority for a folder prefix ending in /, or root (repeatable)", (prefix, values = []) => [...values, prefix]).option("--replication-allow-secrets", "Allow replication of secret material (network-wide decrypt warning)").action(async (options2, cmd) => {
|
|
12870
12957
|
try {
|
|
12871
12958
|
if (options2.device && options2.paste) {
|
|
12872
12959
|
throw new CLIError("INVALID_ARGUMENT", "--device and --paste are mutually exclusive.", ExitCode.USAGE_ERROR);
|
|
@@ -13015,7 +13102,7 @@ function registerAuthCommand(program, options) {
|
|
|
13015
13102
|
authenticated: false,
|
|
13016
13103
|
replicasRemoved,
|
|
13017
13104
|
replicasKept: options2.keepReplicas === true,
|
|
13018
|
-
warning: online ? "The node confirmed revocation of the listed CIDs before local logout." : options2.keepReplicas ? "Legacy replica files remain on disk; this local logout clears the session but does not revoke node grants. Purge the files before a future sign-in if they must not be reused." : `Logout is local; delegations and grants on the node stay valid until they expire. Use \`${
|
|
13105
|
+
warning: online ? "The node confirmed revocation of the listed CIDs before local logout." : options2.keepReplicas ? "Legacy replica files remain on disk; this local logout clears the session but does not revoke node grants. Purge the files before a future sign-in if they must not be reused." : `Logout is local; delegations and grants on the node stay valid until they expire. Use \`${CLI_NAME15} delegation revoke <cid>\` to end access now.`,
|
|
13019
13106
|
...online === void 0 ? {} : {
|
|
13020
13107
|
revokedCids: online.plan.cids,
|
|
13021
13108
|
revocationExcluded: online.plan.excluded
|
|
@@ -13105,11 +13192,14 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
|
|
|
13105
13192
|
).option("--permission <file>", 'JSON permission request: { "permissions": PermissionEntry[] }').option("--manifest <fileOrBase64>", "Manifest file, base64:<json>, or raw base64 JSON").option(
|
|
13106
13193
|
"--expiry <duration>",
|
|
13107
13194
|
`Lifetime of the granted delegation. ms-format string (e.g. "7d", "30m") or raw milliseconds. Defaults to 7d, capped by the active session's expiry.`
|
|
13108
|
-
).option("--emit [file]", "Emit the request artifact to stdout, or write it to file when provided").option("--grant", "Grant the requested permissions immediately with this owner profile").option("--yes", "Skip local-key TTY confirmation", false).option("--no-popup", "Print the OpenKey URL without opening a browser when granting with OpenKey").option("--device", "With --grant: approve on another device (e.g. a phone) through OpenKey device authorization (KV-scoped; SQL needs browser login)").action(async (options2, cmd) => {
|
|
13195
|
+
).option("--emit [file]", "Emit the request artifact to stdout, or write it to file when provided").option("--grant", "Grant the requested permissions immediately with this owner profile").option("--yes", "Skip local-key TTY confirmation", false).option("--no-popup", "Print the OpenKey URL without opening a browser when granting with OpenKey").option("--device", "With --grant: approve on another device (e.g. a phone) through OpenKey device authorization (KV-scoped; SQL needs browser login)").option("--paste", "With --grant on an OpenKey profile: wait up to 5 minutes for a signed code from OpenKey on stdin instead of a browser callback (use when the browser cannot reach this CLI)").action(async (options2, cmd) => {
|
|
13109
13196
|
try {
|
|
13110
13197
|
const globalOpts = cmd.optsWithGlobals();
|
|
13111
13198
|
const ctx = await ProfileManager.resolveContext(globalOpts, options2.grant ? {} : { storedSession: "unused" });
|
|
13112
13199
|
const profile = await ProfileManager.getProfile(ctx.profile);
|
|
13200
|
+
if (options2.paste && (!options2.grant || options2.device || resolveProfilePosture(profile) !== "owner-openkey")) {
|
|
13201
|
+
throw new CLIError("INVALID_ARGUMENT", "--paste requires --grant on an OpenKey owner profile and cannot be combined with --device.", ExitCode.USAGE_ERROR);
|
|
13202
|
+
}
|
|
13113
13203
|
if (options2.device && (!options2.grant || resolveProfilePosture(profile) !== "owner-openkey")) {
|
|
13114
13204
|
throw new CLIError("INVALID_ARGUMENT", "--device requires --grant on an OpenKey owner profile.", ExitCode.USAGE_ERROR);
|
|
13115
13205
|
}
|
|
@@ -13142,7 +13232,7 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
|
|
|
13142
13232
|
if (profile.authMethod === "openkey" || options2.device) {
|
|
13143
13233
|
const key = await ProfileManager.getKey(ctx.profile);
|
|
13144
13234
|
if (!key) {
|
|
13145
|
-
throw new CLIError("NO_KEY", `No key found for profile "${ctx.profile}". Run \`${
|
|
13235
|
+
throw new CLIError("NO_KEY", `No key found for profile "${ctx.profile}". Run \`${CLI_NAME15} --profile ${ctx.profile} auth rotate\` to create a new key and sign in.`, ExitCode.AUTH_REQUIRED);
|
|
13146
13236
|
}
|
|
13147
13237
|
const openkeyHost = resolveOpenKeyHost(profile);
|
|
13148
13238
|
const grants = [];
|
|
@@ -13154,9 +13244,15 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
|
|
|
13154
13244
|
expiry: expiryCap
|
|
13155
13245
|
};
|
|
13156
13246
|
const declined = [];
|
|
13157
|
-
|
|
13247
|
+
const groups = groupPermissionsBySpace(requested);
|
|
13248
|
+
const notices = approvalNotices(groups);
|
|
13249
|
+
if (notices) process.stderr.write(`${notices.intro}
|
|
13250
|
+
`);
|
|
13251
|
+
for (const [index, group] of groups.entries()) {
|
|
13252
|
+
if (notices) process.stderr.write(`${notices.prompts[index]}
|
|
13253
|
+
`);
|
|
13158
13254
|
const reason = permissionGrantReason(
|
|
13159
|
-
`Grant requested TinyCloud permissions from \`${
|
|
13255
|
+
`Grant requested TinyCloud permissions from \`${CLI_NAME15} auth request --grant\`.`,
|
|
13160
13256
|
group
|
|
13161
13257
|
);
|
|
13162
13258
|
const request = grantRequestPermissions(group, profile.spaceId ?? profile.spaceName);
|
|
@@ -13183,7 +13279,8 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
|
|
|
13183
13279
|
reason,
|
|
13184
13280
|
openkeyHost,
|
|
13185
13281
|
expiry: expiryCap === void 0 ? void 0 : openKeyExpiryParam(expiryCap),
|
|
13186
|
-
noPopup: options2.popup === false
|
|
13282
|
+
noPopup: options2.popup === false,
|
|
13283
|
+
paste: options2.paste
|
|
13187
13284
|
});
|
|
13188
13285
|
}
|
|
13189
13286
|
const delegation = portableFromOpenKeyDelegation(delegationData, request, ctx.host, proof);
|
|
@@ -13265,7 +13362,7 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
|
|
|
13265
13362
|
kind: parsed.kind,
|
|
13266
13363
|
requestId: parsed.requestId,
|
|
13267
13364
|
requested: parsed.requested,
|
|
13268
|
-
next: `${
|
|
13365
|
+
next: `${CLI_NAME15} auth retry ${parsed.requestId}`
|
|
13269
13366
|
});
|
|
13270
13367
|
return;
|
|
13271
13368
|
}
|
|
@@ -13816,7 +13913,7 @@ async function ensureDelegationAuthority(params) {
|
|
|
13816
13913
|
if (!key) {
|
|
13817
13914
|
throw new CLIError(
|
|
13818
13915
|
"NO_KEY",
|
|
13819
|
-
`No key found for profile "${params.ctx.profile}". Run \`${
|
|
13916
|
+
`No key found for profile "${params.ctx.profile}". Run \`${CLI_NAME15} --profile ${params.ctx.profile} auth rotate\` to create a new key and sign in.`,
|
|
13820
13917
|
ExitCode.AUTH_REQUIRED
|
|
13821
13918
|
);
|
|
13822
13919
|
}
|
|
@@ -14062,7 +14159,7 @@ async function rotateAuthKey(profileName2, host, options = {}) {
|
|
|
14062
14159
|
if (!profile.privateKey) {
|
|
14063
14160
|
throw new CLIError(
|
|
14064
14161
|
"LOCAL_OWNER_KEY_REQUIRED",
|
|
14065
|
-
`Profile "${profileName2}" does not have a local owner private key. Run \`${
|
|
14162
|
+
`Profile "${profileName2}" does not have a local owner private key. Run \`${CLI_NAME15} auth login --method local\` first.`,
|
|
14066
14163
|
ExitCode.AUTH_REQUIRED
|
|
14067
14164
|
);
|
|
14068
14165
|
}
|
|
@@ -14452,9 +14549,10 @@ async function refreshOpenKeySession(profileName2, host, options = {}) {
|
|
|
14452
14549
|
jwk: key,
|
|
14453
14550
|
host,
|
|
14454
14551
|
openkeyHost: resolveOpenKeyHost(profile),
|
|
14455
|
-
// A plain login
|
|
14456
|
-
// so OpenKey preselects its key; the signed
|
|
14457
|
-
|
|
14552
|
+
// A plain login sends the default abilities plus delegation/list explicitly. With a pinned
|
|
14553
|
+
// owner (or --owner) the space is that owner's, so OpenKey preselects its key; the signed
|
|
14554
|
+
// owner is verified below.
|
|
14555
|
+
permissions: permissions ?? ownerLoginPermissions(expectedOwner),
|
|
14458
14556
|
expiry: openKeyExpiry,
|
|
14459
14557
|
...permissions ? { reason: "Allow this TinyCloud CLI profile to use the permissions in the requested manifest." } : {}
|
|
14460
14558
|
});
|
|
@@ -14501,7 +14599,7 @@ async function refreshOpenKeySession(profileName2, host, options = {}) {
|
|
|
14501
14599
|
}
|
|
14502
14600
|
|
|
14503
14601
|
// src/commands/completion.ts
|
|
14504
|
-
import { CLI_NAME as
|
|
14602
|
+
import { CLI_NAME as CLI_NAME16 } from "@tinycloud/operations/cli-name";
|
|
14505
14603
|
function registerCompletionCommand(program) {
|
|
14506
14604
|
const completion = program.command("completion").description("Generate shell completions");
|
|
14507
14605
|
completion.command("bash").description("Output bash completions").action(() => {
|
|
@@ -14518,7 +14616,7 @@ function registerCompletionCommand(program) {
|
|
|
14518
14616
|
});
|
|
14519
14617
|
}
|
|
14520
14618
|
function generateBashCompletion() {
|
|
14521
|
-
return `# ${
|
|
14619
|
+
return `# ${CLI_NAME16} bash completion
|
|
14522
14620
|
_tc_completions() {
|
|
14523
14621
|
local cur prev commands subcommands
|
|
14524
14622
|
COMPREPLY=()
|
|
@@ -14543,11 +14641,11 @@ _tc_completions() {
|
|
|
14543
14641
|
COMPREPLY=( $(compgen -W "\${subcommands}" -- "\${cur}") )
|
|
14544
14642
|
fi
|
|
14545
14643
|
}
|
|
14546
|
-
complete -F _tc_completions ${
|
|
14644
|
+
complete -F _tc_completions ${CLI_NAME16}
|
|
14547
14645
|
`;
|
|
14548
14646
|
}
|
|
14549
14647
|
function generateZshCompletion() {
|
|
14550
|
-
return `#compdef ${
|
|
14648
|
+
return `#compdef ${CLI_NAME16}
|
|
14551
14649
|
|
|
14552
14650
|
_tc() {
|
|
14553
14651
|
local -a commands
|
|
@@ -14598,50 +14696,50 @@ _tc
|
|
|
14598
14696
|
`;
|
|
14599
14697
|
}
|
|
14600
14698
|
function generateFishCompletion() {
|
|
14601
|
-
return `# ${
|
|
14699
|
+
return `# ${CLI_NAME16} fish completion
|
|
14602
14700
|
set -l commands init auth kv space delegation share node profile completion
|
|
14603
14701
|
|
|
14604
14702
|
# Disable file completion by default
|
|
14605
|
-
complete -c ${
|
|
14703
|
+
complete -c ${CLI_NAME16} -f
|
|
14606
14704
|
|
|
14607
14705
|
# Top-level commands
|
|
14608
|
-
complete -c ${
|
|
14609
|
-
complete -c ${
|
|
14610
|
-
complete -c ${
|
|
14611
|
-
complete -c ${
|
|
14612
|
-
complete -c ${
|
|
14613
|
-
complete -c ${
|
|
14614
|
-
complete -c ${
|
|
14615
|
-
complete -c ${
|
|
14616
|
-
complete -c ${
|
|
14706
|
+
complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a init -d "Initialize a new TinyCloud profile"
|
|
14707
|
+
complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a auth -d "Authentication management"
|
|
14708
|
+
complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a kv -d "Key-value store operations"
|
|
14709
|
+
complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a space -d "Space management"
|
|
14710
|
+
complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a delegation -d "Manage delegations"
|
|
14711
|
+
complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a share -d "Share data with others"
|
|
14712
|
+
complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a node -d "Node health and info"
|
|
14713
|
+
complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a profile -d "Profile management"
|
|
14714
|
+
complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a completion -d "Generate shell completions"
|
|
14617
14715
|
|
|
14618
14716
|
# Subcommands
|
|
14619
|
-
complete -c ${
|
|
14620
|
-
complete -c ${
|
|
14621
|
-
complete -c ${
|
|
14622
|
-
complete -c ${
|
|
14623
|
-
complete -c ${
|
|
14624
|
-
complete -c ${
|
|
14625
|
-
complete -c ${
|
|
14626
|
-
complete -c ${
|
|
14717
|
+
complete -c ${CLI_NAME16} -n "__fish_seen_subcommand_from auth" -a "login logout rotate status whoami"
|
|
14718
|
+
complete -c ${CLI_NAME16} -n "__fish_seen_subcommand_from kv" -a "get put delete list head"
|
|
14719
|
+
complete -c ${CLI_NAME16} -n "__fish_seen_subcommand_from space" -a "list create info switch"
|
|
14720
|
+
complete -c ${CLI_NAME16} -n "__fish_seen_subcommand_from delegation" -a "create list info revoke"
|
|
14721
|
+
complete -c ${CLI_NAME16} -n "__fish_seen_subcommand_from share" -a "create receive list revoke"
|
|
14722
|
+
complete -c ${CLI_NAME16} -n "__fish_seen_subcommand_from node" -a "health version status"
|
|
14723
|
+
complete -c ${CLI_NAME16} -n "__fish_seen_subcommand_from profile" -a "list create show switch delete"
|
|
14724
|
+
complete -c ${CLI_NAME16} -n "__fish_seen_subcommand_from completion" -a "bash zsh fish"
|
|
14627
14725
|
|
|
14628
14726
|
# Global options
|
|
14629
|
-
complete -c ${
|
|
14630
|
-
complete -c ${
|
|
14631
|
-
complete -c ${
|
|
14632
|
-
complete -c ${
|
|
14633
|
-
complete -c ${
|
|
14634
|
-
complete -c ${
|
|
14635
|
-
complete -c ${
|
|
14636
|
-
complete -c ${
|
|
14727
|
+
complete -c ${CLI_NAME16} -l profile -s p -d "Profile to use"
|
|
14728
|
+
complete -c ${CLI_NAME16} -l host -s H -d "TinyCloud node URL"
|
|
14729
|
+
complete -c ${CLI_NAME16} -l verbose -s v -d "Enable verbose output"
|
|
14730
|
+
complete -c ${CLI_NAME16} -l no-cache -d "Disable caching"
|
|
14731
|
+
complete -c ${CLI_NAME16} -l quiet -s q -d "Suppress non-essential output"
|
|
14732
|
+
complete -c ${CLI_NAME16} -l replication -d "Enable read-through replication"
|
|
14733
|
+
complete -c ${CLI_NAME16} -l no-replication -d "Disable replication"
|
|
14734
|
+
complete -c ${CLI_NAME16} -l replication-debug -d "Write replication diagnostics to stderr"
|
|
14637
14735
|
`;
|
|
14638
14736
|
}
|
|
14639
14737
|
|
|
14640
14738
|
// src/commands/courier.ts
|
|
14641
|
-
import { CLI_NAME as
|
|
14642
|
-
var TC_OPTIONS_BEFORE_COURIER = `${
|
|
14739
|
+
import { CLI_NAME as CLI_NAME17 } from "@tinycloud/operations/cli-name";
|
|
14740
|
+
var TC_OPTIONS_BEFORE_COURIER = `${CLI_NAME17} courier takes no ${CLI_NAME17} options and does not use ${CLI_NAME17} profiles; Courier keeps its own profile in $COURIER_HOME (default ~/.courier). Put flags after "courier", e.g. ${CLI_NAME17} courier init --host <url>.`;
|
|
14643
14741
|
function registerCourierCommand(program) {
|
|
14644
|
-
const courier = program.command("courier").description(`Agent-to-agent threads over TinyCloud; run \`${
|
|
14742
|
+
const courier = program.command("courier").description(`Agent-to-agent threads over TinyCloud; run \`${CLI_NAME17} courier --help\` (beta)`).argument("[args...]").allowUnknownOption().helpOption(false);
|
|
14645
14743
|
courier.action(() => courier.error(TC_OPTIONS_BEFORE_COURIER, { exitCode: 2, code: "tc.courier.options" }));
|
|
14646
14744
|
}
|
|
14647
14745
|
|
|
@@ -14686,14 +14784,42 @@ init_formatter();
|
|
|
14686
14784
|
init_errors();
|
|
14687
14785
|
init_constants();
|
|
14688
14786
|
init_sdk();
|
|
14689
|
-
|
|
14690
|
-
|
|
14691
|
-
|
|
14692
|
-
|
|
14787
|
+
import { CLI_NAME as CLI_NAME18 } from "@tinycloud/operations/cli-name";
|
|
14788
|
+
import { PermissionNotInManifestError } from "@tinycloud/node-sdk";
|
|
14789
|
+
async function queryDelegations(node, options, until) {
|
|
14790
|
+
const found = [];
|
|
14791
|
+
let cursor;
|
|
14792
|
+
do {
|
|
14793
|
+
const page = await node.account.delegations.query({ ...options, limit: 100, cursor });
|
|
14794
|
+
if (!page.ok) {
|
|
14795
|
+
if (page.error.code === "PERMISSION_DENIED") {
|
|
14796
|
+
throw new CLIError(
|
|
14797
|
+
"PERMISSION_DENIED",
|
|
14798
|
+
`This session cannot read delegations: it lacks tinycloud.delegation/list. Run \`${CLI_NAME18} auth login\` again to grant it.`,
|
|
14799
|
+
ExitCode.PERMISSION_DENIED
|
|
14800
|
+
);
|
|
14801
|
+
}
|
|
14802
|
+
throw cliErrorFromService(page.error);
|
|
14803
|
+
}
|
|
14804
|
+
found.push(...page.data.items);
|
|
14805
|
+
cursor = page.data.nextCursor;
|
|
14806
|
+
} while (cursor && !(until && found.some((d) => d.cid === until)));
|
|
14807
|
+
return found;
|
|
14693
14808
|
}
|
|
14694
|
-
function
|
|
14695
|
-
|
|
14696
|
-
|
|
14809
|
+
function listedDelegation(d) {
|
|
14810
|
+
const resources = d.resources.map((r) => {
|
|
14811
|
+
const [space, service, ...path] = r.resource.split("/");
|
|
14812
|
+
return { space, service, path: path.join("/"), actions: r.actions };
|
|
14813
|
+
});
|
|
14814
|
+
return {
|
|
14815
|
+
cid: d.cid,
|
|
14816
|
+
delegatee: d.delegateDid,
|
|
14817
|
+
delegator: d.delegatorDid,
|
|
14818
|
+
direction: d.direction,
|
|
14819
|
+
status: d.status,
|
|
14820
|
+
expiry: d.expiresAt?.toISOString() ?? null,
|
|
14821
|
+
resources
|
|
14822
|
+
};
|
|
14697
14823
|
}
|
|
14698
14824
|
async function confirmCurrentSessionRevoke(yes) {
|
|
14699
14825
|
if (yes) return;
|
|
@@ -14724,69 +14850,87 @@ function registerDelegationCommand(program, configuration = {}) {
|
|
|
14724
14850
|
const trimmed = a.trim();
|
|
14725
14851
|
return trimmed.startsWith("tinycloud.") ? trimmed : `tinycloud.${trimmed}`;
|
|
14726
14852
|
});
|
|
14853
|
+
const byService = {};
|
|
14854
|
+
for (const action of actions) {
|
|
14855
|
+
const slash = action.indexOf("/");
|
|
14856
|
+
if (slash <= "tinycloud.".length || slash === action.length - 1) {
|
|
14857
|
+
throw new CLIError("USAGE_ERROR", `Invalid action "${action}"; use service/action, e.g. kv/get`, ExitCode.USAGE_ERROR);
|
|
14858
|
+
}
|
|
14859
|
+
(byService[action.slice(0, slash)] ??= []).push(action);
|
|
14860
|
+
}
|
|
14727
14861
|
const expiry = parseExpiry2(options.expiry);
|
|
14728
|
-
const
|
|
14729
|
-
|
|
14730
|
-
|
|
14731
|
-
|
|
14732
|
-
|
|
14862
|
+
const recipient = options.to.split("#")[0];
|
|
14863
|
+
const { delegation: created } = await node.delegateTo(
|
|
14864
|
+
recipient,
|
|
14865
|
+
Object.entries(byService).map(([service, serviceActions]) => ({
|
|
14866
|
+
service,
|
|
14867
|
+
space: "default",
|
|
14868
|
+
path: options.path,
|
|
14869
|
+
actions: serviceActions
|
|
14870
|
+
})),
|
|
14871
|
+
{ expiry: expiry.getTime() - Date.now(), requireActivation: true }
|
|
14872
|
+
).catch((error) => {
|
|
14873
|
+
if (error instanceof PermissionNotInManifestError) {
|
|
14874
|
+
const needed = error.missing.map((m) => `${m.actions.join(", ")} on space "${m.space ?? "default"}" path "${m.path}"`).join("; ");
|
|
14875
|
+
throw new CLIError(
|
|
14876
|
+
"PERMISSION_DENIED",
|
|
14877
|
+
`This session cannot delegate what was requested. Missing: ${needed}. Sign in again with these permissions.`,
|
|
14878
|
+
ExitCode.PERMISSION_DENIED
|
|
14879
|
+
);
|
|
14880
|
+
}
|
|
14881
|
+
throw error;
|
|
14733
14882
|
});
|
|
14734
|
-
|
|
14735
|
-
|
|
14883
|
+
const confirmed = (await queryDelegations(node, { direction: "granted" }, created.cid)).find((d) => d.cid === created.cid);
|
|
14884
|
+
if (confirmed?.status !== "active") {
|
|
14885
|
+
throw new CLIError(
|
|
14886
|
+
"DELEGATION_NOT_CONFIRMED",
|
|
14887
|
+
confirmed ? `The node lists delegation ${created.cid} as ${confirmed.status}, not active; treat it as not created.` : `The node accepted delegation ${created.cid} but does not list it; treat it as not created.`,
|
|
14888
|
+
ExitCode.ERROR
|
|
14889
|
+
);
|
|
14736
14890
|
}
|
|
14737
14891
|
outputJson({
|
|
14738
|
-
cid:
|
|
14739
|
-
delegateDid:
|
|
14892
|
+
cid: created.cid,
|
|
14893
|
+
delegateDid: recipient,
|
|
14894
|
+
delegatorDid: confirmed.delegatorDid,
|
|
14740
14895
|
path: options.path,
|
|
14741
14896
|
actions,
|
|
14742
|
-
expiry: expiry.toISOString()
|
|
14897
|
+
expiry: created.expiry.toISOString(),
|
|
14898
|
+
status: confirmed.status
|
|
14743
14899
|
});
|
|
14744
14900
|
} catch (error) {
|
|
14745
14901
|
return handleError(error);
|
|
14746
14902
|
}
|
|
14747
14903
|
});
|
|
14748
|
-
delegation.command("list").description("List delegations").option("--granted", "Show only delegations I've granted").option("--received", "Show only delegations I've received").action(async (options, cmd) => {
|
|
14904
|
+
delegation.command("list").description("List delegations").option("--granted", "Show only delegations I've granted").option("--received", "Show only delegations I've received").option("--space <space>", "Filter by space name or ID").action(async (options, cmd) => {
|
|
14749
14905
|
try {
|
|
14750
14906
|
const globalOpts = cmd.optsWithGlobals();
|
|
14751
14907
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
14752
14908
|
const node = await ensureAuthenticated(ctx);
|
|
14753
|
-
|
|
14754
|
-
|
|
14755
|
-
throw cliErrorFromService(result.error);
|
|
14756
|
-
}
|
|
14757
|
-
let delegations = result.data;
|
|
14758
|
-
if (options.granted) {
|
|
14759
|
-
const myDid = node.did;
|
|
14760
|
-
delegations = delegations.filter((d) => didMatches(d.delegatorDID, myDid));
|
|
14761
|
-
} else if (options.received) {
|
|
14762
|
-
const myDid = node.did;
|
|
14763
|
-
delegations = delegations.filter((d) => didMatches(d.delegateDID, myDid));
|
|
14909
|
+
if (options.granted && options.received) {
|
|
14910
|
+
throw new CLIError("USAGE_ERROR", "Use only one of --granted or --received.", ExitCode.USAGE_ERROR);
|
|
14764
14911
|
}
|
|
14912
|
+
const delegations = await queryDelegations(node, {
|
|
14913
|
+
direction: options.granted ? "granted" : options.received ? "received" : "all",
|
|
14914
|
+
space: options.space
|
|
14915
|
+
});
|
|
14765
14916
|
outputJson({
|
|
14766
|
-
delegations: delegations.map(
|
|
14767
|
-
cid: d.cid,
|
|
14768
|
-
delegatee: d.delegateDID,
|
|
14769
|
-
delegator: d.delegatorDID,
|
|
14770
|
-
path: d.path,
|
|
14771
|
-
actions: d.actions,
|
|
14772
|
-
expiry: d.expiry instanceof Date ? d.expiry.toISOString() : d.expiry
|
|
14773
|
-
})),
|
|
14917
|
+
delegations: delegations.map(listedDelegation),
|
|
14774
14918
|
count: delegations.length
|
|
14775
14919
|
});
|
|
14776
14920
|
} catch (error) {
|
|
14777
14921
|
return handleError(error);
|
|
14778
14922
|
}
|
|
14779
14923
|
});
|
|
14780
|
-
delegation.command("info <cid>").description("Get delegation details").action(async (cid,
|
|
14924
|
+
delegation.command("info <cid>").description("Get delegation details").option("--space <space>", "Filter by space name or ID").action(async (cid, options, cmd) => {
|
|
14781
14925
|
try {
|
|
14782
14926
|
const globalOpts = cmd.optsWithGlobals();
|
|
14783
14927
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
14784
14928
|
const node = await ensureAuthenticated(ctx);
|
|
14785
|
-
const
|
|
14786
|
-
if (!
|
|
14787
|
-
throw new CLIError("NOT_FOUND", `Delegation "${cid}" not found`, ExitCode.NOT_FOUND);
|
|
14929
|
+
const found = (await queryDelegations(node, { direction: "all", space: options.space })).find((d) => d.cid === cid);
|
|
14930
|
+
if (!found) {
|
|
14931
|
+
throw new CLIError("NOT_FOUND", `Delegation "${cid}" not found in this account's delegations`, ExitCode.NOT_FOUND);
|
|
14788
14932
|
}
|
|
14789
|
-
outputJson(
|
|
14933
|
+
outputJson(listedDelegation(found));
|
|
14790
14934
|
} catch (error) {
|
|
14791
14935
|
return handleError(error);
|
|
14792
14936
|
}
|
|
@@ -14826,7 +14970,7 @@ init_constants();
|
|
|
14826
14970
|
init_formatter();
|
|
14827
14971
|
init_theme();
|
|
14828
14972
|
init_errors();
|
|
14829
|
-
import { CLI_NAME as
|
|
14973
|
+
import { CLI_NAME as CLI_NAME19 } from "@tinycloud/operations/cli-name";
|
|
14830
14974
|
init_session_expired();
|
|
14831
14975
|
init_types();
|
|
14832
14976
|
function registerDoctorCommand(program) {
|
|
@@ -14860,7 +15004,7 @@ function registerDoctorCommand(program) {
|
|
|
14860
15004
|
const profile = await ProfileManager.getProfile(profileName2);
|
|
14861
15005
|
keyDetail = profile.did ? `${profile.did.slice(0, 20)}...` : "key found";
|
|
14862
15006
|
} else {
|
|
14863
|
-
keyDetail = `no key \u2014 run ${
|
|
15007
|
+
keyDetail = `no key \u2014 run ${CLI_NAME19} init`;
|
|
14864
15008
|
}
|
|
14865
15009
|
} catch {
|
|
14866
15010
|
keyDetail = "error reading key";
|
|
@@ -14882,7 +15026,7 @@ function registerDoctorCommand(program) {
|
|
|
14882
15026
|
} else if (state2 === "unverified") {
|
|
14883
15027
|
sessionDetail = `not usable (no signed OpenKey proof) \u2014 ${signInAgainHint(profileName2, resolveProfilePosture(profile))}`;
|
|
14884
15028
|
} else {
|
|
14885
|
-
sessionDetail = sessionOk ? "active" : `no session \u2014 run ${
|
|
15029
|
+
sessionDetail = sessionOk ? "active" : `no session \u2014 run ${CLI_NAME19} auth login`;
|
|
14886
15030
|
}
|
|
14887
15031
|
} catch {
|
|
14888
15032
|
sessionDetail = "error reading session";
|
|
@@ -14910,7 +15054,7 @@ function registerDoctorCommand(program) {
|
|
|
14910
15054
|
try {
|
|
14911
15055
|
const profile = await ProfileManager.getProfile(profileName2);
|
|
14912
15056
|
spaceOk = Boolean(profile.spaceId);
|
|
14913
|
-
spaceDetail = spaceOk ? `${profile.spaceId.slice(0, 16)}...` : `no space \u2014 run ${
|
|
15057
|
+
spaceDetail = spaceOk ? `${profile.spaceId.slice(0, 16)}...` : `no space \u2014 run ${CLI_NAME19} space create`;
|
|
14914
15058
|
} catch {
|
|
14915
15059
|
spaceDetail = "error checking space";
|
|
14916
15060
|
}
|
|
@@ -15100,7 +15244,7 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
|
|
|
15100
15244
|
}
|
|
15101
15245
|
|
|
15102
15246
|
// src/commands/enable.ts
|
|
15103
|
-
import { CLI_NAME as
|
|
15247
|
+
import { CLI_NAME as CLI_NAME20 } from "@tinycloud/operations/cli-name";
|
|
15104
15248
|
init_constants();
|
|
15105
15249
|
init_profiles();
|
|
15106
15250
|
init_errors();
|
|
@@ -15108,7 +15252,7 @@ init_formatter();
|
|
|
15108
15252
|
init_publishing_manifest();
|
|
15109
15253
|
function registerEnableCommand(program) {
|
|
15110
15254
|
const enable = program.command("enable").description("Enable a narrowly scoped TinyCloud service");
|
|
15111
|
-
enable.command("share").description(`Approve Share publishing scope through OpenKey device authorization (same as \`${
|
|
15255
|
+
enable.command("share").description(`Approve Share publishing scope through OpenKey device authorization (same as \`${CLI_NAME20} auth login --device --manifest builtin:share-publishing\`). Use a dedicated profile, e.g. \`${CLI_NAME20} init --name publisher --key-only && ${CLI_NAME20} --profile publisher enable share\`; keep existing profiles for their apps.`).option("--replace-session", "Replace this profile's live session even though the new scope would narrow, change or shorten it (prefer a new profile)").action(async (options, command) => {
|
|
15112
15256
|
try {
|
|
15113
15257
|
const globalOptions = command.optsWithGlobals();
|
|
15114
15258
|
const context = await ProfileManager.resolveContext(globalOptions, { storedSession: "unused" });
|
|
@@ -15144,7 +15288,7 @@ init_profiles();
|
|
|
15144
15288
|
init_formatter();
|
|
15145
15289
|
init_errors();
|
|
15146
15290
|
init_constants();
|
|
15147
|
-
import { CLI_NAME as
|
|
15291
|
+
import { CLI_NAME as CLI_NAME21 } from "@tinycloud/operations/cli-name";
|
|
15148
15292
|
function registerInitCommand(program) {
|
|
15149
15293
|
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) => {
|
|
15150
15294
|
try {
|
|
@@ -15162,7 +15306,7 @@ function registerInitCommand(program) {
|
|
|
15162
15306
|
if (await ProfileManager.profileExists(profileName2)) {
|
|
15163
15307
|
throw new CLIError(
|
|
15164
15308
|
"PROFILE_EXISTS",
|
|
15165
|
-
`Profile "${profileName2}" already exists. Use \`${
|
|
15309
|
+
`Profile "${profileName2}" already exists. Use \`${CLI_NAME21} profile delete ${profileName2}\` first or choose a different name.`,
|
|
15166
15310
|
ExitCode.ERROR
|
|
15167
15311
|
);
|
|
15168
15312
|
}
|
|
@@ -15512,7 +15656,7 @@ init_errors();
|
|
|
15512
15656
|
init_constants();
|
|
15513
15657
|
init_theme();
|
|
15514
15658
|
import { resolveManifestKnowledgeRoot as resolveManifestKnowledgeRoot2 } from "@tinycloud/sdk-core";
|
|
15515
|
-
import { CLI_NAME as
|
|
15659
|
+
import { CLI_NAME as CLI_NAME22 } from "@tinycloud/operations/cli-name";
|
|
15516
15660
|
import { readFile as readFile11 } from "fs/promises";
|
|
15517
15661
|
var DEFAULT_APP_SPACE = "applications";
|
|
15518
15662
|
function registerManifestCommand(program) {
|
|
@@ -15520,8 +15664,8 @@ function registerManifestCommand(program) {
|
|
|
15520
15664
|
manifest.command("resolve <source>").description("Resolve a manifest file or URL to its effective space, paths, and DB basenames").addHelpText("after", `
|
|
15521
15665
|
|
|
15522
15666
|
Examples:
|
|
15523
|
-
$ ${
|
|
15524
|
-
$ ${
|
|
15667
|
+
$ ${CLI_NAME22} manifest resolve ./manifest.json
|
|
15668
|
+
$ ${CLI_NAME22} manifest resolve https://app.example.com/manifest.json --json
|
|
15525
15669
|
|
|
15526
15670
|
What it shows:
|
|
15527
15671
|
- app_id, name, manifest_version
|
|
@@ -15531,7 +15675,7 @@ What it shows:
|
|
|
15531
15675
|
|
|
15532
15676
|
This command is read-only and does NOT contact the node \u2014 it just resolves
|
|
15533
15677
|
the manifest against the active profile's address/chain so you know which
|
|
15534
|
-
\`--space\` and \`--db\` values to pass to other ${
|
|
15678
|
+
\`--space\` and \`--db\` values to pass to other ${CLI_NAME22} commands.
|
|
15535
15679
|
`).action(async (source, _options, cmd) => {
|
|
15536
15680
|
try {
|
|
15537
15681
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -15600,7 +15744,7 @@ ${theme.heading("SQL databases")}
|
|
|
15600
15744
|
`);
|
|
15601
15745
|
}
|
|
15602
15746
|
process.stdout.write(theme.muted(`
|
|
15603
|
-
Use with: ${
|
|
15747
|
+
Use with: ${CLI_NAME22} sql query --space ${spaceName} --db <db> "..."
|
|
15604
15748
|
`));
|
|
15605
15749
|
}
|
|
15606
15750
|
if (permissions.length > 0) {
|
|
@@ -15720,7 +15864,7 @@ init_profiles();
|
|
|
15720
15864
|
init_formatter();
|
|
15721
15865
|
init_errors();
|
|
15722
15866
|
init_constants();
|
|
15723
|
-
import { CLI_NAME as
|
|
15867
|
+
import { CLI_NAME as CLI_NAME23 } from "@tinycloud/operations/cli-name";
|
|
15724
15868
|
import { createInterface as createInterface5 } from "readline";
|
|
15725
15869
|
init_theme();
|
|
15726
15870
|
init_types();
|
|
@@ -15866,9 +16010,9 @@ profile at command time. Precedence for every kv/sql command:
|
|
|
15866
16010
|
explicit --space flag > profile defaultSpace > primary space.
|
|
15867
16011
|
|
|
15868
16012
|
Examples:
|
|
15869
|
-
$ ${
|
|
15870
|
-
$ ${
|
|
15871
|
-
$ ${
|
|
16013
|
+
$ ${CLI_NAME23} profile set-default-space applications
|
|
16014
|
+
$ ${CLI_NAME23} profile set-default-space applications --profile cli-test
|
|
16015
|
+
$ ${CLI_NAME23} profile set-default-space --unset
|
|
15872
16016
|
`).action(async (name, options, cmd) => {
|
|
15873
16017
|
try {
|
|
15874
16018
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -15880,7 +16024,7 @@ Examples:
|
|
|
15880
16024
|
if (!options.unset && (name === void 0 || name === "")) {
|
|
15881
16025
|
throw new CLIError(
|
|
15882
16026
|
"USAGE_ERROR",
|
|
15883
|
-
`Provide a space name (e.g. \`${
|
|
16027
|
+
`Provide a space name (e.g. \`${CLI_NAME23} profile set-default-space applications\`) or pass --unset.`,
|
|
15884
16028
|
ExitCode.USAGE_ERROR
|
|
15885
16029
|
);
|
|
15886
16030
|
}
|
|
@@ -15939,7 +16083,7 @@ function parseOperatorType(raw) {
|
|
|
15939
16083
|
|
|
15940
16084
|
// src/commands/replica.ts
|
|
15941
16085
|
init_profile_replicas();
|
|
15942
|
-
import { CLI_NAME as
|
|
16086
|
+
import { CLI_NAME as CLI_NAME24 } from "@tinycloud/operations/cli-name";
|
|
15943
16087
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
15944
16088
|
import { constants as fsConstants5 } from "fs";
|
|
15945
16089
|
import { mkdir as mkdir5, open as open9, readdir as readdir6, rename as rename4, unlink as unlink4, lstat as lstat9 } from "fs/promises";
|
|
@@ -16080,11 +16224,11 @@ var EXIT_BY_CODE = {
|
|
|
16080
16224
|
var READ_FAILURE = {
|
|
16081
16225
|
absent: { code: "KEY_ABSENT", exit: ExitCode.NOT_FOUND, what: "is not in the replica (the replica covers its whole prefix)" },
|
|
16082
16226
|
deleted: { code: "KEY_DELETED", exit: ExitCode.NOT_FOUND, what: "was deleted at the source" },
|
|
16083
|
-
content_missing: { code: "CONTENT_MISSING", exit: ExitCode.NOT_FOUND, what: `is known but its content has not been fetched yet; run ${
|
|
16227
|
+
content_missing: { code: "CONTENT_MISSING", exit: ExitCode.NOT_FOUND, what: `is known but its content has not been fetched yet; run ${CLI_NAME24} replica sync` },
|
|
16084
16228
|
coverage_incomplete: {
|
|
16085
16229
|
code: "COVERAGE_INCOMPLETE",
|
|
16086
16230
|
exit: ExitCode.NOT_FOUND,
|
|
16087
|
-
what: `is not in the replica, which has not finished its first sync; run ${
|
|
16231
|
+
what: `is not in the replica, which has not finished its first sync; run ${CLI_NAME24} replica sync`
|
|
16088
16232
|
},
|
|
16089
16233
|
not_covered: { code: "NOT_COVERED", exit: ExitCode.USAGE_ERROR, what: "is outside the replica's prefix" }
|
|
16090
16234
|
};
|
|
@@ -16202,7 +16346,7 @@ async function existingReplicaName(profile, option) {
|
|
|
16202
16346
|
const names = await replicaNames(profile);
|
|
16203
16347
|
if (names.length === 1) return names[0];
|
|
16204
16348
|
if (names.length === 0) {
|
|
16205
|
-
throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, `Profile "${profile}" has no replicas. Create one with: ${
|
|
16349
|
+
throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, `Profile "${profile}" has no replicas. Create one with: ${CLI_NAME24} replica sync --prefix <prefix>`);
|
|
16206
16350
|
}
|
|
16207
16351
|
throw new CLIError("USAGE_ERROR", `Profile "${profile}" has several replicas (${names.join(", ")}); pass --replica <name>.`, ExitCode.USAGE_ERROR);
|
|
16208
16352
|
}
|
|
@@ -16310,9 +16454,9 @@ async function storedGrants(profile) {
|
|
|
16310
16454
|
function grantHint(space, prefix) {
|
|
16311
16455
|
return [
|
|
16312
16456
|
"A replica needs a device grant with get and sync on its prefix:",
|
|
16313
|
-
` device: ${
|
|
16314
|
-
` owner: ${
|
|
16315
|
-
` device: ${
|
|
16457
|
+
` device: ${CLI_NAME24} auth request --cap tinycloud.kv:${space}:${prefix}:get,list,metadata,sync --expiry 30d --emit req.json`,
|
|
16458
|
+
` owner: ${CLI_NAME24} auth grant req.json > grant.json`,
|
|
16459
|
+
` device: ${CLI_NAME24} auth import grant.json`
|
|
16316
16460
|
].join("\n");
|
|
16317
16461
|
}
|
|
16318
16462
|
function resolveSpace(input, prefix, grants) {
|
|
@@ -16403,7 +16547,7 @@ function describeStatus(status) {
|
|
|
16403
16547
|
formatField("Size", formatBytes(status.bytes)),
|
|
16404
16548
|
formatField("Grant", status.device.delegationCid),
|
|
16405
16549
|
formatField("Last error", status.lastError ? `${status.lastError.code}: ${status.lastError.message}` : null),
|
|
16406
|
-
formatField("Purge", status.purgePending ? `incomplete: content files remain; the next ${
|
|
16550
|
+
formatField("Purge", status.purgePending ? `incomplete: content files remain; the next ${CLI_NAME24} replica command retries` : null)
|
|
16407
16551
|
].join("\n");
|
|
16408
16552
|
}
|
|
16409
16553
|
async function writeFileAtomic(path, bytes) {
|
|
@@ -16567,7 +16711,7 @@ or coverage incomplete; 5 grant expired, revoked or not yet valid; 6 network;
|
|
|
16567
16711
|
(options, cmd) => run(async () => {
|
|
16568
16712
|
const profile = await profileName(cmd);
|
|
16569
16713
|
if (options.purge) {
|
|
16570
|
-
throw new CLIError("REPORT_READ_ONLY", `Replica report is read-only. To remove a named local replica, run ${
|
|
16714
|
+
throw new CLIError("REPORT_READ_ONLY", `Replica report is read-only. To remove a named local replica, run ${CLI_NAME24} replica reset --replica <name> --purge; flag-owned purge has no equivalent yet (TC-1309).`, ExitCode.USAGE_ERROR);
|
|
16571
16715
|
}
|
|
16572
16716
|
if (options.clearPending) {
|
|
16573
16717
|
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);
|
|
@@ -16669,7 +16813,7 @@ or coverage incomplete; 5 grant expired, revoked or not yet valid; 6 network;
|
|
|
16669
16813
|
if (differs !== void 0) {
|
|
16670
16814
|
throw new ReplicaError(
|
|
16671
16815
|
ReplicaErrorCode.CONFIG_MISMATCH,
|
|
16672
|
-
`Replica ${name} already follows ${differs}. Use another --replica name, or ${
|
|
16816
|
+
`Replica ${name} already follows ${differs}. Use another --replica name, or ${CLI_NAME24} replica reset --replica ${name} --purge first.`
|
|
16673
16817
|
);
|
|
16674
16818
|
}
|
|
16675
16819
|
}
|
|
@@ -16834,7 +16978,7 @@ init_theme();
|
|
|
16834
16978
|
init_errors();
|
|
16835
16979
|
init_warnings();
|
|
16836
16980
|
init_constants();
|
|
16837
|
-
import { CLI_NAME as
|
|
16981
|
+
import { CLI_NAME as CLI_NAME26 } from "@tinycloud/operations/cli-name";
|
|
16838
16982
|
import { readFile as readFile12 } from "fs/promises";
|
|
16839
16983
|
import { join as join16 } from "path";
|
|
16840
16984
|
import { homedir, hostname, userInfo } from "os";
|
|
@@ -17045,7 +17189,7 @@ function parseStatusEnvelope(line) {
|
|
|
17045
17189
|
// src/lib/secrets-wait.ts
|
|
17046
17190
|
init_constants();
|
|
17047
17191
|
init_errors();
|
|
17048
|
-
import { CLI_NAME as
|
|
17192
|
+
import { CLI_NAME as CLI_NAME25 } from "@tinycloud/operations/cli-name";
|
|
17049
17193
|
var WAIT_BACKOFF_MS = [3e3, 4500, 6750, 10125];
|
|
17050
17194
|
var WAIT_STEADY_INTERVAL_MS = 15e3;
|
|
17051
17195
|
var DEFAULT_WAIT_TIMEOUT = "5m";
|
|
@@ -17053,7 +17197,7 @@ var MAX_WAIT_TIMEOUT_MS = 9 * 6e4;
|
|
|
17053
17197
|
var MAX_CONSECUTIVE_FAILURES = 5;
|
|
17054
17198
|
var TIMEOUT_RE = /^(0|\d+s|\d+m)$/;
|
|
17055
17199
|
var RETRYABLE_STATUS_ERRORS = { NODE_UNREACHABLE: true, NODE_ERROR: true };
|
|
17056
|
-
var EXPLICIT_KEY_HINT = `${
|
|
17200
|
+
var EXPLICIT_KEY_HINT = `${CLI_NAME25} secrets wait never signs in, and an explicit private key has no stored session. Use a local profile signed in once with ${CLI_NAME25} --profile <profile> auth login --method local, without TC_PRIVATE_KEY.`;
|
|
17057
17201
|
function abortableSleep(ms2, signal) {
|
|
17058
17202
|
return new Promise((resolve8) => {
|
|
17059
17203
|
if (signal.aborted) {
|
|
@@ -17089,7 +17233,7 @@ async function runSecretsWait(request, deps) {
|
|
|
17089
17233
|
let step = 0;
|
|
17090
17234
|
const cancelled = () => new CLIError(
|
|
17091
17235
|
"CANCELLED",
|
|
17092
|
-
`Interrupted: ${
|
|
17236
|
+
`Interrupted: ${CLI_NAME25} secrets wait stopped before the secrets were ready.`,
|
|
17093
17237
|
ExitCode.CANCELLED,
|
|
17094
17238
|
progress === void 0 ? {} : { progress }
|
|
17095
17239
|
);
|
|
@@ -17233,13 +17377,13 @@ function statusError(error, profile, warnings) {
|
|
|
17233
17377
|
if (error.code === "AUTH_REQUIRED" || error.code === "SESSION_NOT_FOUND") {
|
|
17234
17378
|
const recovery = recoveryOf(error, "sign_in");
|
|
17235
17379
|
if (recovery !== void 0) {
|
|
17236
|
-
const hint = recovery.command !== void 0 ? `Sign in once with: ${recovery.command}, then retry.` : `Sign in to profile "${profile}" once with ${
|
|
17380
|
+
const hint = recovery.command !== void 0 ? `Sign in once with: ${recovery.command}, then retry.` : `Sign in to profile "${profile}" once with ${CLI_NAME25} auth login, then retry.`;
|
|
17237
17381
|
return new CLIError("AUTH_REQUIRED", error.message, ExitCode.AUTH_REQUIRED, { hint, recovery, ...extra });
|
|
17238
17382
|
}
|
|
17239
17383
|
}
|
|
17240
17384
|
if (error.code === "PROFILE_MIGRATION_REQUIRED") {
|
|
17241
17385
|
const recovery = recoveryOf(error, "complete_migration");
|
|
17242
|
-
const hint = recovery?.command !== void 0 ? `Run ${recovery.command} once to finish this profile's one-time upgrade, then retry wait.` : `Run ${
|
|
17386
|
+
const hint = recovery?.command !== void 0 ? `Run ${recovery.command} once to finish this profile's one-time upgrade, then retry wait.` : `Run ${CLI_NAME25} auth caps once with profile "${profile}" to finish its one-time upgrade, then retry wait.`;
|
|
17243
17387
|
return new CLIError("PROFILE_MIGRATION_REQUIRED", error.message, ExitCode.ERROR, {
|
|
17244
17388
|
hint,
|
|
17245
17389
|
...recovery === void 0 ? {} : { recovery },
|
|
@@ -17431,11 +17575,11 @@ async function runSecretOperation(params) {
|
|
|
17431
17575
|
}
|
|
17432
17576
|
function secretPermissionReason(action, name) {
|
|
17433
17577
|
const target = name ? ` secret "${name}"` : " secrets";
|
|
17434
|
-
return `Allow \`${
|
|
17578
|
+
return `Allow \`${CLI_NAME26} secrets ${action}${name ? ` ${name}` : ""}\` to access${target} with the required TinyCloud permissions.`;
|
|
17435
17579
|
}
|
|
17436
17580
|
function assertOwnerApprovalPossible(profileName2, profile, action, name, missing = []) {
|
|
17437
17581
|
if (profile.authMethod !== "openkey" || process.stdin.isTTY || process.stderr.isTTY) return;
|
|
17438
|
-
const command = `${
|
|
17582
|
+
const command = `${CLI_NAME26} secrets ${action === "del" ? "delete" : action}${name ? ` ${name}` : ""}`;
|
|
17439
17583
|
throw new CLIError(
|
|
17440
17584
|
"PERMISSION_DENIED",
|
|
17441
17585
|
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.`,
|
|
@@ -17446,7 +17590,7 @@ function assertOwnerApprovalPossible(profileName2, profile, action, name, missin
|
|
|
17446
17590
|
);
|
|
17447
17591
|
}
|
|
17448
17592
|
function scopedSecretLoginHint(profileName2) {
|
|
17449
|
-
return `Have the owner approve a scoped login whose manifest names the secret: ${
|
|
17593
|
+
return `Have the owner approve a scoped login whose manifest names the secret: ${CLI_NAME26} --profile ${profileName2} auth login --method openkey --paste --manifest <manifest.json>. Pass the owner's code on stdin, newline-terminated.`;
|
|
17450
17594
|
}
|
|
17451
17595
|
async function runSecretOperationAttempt(label, operation) {
|
|
17452
17596
|
try {
|
|
@@ -17580,7 +17724,7 @@ ${message}`,
|
|
|
17580
17724
|
"AUTH_REQUIRED",
|
|
17581
17725
|
"Not signed in to TinyCloud.",
|
|
17582
17726
|
ExitCode.AUTH_REQUIRED,
|
|
17583
|
-
{ hint: `Sign in with: ${
|
|
17727
|
+
{ hint: `Sign in with: ${CLI_NAME26} --profile ${result.context.profile} auth login` }
|
|
17584
17728
|
);
|
|
17585
17729
|
}
|
|
17586
17730
|
if (result.error.code === "NODE_UNREACHABLE") {
|
|
@@ -18057,7 +18201,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
18057
18201
|
name: "Encryption network",
|
|
18058
18202
|
ok: false,
|
|
18059
18203
|
detail: `${networkName} not found`,
|
|
18060
|
-
hint: `${
|
|
18204
|
+
hint: `${CLI_NAME26} secrets network init ${networkName}`
|
|
18061
18205
|
}
|
|
18062
18206
|
];
|
|
18063
18207
|
let secret;
|
|
@@ -18103,7 +18247,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
18103
18247
|
name: "Secret access",
|
|
18104
18248
|
ok: false,
|
|
18105
18249
|
detail: notFound ? `${resolved.permissionPaths.vault} not found` : result.error.message,
|
|
18106
|
-
hint: notFound ? local ? `${
|
|
18250
|
+
hint: notFound ? local ? `${CLI_NAME26} secrets put ${resolved.name}${formatSecretScopeFlag(scopeOptions)} --stdin` : `${CLI_NAME26} secrets request ${resolved.name}${formatSecretScopeFlag(scopeOptions)}` : `Ask the owner profile to grant ${secretCapabilityAction("get")} and ${SECRET_DECRYPT_CAPABILITY}.`
|
|
18107
18251
|
});
|
|
18108
18252
|
}
|
|
18109
18253
|
} else {
|
|
@@ -18347,7 +18491,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
18347
18491
|
if (effectiveLocal(profile, options)) {
|
|
18348
18492
|
throw new CLIError(
|
|
18349
18493
|
"LOCAL_OWNER_PROFILE",
|
|
18350
|
-
`Profile "${ctx.profile}" uses a local key, which owns its secrets: store them with ${
|
|
18494
|
+
`Profile "${ctx.profile}" uses a local key, which owns its secrets: store them with ${CLI_NAME26} secrets put NAME --stdin instead of requesting them.`,
|
|
18351
18495
|
ExitCode.USAGE_ERROR
|
|
18352
18496
|
);
|
|
18353
18497
|
}
|
|
@@ -18470,7 +18614,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
18470
18614
|
|
|
18471
18615
|
// src/commands/share.ts
|
|
18472
18616
|
import { Option } from "commander";
|
|
18473
|
-
import { CLI_NAME as
|
|
18617
|
+
import { CLI_NAME as CLI_NAME27 } from "@tinycloud/operations/cli-name";
|
|
18474
18618
|
|
|
18475
18619
|
// ../share-sdk/dist/artifact.js
|
|
18476
18620
|
init_sha256();
|
|
@@ -36601,12 +36745,12 @@ function shareCliError(error, operation = "publish") {
|
|
|
36601
36745
|
const profileName2 = "profileName" in failure ? failure.profileName : void 0;
|
|
36602
36746
|
const localKey = "localKey" in failure && failure.localKey === true;
|
|
36603
36747
|
const profileHint = profileName2 === void 0 ? "" : `--profile ${profileName2} `;
|
|
36604
|
-
const loginHint = localKey ? `\`${
|
|
36748
|
+
const loginHint = localKey ? `\`${CLI_NAME27} ${profileHint}auth login --method local\`` : `\`${CLI_NAME27} ${profileHint}auth login --device --manifest builtin:share-publishing\` (or \`${CLI_NAME27} ${profileHint}enable share\`)`;
|
|
36605
36749
|
if (failure.kind === "caveated-session") {
|
|
36606
36750
|
const holder = profileName2 === void 0 ? "this session" : `profile ${profileName2}'s session`;
|
|
36607
36751
|
return new CLIError(
|
|
36608
36752
|
"PERMISSION_DENIED",
|
|
36609
|
-
`${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): \`${
|
|
36753
|
+
`${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_NAME27} init --name publisher --key-only && ${CLI_NAME27} --profile publisher enable share\``,
|
|
36610
36754
|
5
|
|
36611
36755
|
);
|
|
36612
36756
|
}
|
|
@@ -37011,7 +37155,7 @@ init_constants();
|
|
|
37011
37155
|
init_sdk();
|
|
37012
37156
|
init_host();
|
|
37013
37157
|
init_theme();
|
|
37014
|
-
import { CLI_NAME as
|
|
37158
|
+
import { CLI_NAME as CLI_NAME28 } from "@tinycloud/operations/cli-name";
|
|
37015
37159
|
import { ACCOUNT_REGISTRY_SPACE } from "@tinycloud/node-sdk";
|
|
37016
37160
|
import { randomBytes as randomBytes9 } from "crypto";
|
|
37017
37161
|
import { mkdir as mkdir7, writeFile as writeFile4 } from "fs/promises";
|
|
@@ -37058,7 +37202,7 @@ function registerSpaceCommand(program) {
|
|
|
37058
37202
|
} else {
|
|
37059
37203
|
const profile = await ProfileManager.getProfile(ctx.profile);
|
|
37060
37204
|
const cap = "tinycloud.kv:account:spaces/:get,list";
|
|
37061
|
-
const hint = resolveProfilePosture(profile) === "delegate-session" ? `Ask the space owner to grant it. Create the request with: ${
|
|
37205
|
+
const hint = resolveProfilePosture(profile) === "delegate-session" ? `Ask the space owner to grant it. Create the request with: ${CLI_NAME28} --profile ${ctx.profile} auth request --cap ${cap} --emit` : `Grant it to this session with: ${CLI_NAME28} --profile ${ctx.profile} auth request --grant --cap ${cap}. The default OpenKey owner sign-in covers only the default space, so it includes neither.`;
|
|
37062
37206
|
throw new CLIError(
|
|
37063
37207
|
"PERMISSION_DENIED",
|
|
37064
37208
|
`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.`,
|
|
@@ -37095,10 +37239,10 @@ function registerSpaceCommand(program) {
|
|
|
37095
37239
|
|
|
37096
37240
|
A delegate cannot host a space \u2014 only its owner (root authority) can. This
|
|
37097
37241
|
emits a tinycloud.host.request artifact naming the space and its owner so you
|
|
37098
|
-
can hand it to the owner; they run \`${
|
|
37242
|
+
can hand it to the owner; they run \`${CLI_NAME28} space host <name>\` and confirm.
|
|
37099
37243
|
|
|
37100
37244
|
If you ARE the owner of the resolved space, this refuses and tells you to host
|
|
37101
|
-
it directly with \`${
|
|
37245
|
+
it directly with \`${CLI_NAME28} space host <name>\` (no request needed).
|
|
37102
37246
|
`).action(async (name, options, cmd) => {
|
|
37103
37247
|
try {
|
|
37104
37248
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -37109,7 +37253,7 @@ it directly with \`${CLI_NAME26} space host <name>\` (no request needed).
|
|
|
37109
37253
|
if (await isRootAuthority(spaceId, ctx.profile)) {
|
|
37110
37254
|
throw new CLIError(
|
|
37111
37255
|
"ALREADY_ROOT_AUTHORITY",
|
|
37112
|
-
`You are the owner of ${spaceId}. Host it directly: ${
|
|
37256
|
+
`You are the owner of ${spaceId}. Host it directly: ${CLI_NAME28} space host ${spaceName}`,
|
|
37113
37257
|
ExitCode.USAGE_ERROR
|
|
37114
37258
|
);
|
|
37115
37259
|
}
|
|
@@ -37195,7 +37339,7 @@ init_sdk();
|
|
|
37195
37339
|
init_space();
|
|
37196
37340
|
init_host();
|
|
37197
37341
|
init_theme();
|
|
37198
|
-
import { CLI_NAME as
|
|
37342
|
+
import { CLI_NAME as CLI_NAME29 } from "@tinycloud/operations/cli-name";
|
|
37199
37343
|
import { writeFile as writeFile5 } from "fs/promises";
|
|
37200
37344
|
import { resolve as resolve7 } from "path";
|
|
37201
37345
|
async function dbHandle(node, dbName, spaceInput, profileName2) {
|
|
@@ -37219,12 +37363,12 @@ database for simple apps, or pass --db to target a named database. Pass
|
|
|
37219
37363
|
--space to target a non-primary space (e.g. the manifest "applications" space).
|
|
37220
37364
|
|
|
37221
37365
|
Common workflows:
|
|
37222
|
-
$ ${
|
|
37223
|
-
$ ${
|
|
37224
|
-
$ ${
|
|
37225
|
-
$ ${
|
|
37226
|
-
$ ${
|
|
37227
|
-
$ ${
|
|
37366
|
+
$ ${CLI_NAME29} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
|
|
37367
|
+
$ ${CLI_NAME29} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["ship docs"]'
|
|
37368
|
+
$ ${CLI_NAME29} sql query "SELECT id, body FROM notes ORDER BY id"
|
|
37369
|
+
$ ${CLI_NAME29} sql query "SELECT * FROM events WHERE type = ?" --db analytics --params '["signup"]'
|
|
37370
|
+
$ ${CLI_NAME29} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
|
|
37371
|
+
$ ${CLI_NAME29} sql export --db analytics --output analytics.db
|
|
37228
37372
|
|
|
37229
37373
|
Commands:
|
|
37230
37374
|
query Read rows with SELECT statements
|
|
@@ -37241,10 +37385,10 @@ Tips:
|
|
|
37241
37385
|
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", `
|
|
37242
37386
|
|
|
37243
37387
|
Examples:
|
|
37244
|
-
$ ${
|
|
37245
|
-
$ ${
|
|
37246
|
-
$ ${
|
|
37247
|
-
$ ${
|
|
37388
|
+
$ ${CLI_NAME29} sql query "SELECT * FROM notes ORDER BY id"
|
|
37389
|
+
$ ${CLI_NAME29} sql query "SELECT * FROM notes WHERE id = ?" --params '[42]'
|
|
37390
|
+
$ ${CLI_NAME29} sql query "SELECT count(*) AS total FROM events" --db analytics --json
|
|
37391
|
+
$ ${CLI_NAME29} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
|
|
37248
37392
|
|
|
37249
37393
|
Output:
|
|
37250
37394
|
Human output is formatted as a table. Piped output or --json returns
|
|
@@ -37285,11 +37429,11 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
|
|
|
37285
37429
|
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", `
|
|
37286
37430
|
|
|
37287
37431
|
Examples:
|
|
37288
|
-
$ ${
|
|
37289
|
-
$ ${
|
|
37290
|
-
$ ${
|
|
37291
|
-
$ ${
|
|
37292
|
-
$ ${
|
|
37432
|
+
$ ${CLI_NAME29} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
|
|
37433
|
+
$ ${CLI_NAME29} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["first note"]'
|
|
37434
|
+
$ ${CLI_NAME29} sql execute "UPDATE notes SET body = ? WHERE id = ?" --params '["edited", 1]'
|
|
37435
|
+
$ ${CLI_NAME29} sql execute "DROP TABLE old_notes" --db archive
|
|
37436
|
+
$ ${CLI_NAME29} sql execute "DELETE FROM conversation WHERE id = ?" --space applications --db xyz.tinycloud.listen/conversations --params '["abc"]'
|
|
37293
37437
|
|
|
37294
37438
|
Output:
|
|
37295
37439
|
Returns JSON with the changed row count and last inserted row id when available.
|
|
@@ -37318,9 +37462,9 @@ Output:
|
|
|
37318
37462
|
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", `
|
|
37319
37463
|
|
|
37320
37464
|
Examples:
|
|
37321
|
-
$ ${
|
|
37322
|
-
$ ${
|
|
37323
|
-
$ ${
|
|
37465
|
+
$ ${CLI_NAME29} sql export
|
|
37466
|
+
$ ${CLI_NAME29} sql export --db analytics --output analytics.db
|
|
37467
|
+
$ ${CLI_NAME29} sql export --space applications --db xyz.tinycloud.listen/conversations --output listen.db
|
|
37324
37468
|
|
|
37325
37469
|
Output:
|
|
37326
37470
|
Writes the database file locally and returns JSON with the path and size.
|
|
@@ -37353,19 +37497,19 @@ Output:
|
|
|
37353
37497
|
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", `
|
|
37354
37498
|
|
|
37355
37499
|
Examples:
|
|
37356
|
-
$ ${
|
|
37500
|
+
$ ${CLI_NAME29} sql copy --from-db com.tinycloud.conversation-sync/conversations \\
|
|
37357
37501
|
--to-db xyz.tinycloud.listen/conversations \\
|
|
37358
37502
|
--space applications --dry-run
|
|
37359
|
-
$ ${
|
|
37503
|
+
$ ${CLI_NAME29} sql copy --from-space applications --from-db com.foo/data \\
|
|
37360
37504
|
--to-space applications --to-db com.bar/data \\
|
|
37361
37505
|
--table conversation --table participant
|
|
37362
37506
|
|
|
37363
37507
|
Notes:
|
|
37364
37508
|
- Refuses to run when (resolved space, db) is identical for source and destination.
|
|
37365
|
-
- Does NOT create destination tables. Run the target app once (or use \`${
|
|
37509
|
+
- Does NOT create destination tables. Run the target app once (or use \`${CLI_NAME29} sql execute\`)
|
|
37366
37510
|
to materialize the schema before copying.
|
|
37367
37511
|
- One row at a time; suitable for small/medium datasets. Large copies should
|
|
37368
|
-
use \`${
|
|
37512
|
+
use \`${CLI_NAME29} sql export\` + bulk import.
|
|
37369
37513
|
- Authorization: the active session/delegation must cover sql/read on source
|
|
37370
37514
|
AND sql/write on destination. Otherwise the relevant operation will fail.
|
|
37371
37515
|
`).action(async (options, cmd) => {
|
|
@@ -37465,7 +37609,7 @@ init_errors();
|
|
|
37465
37609
|
init_formatter();
|
|
37466
37610
|
init_theme();
|
|
37467
37611
|
init_permissions();
|
|
37468
|
-
import { CLI_NAME as
|
|
37612
|
+
import { CLI_NAME as CLI_NAME30 } from "@tinycloud/operations/cli-name";
|
|
37469
37613
|
import {
|
|
37470
37614
|
NodeWasmBindings as NodeWasmBindings2
|
|
37471
37615
|
} from "@tinycloud/node-sdk";
|
|
@@ -37739,7 +37883,7 @@ function formatStatus(summary) {
|
|
|
37739
37883
|
lines.push(`Default profile: ${theme.value(summary.defaultProfile)}`);
|
|
37740
37884
|
lines.push("");
|
|
37741
37885
|
if (summary.profiles.length === 0) {
|
|
37742
|
-
lines.push(theme.muted(`No profiles configured. Run: ${
|
|
37886
|
+
lines.push(theme.muted(`No profiles configured. Run: ${CLI_NAME30} init`));
|
|
37743
37887
|
return `${lines.join("\n")}
|
|
37744
37888
|
`;
|
|
37745
37889
|
}
|