@tinycloud/cli 1.5.0-beta.0 → 1.5.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +17 -4
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +376 -259
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +9 -9
package/dist/legacy-entry.js
CHANGED
|
@@ -1857,7 +1857,7 @@ var init_sha256 = __esm({
|
|
|
1857
1857
|
});
|
|
1858
1858
|
|
|
1859
1859
|
// ../sdk-core/src/manifest.ts
|
|
1860
|
-
import { resolveSecretPath, SECRET_NAME_RE } from "@tinycloud/sdk-services";
|
|
1860
|
+
import { resolveSecretPath, SECRET_NAME_RE, parseNetworkId } from "@tinycloud/sdk-services";
|
|
1861
1861
|
function parseExpiry(duration) {
|
|
1862
1862
|
if (typeof duration !== "string" || duration.length === 0) {
|
|
1863
1863
|
throw new ManifestValidationError(
|
|
@@ -1900,6 +1900,14 @@ function expandEncryptionPermissionEntry(entry) {
|
|
|
1900
1900
|
`tinycloud.encryption entries require path to be a networkId URN (got ${JSON.stringify(entry.path)})`
|
|
1901
1901
|
);
|
|
1902
1902
|
}
|
|
1903
|
+
if (entry.path.includes("/")) {
|
|
1904
|
+
throw new ManifestValidationError("named keys go in manifest.keys");
|
|
1905
|
+
}
|
|
1906
|
+
try {
|
|
1907
|
+
parseNetworkId(entry.path);
|
|
1908
|
+
} catch {
|
|
1909
|
+
throw new ManifestValidationError("tinycloud.encryption entries require a valid networkId URN");
|
|
1910
|
+
}
|
|
1903
1911
|
const normalizedActions = [];
|
|
1904
1912
|
for (const action of entry.actions) {
|
|
1905
1913
|
if (action === "decrypt" || action === "tinycloud.encryption/decrypt") {
|
|
@@ -2004,8 +2012,52 @@ function validateManifest(input) {
|
|
|
2004
2012
|
if (m.secrets !== void 0) {
|
|
2005
2013
|
validateManifestSecrets(m.secrets);
|
|
2006
2014
|
}
|
|
2015
|
+
if (Object.prototype.hasOwnProperty.call(m, "keys_app_id") && (typeof m.keys_app_id !== "string" || !KEY_APP_ID.test(m.keys_app_id))) {
|
|
2016
|
+
throw new ManifestValidationError("manifest.keys_app_id is invalid");
|
|
2017
|
+
}
|
|
2018
|
+
validateManifestKeys(m.keys_app_id ?? m.app_id, m.keys);
|
|
2007
2019
|
return m;
|
|
2008
2020
|
}
|
|
2021
|
+
function validateManifestKeys(appId, keys) {
|
|
2022
|
+
if (keys === void 0) return;
|
|
2023
|
+
if (!Array.isArray(keys) || keys.length > 32) {
|
|
2024
|
+
throw new ManifestValidationError("manifest.keys must contain at most 32 entries");
|
|
2025
|
+
}
|
|
2026
|
+
if (keys.length === 0) return;
|
|
2027
|
+
if (!KEY_APP_ID.test(appId)) {
|
|
2028
|
+
throw new ManifestValidationError("manifest.keys requires a valid app_id");
|
|
2029
|
+
}
|
|
2030
|
+
const paths = /* @__PURE__ */ new Set();
|
|
2031
|
+
let prefixSeen = false;
|
|
2032
|
+
for (const [index, value] of keys.entries()) {
|
|
2033
|
+
const label = `manifest.keys[${index}]`;
|
|
2034
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
2035
|
+
throw new ManifestValidationError(`${label} must be an object`);
|
|
2036
|
+
}
|
|
2037
|
+
const entry = value;
|
|
2038
|
+
const fields = Object.keys(entry);
|
|
2039
|
+
const hasPath = Object.prototype.hasOwnProperty.call(entry, "path");
|
|
2040
|
+
const hasPrefix = Object.prototype.hasOwnProperty.call(entry, "prefix");
|
|
2041
|
+
if (hasPath === hasPrefix || fields.some((field) => !["path", "prefix", "abilities"].includes(field))) {
|
|
2042
|
+
throw new ManifestValidationError(`${label} must have one path or prefix and abilities`);
|
|
2043
|
+
}
|
|
2044
|
+
if (!Array.isArray(entry.abilities) || entry.abilities.length === 0 || new Set(entry.abilities).size !== entry.abilities.length || entry.abilities.some((ability) => typeof ability !== "string" || !KEY_ABILITIES.includes(ability))) {
|
|
2045
|
+
throw new ManifestValidationError(`${label}.abilities is invalid`);
|
|
2046
|
+
}
|
|
2047
|
+
if (hasPath) {
|
|
2048
|
+
const path = entry.path;
|
|
2049
|
+
if (typeof path !== "string" || path.length > 255 || !KEY_PATH.test(path) || !path.startsWith(`apps/${appId}/`) || paths.has(path)) {
|
|
2050
|
+
throw new ManifestValidationError(`${label}.path is invalid`);
|
|
2051
|
+
}
|
|
2052
|
+
paths.add(path);
|
|
2053
|
+
} else {
|
|
2054
|
+
if (entry.prefix !== `apps/${appId}/` || prefixSeen) {
|
|
2055
|
+
throw new ManifestValidationError(`${label}.prefix is invalid`);
|
|
2056
|
+
}
|
|
2057
|
+
prefixSeen = true;
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2009
2061
|
function resolveManifestKnowledgeRoot(knowledge) {
|
|
2010
2062
|
if (knowledge === void 0) {
|
|
2011
2063
|
return void 0;
|
|
@@ -2405,7 +2457,7 @@ function withCapabilitiesReadForSpaces(resources) {
|
|
|
2405
2457
|
...[...spaces].map(capabilitiesReadPermission)
|
|
2406
2458
|
]);
|
|
2407
2459
|
}
|
|
2408
|
-
var import_ms, DEFAULT_KNOWLEDGE_ROOT, ManifestValidationError, DEFAULT_EXPIRY, DEFAULT_DEFAULTS, DEFAULT_MANIFEST_VERSION, DEFAULT_MANIFEST_SPACE, SECRETS_SPACE, VAULT_PERMISSION_SERVICE, SERVICE_SHORT_TO_LONG, ENCRYPTION_PERMISSION_SERVICE, ENCRYPTION_MANIFEST_SPACE, SERVICE_LONG_TO_SHORT2, DEFAULT_STANDARD_ENTRIES, DEFAULT_ADMIN_ENTRIES, DEFAULT_ALL_ENTRIES;
|
|
2460
|
+
var import_ms, DEFAULT_KNOWLEDGE_ROOT, ManifestValidationError, DEFAULT_EXPIRY, DEFAULT_DEFAULTS, DEFAULT_MANIFEST_VERSION, DEFAULT_MANIFEST_SPACE, SECRETS_SPACE, VAULT_PERMISSION_SERVICE, SERVICE_SHORT_TO_LONG, ENCRYPTION_PERMISSION_SERVICE, ENCRYPTION_MANIFEST_SPACE, SERVICE_LONG_TO_SHORT2, DEFAULT_STANDARD_ENTRIES, DEFAULT_ADMIN_ENTRIES, DEFAULT_ALL_ENTRIES, KEY_SEGMENT, KEY_APP_ID, KEY_PATH, KEY_ABILITIES;
|
|
2409
2461
|
var init_manifest = __esm({
|
|
2410
2462
|
"../sdk-core/src/manifest.ts"() {
|
|
2411
2463
|
"use strict";
|
|
@@ -2488,6 +2540,10 @@ var init_manifest = __esm({
|
|
|
2488
2540
|
actions: ["read", "write"]
|
|
2489
2541
|
}
|
|
2490
2542
|
];
|
|
2543
|
+
KEY_SEGMENT = "[a-z0-9][a-z0-9._-]{0,63}";
|
|
2544
|
+
KEY_APP_ID = new RegExp(`^${KEY_SEGMENT}$`);
|
|
2545
|
+
KEY_PATH = new RegExp(`^${KEY_SEGMENT}(?:/${KEY_SEGMENT}){2,7}$`);
|
|
2546
|
+
KEY_ABILITIES = ["key.create", "decrypt", "key.block", "key.unblock"];
|
|
2491
2547
|
}
|
|
2492
2548
|
});
|
|
2493
2549
|
|
|
@@ -15794,7 +15850,7 @@ async function refusal(response) {
|
|
|
15794
15850
|
async function prepareLinkRecord(input) {
|
|
15795
15851
|
const apiOrigin2 = checkApiOrigin(input.apiOrigin);
|
|
15796
15852
|
const nowMs = toEpochMs(input.now);
|
|
15797
|
-
const expiresMs = input.expiresAt.getTime();
|
|
15853
|
+
const expiresMs = Math.floor(input.expiresAt.getTime() / 1e3) * 1e3;
|
|
15798
15854
|
if (!Number.isFinite(nowMs) || !Number.isFinite(expiresMs)) throw new LinkError("invalid_argument", "now and expiresAt must be valid dates");
|
|
15799
15855
|
let blob;
|
|
15800
15856
|
let key;
|
|
@@ -16456,7 +16512,7 @@ function formatSpace(space) {
|
|
|
16456
16512
|
init_profile_gate();
|
|
16457
16513
|
init_dist();
|
|
16458
16514
|
init_profiles();
|
|
16459
|
-
import { CLI_NAME as
|
|
16515
|
+
import { CLI_NAME as CLI_NAME18 } from "@tinycloud/operations/cli-name";
|
|
16460
16516
|
import { get as httpGet } from "http";
|
|
16461
16517
|
import { get as httpsGet } from "https";
|
|
16462
16518
|
import { spawn } from "child_process";
|
|
@@ -31318,6 +31374,7 @@ init_errors();
|
|
|
31318
31374
|
init_constants();
|
|
31319
31375
|
import { NodeWasmBindings, signedCompactUcanExpiry as signedCompactUcanExpiry2 } from "@tinycloud/node-sdk";
|
|
31320
31376
|
import { isLocalOwnerProfile as isLocalOwnerProfile2, ownerSessionLacksProof as ownerSessionLacksProof2 } from "@tinycloud/operations/profile";
|
|
31377
|
+
import { CLI_NAME as CLI_NAME12 } from "@tinycloud/operations/cli-name";
|
|
31321
31378
|
|
|
31322
31379
|
// src/lib/duration.ts
|
|
31323
31380
|
function parseDuration(input) {
|
|
@@ -31353,6 +31410,87 @@ init_manifest();
|
|
|
31353
31410
|
init_raw_encryption();
|
|
31354
31411
|
init_owner_did();
|
|
31355
31412
|
init_caveat_refusal();
|
|
31413
|
+
|
|
31414
|
+
// src/lib/named-keys.ts
|
|
31415
|
+
init_constants();
|
|
31416
|
+
init_errors();
|
|
31417
|
+
import { extractRecapAttenuations } from "@tinycloud/sdk-core";
|
|
31418
|
+
import {
|
|
31419
|
+
EncryptionResourceError,
|
|
31420
|
+
KEY_CREATE_ABILITY,
|
|
31421
|
+
formatPrefixResource,
|
|
31422
|
+
nodeSupportsNamedKeys
|
|
31423
|
+
} from "@tinycloud/sdk-services/encryption/resource";
|
|
31424
|
+
import { DECRYPT_ACTION } from "@tinycloud/sdk-services";
|
|
31425
|
+
var NAMED_KEYS_NETWORK = "default";
|
|
31426
|
+
function namedKeysNetworkId(ownerDid) {
|
|
31427
|
+
return `urn:tinycloud:encryption:${ownerDid}:${NAMED_KEYS_NETWORK}`;
|
|
31428
|
+
}
|
|
31429
|
+
function parseKeysPrefix(input, ownerDid) {
|
|
31430
|
+
const prefix = input.endsWith("/") ? input.slice(0, -1) : input;
|
|
31431
|
+
const invalid3 = (reason) => new CLIError("INVALID_KEY_PREFIX", `Invalid \`--keys\` folder "${input}": ${reason}`, ExitCode.USAGE_ERROR);
|
|
31432
|
+
if (prefix.length === 0) throw invalid3("the folder can't be empty.");
|
|
31433
|
+
if (prefix === "apps" || prefix.startsWith("apps/")) {
|
|
31434
|
+
throw invalid3("folders under `apps/` belong to apps and can't be used from the CLI.");
|
|
31435
|
+
}
|
|
31436
|
+
try {
|
|
31437
|
+
return { prefix, resource: formatPrefixResource(namedKeysNetworkId(ownerDid), prefix) };
|
|
31438
|
+
} catch (error) {
|
|
31439
|
+
if (error instanceof EncryptionResourceError) throw invalid3(error.message);
|
|
31440
|
+
throw error;
|
|
31441
|
+
}
|
|
31442
|
+
}
|
|
31443
|
+
function keyAbilitiesFor(keys) {
|
|
31444
|
+
return { [keys.resource]: [KEY_CREATE_ABILITY, DECRYPT_ACTION] };
|
|
31445
|
+
}
|
|
31446
|
+
function keyPathInPrefix(prefix, path) {
|
|
31447
|
+
return path.startsWith(`${prefix}/`) && path.length > prefix.length + 1;
|
|
31448
|
+
}
|
|
31449
|
+
async function assertNodeSupportsNamedKeys(host, fetchImpl = fetch) {
|
|
31450
|
+
let info = null;
|
|
31451
|
+
try {
|
|
31452
|
+
const response = await fetchImpl(`${host}/info`);
|
|
31453
|
+
if (response.ok) info = await response.json();
|
|
31454
|
+
} catch {
|
|
31455
|
+
info = null;
|
|
31456
|
+
}
|
|
31457
|
+
if (!nodeSupportsNamedKeys(info)) {
|
|
31458
|
+
throw new CLIError(
|
|
31459
|
+
"NAMED_KEYS_UNAVAILABLE",
|
|
31460
|
+
`${host} doesn't support named keys, so \`--keys\` can't be used there. Nothing was signed.`,
|
|
31461
|
+
ExitCode.NODE_ERROR
|
|
31462
|
+
);
|
|
31463
|
+
}
|
|
31464
|
+
}
|
|
31465
|
+
function partialKeysGrantError() {
|
|
31466
|
+
return new CLIError(
|
|
31467
|
+
"KEYS_PARTIAL_GRANT",
|
|
31468
|
+
"Named keys need both permissions (create and use), but only one was approved, so this sign-in wasn't saved. Sign in again and approve both.",
|
|
31469
|
+
ExitCode.PERMISSION_DENIED
|
|
31470
|
+
);
|
|
31471
|
+
}
|
|
31472
|
+
function namedKeysGrantIn(siwe, keys) {
|
|
31473
|
+
let recap = {};
|
|
31474
|
+
try {
|
|
31475
|
+
recap = siwe === void 0 ? {} : extractRecapAttenuations(siwe);
|
|
31476
|
+
} catch {
|
|
31477
|
+
return "none";
|
|
31478
|
+
}
|
|
31479
|
+
const row = recap[keys.resource] ?? {};
|
|
31480
|
+
const wanted = keyAbilitiesFor(keys)[keys.resource];
|
|
31481
|
+
const granted = wanted.filter((ability) => ability in row);
|
|
31482
|
+
if (granted.length === 0) return "none";
|
|
31483
|
+
return granted.length === wanted.length && Object.keys(row).length === wanted.length ? "both" : "partial";
|
|
31484
|
+
}
|
|
31485
|
+
function withVerifiedNamedKeys(profile, session) {
|
|
31486
|
+
if (profile.namedKeys === void 0) return profile;
|
|
31487
|
+
const siwe = typeof session === "object" && session !== null ? session.siwe : void 0;
|
|
31488
|
+
if (typeof siwe === "string" && namedKeysGrantIn(siwe, profile.namedKeys) === "both") return profile;
|
|
31489
|
+
const { namedKeys: _dropped, ...rest } = profile;
|
|
31490
|
+
return rest;
|
|
31491
|
+
}
|
|
31492
|
+
|
|
31493
|
+
// src/auth/scoped-login.ts
|
|
31356
31494
|
var CAPABILITIES_READ = "tinycloud.capabilities/read";
|
|
31357
31495
|
var CLOCK_SKEW_MS = 3e4;
|
|
31358
31496
|
var SIGNED_RECAP = "signed-recap";
|
|
@@ -31605,6 +31743,35 @@ function verifySignedSession(data, key, sessionDid, expected = {}) {
|
|
|
31605
31743
|
}
|
|
31606
31744
|
return { ownerDid, permissions, expiresAt };
|
|
31607
31745
|
}
|
|
31746
|
+
function withoutRequestedKeyRow(signed, keys, signerDid) {
|
|
31747
|
+
const mismatch = () => new CLIError(
|
|
31748
|
+
"OPENKEY_SCOPE_MISMATCH",
|
|
31749
|
+
`The key permission in the approval didn't match what was asked for "<folder>/", so this sign-in wasn't saved. Sign in again with \`${CLI_NAME12} auth login --keys <folder>\`.`,
|
|
31750
|
+
ExitCode.PERMISSION_DENIED
|
|
31751
|
+
);
|
|
31752
|
+
let expectedResource;
|
|
31753
|
+
try {
|
|
31754
|
+
expectedResource = parseKeysPrefix(keys.prefix, canonicalOwnerDid(signerDid)).resource;
|
|
31755
|
+
} catch {
|
|
31756
|
+
throw mismatch();
|
|
31757
|
+
}
|
|
31758
|
+
if (keys.resource !== expectedResource) throw mismatch();
|
|
31759
|
+
const rest = [];
|
|
31760
|
+
const actions = /* @__PURE__ */ new Set();
|
|
31761
|
+
for (const permission of signed) {
|
|
31762
|
+
if (!isVerifiedRawEncryptionPermission(permission) || permission.path !== expectedResource) {
|
|
31763
|
+
rest.push(permission);
|
|
31764
|
+
continue;
|
|
31765
|
+
}
|
|
31766
|
+
if (permission.caveats?.some((caveat) => Object.keys(caveat).length > 0)) throw mismatch();
|
|
31767
|
+
for (const action of permission.actions) actions.add(action);
|
|
31768
|
+
}
|
|
31769
|
+
if (actions.size === 0) return rest;
|
|
31770
|
+
const wanted = keyAbilitiesFor(keys)[expectedResource];
|
|
31771
|
+
if ([...actions].some((action) => !wanted.includes(action))) throw mismatch();
|
|
31772
|
+
if (actions.size !== wanted.length) throw partialKeysGrantError();
|
|
31773
|
+
return rest;
|
|
31774
|
+
}
|
|
31608
31775
|
function verifyScopedLogin(data, key, sessionDid, requested, expected = {}) {
|
|
31609
31776
|
const notStored = expected.purpose === "grant" ? "No grant was stored." : "No scoped session was saved.";
|
|
31610
31777
|
let signed;
|
|
@@ -31630,10 +31797,11 @@ function verifyScopedLogin(data, key, sessionDid, requested, expected = {}) {
|
|
|
31630
31797
|
}
|
|
31631
31798
|
const legacyNested = [];
|
|
31632
31799
|
const approved = [];
|
|
31633
|
-
|
|
31800
|
+
const keyRowRest = expected.namedKeyRow === void 0 ? signed.permissions : withoutRequestedKeyRow(signed.permissions, expected.namedKeyRow, signed.ownerDid);
|
|
31801
|
+
for (const permission of keyRowRest) {
|
|
31634
31802
|
(isLegacyNestedDecrypt(permission, requested, signed.ownerDid, spaceId) ? legacyNested : approved).push(permission);
|
|
31635
31803
|
}
|
|
31636
|
-
for (const permission of
|
|
31804
|
+
for (const permission of keyRowRest) {
|
|
31637
31805
|
if (isVerifiedRawEncryptionPermission(permission) && !rawEncryptionOwnerMatches(permission.path, signed.ownerDid)) {
|
|
31638
31806
|
throw new CLIError("OPENKEY_SCOPE_MISMATCH", `OpenKey signed decrypt for a network not owned by the approving identity. ${notStored}`, ExitCode.PERMISSION_DENIED);
|
|
31639
31807
|
}
|
|
@@ -31650,80 +31818,6 @@ function verifyScopedLogin(data, key, sessionDid, requested, expected = {}) {
|
|
|
31650
31818
|
|
|
31651
31819
|
// src/auth/login-commit.ts
|
|
31652
31820
|
import { lstat as lstat6 } from "fs/promises";
|
|
31653
|
-
|
|
31654
|
-
// src/lib/named-keys.ts
|
|
31655
|
-
init_constants();
|
|
31656
|
-
init_errors();
|
|
31657
|
-
import { extractRecapAttenuations } from "@tinycloud/sdk-core";
|
|
31658
|
-
import {
|
|
31659
|
-
EncryptionResourceError,
|
|
31660
|
-
KEY_CREATE_ABILITY,
|
|
31661
|
-
formatPrefixResource,
|
|
31662
|
-
nodeSupportsNamedKeys
|
|
31663
|
-
} from "@tinycloud/sdk-services/encryption/resource";
|
|
31664
|
-
import { DECRYPT_ACTION } from "@tinycloud/sdk-services";
|
|
31665
|
-
var NAMED_KEYS_NETWORK = "default";
|
|
31666
|
-
function namedKeysNetworkId(ownerDid) {
|
|
31667
|
-
return `urn:tinycloud:encryption:${ownerDid}:${NAMED_KEYS_NETWORK}`;
|
|
31668
|
-
}
|
|
31669
|
-
function parseKeysPrefix(input, ownerDid) {
|
|
31670
|
-
const prefix = input.endsWith("/") ? input.slice(0, -1) : input;
|
|
31671
|
-
const invalid3 = (reason) => new CLIError("INVALID_KEY_PREFIX", `Invalid \`--keys\` folder "${input}": ${reason}`, ExitCode.USAGE_ERROR);
|
|
31672
|
-
if (prefix.length === 0) throw invalid3("the folder can't be empty.");
|
|
31673
|
-
if (prefix === "apps" || prefix.startsWith("apps/")) {
|
|
31674
|
-
throw invalid3("folders under `apps/` belong to apps and can't be used from the CLI.");
|
|
31675
|
-
}
|
|
31676
|
-
try {
|
|
31677
|
-
return { prefix, resource: formatPrefixResource(namedKeysNetworkId(ownerDid), prefix) };
|
|
31678
|
-
} catch (error) {
|
|
31679
|
-
if (error instanceof EncryptionResourceError) throw invalid3(error.message);
|
|
31680
|
-
throw error;
|
|
31681
|
-
}
|
|
31682
|
-
}
|
|
31683
|
-
function keyAbilitiesFor(keys) {
|
|
31684
|
-
return { [keys.resource]: [KEY_CREATE_ABILITY, DECRYPT_ACTION] };
|
|
31685
|
-
}
|
|
31686
|
-
function keyPathInPrefix(prefix, path) {
|
|
31687
|
-
return path.startsWith(`${prefix}/`) && path.length > prefix.length + 1;
|
|
31688
|
-
}
|
|
31689
|
-
async function assertNodeSupportsNamedKeys(host, fetchImpl = fetch) {
|
|
31690
|
-
let info = null;
|
|
31691
|
-
try {
|
|
31692
|
-
const response = await fetchImpl(`${host}/info`);
|
|
31693
|
-
if (response.ok) info = await response.json();
|
|
31694
|
-
} catch {
|
|
31695
|
-
info = null;
|
|
31696
|
-
}
|
|
31697
|
-
if (!nodeSupportsNamedKeys(info)) {
|
|
31698
|
-
throw new CLIError(
|
|
31699
|
-
"NAMED_KEYS_UNAVAILABLE",
|
|
31700
|
-
`${host} doesn't support named keys, so \`--keys\` can't be used there. Nothing was signed.`,
|
|
31701
|
-
ExitCode.NODE_ERROR
|
|
31702
|
-
);
|
|
31703
|
-
}
|
|
31704
|
-
}
|
|
31705
|
-
function namedKeysGrantIn(siwe, keys) {
|
|
31706
|
-
let recap = {};
|
|
31707
|
-
try {
|
|
31708
|
-
recap = siwe === void 0 ? {} : extractRecapAttenuations(siwe);
|
|
31709
|
-
} catch {
|
|
31710
|
-
return "none";
|
|
31711
|
-
}
|
|
31712
|
-
const row = recap[keys.resource] ?? {};
|
|
31713
|
-
const wanted = keyAbilitiesFor(keys)[keys.resource];
|
|
31714
|
-
const granted = wanted.filter((ability) => ability in row);
|
|
31715
|
-
if (granted.length === 0) return "none";
|
|
31716
|
-
return granted.length === wanted.length && Object.keys(row).length === wanted.length ? "both" : "partial";
|
|
31717
|
-
}
|
|
31718
|
-
function withVerifiedNamedKeys(profile, session) {
|
|
31719
|
-
if (profile.namedKeys === void 0) return profile;
|
|
31720
|
-
const siwe = typeof session === "object" && session !== null ? session.siwe : void 0;
|
|
31721
|
-
if (typeof siwe === "string" && namedKeysGrantIn(siwe, profile.namedKeys) === "both") return profile;
|
|
31722
|
-
const { namedKeys: _dropped, ...rest } = profile;
|
|
31723
|
-
return rest;
|
|
31724
|
-
}
|
|
31725
|
-
|
|
31726
|
-
// src/auth/login-commit.ts
|
|
31727
31821
|
init_constants();
|
|
31728
31822
|
init_profiles();
|
|
31729
31823
|
init_storage();
|
|
@@ -31731,14 +31825,14 @@ init_errors();
|
|
|
31731
31825
|
init_space();
|
|
31732
31826
|
import { join as join13 } from "path";
|
|
31733
31827
|
import { REPLICA_LOGOUT_PENDING_FILE as REPLICA_LOGOUT_PENDING_FILE3 } from "@tinycloud/node-sdk";
|
|
31734
|
-
import { CLI_NAME as
|
|
31828
|
+
import { CLI_NAME as CLI_NAME13 } from "@tinycloud/operations/cli-name";
|
|
31735
31829
|
import { isLocalOwnerProfile as isLocalOwnerProfile3, ownerSessionLacksProof as ownerSessionLacksProof3 } from "@tinycloud/operations/profile";
|
|
31736
31830
|
import { ProfileLockTimeoutError as ProfileLockTimeoutError2, profilePath as profilePath7 } from "@tinycloud/operations/state";
|
|
31737
31831
|
function assertNotLocalOwner(profileName2, profile, flow) {
|
|
31738
31832
|
if (profile === null || !isLocalOwnerProfile3(profile)) return;
|
|
31739
31833
|
throw new CLIError(
|
|
31740
31834
|
"LOCAL_OWNER_PROFILE",
|
|
31741
|
-
`Profile "${profileName2}" holds a local owner key. ${flow} would turn it into an OpenKey profile while keeping that key. Use a separate profile: \`${
|
|
31835
|
+
`Profile "${profileName2}" holds a local owner key. ${flow} would turn it into an OpenKey profile while keeping that key. Use a separate profile: \`${CLI_NAME13} init --name publisher --key-only\`, then \`${CLI_NAME13} --profile publisher auth login --device --manifest ...\`.`,
|
|
31742
31836
|
ExitCode.USAGE_ERROR
|
|
31743
31837
|
);
|
|
31744
31838
|
}
|
|
@@ -31773,7 +31867,7 @@ function assertSessionReplaceable(profileName2, snapshot, ownerDid, scope, newEx
|
|
|
31773
31867
|
if (problem !== void 0) {
|
|
31774
31868
|
throw new CLIError(
|
|
31775
31869
|
"PROFILE_STATE_INCONSISTENT",
|
|
31776
|
-
`Profile "${profileName2}" is inconsistent (${problem}), possibly from an interrupted write. Nothing was saved. Check \`${
|
|
31870
|
+
`Profile "${profileName2}" is inconsistent (${problem}), possibly from an interrupted write. Nothing was saved. Check \`${CLI_NAME13} --profile ${profileName2} context\`, then pass --replace-session to replace this state, or use a new profile.`,
|
|
31777
31871
|
ExitCode.ERROR
|
|
31778
31872
|
);
|
|
31779
31873
|
}
|
|
@@ -31789,7 +31883,7 @@ function assertSessionReplaceable(profileName2, snapshot, ownerDid, scope, newEx
|
|
|
31789
31883
|
const space = typeof session.spaceId === "string" ? session.spaceId : "an unknown space";
|
|
31790
31884
|
throw new CLIError(
|
|
31791
31885
|
"SESSION_IN_USE",
|
|
31792
|
-
`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 (\`${
|
|
31886
|
+
`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_NAME13} init --name publisher --key-only\`, then \`${CLI_NAME13} --profile publisher auth login --device --manifest ...\`), or pass --replace-session to replace this session.`,
|
|
31793
31887
|
ExitCode.USAGE_ERROR
|
|
31794
31888
|
);
|
|
31795
31889
|
}
|
|
@@ -31823,7 +31917,7 @@ async function commitLogin(profileName2, snapshot, commit) {
|
|
|
31823
31917
|
if (canonicalJson(current.profile) !== canonicalJson(snapshot.profile) || canonicalJson(current.key) !== canonicalJson(snapshot.key) || canonicalJson(current.session) !== canonicalJson(snapshot.session)) {
|
|
31824
31918
|
throw new CLIError(
|
|
31825
31919
|
"PROFILE_CHANGED_DURING_LOGIN",
|
|
31826
|
-
`Profile "${profileName2}" changed while this login was in progress (another login, key rotation or logout). Nothing was saved; check \`${
|
|
31920
|
+
`Profile "${profileName2}" changed while this login was in progress (another login, key rotation or logout). Nothing was saved; check \`${CLI_NAME13} --profile ${profileName2} context\` and run the login again if it is still needed.`,
|
|
31827
31921
|
ExitCode.ERROR
|
|
31828
31922
|
);
|
|
31829
31923
|
}
|
|
@@ -31856,7 +31950,7 @@ async function commitLogin(profileName2, snapshot, commit) {
|
|
|
31856
31950
|
if (failures.length === 0) throw error;
|
|
31857
31951
|
throw new CLIError(
|
|
31858
31952
|
"PROFILE_STATE_INCONSISTENT",
|
|
31859
|
-
`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 \`${
|
|
31953
|
+
`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_NAME13} --profile ${profileName2} context\`, then run the login again with --replace-session, or use a new profile.`,
|
|
31860
31954
|
ExitCode.ERROR
|
|
31861
31955
|
);
|
|
31862
31956
|
}
|
|
@@ -31872,7 +31966,7 @@ async function commitLogin(profileName2, snapshot, commit) {
|
|
|
31872
31966
|
|
|
31873
31967
|
// src/auth/owner-key.ts
|
|
31874
31968
|
init_theme();
|
|
31875
|
-
import { CLI_NAME as
|
|
31969
|
+
import { CLI_NAME as CLI_NAME14 } from "@tinycloud/operations/cli-name";
|
|
31876
31970
|
function openKeyPrimaryFlag(delegation) {
|
|
31877
31971
|
return typeof delegation.primary === "boolean" ? delegation.primary : void 0;
|
|
31878
31972
|
}
|
|
@@ -31883,7 +31977,7 @@ function withOwnerKeyPrimary(profile, primary) {
|
|
|
31883
31977
|
function nonPrimaryKeyWarning(ownerDid) {
|
|
31884
31978
|
const address = ownerDid.split(":")[4] ?? ownerDid;
|
|
31885
31979
|
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.
|
|
31886
|
-
To use your primary key, log in again and choose the primary key in OpenKey, for example into a new profile: ${
|
|
31980
|
+
To use your primary key, log in again and choose the primary key in OpenKey, for example into a new profile: ${CLI_NAME14} init --name <profile>
|
|
31887
31981
|
`;
|
|
31888
31982
|
}
|
|
31889
31983
|
function warnIfNotPrimaryKey(ownerDid, primary) {
|
|
@@ -31892,7 +31986,7 @@ function warnIfNotPrimaryKey(ownerDid, primary) {
|
|
|
31892
31986
|
}
|
|
31893
31987
|
function announceFirstOwner(profileName2, expectedOwner, ownerDid) {
|
|
31894
31988
|
if (expectedOwner !== void 0 || ownerDid === void 0) return;
|
|
31895
|
-
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 ${
|
|
31989
|
+
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_NAME14} init.
|
|
31896
31990
|
`);
|
|
31897
31991
|
}
|
|
31898
31992
|
function ownerLoginPermissions(owner) {
|
|
@@ -32723,7 +32817,7 @@ init_types();
|
|
|
32723
32817
|
init_constants();
|
|
32724
32818
|
init_permissions();
|
|
32725
32819
|
init_sdk();
|
|
32726
|
-
import { CLI_NAME as
|
|
32820
|
+
import { CLI_NAME as CLI_NAME16 } from "@tinycloud/operations/cli-name";
|
|
32727
32821
|
import { TinyCloudNode as TinyCloudNode2 } from "@tinycloud/node-sdk";
|
|
32728
32822
|
import { DelegationRequestBindingSchema, PermissionEntrySchema } from "@tinycloud/operations/artifacts";
|
|
32729
32823
|
import {
|
|
@@ -32738,7 +32832,7 @@ import {
|
|
|
32738
32832
|
// src/lib/revoke-delegation.ts
|
|
32739
32833
|
init_constants();
|
|
32740
32834
|
init_errors();
|
|
32741
|
-
import { CLI_NAME as
|
|
32835
|
+
import { CLI_NAME as CLI_NAME15 } from "@tinycloud/operations/cli-name";
|
|
32742
32836
|
import { parseSignedCompactUcanAttenuation as parseSignedCompactUcanAttenuation3 } from "@tinycloud/sdk-core";
|
|
32743
32837
|
import { inspectStoredDelegation, withoutBearer as withoutBearer3 } from "@tinycloud/operations/delegation-binding";
|
|
32744
32838
|
init_permissions();
|
|
@@ -32964,7 +33058,8 @@ async function revokeDelegationOnNode(params) {
|
|
|
32964
33058
|
throw new CLIError(
|
|
32965
33059
|
"RAW_RECAP_RESOURCE_UNSUPPORTED",
|
|
32966
33060
|
`Cannot revoke "${cid}": this CLI profile has no record of this grant and the node's record does not match the signed-in owner.`,
|
|
32967
|
-
ExitCode.PERMISSION_DENIED
|
|
33061
|
+
ExitCode.PERMISSION_DENIED,
|
|
33062
|
+
profile.authMethod === "openkey" ? { hint: "This permission can't be revoked from the CLI yet. Revoke it at https://account.tinycloud.xyz/delegations." } : void 0
|
|
32968
33063
|
);
|
|
32969
33064
|
}
|
|
32970
33065
|
fallbackSpaceId = fallback.space;
|
|
@@ -33002,7 +33097,7 @@ async function revokeDelegationOnNode(params) {
|
|
|
33002
33097
|
"REVOKE_UNAUTHORIZED",
|
|
33003
33098
|
"The node rejected this revocation: Unauthorized Revoker.",
|
|
33004
33099
|
ExitCode.PERMISSION_DENIED,
|
|
33005
|
-
{ 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 \`${
|
|
33100
|
+
{ 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_NAME15} --profile <profile-name> delegation revoke ${cid}\`.` }
|
|
33006
33101
|
);
|
|
33007
33102
|
}
|
|
33008
33103
|
throw cliErrorFromService(result.error);
|
|
@@ -33085,7 +33180,7 @@ function renderList(result) {
|
|
|
33085
33180
|
}
|
|
33086
33181
|
process.stdout.write(`Profile: ${result.profile}
|
|
33087
33182
|
`);
|
|
33088
|
-
process.stdout.write(`Session CID: ${result.session?.cid ?? "unavailable"}` + (result.session?.cid ? ` (current session; revoke with ${
|
|
33183
|
+
process.stdout.write(`Session CID: ${result.session?.cid ?? "unavailable"}` + (result.session?.cid ? ` (current session; revoke with ${CLI_NAME16} delegation revoke ${result.session.cid})
|
|
33089
33184
|
` : "\n"));
|
|
33090
33185
|
process.stdout.write(`Local verification: ${result.localVerification}; node validation and revocation are not checked.
|
|
33091
33186
|
`);
|
|
@@ -33183,7 +33278,7 @@ function registerAuthGrantsCommand(auth, options = {}) {
|
|
|
33183
33278
|
ExitCode.ERROR,
|
|
33184
33279
|
{
|
|
33185
33280
|
causeCode: cause.code,
|
|
33186
|
-
hint: `Do not use --revoke again. After fixing local profile storage, run: ${
|
|
33281
|
+
hint: `Do not use --revoke again. After fixing local profile storage, run: ${CLI_NAME16} --profile ${ctx.profile} auth grants remove ${cid}`
|
|
33187
33282
|
}
|
|
33188
33283
|
);
|
|
33189
33284
|
}
|
|
@@ -33248,7 +33343,7 @@ init_safe_text();
|
|
|
33248
33343
|
init_raw_encryption();
|
|
33249
33344
|
init_space();
|
|
33250
33345
|
import { DEFAULT_EXPIRY as DEFAULT_EXPIRY2, parseExpiry as parseExpiry3, SERVICE_LONG_TO_SHORT as SERVICE_LONG_TO_SHORT3, VAULT_PERMISSION_SERVICE as VAULT_PERMISSION_SERVICE2 } from "@tinycloud/sdk-core";
|
|
33251
|
-
import { CLI_NAME as
|
|
33346
|
+
import { CLI_NAME as CLI_NAME17 } from "@tinycloud/operations/cli-name";
|
|
33252
33347
|
var APPS_AGENT_PRESET = "apps-agent";
|
|
33253
33348
|
var APPS_AGENT_SPACE = "applications";
|
|
33254
33349
|
var APPS_AGENT_EXPIRY_DAYS = 7;
|
|
@@ -33330,7 +33425,7 @@ function lifetimePhrase(ms2) {
|
|
|
33330
33425
|
return `${hours} hour${hours === 1 ? "" : "s"}`;
|
|
33331
33426
|
}
|
|
33332
33427
|
function revokeSentence(lifetimeMs2) {
|
|
33333
|
-
return `It lasts up to ${lifetimePhrase(lifetimeMs2)}; revoke it any time with \`${
|
|
33428
|
+
return `It lasts up to ${lifetimePhrase(lifetimeMs2)}; revoke it any time with \`${CLI_NAME17} delegation revoke <id>\`.`;
|
|
33334
33429
|
}
|
|
33335
33430
|
function appsAgentScopeNotice(mode, appIds = []) {
|
|
33336
33431
|
const variant = APPS_AGENT_VARIANTS[mode];
|
|
@@ -33525,7 +33620,7 @@ function describeGrantScope(requested, requestedExpiry, applicationsSpace, maxLi
|
|
|
33525
33620
|
const durations = [lifetimeMs(requestedExpiry), ...entryLifetimes].filter((ms2) => ms2 !== void 0).map((ms2) => Math.min(ms2, maxLifetimeMs ?? Number.POSITIVE_INFINITY));
|
|
33526
33621
|
const longest = Math.max(...durations);
|
|
33527
33622
|
lines.push(
|
|
33528
|
-
durations.length === 0 || !Number.isFinite(longest) ? "Its lifetime could not be read from the request." : `It lasts up to ${lifetimePhrase(longest)}${requestedExpiry === void 0 ? " (no lifetime was requested; this is the default)" : ""}; revoke it any time with \`${
|
|
33623
|
+
durations.length === 0 || !Number.isFinite(longest) ? "Its lifetime could not be read from the request." : `It lasts up to ${lifetimePhrase(longest)}${requestedExpiry === void 0 ? " (no lifetime was requested; this is the default)" : ""}; revoke it any time with \`${CLI_NAME17} delegation revoke <id>\`.`
|
|
33529
33624
|
);
|
|
33530
33625
|
return { text: lines.join("\n"), entries: requested, extras };
|
|
33531
33626
|
}
|
|
@@ -33789,7 +33884,7 @@ async function prepareProfileRemoteLogout(ctx, expectedSessionCid, discardLocalC
|
|
|
33789
33884
|
"REVOCATION_AUTHORITY_UNAVAILABLE",
|
|
33790
33885
|
"OpenKey owner CID revocation requires a verified signed revoke-control grant or wallet proof. No CID was revoked; session, key and replica files remain for retry, while local replica reads stay denied.",
|
|
33791
33886
|
ExitCode.AUTH_REQUIRED,
|
|
33792
|
-
{ hint:
|
|
33887
|
+
{ hint: `This sign-in can't be revoked from the CLI yet. Revoke it at https://account.tinycloud.xyz/delegations. To sign out on this computer only, run \`${CLI_NAME18} auth logout\`.` }
|
|
33793
33888
|
);
|
|
33794
33889
|
}
|
|
33795
33890
|
const signerDid = addressToDID(
|
|
@@ -33833,7 +33928,7 @@ async function revokeForLogout(ctx, discardLocalChanges) {
|
|
|
33833
33928
|
"REVOCATION_FAILED",
|
|
33834
33929
|
`Revocation failed for ${cid}: ${result.error.message}. Session, key, and replica files are retained for retry; local reads stay denied.`,
|
|
33835
33930
|
ExitCode.ERROR,
|
|
33836
|
-
{ cid, hint: `Retry \`${
|
|
33931
|
+
{ cid, hint: `Retry \`${CLI_NAME18} auth logout --revoke\` when the node is available.` }
|
|
33837
33932
|
);
|
|
33838
33933
|
}
|
|
33839
33934
|
if (result.data.cid !== cid || result.data.revoked !== true) {
|
|
@@ -33852,7 +33947,7 @@ async function revokeForLogout(ctx, discardLocalChanges) {
|
|
|
33852
33947
|
"REVOCATION_FAILED",
|
|
33853
33948
|
`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.`,
|
|
33854
33949
|
ExitCode.ERROR,
|
|
33855
|
-
{ hint: `Retry \`${
|
|
33950
|
+
{ hint: `Retry \`${CLI_NAME18} auth logout --revoke\` when online authority is available.` }
|
|
33856
33951
|
);
|
|
33857
33952
|
}
|
|
33858
33953
|
}
|
|
@@ -34051,7 +34146,7 @@ async function finishProfileLogout(ctx, options = {}) {
|
|
|
34051
34146
|
function registerAuthCommand(program, options) {
|
|
34052
34147
|
const auth = program.command("auth").description("Authentication management");
|
|
34053
34148
|
registerAuthGrantsCommand(auth, { openKeyAcquisition: options?.openKeyAcquisition });
|
|
34054
|
-
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 \`${
|
|
34149
|
+
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_NAME18} 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("--force", "Replica-enabled OpenKey login: replace the stored session key even when recorded shares could not be checked or hold unexpired shares that need the current key").option("--keys <folder>", "Also let this sign-in create and use encryption keys in this folder (for example `qa`). The node must support named keys. With OpenKey, the first time on a profile also needs `--owner`.").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) => {
|
|
34055
34150
|
try {
|
|
34056
34151
|
if (options2.device && options2.paste) {
|
|
34057
34152
|
throw new CLIError("INVALID_ARGUMENT", "--device and --paste are mutually exclusive.", ExitCode.USAGE_ERROR);
|
|
@@ -34209,7 +34304,7 @@ function registerAuthCommand(program, options) {
|
|
|
34209
34304
|
authenticated: false,
|
|
34210
34305
|
replicasRemoved,
|
|
34211
34306
|
replicasKept: options2.keepReplicas === true,
|
|
34212
|
-
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 \`${
|
|
34307
|
+
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_NAME18} delegation revoke <cid>\` to end access now.`,
|
|
34213
34308
|
...online === void 0 ? {} : {
|
|
34214
34309
|
revokedCids: online.plan.cids,
|
|
34215
34310
|
revocationExcluded: online.plan.excluded
|
|
@@ -34354,7 +34449,7 @@ ${appsAgentScopeNotice(preset.mode, preset.appIds)}
|
|
|
34354
34449
|
if (profile.authMethod === "openkey" || options2.device) {
|
|
34355
34450
|
const key = await ProfileManager.getKey(ctx.profile);
|
|
34356
34451
|
if (!key) {
|
|
34357
|
-
throw new CLIError("NO_KEY", `No key found for profile "${ctx.profile}". Run \`${
|
|
34452
|
+
throw new CLIError("NO_KEY", `No key found for profile "${ctx.profile}". Run \`${CLI_NAME18} --profile ${ctx.profile} auth rotate\` to create a new key and sign in.`, ExitCode.AUTH_REQUIRED);
|
|
34358
34453
|
}
|
|
34359
34454
|
const openkeyHost = resolveOpenKeyHost(profile);
|
|
34360
34455
|
const grants = [];
|
|
@@ -34374,7 +34469,7 @@ ${appsAgentScopeNotice(preset.mode, preset.appIds)}
|
|
|
34374
34469
|
if (notices) process.stderr.write(`${notices.prompts[index]}
|
|
34375
34470
|
`);
|
|
34376
34471
|
const reason = permissionGrantReason(
|
|
34377
|
-
`Grant requested TinyCloud permissions from \`${
|
|
34472
|
+
`Grant requested TinyCloud permissions from \`${CLI_NAME18} auth request --grant\`.`,
|
|
34378
34473
|
group
|
|
34379
34474
|
);
|
|
34380
34475
|
const request = grantRequestPermissions(group, profile.spaceId ?? profile.spaceName);
|
|
@@ -34484,7 +34579,7 @@ ${appsAgentScopeNotice(preset.mode, preset.appIds)}
|
|
|
34484
34579
|
kind: parsed.kind,
|
|
34485
34580
|
requestId: parsed.requestId,
|
|
34486
34581
|
requested: parsed.requested,
|
|
34487
|
-
next: `${
|
|
34582
|
+
next: `${CLI_NAME18} auth retry ${parsed.requestId}`
|
|
34488
34583
|
});
|
|
34489
34584
|
return;
|
|
34490
34585
|
}
|
|
@@ -35093,7 +35188,7 @@ async function acquireOpenKeyApproval(params) {
|
|
|
35093
35188
|
if (!key) {
|
|
35094
35189
|
throw new CLIError(
|
|
35095
35190
|
"NO_KEY",
|
|
35096
|
-
`No key found for profile "${params.ctx.profile}". Run \`${
|
|
35191
|
+
`No key found for profile "${params.ctx.profile}". Run \`${CLI_NAME18} --profile ${params.ctx.profile} auth rotate\` to create a new key and sign in.`,
|
|
35097
35192
|
ExitCode.AUTH_REQUIRED
|
|
35098
35193
|
);
|
|
35099
35194
|
}
|
|
@@ -35166,7 +35261,7 @@ async function ensureDelegationAuthority(params) {
|
|
|
35166
35261
|
if (!key) {
|
|
35167
35262
|
throw new CLIError(
|
|
35168
35263
|
"NO_KEY",
|
|
35169
|
-
`No key found for profile "${params.ctx.profile}". Run \`${
|
|
35264
|
+
`No key found for profile "${params.ctx.profile}". Run \`${CLI_NAME18} --profile ${params.ctx.profile} auth rotate\` to create a new key and sign in.`,
|
|
35170
35265
|
ExitCode.AUTH_REQUIRED
|
|
35171
35266
|
);
|
|
35172
35267
|
}
|
|
@@ -35420,7 +35515,7 @@ async function rotateAuthKey(profileName2, host, options = {}) {
|
|
|
35420
35515
|
if (!profile.privateKey) {
|
|
35421
35516
|
throw new CLIError(
|
|
35422
35517
|
"LOCAL_OWNER_KEY_REQUIRED",
|
|
35423
|
-
`Profile "${profileName2}" does not have a local owner private key. Run \`${
|
|
35518
|
+
`Profile "${profileName2}" does not have a local owner private key. Run \`${CLI_NAME18} auth login --method local\` first.`,
|
|
35424
35519
|
ExitCode.AUTH_REQUIRED
|
|
35425
35520
|
);
|
|
35426
35521
|
}
|
|
@@ -35678,6 +35773,8 @@ async function refreshPreparedOpenKeySession(params) {
|
|
|
35678
35773
|
if (expectedOwner && normalizePkhIdentifier(owner) !== normalizePkhIdentifier(expectedOwner)) {
|
|
35679
35774
|
throw new CLIError("OPENKEY_OWNER_MISMATCH", "The verified owner differs from this profile. No approval was started.", ExitCode.PERMISSION_DENIED);
|
|
35680
35775
|
}
|
|
35776
|
+
const namedKeys = options.keys === void 0 ? void 0 : parseKeysPrefix(options.keys, canonicalOwnerDid(owner));
|
|
35777
|
+
if (namedKeys !== void 0) await assertNodeSupportsNamedKeys(host);
|
|
35681
35778
|
if (options.force !== true) await assertShareHistoryAllowsKeyDrop(profileName2, "login");
|
|
35682
35779
|
const replicationConfig = createCliReplicationConfig(profileName2, replication, { debug: false, quiet: true }, true);
|
|
35683
35780
|
if (!replicationConfig) throw new CLIError("V2_REQUIRED", "Replica configuration is unavailable; no approval was started.", ExitCode.AUTH_REQUIRED);
|
|
@@ -35698,12 +35795,15 @@ async function refreshPreparedOpenKeySession(params) {
|
|
|
35698
35795
|
capabilityRequest,
|
|
35699
35796
|
includeAccountRegistryPermissions: false,
|
|
35700
35797
|
autoBootstrapAccount: false,
|
|
35701
|
-
replication: replicationConfig
|
|
35798
|
+
replication: replicationConfig,
|
|
35799
|
+
...namedKeys === void 0 ? {} : { additionalRawAbilities: keyAbilitiesFor(namedKeys) }
|
|
35702
35800
|
});
|
|
35703
35801
|
let handoff;
|
|
35704
35802
|
let preparedDid;
|
|
35803
|
+
let handedBackSiwe;
|
|
35705
35804
|
const gateWasActive = await readGateSnapshot(profilePath8(profileName2)).then((gate) => gate !== void 0 && gate.principalHash !== null, () => true);
|
|
35706
35805
|
try {
|
|
35806
|
+
if (namedKeys !== void 0) process.stderr.write(KEYS_APPROVAL_NOTE(namedKeys.prefix));
|
|
35707
35807
|
await node.signInWithOpenKey(async (prepared) => {
|
|
35708
35808
|
preparedDid = prepared.verificationMethod;
|
|
35709
35809
|
handoff = await startPreparedOpenKeyHandoff({
|
|
@@ -35712,14 +35812,23 @@ async function refreshPreparedOpenKeySession(params) {
|
|
|
35712
35812
|
noPopup: options.noPopup,
|
|
35713
35813
|
paste: options.paste
|
|
35714
35814
|
});
|
|
35715
|
-
return handoff.result
|
|
35815
|
+
return handoff.result.then((result) => {
|
|
35816
|
+
handedBackSiwe = result.signedMessage;
|
|
35817
|
+
return result;
|
|
35818
|
+
});
|
|
35716
35819
|
});
|
|
35717
35820
|
const active = node.restorableSession;
|
|
35718
35821
|
if (!active || !preparedDid || node.sessionDid !== preparedDid || active.verificationMethod !== preparedDid) {
|
|
35719
35822
|
throw new CLIError("OPENKEY_SESSION_MISMATCH", "OpenKey signed a different SDK session key; no profile or session was saved.", ExitCode.PERMISSION_DENIED);
|
|
35720
35823
|
}
|
|
35721
35824
|
const key = active.jwk;
|
|
35722
|
-
const verified = verifyScopedLogin({ ...active }, key, preparedDid, permissions, {
|
|
35825
|
+
const verified = verifyScopedLogin({ ...active }, key, preparedDid, permissions, {
|
|
35826
|
+
expectedOwner: owner,
|
|
35827
|
+
expiry,
|
|
35828
|
+
...namedKeys === void 0 ? {} : { namedKeyRow: namedKeys }
|
|
35829
|
+
});
|
|
35830
|
+
const grant = namedKeys === void 0 ? "none" : namedKeysGrantIn(active.siwe, namedKeys);
|
|
35831
|
+
if (grant === "partial") throw partialKeysGrantError();
|
|
35723
35832
|
const session = {
|
|
35724
35833
|
...verified.session,
|
|
35725
35834
|
...node.replicaStamp ? { replicaStamp: node.replicaStamp } : {},
|
|
@@ -35740,6 +35849,10 @@ async function refreshPreparedOpenKeySession(params) {
|
|
|
35740
35849
|
...replication.allowSecrets === true ? { allowSecrets: true } : {}
|
|
35741
35850
|
}
|
|
35742
35851
|
};
|
|
35852
|
+
if (namedKeys !== void 0) {
|
|
35853
|
+
delete updatedProfile.namedKeys;
|
|
35854
|
+
if (grant === "both") updatedProfile.namedKeys = namedKeys;
|
|
35855
|
+
}
|
|
35743
35856
|
await commitLogin(profileName2, snapshot, {
|
|
35744
35857
|
key,
|
|
35745
35858
|
session,
|
|
@@ -35749,6 +35862,10 @@ async function refreshPreparedOpenKeySession(params) {
|
|
|
35749
35862
|
});
|
|
35750
35863
|
await handoff?.complete(true);
|
|
35751
35864
|
announceFirstOwner(profileName2, expectedOwner, owner);
|
|
35865
|
+
if (namedKeys !== void 0) {
|
|
35866
|
+
if (grant === "both") await readyNamedKeysNetwork(node);
|
|
35867
|
+
else process.stderr.write(keysNotGrantedAfterLogin(profileName2));
|
|
35868
|
+
}
|
|
35752
35869
|
return {
|
|
35753
35870
|
profile: updatedProfile,
|
|
35754
35871
|
delegationData: session,
|
|
@@ -35758,14 +35875,14 @@ async function refreshPreparedOpenKeySession(params) {
|
|
|
35758
35875
|
} catch (error) {
|
|
35759
35876
|
await handoff?.complete(false);
|
|
35760
35877
|
await undoPreparedAttempt(profileName2, node, owner, gateWasActive).catch(() => void 0);
|
|
35761
|
-
throw error;
|
|
35878
|
+
throw namedKeys !== void 0 && isNarrowedRecapMismatch(error) && namedKeysGrantIn(handedBackSiwe, namedKeys) === "partial" ? partialKeysGrantError() : error;
|
|
35762
35879
|
}
|
|
35763
35880
|
}
|
|
35764
35881
|
var KEYS_APPROVAL_NOTE = (folder) => `On the OpenKey page, the row that mentions tinycloud.encryption lets this profile create and use keys in the folder "${folder}/". Keep every row ticked. To sign in without keys, untick that whole row and nothing else; any other change makes OpenKey refuse the sign-in.
|
|
35765
35882
|
`;
|
|
35766
|
-
var keysNotGrantedAfterLogin = (profileName2) => `You're signed in, but without named keys: the tinycloud.encryption permission wasn't approved. To add them, sign in again with \`${
|
|
35883
|
+
var keysNotGrantedAfterLogin = (profileName2) => `You're signed in, but without named keys: the tinycloud.encryption permission wasn't approved. To add them, sign in again with \`${CLI_NAME18} --profile ${profileName2} auth login --keys <folder>\` and leave the row that mentions tinycloud.encryption ticked.
|
|
35767
35884
|
`;
|
|
35768
|
-
var KEYS_FOLDER_NOT_READY = `You're signed in, but your encryption keys aren't set up yet. Try \`${
|
|
35885
|
+
var KEYS_FOLDER_NOT_READY = `You're signed in, but your encryption keys aren't set up yet. Try \`${CLI_NAME18} keys ensure <folder>/<name>\` again in a minute.
|
|
35769
35886
|
`;
|
|
35770
35887
|
async function readyNamedKeysNetwork(node) {
|
|
35771
35888
|
try {
|
|
@@ -35792,7 +35909,7 @@ async function refreshPreparedKeysSession(params) {
|
|
|
35792
35909
|
if (expectedOwner && normalizePkhIdentifier(owner) !== normalizePkhIdentifier(expectedOwner)) {
|
|
35793
35910
|
throw new CLIError("OPENKEY_OWNER_MISMATCH", "The verified owner differs from this profile. No approval was started.", ExitCode.PERMISSION_DENIED);
|
|
35794
35911
|
}
|
|
35795
|
-
const namedKeys = parseKeysPrefix(options.keys, owner);
|
|
35912
|
+
const namedKeys = parseKeysPrefix(options.keys, canonicalOwnerDid(owner));
|
|
35796
35913
|
await assertNodeSupportsNamedKeys(host);
|
|
35797
35914
|
if (options.force !== true) await assertShareHistoryAllowsKeyDrop(profileName2, "login");
|
|
35798
35915
|
const node = new TinyCloudNode3({
|
|
@@ -35805,6 +35922,7 @@ async function refreshPreparedKeysSession(params) {
|
|
|
35805
35922
|
});
|
|
35806
35923
|
let handoff;
|
|
35807
35924
|
let preparedDid;
|
|
35925
|
+
let handedBackSiwe;
|
|
35808
35926
|
try {
|
|
35809
35927
|
process.stderr.write(KEYS_APPROVAL_NOTE(namedKeys.prefix));
|
|
35810
35928
|
await node.signInWithOpenKey(async (prepared) => {
|
|
@@ -35815,7 +35933,10 @@ async function refreshPreparedKeysSession(params) {
|
|
|
35815
35933
|
noPopup: options.noPopup,
|
|
35816
35934
|
paste: options.paste
|
|
35817
35935
|
});
|
|
35818
|
-
return handoff.result
|
|
35936
|
+
return handoff.result.then((result) => {
|
|
35937
|
+
handedBackSiwe = result.signedMessage;
|
|
35938
|
+
return result;
|
|
35939
|
+
});
|
|
35819
35940
|
});
|
|
35820
35941
|
const active = node.restorableSession;
|
|
35821
35942
|
if (!active || !preparedDid || node.sessionDid !== preparedDid || active.verificationMethod !== preparedDid) {
|
|
@@ -35849,16 +35970,9 @@ async function refreshPreparedKeysSession(params) {
|
|
|
35849
35970
|
} catch (error) {
|
|
35850
35971
|
await handoff?.complete(false);
|
|
35851
35972
|
await node.replication?.close();
|
|
35852
|
-
throw isNarrowedRecapMismatch(error) ? partialKeysGrantError() : error;
|
|
35973
|
+
throw isNarrowedRecapMismatch(error) && namedKeysGrantIn(handedBackSiwe, namedKeys) === "partial" ? partialKeysGrantError() : error;
|
|
35853
35974
|
}
|
|
35854
35975
|
}
|
|
35855
|
-
function partialKeysGrantError() {
|
|
35856
|
-
return new CLIError(
|
|
35857
|
-
"KEYS_PARTIAL_GRANT",
|
|
35858
|
-
"Named keys need both permissions (create and use), but only one was approved, so this sign-in wasn't saved. Sign in again and approve both.",
|
|
35859
|
-
ExitCode.PERMISSION_DENIED
|
|
35860
|
-
);
|
|
35861
|
-
}
|
|
35862
35976
|
async function undoPreparedAttempt(profileName2, node, owner, gateWasActive) {
|
|
35863
35977
|
if (!gateWasActive) {
|
|
35864
35978
|
const undone = await ProfileManager.withLock(profileName2, async () => {
|
|
@@ -35897,14 +36011,7 @@ async function refreshOpenKeySession(profileName2, host, options = {}) {
|
|
|
35897
36011
|
const estimatedExpiry = expiry === void 0 ? void 0 : new Date(Date.now() + expiry.durationMs).toISOString();
|
|
35898
36012
|
assertSessionReplaceable(profileName2, snapshot, expectedOwner, permissions, estimatedExpiry);
|
|
35899
36013
|
}
|
|
35900
|
-
if (options.keys !== void 0) {
|
|
35901
|
-
if (replication.prefixes.length > 0) {
|
|
35902
|
-
throw new CLIError(
|
|
35903
|
-
"INVALID_ARGUMENT",
|
|
35904
|
-
`With OpenKey sign-in, \`--keys\` can't be used on a profile that has replication prefixes (given with \`--replication-prefix\` or saved from an earlier sign-in). Nothing was started. Use a separate profile for named keys: \`${CLI_NAME17} --profile <name> auth login --owner did:pkh:eip155:<chain>:<address> --keys <folder>\`.`,
|
|
35905
|
-
ExitCode.USAGE_ERROR
|
|
35906
|
-
);
|
|
35907
|
-
}
|
|
36014
|
+
if (options.keys !== void 0 && replication.prefixes.length === 0) {
|
|
35908
36015
|
return refreshPreparedKeysSession({ profileName: profileName2, host, profile, snapshot, options, expectedOwner, expiry });
|
|
35909
36016
|
}
|
|
35910
36017
|
if (replication.prefixes.length > 0) {
|
|
@@ -35982,7 +36089,7 @@ async function refreshOpenKeySession(profileName2, host, options = {}) {
|
|
|
35982
36089
|
}
|
|
35983
36090
|
|
|
35984
36091
|
// src/commands/completion.ts
|
|
35985
|
-
import { CLI_NAME as
|
|
36092
|
+
import { CLI_NAME as CLI_NAME19 } from "@tinycloud/operations/cli-name";
|
|
35986
36093
|
function registerCompletionCommand(program) {
|
|
35987
36094
|
const completion = program.command("completion").description("Generate shell completions");
|
|
35988
36095
|
completion.command("bash").description("Output bash completions").action(() => {
|
|
@@ -35999,7 +36106,7 @@ function registerCompletionCommand(program) {
|
|
|
35999
36106
|
});
|
|
36000
36107
|
}
|
|
36001
36108
|
function generateBashCompletion() {
|
|
36002
|
-
return `# ${
|
|
36109
|
+
return `# ${CLI_NAME19} bash completion
|
|
36003
36110
|
_tc_completions() {
|
|
36004
36111
|
local cur prev commands subcommands
|
|
36005
36112
|
COMPREPLY=()
|
|
@@ -36024,11 +36131,11 @@ _tc_completions() {
|
|
|
36024
36131
|
COMPREPLY=( $(compgen -W "\${subcommands}" -- "\${cur}") )
|
|
36025
36132
|
fi
|
|
36026
36133
|
}
|
|
36027
|
-
complete -F _tc_completions ${
|
|
36134
|
+
complete -F _tc_completions ${CLI_NAME19}
|
|
36028
36135
|
`;
|
|
36029
36136
|
}
|
|
36030
36137
|
function generateZshCompletion() {
|
|
36031
|
-
return `#compdef ${
|
|
36138
|
+
return `#compdef ${CLI_NAME19}
|
|
36032
36139
|
|
|
36033
36140
|
_tc() {
|
|
36034
36141
|
local -a commands
|
|
@@ -36079,50 +36186,50 @@ _tc
|
|
|
36079
36186
|
`;
|
|
36080
36187
|
}
|
|
36081
36188
|
function generateFishCompletion() {
|
|
36082
|
-
return `# ${
|
|
36189
|
+
return `# ${CLI_NAME19} fish completion
|
|
36083
36190
|
set -l commands init auth kv space delegation share node profile completion
|
|
36084
36191
|
|
|
36085
36192
|
# Disable file completion by default
|
|
36086
|
-
complete -c ${
|
|
36193
|
+
complete -c ${CLI_NAME19} -f
|
|
36087
36194
|
|
|
36088
36195
|
# Top-level commands
|
|
36089
|
-
complete -c ${
|
|
36090
|
-
complete -c ${
|
|
36091
|
-
complete -c ${
|
|
36092
|
-
complete -c ${
|
|
36093
|
-
complete -c ${
|
|
36094
|
-
complete -c ${
|
|
36095
|
-
complete -c ${
|
|
36096
|
-
complete -c ${
|
|
36097
|
-
complete -c ${
|
|
36196
|
+
complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a init -d "Initialize a new TinyCloud profile"
|
|
36197
|
+
complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a auth -d "Authentication management"
|
|
36198
|
+
complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a kv -d "Key-value store operations"
|
|
36199
|
+
complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a space -d "Space management"
|
|
36200
|
+
complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a delegation -d "Manage delegations"
|
|
36201
|
+
complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a share -d "Share data with others"
|
|
36202
|
+
complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a node -d "Node health and info"
|
|
36203
|
+
complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a profile -d "Profile management"
|
|
36204
|
+
complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a completion -d "Generate shell completions"
|
|
36098
36205
|
|
|
36099
36206
|
# Subcommands
|
|
36100
|
-
complete -c ${
|
|
36101
|
-
complete -c ${
|
|
36102
|
-
complete -c ${
|
|
36103
|
-
complete -c ${
|
|
36104
|
-
complete -c ${
|
|
36105
|
-
complete -c ${
|
|
36106
|
-
complete -c ${
|
|
36107
|
-
complete -c ${
|
|
36207
|
+
complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from auth" -a "login logout rotate status whoami"
|
|
36208
|
+
complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from kv" -a "get put delete list head"
|
|
36209
|
+
complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from space" -a "list create info switch"
|
|
36210
|
+
complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from delegation" -a "create list info revoke"
|
|
36211
|
+
complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from share" -a "create receive list revoke"
|
|
36212
|
+
complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from node" -a "health version status"
|
|
36213
|
+
complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from profile" -a "list create show switch delete"
|
|
36214
|
+
complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from completion" -a "bash zsh fish"
|
|
36108
36215
|
|
|
36109
36216
|
# Global options
|
|
36110
|
-
complete -c ${
|
|
36111
|
-
complete -c ${
|
|
36112
|
-
complete -c ${
|
|
36113
|
-
complete -c ${
|
|
36114
|
-
complete -c ${
|
|
36115
|
-
complete -c ${
|
|
36116
|
-
complete -c ${
|
|
36117
|
-
complete -c ${
|
|
36217
|
+
complete -c ${CLI_NAME19} -l profile -s p -d "Profile to use"
|
|
36218
|
+
complete -c ${CLI_NAME19} -l host -s H -d "TinyCloud node URL"
|
|
36219
|
+
complete -c ${CLI_NAME19} -l verbose -s v -d "Enable verbose output"
|
|
36220
|
+
complete -c ${CLI_NAME19} -l no-cache -d "Disable caching"
|
|
36221
|
+
complete -c ${CLI_NAME19} -l quiet -s q -d "Suppress non-essential output"
|
|
36222
|
+
complete -c ${CLI_NAME19} -l replication -d "Enable read-through replication"
|
|
36223
|
+
complete -c ${CLI_NAME19} -l no-replication -d "Disable replication"
|
|
36224
|
+
complete -c ${CLI_NAME19} -l replication-debug -d "Write replication diagnostics to stderr"
|
|
36118
36225
|
`;
|
|
36119
36226
|
}
|
|
36120
36227
|
|
|
36121
36228
|
// src/commands/courier.ts
|
|
36122
|
-
import { CLI_NAME as
|
|
36123
|
-
var TC_OPTIONS_BEFORE_COURIER = `${
|
|
36229
|
+
import { CLI_NAME as CLI_NAME20 } from "@tinycloud/operations/cli-name";
|
|
36230
|
+
var TC_OPTIONS_BEFORE_COURIER = `${CLI_NAME20} courier takes no ${CLI_NAME20} options and does not use ${CLI_NAME20} profiles; Courier keeps its own profile in $COURIER_HOME (default ~/.courier). Put flags after "courier", e.g. ${CLI_NAME20} courier init --host <url>.`;
|
|
36124
36231
|
function registerCourierCommand(program) {
|
|
36125
|
-
const courier = program.command("courier").description(`Agent-to-agent threads over TinyCloud; run \`${
|
|
36232
|
+
const courier = program.command("courier").description(`Agent-to-agent threads over TinyCloud; run \`${CLI_NAME20} courier --help\` (beta)`).argument("[args...]").allowUnknownOption().helpOption(false);
|
|
36126
36233
|
courier.action(() => courier.error(TC_OPTIONS_BEFORE_COURIER, { exitCode: 2, code: "tc.courier.options" }));
|
|
36127
36234
|
}
|
|
36128
36235
|
|
|
@@ -36167,7 +36274,7 @@ init_formatter();
|
|
|
36167
36274
|
init_errors();
|
|
36168
36275
|
init_constants();
|
|
36169
36276
|
init_sdk();
|
|
36170
|
-
import { CLI_NAME as
|
|
36277
|
+
import { CLI_NAME as CLI_NAME21 } from "@tinycloud/operations/cli-name";
|
|
36171
36278
|
import { PermissionNotInManifestError } from "@tinycloud/node-sdk";
|
|
36172
36279
|
async function queryDelegations(node, options, until) {
|
|
36173
36280
|
const found = [];
|
|
@@ -36178,7 +36285,7 @@ async function queryDelegations(node, options, until) {
|
|
|
36178
36285
|
if (page.error.code === "PERMISSION_DENIED") {
|
|
36179
36286
|
throw new CLIError(
|
|
36180
36287
|
"PERMISSION_DENIED",
|
|
36181
|
-
`This session cannot read delegations: it lacks tinycloud.delegation/list. Run \`${
|
|
36288
|
+
`This session cannot read delegations: it lacks tinycloud.delegation/list. Run \`${CLI_NAME21} auth login\` again to grant it.`,
|
|
36182
36289
|
ExitCode.PERMISSION_DENIED
|
|
36183
36290
|
);
|
|
36184
36291
|
}
|
|
@@ -36353,7 +36460,7 @@ init_constants();
|
|
|
36353
36460
|
init_formatter();
|
|
36354
36461
|
init_theme();
|
|
36355
36462
|
init_errors();
|
|
36356
|
-
import { CLI_NAME as
|
|
36463
|
+
import { CLI_NAME as CLI_NAME22 } from "@tinycloud/operations/cli-name";
|
|
36357
36464
|
init_session_expired();
|
|
36358
36465
|
init_types();
|
|
36359
36466
|
function registerDoctorCommand(program) {
|
|
@@ -36387,7 +36494,7 @@ function registerDoctorCommand(program) {
|
|
|
36387
36494
|
const profile = await ProfileManager.getProfile(profileName2);
|
|
36388
36495
|
keyDetail = profile.did ? `${profile.did.slice(0, 20)}...` : "key found";
|
|
36389
36496
|
} else {
|
|
36390
|
-
keyDetail = `no key \u2014 run ${
|
|
36497
|
+
keyDetail = `no key \u2014 run ${CLI_NAME22} init`;
|
|
36391
36498
|
}
|
|
36392
36499
|
} catch {
|
|
36393
36500
|
keyDetail = "error reading key";
|
|
@@ -36409,7 +36516,7 @@ function registerDoctorCommand(program) {
|
|
|
36409
36516
|
} else if (state2 === "unverified") {
|
|
36410
36517
|
sessionDetail = `not usable (no signed OpenKey proof) \u2014 ${signInAgainHint(profileName2, resolveProfilePosture(profile))}`;
|
|
36411
36518
|
} else {
|
|
36412
|
-
sessionDetail = sessionOk ? "active" : `no session \u2014 run ${
|
|
36519
|
+
sessionDetail = sessionOk ? "active" : `no session \u2014 run ${CLI_NAME22} auth login`;
|
|
36413
36520
|
}
|
|
36414
36521
|
} catch {
|
|
36415
36522
|
sessionDetail = "error reading session";
|
|
@@ -36437,7 +36544,7 @@ function registerDoctorCommand(program) {
|
|
|
36437
36544
|
try {
|
|
36438
36545
|
const profile = await ProfileManager.getProfile(profileName2);
|
|
36439
36546
|
spaceOk = Boolean(profile.spaceId);
|
|
36440
|
-
spaceDetail = spaceOk ? `${profile.spaceId.slice(0, 16)}...` : `no space \u2014 run ${
|
|
36547
|
+
spaceDetail = spaceOk ? `${profile.spaceId.slice(0, 16)}...` : `no space \u2014 run ${CLI_NAME22} space create`;
|
|
36441
36548
|
} catch {
|
|
36442
36549
|
spaceDetail = "error checking space";
|
|
36443
36550
|
}
|
|
@@ -36627,7 +36734,7 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
|
|
|
36627
36734
|
}
|
|
36628
36735
|
|
|
36629
36736
|
// src/commands/enable.ts
|
|
36630
|
-
import { CLI_NAME as
|
|
36737
|
+
import { CLI_NAME as CLI_NAME23 } from "@tinycloud/operations/cli-name";
|
|
36631
36738
|
init_constants();
|
|
36632
36739
|
init_profiles();
|
|
36633
36740
|
init_errors();
|
|
@@ -36635,7 +36742,7 @@ init_formatter();
|
|
|
36635
36742
|
init_publishing_manifest();
|
|
36636
36743
|
function registerEnableCommand(program) {
|
|
36637
36744
|
const enable = program.command("enable").description("Enable a narrowly scoped TinyCloud service");
|
|
36638
|
-
enable.command("share").description(`Approve Share publishing scope through OpenKey device authorization (same as \`${
|
|
36745
|
+
enable.command("share").description(`Approve Share publishing scope through OpenKey device authorization (same as \`${CLI_NAME23} auth login --device --manifest builtin:share-publishing\`). Use a dedicated profile, e.g. \`${CLI_NAME23} init --name publisher --key-only && ${CLI_NAME23} --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) => {
|
|
36639
36746
|
try {
|
|
36640
36747
|
const globalOptions = command.optsWithGlobals();
|
|
36641
36748
|
const context = await ProfileManager.resolveContext(globalOptions, { storedSession: "unused" });
|
|
@@ -36670,7 +36777,7 @@ function registerEnableCommand(program) {
|
|
|
36670
36777
|
init_profiles();
|
|
36671
36778
|
init_constants();
|
|
36672
36779
|
init_sdk();
|
|
36673
|
-
import { CLI_NAME as
|
|
36780
|
+
import { CLI_NAME as CLI_NAME24 } from "@tinycloud/operations/cli-name";
|
|
36674
36781
|
import { operationSpaceResolver as operationSpaceResolver2 } from "@tinycloud/operations/delegation-binding";
|
|
36675
36782
|
|
|
36676
36783
|
// src/lib/knowledge-input.ts
|
|
@@ -36936,7 +37043,7 @@ function registerAppsCommand(program) {
|
|
|
36936
37043
|
"after",
|
|
36937
37044
|
`
|
|
36938
37045
|
Examples:
|
|
36939
|
-
$ ${
|
|
37046
|
+
$ ${CLI_NAME24} apps install xyz.tinycloud.sample.healthlog
|
|
36940
37047
|
`
|
|
36941
37048
|
).action(async (appIdInput, _options, cmd) => {
|
|
36942
37049
|
try {
|
|
@@ -37028,7 +37135,7 @@ init_profiles();
|
|
|
37028
37135
|
init_formatter();
|
|
37029
37136
|
init_errors();
|
|
37030
37137
|
init_constants();
|
|
37031
|
-
import { CLI_NAME as
|
|
37138
|
+
import { CLI_NAME as CLI_NAME25 } from "@tinycloud/operations/cli-name";
|
|
37032
37139
|
function registerInitCommand(program) {
|
|
37033
37140
|
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) => {
|
|
37034
37141
|
try {
|
|
@@ -37046,7 +37153,7 @@ function registerInitCommand(program) {
|
|
|
37046
37153
|
if (await ProfileManager.profileExists(profileName2)) {
|
|
37047
37154
|
throw new CLIError(
|
|
37048
37155
|
"PROFILE_EXISTS",
|
|
37049
|
-
`Profile "${profileName2}" already exists. Use \`${
|
|
37156
|
+
`Profile "${profileName2}" already exists. Use \`${CLI_NAME25} profile delete ${profileName2}\` first or choose a different name.`,
|
|
37050
37157
|
ExitCode.ERROR
|
|
37051
37158
|
);
|
|
37052
37159
|
}
|
|
@@ -37102,7 +37209,7 @@ init_session_expired();
|
|
|
37102
37209
|
init_constants();
|
|
37103
37210
|
init_profiles();
|
|
37104
37211
|
import { invokeOperation as invokeOperation2 } from "@tinycloud/operations";
|
|
37105
|
-
import { CLI_NAME as
|
|
37212
|
+
import { CLI_NAME as CLI_NAME26 } from "@tinycloud/operations/cli-name";
|
|
37106
37213
|
init_permissions();
|
|
37107
37214
|
init_errors();
|
|
37108
37215
|
init_formatter();
|
|
@@ -37205,7 +37312,7 @@ function knowledgeFailure(result, profile, appId) {
|
|
|
37205
37312
|
case "error": {
|
|
37206
37313
|
if (result.error.code === "SESSION_NOT_FOUND" || result.error.code === "PROFILE_POSTURE_NOT_ALLOWED" && result.context.posture === "unauthenticated") {
|
|
37207
37314
|
return new CLIError("AUTH_REQUIRED", "Not signed in to TinyCloud.", ExitCode.AUTH_REQUIRED, {
|
|
37208
|
-
hint: `Sign in with: ${
|
|
37315
|
+
hint: `Sign in with: ${CLI_NAME26} --profile ${result.context.profile} auth login`
|
|
37209
37316
|
});
|
|
37210
37317
|
}
|
|
37211
37318
|
if (result.error.code === "NODE_UNREACHABLE") {
|
|
@@ -37235,7 +37342,7 @@ function knowledgeFailure(result, profile, appId) {
|
|
|
37235
37342
|
}
|
|
37236
37343
|
}
|
|
37237
37344
|
function missingGrantHint(profile, appId) {
|
|
37238
|
-
const request = `${
|
|
37345
|
+
const request = `${CLI_NAME26} --profile ${profile} auth request`;
|
|
37239
37346
|
const lines = appId === void 0 ? [
|
|
37240
37347
|
` ${request} --preset ${APPS_AGENT_PRESET} --emit req.json (read-only, all installed apps)`,
|
|
37241
37348
|
` ${request} --cap tinycloud.kv:${APPS_AGENT_SPACE}::get,list --emit req.json (all files, no table access)`,
|
|
@@ -37248,7 +37355,7 @@ function missingGrantHint(profile, appId) {
|
|
|
37248
37355
|
return [
|
|
37249
37356
|
"Ask the owner for a grant, narrowest first. Create the request:",
|
|
37250
37357
|
...lines,
|
|
37251
|
-
`The owner runs \`${
|
|
37358
|
+
`The owner runs \`${CLI_NAME26} auth grant req.json > grant.json\`; then import the result with \`${CLI_NAME26} --profile ${profile} auth import grant.json\`.`
|
|
37252
37359
|
].join("\n");
|
|
37253
37360
|
}
|
|
37254
37361
|
|
|
@@ -37397,9 +37504,12 @@ function registerKvCommand(program) {
|
|
|
37397
37504
|
return handleError(error);
|
|
37398
37505
|
}
|
|
37399
37506
|
});
|
|
37400
|
-
kv.command("put <key> [value]").description("Set a value").option("--file <path>", "Read value from file").option("--stdin", "Read value from stdin").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").action(async (key, value, options, cmd) => {
|
|
37507
|
+
kv.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) => {
|
|
37401
37508
|
try {
|
|
37402
37509
|
assertAddressableKey(key);
|
|
37510
|
+
if (options.ifNoneMatch !== void 0 && options.ifNoneMatch !== "*") {
|
|
37511
|
+
throw new CLIError("USAGE_ERROR", "`--if-none-match` only accepts `'*'`.", ExitCode.USAGE_ERROR);
|
|
37512
|
+
}
|
|
37403
37513
|
const globalOpts = cmd.optsWithGlobals();
|
|
37404
37514
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
37405
37515
|
const node = await ensureAuthenticated(ctx);
|
|
@@ -37423,8 +37533,15 @@ function registerKvCommand(program) {
|
|
|
37423
37533
|
}
|
|
37424
37534
|
}
|
|
37425
37535
|
const { kv: kv2, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
37426
|
-
const result = await withSpinner(`Writing ${key}...`, () => kv2.put(
|
|
37536
|
+
const result = await withSpinner(`Writing ${key}...`, () => kv2.put(
|
|
37537
|
+
key,
|
|
37538
|
+
putValue,
|
|
37539
|
+
options.ifNoneMatch === "*" ? { ifNoneMatch: "*" } : void 0
|
|
37540
|
+
));
|
|
37427
37541
|
if (!result.ok) {
|
|
37542
|
+
if (options.ifNoneMatch === "*" && result.error.code === "KV_PRECONDITION_FAILED") {
|
|
37543
|
+
throw new CLIError("KV_ALREADY_EXISTS", `Key "${key}" already exists, so nothing was written.`, ExitCode.PERMISSION_DENIED);
|
|
37544
|
+
}
|
|
37428
37545
|
await throwKvError(result.error, spaceUri, ctx.profile);
|
|
37429
37546
|
}
|
|
37430
37547
|
outputJson({ key, written: true, ...retryFields(result.data?.retry) });
|
|
@@ -37555,7 +37672,7 @@ init_errors();
|
|
|
37555
37672
|
init_sdk();
|
|
37556
37673
|
import { readFile as readFile12 } from "fs/promises";
|
|
37557
37674
|
import { NamedKeyError, createNamedKeyClient } from "@tinycloud/node-sdk/encryption-keys";
|
|
37558
|
-
import { CLI_NAME as
|
|
37675
|
+
import { CLI_NAME as CLI_NAME27 } from "@tinycloud/operations/cli-name";
|
|
37559
37676
|
async function readStdin3() {
|
|
37560
37677
|
const chunks = [];
|
|
37561
37678
|
for await (const chunk of process.stdin) {
|
|
@@ -37579,7 +37696,7 @@ function cliErrorFromNamedKey(error, command, path) {
|
|
|
37579
37696
|
case "KEY_NOT_FOUND":
|
|
37580
37697
|
return new CLIError(
|
|
37581
37698
|
"NOT_FOUND",
|
|
37582
|
-
command === "decrypt" ? "The key this envelope needs doesn't exist on this node, so it can't be decrypted here." : `There's no key at ${path} yet. Create it with \`${
|
|
37699
|
+
command === "decrypt" ? "The key this envelope needs doesn't exist on this node, so it can't be decrypted here." : `There's no key at ${path} yet. Create it with \`${CLI_NAME27} keys ensure ${path}\`.`,
|
|
37583
37700
|
ExitCode.NOT_FOUND
|
|
37584
37701
|
);
|
|
37585
37702
|
case "KEY_BLOCKED":
|
|
@@ -37589,7 +37706,7 @@ function cliErrorFromNamedKey(error, command, path) {
|
|
|
37589
37706
|
}
|
|
37590
37707
|
if (error.status === 401 || error.status === 403) {
|
|
37591
37708
|
return new CLIError("PERMISSION_DENIED", error.message, ExitCode.PERMISSION_DENIED, {
|
|
37592
|
-
hint: `Sign in with \`${
|
|
37709
|
+
hint: `Sign in with \`${CLI_NAME27} auth login --keys <folder>\` for this key's folder.`
|
|
37593
37710
|
});
|
|
37594
37711
|
}
|
|
37595
37712
|
return new CLIError(error.code, error.message, error.status === void 0 ? ExitCode.ERROR : ExitCode.NODE_ERROR);
|
|
@@ -37609,7 +37726,7 @@ async function grantedPrefix(deps, profileName2) {
|
|
|
37609
37726
|
if (keys === void 0) {
|
|
37610
37727
|
throw new CLIError(
|
|
37611
37728
|
"KEYS_NOT_GRANTED",
|
|
37612
|
-
`Profile "${profileName2}" can't use named keys yet. Sign in with \`${
|
|
37729
|
+
`Profile "${profileName2}" can't use named keys yet. Sign in with \`${CLI_NAME27} --profile ${profileName2} auth login --keys <folder>\`.`,
|
|
37613
37730
|
ExitCode.PERMISSION_DENIED
|
|
37614
37731
|
);
|
|
37615
37732
|
}
|
|
@@ -37619,7 +37736,7 @@ function assertInGrantedPrefix(prefix, path, profileName2) {
|
|
|
37619
37736
|
if (!keyPathInPrefix(prefix, path)) {
|
|
37620
37737
|
throw new CLIError(
|
|
37621
37738
|
"KEY_OUTSIDE_GRANTED_PREFIX",
|
|
37622
|
-
`Key "${path}" is outside the folder "${prefix}/" this sign-in covers. To use it, sign in with \`${
|
|
37739
|
+
`Key "${path}" is outside the folder "${prefix}/" this sign-in covers. To use it, sign in with \`${CLI_NAME27} --profile ${profileName2} auth login --method local --keys <folder>\`.`,
|
|
37623
37740
|
ExitCode.PERMISSION_DENIED
|
|
37624
37741
|
);
|
|
37625
37742
|
}
|
|
@@ -37647,7 +37764,7 @@ var defaultDeps = {
|
|
|
37647
37764
|
};
|
|
37648
37765
|
function registerKeysCommand(program, deps = defaultDeps) {
|
|
37649
37766
|
const { outputJson: outputJson2, handleError: handleError2 } = deps;
|
|
37650
|
-
const keys = program.command("keys").description(`Named encryption keys (sign in with \`${
|
|
37767
|
+
const keys = program.command("keys").description(`Named encryption keys (sign in with \`${CLI_NAME27} auth login --keys <folder>\` first)`);
|
|
37651
37768
|
keys.command("ensure <path>").description("Create the named key if it doesn't exist yet, then show it").action(async (path, _options, cmd) => {
|
|
37652
37769
|
try {
|
|
37653
37770
|
const ctx = await deps.resolveContext(cmd.optsWithGlobals());
|
|
@@ -37725,7 +37842,7 @@ init_errors();
|
|
|
37725
37842
|
init_constants();
|
|
37726
37843
|
init_theme();
|
|
37727
37844
|
import { resolveManifestKnowledgeRoot as resolveManifestKnowledgeRoot2 } from "@tinycloud/sdk-core";
|
|
37728
|
-
import { CLI_NAME as
|
|
37845
|
+
import { CLI_NAME as CLI_NAME28 } from "@tinycloud/operations/cli-name";
|
|
37729
37846
|
import { readFile as readFile13 } from "fs/promises";
|
|
37730
37847
|
var DEFAULT_APP_SPACE = "applications";
|
|
37731
37848
|
function registerManifestCommand(program) {
|
|
@@ -37733,8 +37850,8 @@ function registerManifestCommand(program) {
|
|
|
37733
37850
|
manifest.command("resolve <source>").description("Resolve a manifest file or URL to its effective space, paths, and DB basenames").addHelpText("after", `
|
|
37734
37851
|
|
|
37735
37852
|
Examples:
|
|
37736
|
-
$ ${
|
|
37737
|
-
$ ${
|
|
37853
|
+
$ ${CLI_NAME28} manifest resolve ./manifest.json
|
|
37854
|
+
$ ${CLI_NAME28} manifest resolve https://app.example.com/manifest.json --json
|
|
37738
37855
|
|
|
37739
37856
|
What it shows:
|
|
37740
37857
|
- app_id, name, manifest_version
|
|
@@ -37744,7 +37861,7 @@ What it shows:
|
|
|
37744
37861
|
|
|
37745
37862
|
This command is read-only and does NOT contact the node \u2014 it just resolves
|
|
37746
37863
|
the manifest against the active profile's address/chain so you know which
|
|
37747
|
-
\`--space\` and \`--db\` values to pass to other ${
|
|
37864
|
+
\`--space\` and \`--db\` values to pass to other ${CLI_NAME28} commands.
|
|
37748
37865
|
`).action(async (source, _options, cmd) => {
|
|
37749
37866
|
try {
|
|
37750
37867
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -37813,7 +37930,7 @@ ${theme.heading("SQL databases")}
|
|
|
37813
37930
|
`);
|
|
37814
37931
|
}
|
|
37815
37932
|
process.stdout.write(theme.muted(`
|
|
37816
|
-
Use with: ${
|
|
37933
|
+
Use with: ${CLI_NAME28} sql query --space ${spaceName} --db <db> "..."
|
|
37817
37934
|
`));
|
|
37818
37935
|
}
|
|
37819
37936
|
if (permissions.length > 0) {
|
|
@@ -37933,7 +38050,7 @@ init_profiles();
|
|
|
37933
38050
|
init_formatter();
|
|
37934
38051
|
init_errors();
|
|
37935
38052
|
init_constants();
|
|
37936
|
-
import { CLI_NAME as
|
|
38053
|
+
import { CLI_NAME as CLI_NAME29 } from "@tinycloud/operations/cli-name";
|
|
37937
38054
|
import { createInterface as createInterface5 } from "readline";
|
|
37938
38055
|
init_theme();
|
|
37939
38056
|
init_types();
|
|
@@ -38079,9 +38196,9 @@ profile at command time. Precedence for every kv/sql command:
|
|
|
38079
38196
|
explicit --space flag > profile defaultSpace > primary space.
|
|
38080
38197
|
|
|
38081
38198
|
Examples:
|
|
38082
|
-
$ ${
|
|
38083
|
-
$ ${
|
|
38084
|
-
$ ${
|
|
38199
|
+
$ ${CLI_NAME29} profile set-default-space applications
|
|
38200
|
+
$ ${CLI_NAME29} profile set-default-space applications --profile cli-test
|
|
38201
|
+
$ ${CLI_NAME29} profile set-default-space --unset
|
|
38085
38202
|
`).action(async (name, options, cmd) => {
|
|
38086
38203
|
try {
|
|
38087
38204
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -38093,7 +38210,7 @@ Examples:
|
|
|
38093
38210
|
if (!options.unset && (name === void 0 || name === "")) {
|
|
38094
38211
|
throw new CLIError(
|
|
38095
38212
|
"USAGE_ERROR",
|
|
38096
|
-
`Provide a space name (e.g. \`${
|
|
38213
|
+
`Provide a space name (e.g. \`${CLI_NAME29} profile set-default-space applications\`) or pass --unset.`,
|
|
38097
38214
|
ExitCode.USAGE_ERROR
|
|
38098
38215
|
);
|
|
38099
38216
|
}
|
|
@@ -38152,7 +38269,7 @@ function parseOperatorType(raw) {
|
|
|
38152
38269
|
|
|
38153
38270
|
// src/commands/replica.ts
|
|
38154
38271
|
init_profile_replicas();
|
|
38155
|
-
import { CLI_NAME as
|
|
38272
|
+
import { CLI_NAME as CLI_NAME30 } from "@tinycloud/operations/cli-name";
|
|
38156
38273
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
38157
38274
|
import { constants as fsConstants5 } from "fs";
|
|
38158
38275
|
import { mkdir as mkdir5, open as open11, readdir as readdir6, rename as rename4, unlink as unlink5, lstat as lstat9 } from "fs/promises";
|
|
@@ -38293,11 +38410,11 @@ var EXIT_BY_CODE = {
|
|
|
38293
38410
|
var READ_FAILURE = {
|
|
38294
38411
|
absent: { code: "KEY_ABSENT", exit: ExitCode.NOT_FOUND, what: "is not in the replica (the replica covers its whole prefix)" },
|
|
38295
38412
|
deleted: { code: "KEY_DELETED", exit: ExitCode.NOT_FOUND, what: "was deleted at the source" },
|
|
38296
|
-
content_missing: { code: "CONTENT_MISSING", exit: ExitCode.NOT_FOUND, what: `is known but its content has not been fetched yet; run ${
|
|
38413
|
+
content_missing: { code: "CONTENT_MISSING", exit: ExitCode.NOT_FOUND, what: `is known but its content has not been fetched yet; run ${CLI_NAME30} replica sync` },
|
|
38297
38414
|
coverage_incomplete: {
|
|
38298
38415
|
code: "COVERAGE_INCOMPLETE",
|
|
38299
38416
|
exit: ExitCode.NOT_FOUND,
|
|
38300
|
-
what: `is not in the replica, which has not finished its first sync; run ${
|
|
38417
|
+
what: `is not in the replica, which has not finished its first sync; run ${CLI_NAME30} replica sync`
|
|
38301
38418
|
},
|
|
38302
38419
|
not_covered: { code: "NOT_COVERED", exit: ExitCode.USAGE_ERROR, what: "is outside the replica's prefix" }
|
|
38303
38420
|
};
|
|
@@ -38415,7 +38532,7 @@ async function existingReplicaName(profile, option) {
|
|
|
38415
38532
|
const names = await replicaNames(profile);
|
|
38416
38533
|
if (names.length === 1) return names[0];
|
|
38417
38534
|
if (names.length === 0) {
|
|
38418
|
-
throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, `Profile "${profile}" has no replicas. Create one with: ${
|
|
38535
|
+
throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, `Profile "${profile}" has no replicas. Create one with: ${CLI_NAME30} replica sync --prefix <prefix>`);
|
|
38419
38536
|
}
|
|
38420
38537
|
throw new CLIError("USAGE_ERROR", `Profile "${profile}" has several replicas (${names.join(", ")}); pass --replica <name>.`, ExitCode.USAGE_ERROR);
|
|
38421
38538
|
}
|
|
@@ -38523,9 +38640,9 @@ async function storedGrants(profile) {
|
|
|
38523
38640
|
function grantHint(space, prefix) {
|
|
38524
38641
|
return [
|
|
38525
38642
|
"A replica needs a device grant with get and sync on its prefix:",
|
|
38526
|
-
` device: ${
|
|
38527
|
-
` owner: ${
|
|
38528
|
-
` device: ${
|
|
38643
|
+
` device: ${CLI_NAME30} auth request --cap tinycloud.kv:${space}:${prefix}:get,list,metadata,sync --expiry 30d --emit req.json`,
|
|
38644
|
+
` owner: ${CLI_NAME30} auth grant req.json > grant.json`,
|
|
38645
|
+
` device: ${CLI_NAME30} auth import grant.json`
|
|
38529
38646
|
].join("\n");
|
|
38530
38647
|
}
|
|
38531
38648
|
function resolveSpace(input, prefix, grants) {
|
|
@@ -38616,7 +38733,7 @@ function describeStatus(status) {
|
|
|
38616
38733
|
formatField("Size", formatBytes(status.bytes)),
|
|
38617
38734
|
formatField("Grant", status.device.delegationCid),
|
|
38618
38735
|
formatField("Last error", status.lastError ? `${status.lastError.code}: ${status.lastError.message}` : null),
|
|
38619
|
-
formatField("Purge", status.purgePending ? `incomplete: content files remain; the next ${
|
|
38736
|
+
formatField("Purge", status.purgePending ? `incomplete: content files remain; the next ${CLI_NAME30} replica command retries` : null)
|
|
38620
38737
|
].join("\n");
|
|
38621
38738
|
}
|
|
38622
38739
|
async function writeFileAtomic(path, bytes) {
|
|
@@ -38780,7 +38897,7 @@ or coverage incomplete; 5 grant expired, revoked or not yet valid; 6 network;
|
|
|
38780
38897
|
(options, cmd) => run(async () => {
|
|
38781
38898
|
const profile = await profileName(cmd);
|
|
38782
38899
|
if (options.purge) {
|
|
38783
|
-
throw new CLIError("REPORT_READ_ONLY", `Replica report is read-only. To remove a named local replica, run ${
|
|
38900
|
+
throw new CLIError("REPORT_READ_ONLY", `Replica report is read-only. To remove a named local replica, run ${CLI_NAME30} replica reset --replica <name> --purge; flag-owned purge has no equivalent yet (TC-1309).`, ExitCode.USAGE_ERROR);
|
|
38784
38901
|
}
|
|
38785
38902
|
if (options.clearPending) {
|
|
38786
38903
|
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);
|
|
@@ -38882,7 +38999,7 @@ or coverage incomplete; 5 grant expired, revoked or not yet valid; 6 network;
|
|
|
38882
38999
|
if (differs !== void 0) {
|
|
38883
39000
|
throw new ReplicaError(
|
|
38884
39001
|
ReplicaErrorCode.CONFIG_MISMATCH,
|
|
38885
|
-
`Replica ${name} already follows ${differs}. Use another --replica name, or ${
|
|
39002
|
+
`Replica ${name} already follows ${differs}. Use another --replica name, or ${CLI_NAME30} replica reset --replica ${name} --purge first.`
|
|
38886
39003
|
);
|
|
38887
39004
|
}
|
|
38888
39005
|
}
|
|
@@ -39047,7 +39164,7 @@ init_theme();
|
|
|
39047
39164
|
init_errors();
|
|
39048
39165
|
init_warnings();
|
|
39049
39166
|
init_constants();
|
|
39050
|
-
import { CLI_NAME as
|
|
39167
|
+
import { CLI_NAME as CLI_NAME32 } from "@tinycloud/operations/cli-name";
|
|
39051
39168
|
import { readFile as readFile14 } from "fs/promises";
|
|
39052
39169
|
import { join as join17 } from "path";
|
|
39053
39170
|
import { homedir, hostname, userInfo } from "os";
|
|
@@ -39258,7 +39375,7 @@ function parseStatusEnvelope(line) {
|
|
|
39258
39375
|
// src/lib/secrets-wait.ts
|
|
39259
39376
|
init_constants();
|
|
39260
39377
|
init_errors();
|
|
39261
|
-
import { CLI_NAME as
|
|
39378
|
+
import { CLI_NAME as CLI_NAME31 } from "@tinycloud/operations/cli-name";
|
|
39262
39379
|
var WAIT_BACKOFF_MS = [3e3, 4500, 6750, 10125];
|
|
39263
39380
|
var WAIT_STEADY_INTERVAL_MS = 15e3;
|
|
39264
39381
|
var DEFAULT_WAIT_TIMEOUT = "5m";
|
|
@@ -39266,7 +39383,7 @@ var MAX_WAIT_TIMEOUT_MS = 9 * 6e4;
|
|
|
39266
39383
|
var MAX_CONSECUTIVE_FAILURES = 5;
|
|
39267
39384
|
var TIMEOUT_RE = /^(0|\d+s|\d+m)$/;
|
|
39268
39385
|
var RETRYABLE_STATUS_ERRORS = { NODE_UNREACHABLE: true, NODE_ERROR: true };
|
|
39269
|
-
var EXPLICIT_KEY_HINT = `${
|
|
39386
|
+
var EXPLICIT_KEY_HINT = `${CLI_NAME31} secrets wait never signs in, and an explicit private key has no stored session. Use a local profile signed in once with ${CLI_NAME31} --profile <profile> auth login --method local, without TC_PRIVATE_KEY.`;
|
|
39270
39387
|
function abortableSleep(ms2, signal) {
|
|
39271
39388
|
return new Promise((resolve9) => {
|
|
39272
39389
|
if (signal.aborted) {
|
|
@@ -39302,7 +39419,7 @@ async function runSecretsWait(request, deps) {
|
|
|
39302
39419
|
let step = 0;
|
|
39303
39420
|
const cancelled = () => new CLIError(
|
|
39304
39421
|
"CANCELLED",
|
|
39305
|
-
`Interrupted: ${
|
|
39422
|
+
`Interrupted: ${CLI_NAME31} secrets wait stopped before the secrets were ready.`,
|
|
39306
39423
|
ExitCode.CANCELLED,
|
|
39307
39424
|
progress === void 0 ? {} : { progress }
|
|
39308
39425
|
);
|
|
@@ -39446,13 +39563,13 @@ function statusError(error, profile, warnings) {
|
|
|
39446
39563
|
if (error.code === "AUTH_REQUIRED" || error.code === "SESSION_NOT_FOUND") {
|
|
39447
39564
|
const recovery = recoveryOf(error, "sign_in");
|
|
39448
39565
|
if (recovery !== void 0) {
|
|
39449
|
-
const hint = recovery.command !== void 0 ? `Sign in once with: ${recovery.command}, then retry.` : `Sign in to profile "${profile}" once with ${
|
|
39566
|
+
const hint = recovery.command !== void 0 ? `Sign in once with: ${recovery.command}, then retry.` : `Sign in to profile "${profile}" once with ${CLI_NAME31} auth login, then retry.`;
|
|
39450
39567
|
return new CLIError("AUTH_REQUIRED", error.message, ExitCode.AUTH_REQUIRED, { hint, recovery, ...extra });
|
|
39451
39568
|
}
|
|
39452
39569
|
}
|
|
39453
39570
|
if (error.code === "PROFILE_MIGRATION_REQUIRED") {
|
|
39454
39571
|
const recovery = recoveryOf(error, "complete_migration");
|
|
39455
|
-
const hint = recovery?.command !== void 0 ? `Run ${recovery.command} once to finish this profile's one-time upgrade, then retry wait.` : `Run ${
|
|
39572
|
+
const hint = recovery?.command !== void 0 ? `Run ${recovery.command} once to finish this profile's one-time upgrade, then retry wait.` : `Run ${CLI_NAME31} auth caps once with profile "${profile}" to finish its one-time upgrade, then retry wait.`;
|
|
39456
39573
|
return new CLIError("PROFILE_MIGRATION_REQUIRED", error.message, ExitCode.ERROR, {
|
|
39457
39574
|
hint,
|
|
39458
39575
|
...recovery === void 0 ? {} : { recovery },
|
|
@@ -39644,11 +39761,11 @@ async function runSecretOperation(params) {
|
|
|
39644
39761
|
}
|
|
39645
39762
|
function secretPermissionReason(action, name) {
|
|
39646
39763
|
const target = name ? ` secret "${name}"` : " secrets";
|
|
39647
|
-
return `Allow \`${
|
|
39764
|
+
return `Allow \`${CLI_NAME32} secrets ${action}${name ? ` ${name}` : ""}\` to access${target} with the required TinyCloud permissions.`;
|
|
39648
39765
|
}
|
|
39649
39766
|
function assertOwnerApprovalPossible(profileName2, profile, action, name, missing = []) {
|
|
39650
39767
|
if (profile.authMethod !== "openkey" || process.stdin.isTTY || process.stderr.isTTY) return;
|
|
39651
|
-
const command = `${
|
|
39768
|
+
const command = `${CLI_NAME32} secrets ${action === "del" ? "delete" : action}${name ? ` ${name}` : ""}`;
|
|
39652
39769
|
throw new CLIError(
|
|
39653
39770
|
"PERMISSION_DENIED",
|
|
39654
39771
|
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.`,
|
|
@@ -39659,7 +39776,7 @@ function assertOwnerApprovalPossible(profileName2, profile, action, name, missin
|
|
|
39659
39776
|
);
|
|
39660
39777
|
}
|
|
39661
39778
|
function scopedSecretLoginHint(profileName2) {
|
|
39662
|
-
return `Have the owner approve a scoped login whose manifest names the secret: ${
|
|
39779
|
+
return `Have the owner approve a scoped login whose manifest names the secret: ${CLI_NAME32} --profile ${profileName2} auth login --method openkey --paste --manifest <manifest.json>. Pass the owner's code on stdin, newline-terminated.`;
|
|
39663
39780
|
}
|
|
39664
39781
|
async function runSecretOperationAttempt(label, operation) {
|
|
39665
39782
|
try {
|
|
@@ -39793,7 +39910,7 @@ ${message}`,
|
|
|
39793
39910
|
"AUTH_REQUIRED",
|
|
39794
39911
|
"Not signed in to TinyCloud.",
|
|
39795
39912
|
ExitCode.AUTH_REQUIRED,
|
|
39796
|
-
{ hint: `Sign in with: ${
|
|
39913
|
+
{ hint: `Sign in with: ${CLI_NAME32} --profile ${result.context.profile} auth login` }
|
|
39797
39914
|
);
|
|
39798
39915
|
}
|
|
39799
39916
|
if (result.error.code === "NODE_UNREACHABLE") {
|
|
@@ -40270,7 +40387,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
40270
40387
|
name: "Encryption network",
|
|
40271
40388
|
ok: false,
|
|
40272
40389
|
detail: `${networkName} not found`,
|
|
40273
|
-
hint: `${
|
|
40390
|
+
hint: `${CLI_NAME32} secrets network init ${networkName}`
|
|
40274
40391
|
}
|
|
40275
40392
|
];
|
|
40276
40393
|
let secret;
|
|
@@ -40316,7 +40433,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
40316
40433
|
name: "Secret access",
|
|
40317
40434
|
ok: false,
|
|
40318
40435
|
detail: notFound ? `${resolved.permissionPaths.vault} not found` : result.error.message,
|
|
40319
|
-
hint: notFound ? local ? `${
|
|
40436
|
+
hint: notFound ? local ? `${CLI_NAME32} secrets put ${resolved.name}${formatSecretScopeFlag(scopeOptions)} --stdin` : `${CLI_NAME32} secrets request ${resolved.name}${formatSecretScopeFlag(scopeOptions)}` : `Ask the owner profile to grant ${secretCapabilityAction("get")} and ${SECRET_DECRYPT_CAPABILITY}.`
|
|
40320
40437
|
});
|
|
40321
40438
|
}
|
|
40322
40439
|
} else {
|
|
@@ -40560,7 +40677,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
40560
40677
|
if (effectiveLocal(profile, options)) {
|
|
40561
40678
|
throw new CLIError(
|
|
40562
40679
|
"LOCAL_OWNER_PROFILE",
|
|
40563
|
-
`Profile "${ctx.profile}" uses a local key, which owns its secrets: store them with ${
|
|
40680
|
+
`Profile "${ctx.profile}" uses a local key, which owns its secrets: store them with ${CLI_NAME32} secrets put NAME --stdin instead of requesting them.`,
|
|
40564
40681
|
ExitCode.USAGE_ERROR
|
|
40565
40682
|
);
|
|
40566
40683
|
}
|
|
@@ -40683,7 +40800,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
40683
40800
|
|
|
40684
40801
|
// src/commands/share.ts
|
|
40685
40802
|
import { Option } from "commander";
|
|
40686
|
-
import { CLI_NAME as
|
|
40803
|
+
import { CLI_NAME as CLI_NAME33 } from "@tinycloud/operations/cli-name";
|
|
40687
40804
|
import { ProfileLockTimeoutError as ProfileLockTimeoutError3 } from "@tinycloud/operations/state";
|
|
40688
40805
|
init_errors();
|
|
40689
40806
|
|
|
@@ -42144,7 +42261,7 @@ function shareCliError(error, operation = "publish") {
|
|
|
42144
42261
|
"SHARE_HISTORY_UNREADABLE",
|
|
42145
42262
|
"sender history cannot be read with the current session key",
|
|
42146
42263
|
1,
|
|
42147
|
-
{ hint: `Run \`${
|
|
42264
|
+
{ hint: `Run \`${CLI_NAME33} share history recover\` to move the unreadable file aside. The old file is kept, not deleted.` }
|
|
42148
42265
|
);
|
|
42149
42266
|
}
|
|
42150
42267
|
if (error instanceof SharePublishAuthorityError) {
|
|
@@ -42152,12 +42269,12 @@ function shareCliError(error, operation = "publish") {
|
|
|
42152
42269
|
const profileName2 = "profileName" in failure ? failure.profileName : void 0;
|
|
42153
42270
|
const localKey = "localKey" in failure && failure.localKey === true;
|
|
42154
42271
|
const profileHint = profileName2 === void 0 ? "" : `--profile ${profileName2} `;
|
|
42155
|
-
const loginHint = localKey ? `\`${
|
|
42272
|
+
const loginHint = localKey ? `\`${CLI_NAME33} ${profileHint}auth login --method local\`` : `\`${CLI_NAME33} ${profileHint}auth login --device --manifest builtin:share-publishing\` (or \`${CLI_NAME33} ${profileHint}enable share\`)`;
|
|
42156
42273
|
if (failure.kind === "caveated-session") {
|
|
42157
42274
|
const holder = profileName2 === void 0 ? "this session" : `profile ${profileName2}'s session`;
|
|
42158
42275
|
return new CLIError(
|
|
42159
42276
|
"PERMISSION_DENIED",
|
|
42160
|
-
`${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): \`${
|
|
42277
|
+
`${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_NAME33} init --name publisher --key-only && ${CLI_NAME33} --profile publisher enable share\``,
|
|
42161
42278
|
5
|
|
42162
42279
|
);
|
|
42163
42280
|
}
|
|
@@ -42258,7 +42375,7 @@ function shareCliError(error, operation = "publish") {
|
|
|
42258
42375
|
if (nodeCode === "ENOENT") return new CLIError("INVALID_ARGUMENT", "share input was not found", 2);
|
|
42259
42376
|
if (nodeCode === "EISDIR") return new CLIError("INVALID_ARGUMENT", "share input must be a Markdown file", 2);
|
|
42260
42377
|
const mapped = typeof nodeCode === "string" && known[nodeCode] !== void 0 ? known[nodeCode] : known[message];
|
|
42261
|
-
const classified = mapped === void 0 ? classifyShareFailure(error, `\`${
|
|
42378
|
+
const classified = mapped === void 0 ? classifyShareFailure(error, `\`${CLI_NAME33} auth login\``) : void 0;
|
|
42262
42379
|
if (classified !== void 0) return Object.assign(new CLIError(classified.code, classified.reason, classified.exitCode), { cause: error });
|
|
42263
42380
|
if (error instanceof TypeError) return new CLIError("INVALID_ARGUMENT", "share input is invalid", 2);
|
|
42264
42381
|
return new CLIError(mapped?.code ?? "INVALID_ARGUMENT", mapped ? mapped.code : "share operation failed", mapped?.exit ?? 2);
|
|
@@ -42668,7 +42785,7 @@ init_constants();
|
|
|
42668
42785
|
init_sdk();
|
|
42669
42786
|
init_host();
|
|
42670
42787
|
init_theme();
|
|
42671
|
-
import { CLI_NAME as
|
|
42788
|
+
import { CLI_NAME as CLI_NAME34 } from "@tinycloud/operations/cli-name";
|
|
42672
42789
|
import { ACCOUNT_REGISTRY_SPACE } from "@tinycloud/node-sdk";
|
|
42673
42790
|
import { randomBytes as randomBytes9 } from "crypto";
|
|
42674
42791
|
import { mkdir as mkdir7, writeFile as writeFile4 } from "fs/promises";
|
|
@@ -42715,7 +42832,7 @@ function registerSpaceCommand(program) {
|
|
|
42715
42832
|
} else {
|
|
42716
42833
|
const profile = await ProfileManager.getProfile(ctx.profile);
|
|
42717
42834
|
const cap = "tinycloud.kv:account:spaces/:get,list";
|
|
42718
|
-
const hint = resolveProfilePosture(profile) === "delegate-session" ? `Ask the space owner to grant it. Create the request with: ${
|
|
42835
|
+
const hint = resolveProfilePosture(profile) === "delegate-session" ? `Ask the space owner to grant it. Create the request with: ${CLI_NAME34} --profile ${ctx.profile} auth request --cap ${cap} --emit` : `Grant it to this session with: ${CLI_NAME34} --profile ${ctx.profile} auth request --grant --cap ${cap}. The default OpenKey owner sign-in covers only the default space, so it includes neither.`;
|
|
42719
42836
|
throw new CLIError(
|
|
42720
42837
|
"PERMISSION_DENIED",
|
|
42721
42838
|
`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.`,
|
|
@@ -42752,10 +42869,10 @@ function registerSpaceCommand(program) {
|
|
|
42752
42869
|
|
|
42753
42870
|
A delegate cannot host a space \u2014 only its owner (root authority) can. This
|
|
42754
42871
|
emits a tinycloud.host.request artifact naming the space and its owner so you
|
|
42755
|
-
can hand it to the owner; they run \`${
|
|
42872
|
+
can hand it to the owner; they run \`${CLI_NAME34} space host <name>\` and confirm.
|
|
42756
42873
|
|
|
42757
42874
|
If you ARE the owner of the resolved space, this refuses and tells you to host
|
|
42758
|
-
it directly with \`${
|
|
42875
|
+
it directly with \`${CLI_NAME34} space host <name>\` (no request needed).
|
|
42759
42876
|
`).action(async (name, options, cmd) => {
|
|
42760
42877
|
try {
|
|
42761
42878
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -42766,7 +42883,7 @@ it directly with \`${CLI_NAME33} space host <name>\` (no request needed).
|
|
|
42766
42883
|
if (await isRootAuthority(spaceId, ctx.profile)) {
|
|
42767
42884
|
throw new CLIError(
|
|
42768
42885
|
"ALREADY_ROOT_AUTHORITY",
|
|
42769
|
-
`You are the owner of ${spaceId}. Host it directly: ${
|
|
42886
|
+
`You are the owner of ${spaceId}. Host it directly: ${CLI_NAME34} space host ${spaceName}`,
|
|
42770
42887
|
ExitCode.USAGE_ERROR
|
|
42771
42888
|
);
|
|
42772
42889
|
}
|
|
@@ -42852,7 +42969,7 @@ init_sdk();
|
|
|
42852
42969
|
init_space();
|
|
42853
42970
|
init_host();
|
|
42854
42971
|
init_theme();
|
|
42855
|
-
import { CLI_NAME as
|
|
42972
|
+
import { CLI_NAME as CLI_NAME35 } from "@tinycloud/operations/cli-name";
|
|
42856
42973
|
import { writeFile as writeFile5 } from "fs/promises";
|
|
42857
42974
|
import { resolve as resolve8 } from "path";
|
|
42858
42975
|
async function dbHandle(node, dbName, spaceInput, profileName2) {
|
|
@@ -42876,12 +42993,12 @@ database for simple apps, or pass --db to target a named database. Pass
|
|
|
42876
42993
|
--space to target a non-primary space (e.g. the manifest "applications" space).
|
|
42877
42994
|
|
|
42878
42995
|
Common workflows:
|
|
42879
|
-
$ ${
|
|
42880
|
-
$ ${
|
|
42881
|
-
$ ${
|
|
42882
|
-
$ ${
|
|
42883
|
-
$ ${
|
|
42884
|
-
$ ${
|
|
42996
|
+
$ ${CLI_NAME35} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
|
|
42997
|
+
$ ${CLI_NAME35} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["ship docs"]'
|
|
42998
|
+
$ ${CLI_NAME35} sql query "SELECT id, body FROM notes ORDER BY id"
|
|
42999
|
+
$ ${CLI_NAME35} sql query "SELECT * FROM events WHERE type = ?" --db analytics --params '["signup"]'
|
|
43000
|
+
$ ${CLI_NAME35} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
|
|
43001
|
+
$ ${CLI_NAME35} sql export --db analytics --output analytics.db
|
|
42885
43002
|
|
|
42886
43003
|
Commands:
|
|
42887
43004
|
query Read rows with SELECT statements
|
|
@@ -42898,10 +43015,10 @@ Tips:
|
|
|
42898
43015
|
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", `
|
|
42899
43016
|
|
|
42900
43017
|
Examples:
|
|
42901
|
-
$ ${
|
|
42902
|
-
$ ${
|
|
42903
|
-
$ ${
|
|
42904
|
-
$ ${
|
|
43018
|
+
$ ${CLI_NAME35} sql query "SELECT * FROM notes ORDER BY id"
|
|
43019
|
+
$ ${CLI_NAME35} sql query "SELECT * FROM notes WHERE id = ?" --params '[42]'
|
|
43020
|
+
$ ${CLI_NAME35} sql query "SELECT count(*) AS total FROM events" --db analytics --json
|
|
43021
|
+
$ ${CLI_NAME35} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
|
|
42905
43022
|
|
|
42906
43023
|
Output:
|
|
42907
43024
|
Human output is formatted as a table. Piped output or --json returns
|
|
@@ -42942,11 +43059,11 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
|
|
|
42942
43059
|
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", `
|
|
42943
43060
|
|
|
42944
43061
|
Examples:
|
|
42945
|
-
$ ${
|
|
42946
|
-
$ ${
|
|
42947
|
-
$ ${
|
|
42948
|
-
$ ${
|
|
42949
|
-
$ ${
|
|
43062
|
+
$ ${CLI_NAME35} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
|
|
43063
|
+
$ ${CLI_NAME35} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["first note"]'
|
|
43064
|
+
$ ${CLI_NAME35} sql execute "UPDATE notes SET body = ? WHERE id = ?" --params '["edited", 1]'
|
|
43065
|
+
$ ${CLI_NAME35} sql execute "DROP TABLE old_notes" --db archive
|
|
43066
|
+
$ ${CLI_NAME35} sql execute "DELETE FROM conversation WHERE id = ?" --space applications --db xyz.tinycloud.listen/conversations --params '["abc"]'
|
|
42950
43067
|
|
|
42951
43068
|
Output:
|
|
42952
43069
|
Returns JSON with the changed row count and last inserted row id when available.
|
|
@@ -42975,9 +43092,9 @@ Output:
|
|
|
42975
43092
|
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", `
|
|
42976
43093
|
|
|
42977
43094
|
Examples:
|
|
42978
|
-
$ ${
|
|
42979
|
-
$ ${
|
|
42980
|
-
$ ${
|
|
43095
|
+
$ ${CLI_NAME35} sql export
|
|
43096
|
+
$ ${CLI_NAME35} sql export --db analytics --output analytics.db
|
|
43097
|
+
$ ${CLI_NAME35} sql export --space applications --db xyz.tinycloud.listen/conversations --output listen.db
|
|
42981
43098
|
|
|
42982
43099
|
Output:
|
|
42983
43100
|
Writes the database file locally and returns JSON with the path and size.
|
|
@@ -43010,19 +43127,19 @@ Output:
|
|
|
43010
43127
|
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", `
|
|
43011
43128
|
|
|
43012
43129
|
Examples:
|
|
43013
|
-
$ ${
|
|
43130
|
+
$ ${CLI_NAME35} sql copy --from-db com.tinycloud.conversation-sync/conversations \\
|
|
43014
43131
|
--to-db xyz.tinycloud.listen/conversations \\
|
|
43015
43132
|
--space applications --dry-run
|
|
43016
|
-
$ ${
|
|
43133
|
+
$ ${CLI_NAME35} sql copy --from-space applications --from-db com.foo/data \\
|
|
43017
43134
|
--to-space applications --to-db com.bar/data \\
|
|
43018
43135
|
--table conversation --table participant
|
|
43019
43136
|
|
|
43020
43137
|
Notes:
|
|
43021
43138
|
- Refuses to run when (resolved space, db) is identical for source and destination.
|
|
43022
|
-
- Does NOT create destination tables. Run the target app once (or use \`${
|
|
43139
|
+
- Does NOT create destination tables. Run the target app once (or use \`${CLI_NAME35} sql execute\`)
|
|
43023
43140
|
to materialize the schema before copying.
|
|
43024
43141
|
- One row at a time; suitable for small/medium datasets. Large copies should
|
|
43025
|
-
use \`${
|
|
43142
|
+
use \`${CLI_NAME35} sql export\` + bulk import.
|
|
43026
43143
|
- Authorization: the active session/delegation must cover sql/read on source
|
|
43027
43144
|
AND sql/write on destination. Otherwise the relevant operation will fail.
|
|
43028
43145
|
`).action(async (options, cmd) => {
|
|
@@ -43122,7 +43239,7 @@ init_errors();
|
|
|
43122
43239
|
init_formatter();
|
|
43123
43240
|
init_theme();
|
|
43124
43241
|
init_permissions();
|
|
43125
|
-
import { CLI_NAME as
|
|
43242
|
+
import { CLI_NAME as CLI_NAME36 } from "@tinycloud/operations/cli-name";
|
|
43126
43243
|
import {
|
|
43127
43244
|
NodeWasmBindings as NodeWasmBindings2
|
|
43128
43245
|
} from "@tinycloud/node-sdk";
|
|
@@ -43396,7 +43513,7 @@ function formatStatus(summary) {
|
|
|
43396
43513
|
lines.push(`Default profile: ${theme.value(summary.defaultProfile)}`);
|
|
43397
43514
|
lines.push("");
|
|
43398
43515
|
if (summary.profiles.length === 0) {
|
|
43399
|
-
lines.push(theme.muted(`No profiles configured. Run: ${
|
|
43516
|
+
lines.push(theme.muted(`No profiles configured. Run: ${CLI_NAME36} init`));
|
|
43400
43517
|
return `${lines.join("\n")}
|
|
43401
43518
|
`;
|
|
43402
43519
|
}
|