@tinycloud/cli 1.5.0-beta.0 → 1.5.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/legacy-entry.js +314 -254
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +1 -1
package/dist/legacy-entry.js
CHANGED
|
@@ -16456,7 +16456,7 @@ function formatSpace(space) {
|
|
|
16456
16456
|
init_profile_gate();
|
|
16457
16457
|
init_dist();
|
|
16458
16458
|
init_profiles();
|
|
16459
|
-
import { CLI_NAME as
|
|
16459
|
+
import { CLI_NAME as CLI_NAME18 } from "@tinycloud/operations/cli-name";
|
|
16460
16460
|
import { get as httpGet } from "http";
|
|
16461
16461
|
import { get as httpsGet } from "https";
|
|
16462
16462
|
import { spawn } from "child_process";
|
|
@@ -31318,6 +31318,7 @@ init_errors();
|
|
|
31318
31318
|
init_constants();
|
|
31319
31319
|
import { NodeWasmBindings, signedCompactUcanExpiry as signedCompactUcanExpiry2 } from "@tinycloud/node-sdk";
|
|
31320
31320
|
import { isLocalOwnerProfile as isLocalOwnerProfile2, ownerSessionLacksProof as ownerSessionLacksProof2 } from "@tinycloud/operations/profile";
|
|
31321
|
+
import { CLI_NAME as CLI_NAME12 } from "@tinycloud/operations/cli-name";
|
|
31321
31322
|
|
|
31322
31323
|
// src/lib/duration.ts
|
|
31323
31324
|
function parseDuration(input) {
|
|
@@ -31353,6 +31354,87 @@ init_manifest();
|
|
|
31353
31354
|
init_raw_encryption();
|
|
31354
31355
|
init_owner_did();
|
|
31355
31356
|
init_caveat_refusal();
|
|
31357
|
+
|
|
31358
|
+
// src/lib/named-keys.ts
|
|
31359
|
+
init_constants();
|
|
31360
|
+
init_errors();
|
|
31361
|
+
import { extractRecapAttenuations } from "@tinycloud/sdk-core";
|
|
31362
|
+
import {
|
|
31363
|
+
EncryptionResourceError,
|
|
31364
|
+
KEY_CREATE_ABILITY,
|
|
31365
|
+
formatPrefixResource,
|
|
31366
|
+
nodeSupportsNamedKeys
|
|
31367
|
+
} from "@tinycloud/sdk-services/encryption/resource";
|
|
31368
|
+
import { DECRYPT_ACTION } from "@tinycloud/sdk-services";
|
|
31369
|
+
var NAMED_KEYS_NETWORK = "default";
|
|
31370
|
+
function namedKeysNetworkId(ownerDid) {
|
|
31371
|
+
return `urn:tinycloud:encryption:${ownerDid}:${NAMED_KEYS_NETWORK}`;
|
|
31372
|
+
}
|
|
31373
|
+
function parseKeysPrefix(input, ownerDid) {
|
|
31374
|
+
const prefix = input.endsWith("/") ? input.slice(0, -1) : input;
|
|
31375
|
+
const invalid3 = (reason) => new CLIError("INVALID_KEY_PREFIX", `Invalid \`--keys\` folder "${input}": ${reason}`, ExitCode.USAGE_ERROR);
|
|
31376
|
+
if (prefix.length === 0) throw invalid3("the folder can't be empty.");
|
|
31377
|
+
if (prefix === "apps" || prefix.startsWith("apps/")) {
|
|
31378
|
+
throw invalid3("folders under `apps/` belong to apps and can't be used from the CLI.");
|
|
31379
|
+
}
|
|
31380
|
+
try {
|
|
31381
|
+
return { prefix, resource: formatPrefixResource(namedKeysNetworkId(ownerDid), prefix) };
|
|
31382
|
+
} catch (error) {
|
|
31383
|
+
if (error instanceof EncryptionResourceError) throw invalid3(error.message);
|
|
31384
|
+
throw error;
|
|
31385
|
+
}
|
|
31386
|
+
}
|
|
31387
|
+
function keyAbilitiesFor(keys) {
|
|
31388
|
+
return { [keys.resource]: [KEY_CREATE_ABILITY, DECRYPT_ACTION] };
|
|
31389
|
+
}
|
|
31390
|
+
function keyPathInPrefix(prefix, path) {
|
|
31391
|
+
return path.startsWith(`${prefix}/`) && path.length > prefix.length + 1;
|
|
31392
|
+
}
|
|
31393
|
+
async function assertNodeSupportsNamedKeys(host, fetchImpl = fetch) {
|
|
31394
|
+
let info = null;
|
|
31395
|
+
try {
|
|
31396
|
+
const response = await fetchImpl(`${host}/info`);
|
|
31397
|
+
if (response.ok) info = await response.json();
|
|
31398
|
+
} catch {
|
|
31399
|
+
info = null;
|
|
31400
|
+
}
|
|
31401
|
+
if (!nodeSupportsNamedKeys(info)) {
|
|
31402
|
+
throw new CLIError(
|
|
31403
|
+
"NAMED_KEYS_UNAVAILABLE",
|
|
31404
|
+
`${host} doesn't support named keys, so \`--keys\` can't be used there. Nothing was signed.`,
|
|
31405
|
+
ExitCode.NODE_ERROR
|
|
31406
|
+
);
|
|
31407
|
+
}
|
|
31408
|
+
}
|
|
31409
|
+
function partialKeysGrantError() {
|
|
31410
|
+
return new CLIError(
|
|
31411
|
+
"KEYS_PARTIAL_GRANT",
|
|
31412
|
+
"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.",
|
|
31413
|
+
ExitCode.PERMISSION_DENIED
|
|
31414
|
+
);
|
|
31415
|
+
}
|
|
31416
|
+
function namedKeysGrantIn(siwe, keys) {
|
|
31417
|
+
let recap = {};
|
|
31418
|
+
try {
|
|
31419
|
+
recap = siwe === void 0 ? {} : extractRecapAttenuations(siwe);
|
|
31420
|
+
} catch {
|
|
31421
|
+
return "none";
|
|
31422
|
+
}
|
|
31423
|
+
const row = recap[keys.resource] ?? {};
|
|
31424
|
+
const wanted = keyAbilitiesFor(keys)[keys.resource];
|
|
31425
|
+
const granted = wanted.filter((ability) => ability in row);
|
|
31426
|
+
if (granted.length === 0) return "none";
|
|
31427
|
+
return granted.length === wanted.length && Object.keys(row).length === wanted.length ? "both" : "partial";
|
|
31428
|
+
}
|
|
31429
|
+
function withVerifiedNamedKeys(profile, session) {
|
|
31430
|
+
if (profile.namedKeys === void 0) return profile;
|
|
31431
|
+
const siwe = typeof session === "object" && session !== null ? session.siwe : void 0;
|
|
31432
|
+
if (typeof siwe === "string" && namedKeysGrantIn(siwe, profile.namedKeys) === "both") return profile;
|
|
31433
|
+
const { namedKeys: _dropped, ...rest } = profile;
|
|
31434
|
+
return rest;
|
|
31435
|
+
}
|
|
31436
|
+
|
|
31437
|
+
// src/auth/scoped-login.ts
|
|
31356
31438
|
var CAPABILITIES_READ = "tinycloud.capabilities/read";
|
|
31357
31439
|
var CLOCK_SKEW_MS = 3e4;
|
|
31358
31440
|
var SIGNED_RECAP = "signed-recap";
|
|
@@ -31605,6 +31687,35 @@ function verifySignedSession(data, key, sessionDid, expected = {}) {
|
|
|
31605
31687
|
}
|
|
31606
31688
|
return { ownerDid, permissions, expiresAt };
|
|
31607
31689
|
}
|
|
31690
|
+
function withoutRequestedKeyRow(signed, keys, signerDid) {
|
|
31691
|
+
const mismatch = () => new CLIError(
|
|
31692
|
+
"OPENKEY_SCOPE_MISMATCH",
|
|
31693
|
+
`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>\`.`,
|
|
31694
|
+
ExitCode.PERMISSION_DENIED
|
|
31695
|
+
);
|
|
31696
|
+
let expectedResource;
|
|
31697
|
+
try {
|
|
31698
|
+
expectedResource = parseKeysPrefix(keys.prefix, canonicalOwnerDid(signerDid)).resource;
|
|
31699
|
+
} catch {
|
|
31700
|
+
throw mismatch();
|
|
31701
|
+
}
|
|
31702
|
+
if (keys.resource !== expectedResource) throw mismatch();
|
|
31703
|
+
const rest = [];
|
|
31704
|
+
const actions = /* @__PURE__ */ new Set();
|
|
31705
|
+
for (const permission of signed) {
|
|
31706
|
+
if (!isVerifiedRawEncryptionPermission(permission) || permission.path !== expectedResource) {
|
|
31707
|
+
rest.push(permission);
|
|
31708
|
+
continue;
|
|
31709
|
+
}
|
|
31710
|
+
if (permission.caveats?.some((caveat) => Object.keys(caveat).length > 0)) throw mismatch();
|
|
31711
|
+
for (const action of permission.actions) actions.add(action);
|
|
31712
|
+
}
|
|
31713
|
+
if (actions.size === 0) return rest;
|
|
31714
|
+
const wanted = keyAbilitiesFor(keys)[expectedResource];
|
|
31715
|
+
if ([...actions].some((action) => !wanted.includes(action))) throw mismatch();
|
|
31716
|
+
if (actions.size !== wanted.length) throw partialKeysGrantError();
|
|
31717
|
+
return rest;
|
|
31718
|
+
}
|
|
31608
31719
|
function verifyScopedLogin(data, key, sessionDid, requested, expected = {}) {
|
|
31609
31720
|
const notStored = expected.purpose === "grant" ? "No grant was stored." : "No scoped session was saved.";
|
|
31610
31721
|
let signed;
|
|
@@ -31630,10 +31741,11 @@ function verifyScopedLogin(data, key, sessionDid, requested, expected = {}) {
|
|
|
31630
31741
|
}
|
|
31631
31742
|
const legacyNested = [];
|
|
31632
31743
|
const approved = [];
|
|
31633
|
-
|
|
31744
|
+
const keyRowRest = expected.namedKeyRow === void 0 ? signed.permissions : withoutRequestedKeyRow(signed.permissions, expected.namedKeyRow, signed.ownerDid);
|
|
31745
|
+
for (const permission of keyRowRest) {
|
|
31634
31746
|
(isLegacyNestedDecrypt(permission, requested, signed.ownerDid, spaceId) ? legacyNested : approved).push(permission);
|
|
31635
31747
|
}
|
|
31636
|
-
for (const permission of
|
|
31748
|
+
for (const permission of keyRowRest) {
|
|
31637
31749
|
if (isVerifiedRawEncryptionPermission(permission) && !rawEncryptionOwnerMatches(permission.path, signed.ownerDid)) {
|
|
31638
31750
|
throw new CLIError("OPENKEY_SCOPE_MISMATCH", `OpenKey signed decrypt for a network not owned by the approving identity. ${notStored}`, ExitCode.PERMISSION_DENIED);
|
|
31639
31751
|
}
|
|
@@ -31650,80 +31762,6 @@ function verifyScopedLogin(data, key, sessionDid, requested, expected = {}) {
|
|
|
31650
31762
|
|
|
31651
31763
|
// src/auth/login-commit.ts
|
|
31652
31764
|
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
31765
|
init_constants();
|
|
31728
31766
|
init_profiles();
|
|
31729
31767
|
init_storage();
|
|
@@ -31731,14 +31769,14 @@ init_errors();
|
|
|
31731
31769
|
init_space();
|
|
31732
31770
|
import { join as join13 } from "path";
|
|
31733
31771
|
import { REPLICA_LOGOUT_PENDING_FILE as REPLICA_LOGOUT_PENDING_FILE3 } from "@tinycloud/node-sdk";
|
|
31734
|
-
import { CLI_NAME as
|
|
31772
|
+
import { CLI_NAME as CLI_NAME13 } from "@tinycloud/operations/cli-name";
|
|
31735
31773
|
import { isLocalOwnerProfile as isLocalOwnerProfile3, ownerSessionLacksProof as ownerSessionLacksProof3 } from "@tinycloud/operations/profile";
|
|
31736
31774
|
import { ProfileLockTimeoutError as ProfileLockTimeoutError2, profilePath as profilePath7 } from "@tinycloud/operations/state";
|
|
31737
31775
|
function assertNotLocalOwner(profileName2, profile, flow) {
|
|
31738
31776
|
if (profile === null || !isLocalOwnerProfile3(profile)) return;
|
|
31739
31777
|
throw new CLIError(
|
|
31740
31778
|
"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: \`${
|
|
31779
|
+
`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
31780
|
ExitCode.USAGE_ERROR
|
|
31743
31781
|
);
|
|
31744
31782
|
}
|
|
@@ -31773,7 +31811,7 @@ function assertSessionReplaceable(profileName2, snapshot, ownerDid, scope, newEx
|
|
|
31773
31811
|
if (problem !== void 0) {
|
|
31774
31812
|
throw new CLIError(
|
|
31775
31813
|
"PROFILE_STATE_INCONSISTENT",
|
|
31776
|
-
`Profile "${profileName2}" is inconsistent (${problem}), possibly from an interrupted write. Nothing was saved. Check \`${
|
|
31814
|
+
`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
31815
|
ExitCode.ERROR
|
|
31778
31816
|
);
|
|
31779
31817
|
}
|
|
@@ -31789,7 +31827,7 @@ function assertSessionReplaceable(profileName2, snapshot, ownerDid, scope, newEx
|
|
|
31789
31827
|
const space = typeof session.spaceId === "string" ? session.spaceId : "an unknown space";
|
|
31790
31828
|
throw new CLIError(
|
|
31791
31829
|
"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 (\`${
|
|
31830
|
+
`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
31831
|
ExitCode.USAGE_ERROR
|
|
31794
31832
|
);
|
|
31795
31833
|
}
|
|
@@ -31823,7 +31861,7 @@ async function commitLogin(profileName2, snapshot, commit) {
|
|
|
31823
31861
|
if (canonicalJson(current.profile) !== canonicalJson(snapshot.profile) || canonicalJson(current.key) !== canonicalJson(snapshot.key) || canonicalJson(current.session) !== canonicalJson(snapshot.session)) {
|
|
31824
31862
|
throw new CLIError(
|
|
31825
31863
|
"PROFILE_CHANGED_DURING_LOGIN",
|
|
31826
|
-
`Profile "${profileName2}" changed while this login was in progress (another login, key rotation or logout). Nothing was saved; check \`${
|
|
31864
|
+
`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
31865
|
ExitCode.ERROR
|
|
31828
31866
|
);
|
|
31829
31867
|
}
|
|
@@ -31856,7 +31894,7 @@ async function commitLogin(profileName2, snapshot, commit) {
|
|
|
31856
31894
|
if (failures.length === 0) throw error;
|
|
31857
31895
|
throw new CLIError(
|
|
31858
31896
|
"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 \`${
|
|
31897
|
+
`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
31898
|
ExitCode.ERROR
|
|
31861
31899
|
);
|
|
31862
31900
|
}
|
|
@@ -31872,7 +31910,7 @@ async function commitLogin(profileName2, snapshot, commit) {
|
|
|
31872
31910
|
|
|
31873
31911
|
// src/auth/owner-key.ts
|
|
31874
31912
|
init_theme();
|
|
31875
|
-
import { CLI_NAME as
|
|
31913
|
+
import { CLI_NAME as CLI_NAME14 } from "@tinycloud/operations/cli-name";
|
|
31876
31914
|
function openKeyPrimaryFlag(delegation) {
|
|
31877
31915
|
return typeof delegation.primary === "boolean" ? delegation.primary : void 0;
|
|
31878
31916
|
}
|
|
@@ -31883,7 +31921,7 @@ function withOwnerKeyPrimary(profile, primary) {
|
|
|
31883
31921
|
function nonPrimaryKeyWarning(ownerDid) {
|
|
31884
31922
|
const address = ownerDid.split(":")[4] ?? ownerDid;
|
|
31885
31923
|
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: ${
|
|
31924
|
+
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
31925
|
`;
|
|
31888
31926
|
}
|
|
31889
31927
|
function warnIfNotPrimaryKey(ownerDid, primary) {
|
|
@@ -31892,7 +31930,7 @@ function warnIfNotPrimaryKey(ownerDid, primary) {
|
|
|
31892
31930
|
}
|
|
31893
31931
|
function announceFirstOwner(profileName2, expectedOwner, ownerDid) {
|
|
31894
31932
|
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 ${
|
|
31933
|
+
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
31934
|
`);
|
|
31897
31935
|
}
|
|
31898
31936
|
function ownerLoginPermissions(owner) {
|
|
@@ -32723,7 +32761,7 @@ init_types();
|
|
|
32723
32761
|
init_constants();
|
|
32724
32762
|
init_permissions();
|
|
32725
32763
|
init_sdk();
|
|
32726
|
-
import { CLI_NAME as
|
|
32764
|
+
import { CLI_NAME as CLI_NAME16 } from "@tinycloud/operations/cli-name";
|
|
32727
32765
|
import { TinyCloudNode as TinyCloudNode2 } from "@tinycloud/node-sdk";
|
|
32728
32766
|
import { DelegationRequestBindingSchema, PermissionEntrySchema } from "@tinycloud/operations/artifacts";
|
|
32729
32767
|
import {
|
|
@@ -32738,7 +32776,7 @@ import {
|
|
|
32738
32776
|
// src/lib/revoke-delegation.ts
|
|
32739
32777
|
init_constants();
|
|
32740
32778
|
init_errors();
|
|
32741
|
-
import { CLI_NAME as
|
|
32779
|
+
import { CLI_NAME as CLI_NAME15 } from "@tinycloud/operations/cli-name";
|
|
32742
32780
|
import { parseSignedCompactUcanAttenuation as parseSignedCompactUcanAttenuation3 } from "@tinycloud/sdk-core";
|
|
32743
32781
|
import { inspectStoredDelegation, withoutBearer as withoutBearer3 } from "@tinycloud/operations/delegation-binding";
|
|
32744
32782
|
init_permissions();
|
|
@@ -33002,7 +33040,7 @@ async function revokeDelegationOnNode(params) {
|
|
|
33002
33040
|
"REVOKE_UNAUTHORIZED",
|
|
33003
33041
|
"The node rejected this revocation: Unauthorized Revoker.",
|
|
33004
33042
|
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 \`${
|
|
33043
|
+
{ 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
33044
|
);
|
|
33007
33045
|
}
|
|
33008
33046
|
throw cliErrorFromService(result.error);
|
|
@@ -33085,7 +33123,7 @@ function renderList(result) {
|
|
|
33085
33123
|
}
|
|
33086
33124
|
process.stdout.write(`Profile: ${result.profile}
|
|
33087
33125
|
`);
|
|
33088
|
-
process.stdout.write(`Session CID: ${result.session?.cid ?? "unavailable"}` + (result.session?.cid ? ` (current session; revoke with ${
|
|
33126
|
+
process.stdout.write(`Session CID: ${result.session?.cid ?? "unavailable"}` + (result.session?.cid ? ` (current session; revoke with ${CLI_NAME16} delegation revoke ${result.session.cid})
|
|
33089
33127
|
` : "\n"));
|
|
33090
33128
|
process.stdout.write(`Local verification: ${result.localVerification}; node validation and revocation are not checked.
|
|
33091
33129
|
`);
|
|
@@ -33183,7 +33221,7 @@ function registerAuthGrantsCommand(auth, options = {}) {
|
|
|
33183
33221
|
ExitCode.ERROR,
|
|
33184
33222
|
{
|
|
33185
33223
|
causeCode: cause.code,
|
|
33186
|
-
hint: `Do not use --revoke again. After fixing local profile storage, run: ${
|
|
33224
|
+
hint: `Do not use --revoke again. After fixing local profile storage, run: ${CLI_NAME16} --profile ${ctx.profile} auth grants remove ${cid}`
|
|
33187
33225
|
}
|
|
33188
33226
|
);
|
|
33189
33227
|
}
|
|
@@ -33248,7 +33286,7 @@ init_safe_text();
|
|
|
33248
33286
|
init_raw_encryption();
|
|
33249
33287
|
init_space();
|
|
33250
33288
|
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
|
|
33289
|
+
import { CLI_NAME as CLI_NAME17 } from "@tinycloud/operations/cli-name";
|
|
33252
33290
|
var APPS_AGENT_PRESET = "apps-agent";
|
|
33253
33291
|
var APPS_AGENT_SPACE = "applications";
|
|
33254
33292
|
var APPS_AGENT_EXPIRY_DAYS = 7;
|
|
@@ -33330,7 +33368,7 @@ function lifetimePhrase(ms2) {
|
|
|
33330
33368
|
return `${hours} hour${hours === 1 ? "" : "s"}`;
|
|
33331
33369
|
}
|
|
33332
33370
|
function revokeSentence(lifetimeMs2) {
|
|
33333
|
-
return `It lasts up to ${lifetimePhrase(lifetimeMs2)}; revoke it any time with \`${
|
|
33371
|
+
return `It lasts up to ${lifetimePhrase(lifetimeMs2)}; revoke it any time with \`${CLI_NAME17} delegation revoke <id>\`.`;
|
|
33334
33372
|
}
|
|
33335
33373
|
function appsAgentScopeNotice(mode, appIds = []) {
|
|
33336
33374
|
const variant = APPS_AGENT_VARIANTS[mode];
|
|
@@ -33525,7 +33563,7 @@ function describeGrantScope(requested, requestedExpiry, applicationsSpace, maxLi
|
|
|
33525
33563
|
const durations = [lifetimeMs(requestedExpiry), ...entryLifetimes].filter((ms2) => ms2 !== void 0).map((ms2) => Math.min(ms2, maxLifetimeMs ?? Number.POSITIVE_INFINITY));
|
|
33526
33564
|
const longest = Math.max(...durations);
|
|
33527
33565
|
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 \`${
|
|
33566
|
+
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
33567
|
);
|
|
33530
33568
|
return { text: lines.join("\n"), entries: requested, extras };
|
|
33531
33569
|
}
|
|
@@ -33833,7 +33871,7 @@ async function revokeForLogout(ctx, discardLocalChanges) {
|
|
|
33833
33871
|
"REVOCATION_FAILED",
|
|
33834
33872
|
`Revocation failed for ${cid}: ${result.error.message}. Session, key, and replica files are retained for retry; local reads stay denied.`,
|
|
33835
33873
|
ExitCode.ERROR,
|
|
33836
|
-
{ cid, hint: `Retry \`${
|
|
33874
|
+
{ cid, hint: `Retry \`${CLI_NAME18} auth logout --revoke\` when the node is available.` }
|
|
33837
33875
|
);
|
|
33838
33876
|
}
|
|
33839
33877
|
if (result.data.cid !== cid || result.data.revoked !== true) {
|
|
@@ -33852,7 +33890,7 @@ async function revokeForLogout(ctx, discardLocalChanges) {
|
|
|
33852
33890
|
"REVOCATION_FAILED",
|
|
33853
33891
|
`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
33892
|
ExitCode.ERROR,
|
|
33855
|
-
{ hint: `Retry \`${
|
|
33893
|
+
{ hint: `Retry \`${CLI_NAME18} auth logout --revoke\` when online authority is available.` }
|
|
33856
33894
|
);
|
|
33857
33895
|
}
|
|
33858
33896
|
}
|
|
@@ -34051,7 +34089,7 @@ async function finishProfileLogout(ctx, options = {}) {
|
|
|
34051
34089
|
function registerAuthCommand(program, options) {
|
|
34052
34090
|
const auth = program.command("auth").description("Authentication management");
|
|
34053
34091
|
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 \`${
|
|
34092
|
+
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
34093
|
try {
|
|
34056
34094
|
if (options2.device && options2.paste) {
|
|
34057
34095
|
throw new CLIError("INVALID_ARGUMENT", "--device and --paste are mutually exclusive.", ExitCode.USAGE_ERROR);
|
|
@@ -34209,7 +34247,7 @@ function registerAuthCommand(program, options) {
|
|
|
34209
34247
|
authenticated: false,
|
|
34210
34248
|
replicasRemoved,
|
|
34211
34249
|
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 \`${
|
|
34250
|
+
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
34251
|
...online === void 0 ? {} : {
|
|
34214
34252
|
revokedCids: online.plan.cids,
|
|
34215
34253
|
revocationExcluded: online.plan.excluded
|
|
@@ -34354,7 +34392,7 @@ ${appsAgentScopeNotice(preset.mode, preset.appIds)}
|
|
|
34354
34392
|
if (profile.authMethod === "openkey" || options2.device) {
|
|
34355
34393
|
const key = await ProfileManager.getKey(ctx.profile);
|
|
34356
34394
|
if (!key) {
|
|
34357
|
-
throw new CLIError("NO_KEY", `No key found for profile "${ctx.profile}". Run \`${
|
|
34395
|
+
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
34396
|
}
|
|
34359
34397
|
const openkeyHost = resolveOpenKeyHost(profile);
|
|
34360
34398
|
const grants = [];
|
|
@@ -34374,7 +34412,7 @@ ${appsAgentScopeNotice(preset.mode, preset.appIds)}
|
|
|
34374
34412
|
if (notices) process.stderr.write(`${notices.prompts[index]}
|
|
34375
34413
|
`);
|
|
34376
34414
|
const reason = permissionGrantReason(
|
|
34377
|
-
`Grant requested TinyCloud permissions from \`${
|
|
34415
|
+
`Grant requested TinyCloud permissions from \`${CLI_NAME18} auth request --grant\`.`,
|
|
34378
34416
|
group
|
|
34379
34417
|
);
|
|
34380
34418
|
const request = grantRequestPermissions(group, profile.spaceId ?? profile.spaceName);
|
|
@@ -34484,7 +34522,7 @@ ${appsAgentScopeNotice(preset.mode, preset.appIds)}
|
|
|
34484
34522
|
kind: parsed.kind,
|
|
34485
34523
|
requestId: parsed.requestId,
|
|
34486
34524
|
requested: parsed.requested,
|
|
34487
|
-
next: `${
|
|
34525
|
+
next: `${CLI_NAME18} auth retry ${parsed.requestId}`
|
|
34488
34526
|
});
|
|
34489
34527
|
return;
|
|
34490
34528
|
}
|
|
@@ -35093,7 +35131,7 @@ async function acquireOpenKeyApproval(params) {
|
|
|
35093
35131
|
if (!key) {
|
|
35094
35132
|
throw new CLIError(
|
|
35095
35133
|
"NO_KEY",
|
|
35096
|
-
`No key found for profile "${params.ctx.profile}". Run \`${
|
|
35134
|
+
`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
35135
|
ExitCode.AUTH_REQUIRED
|
|
35098
35136
|
);
|
|
35099
35137
|
}
|
|
@@ -35166,7 +35204,7 @@ async function ensureDelegationAuthority(params) {
|
|
|
35166
35204
|
if (!key) {
|
|
35167
35205
|
throw new CLIError(
|
|
35168
35206
|
"NO_KEY",
|
|
35169
|
-
`No key found for profile "${params.ctx.profile}". Run \`${
|
|
35207
|
+
`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
35208
|
ExitCode.AUTH_REQUIRED
|
|
35171
35209
|
);
|
|
35172
35210
|
}
|
|
@@ -35420,7 +35458,7 @@ async function rotateAuthKey(profileName2, host, options = {}) {
|
|
|
35420
35458
|
if (!profile.privateKey) {
|
|
35421
35459
|
throw new CLIError(
|
|
35422
35460
|
"LOCAL_OWNER_KEY_REQUIRED",
|
|
35423
|
-
`Profile "${profileName2}" does not have a local owner private key. Run \`${
|
|
35461
|
+
`Profile "${profileName2}" does not have a local owner private key. Run \`${CLI_NAME18} auth login --method local\` first.`,
|
|
35424
35462
|
ExitCode.AUTH_REQUIRED
|
|
35425
35463
|
);
|
|
35426
35464
|
}
|
|
@@ -35678,6 +35716,8 @@ async function refreshPreparedOpenKeySession(params) {
|
|
|
35678
35716
|
if (expectedOwner && normalizePkhIdentifier(owner) !== normalizePkhIdentifier(expectedOwner)) {
|
|
35679
35717
|
throw new CLIError("OPENKEY_OWNER_MISMATCH", "The verified owner differs from this profile. No approval was started.", ExitCode.PERMISSION_DENIED);
|
|
35680
35718
|
}
|
|
35719
|
+
const namedKeys = options.keys === void 0 ? void 0 : parseKeysPrefix(options.keys, canonicalOwnerDid(owner));
|
|
35720
|
+
if (namedKeys !== void 0) await assertNodeSupportsNamedKeys(host);
|
|
35681
35721
|
if (options.force !== true) await assertShareHistoryAllowsKeyDrop(profileName2, "login");
|
|
35682
35722
|
const replicationConfig = createCliReplicationConfig(profileName2, replication, { debug: false, quiet: true }, true);
|
|
35683
35723
|
if (!replicationConfig) throw new CLIError("V2_REQUIRED", "Replica configuration is unavailable; no approval was started.", ExitCode.AUTH_REQUIRED);
|
|
@@ -35698,12 +35738,15 @@ async function refreshPreparedOpenKeySession(params) {
|
|
|
35698
35738
|
capabilityRequest,
|
|
35699
35739
|
includeAccountRegistryPermissions: false,
|
|
35700
35740
|
autoBootstrapAccount: false,
|
|
35701
|
-
replication: replicationConfig
|
|
35741
|
+
replication: replicationConfig,
|
|
35742
|
+
...namedKeys === void 0 ? {} : { additionalRawAbilities: keyAbilitiesFor(namedKeys) }
|
|
35702
35743
|
});
|
|
35703
35744
|
let handoff;
|
|
35704
35745
|
let preparedDid;
|
|
35746
|
+
let handedBackSiwe;
|
|
35705
35747
|
const gateWasActive = await readGateSnapshot(profilePath8(profileName2)).then((gate) => gate !== void 0 && gate.principalHash !== null, () => true);
|
|
35706
35748
|
try {
|
|
35749
|
+
if (namedKeys !== void 0) process.stderr.write(KEYS_APPROVAL_NOTE(namedKeys.prefix));
|
|
35707
35750
|
await node.signInWithOpenKey(async (prepared) => {
|
|
35708
35751
|
preparedDid = prepared.verificationMethod;
|
|
35709
35752
|
handoff = await startPreparedOpenKeyHandoff({
|
|
@@ -35712,14 +35755,23 @@ async function refreshPreparedOpenKeySession(params) {
|
|
|
35712
35755
|
noPopup: options.noPopup,
|
|
35713
35756
|
paste: options.paste
|
|
35714
35757
|
});
|
|
35715
|
-
return handoff.result
|
|
35758
|
+
return handoff.result.then((result) => {
|
|
35759
|
+
handedBackSiwe = result.signedMessage;
|
|
35760
|
+
return result;
|
|
35761
|
+
});
|
|
35716
35762
|
});
|
|
35717
35763
|
const active = node.restorableSession;
|
|
35718
35764
|
if (!active || !preparedDid || node.sessionDid !== preparedDid || active.verificationMethod !== preparedDid) {
|
|
35719
35765
|
throw new CLIError("OPENKEY_SESSION_MISMATCH", "OpenKey signed a different SDK session key; no profile or session was saved.", ExitCode.PERMISSION_DENIED);
|
|
35720
35766
|
}
|
|
35721
35767
|
const key = active.jwk;
|
|
35722
|
-
const verified = verifyScopedLogin({ ...active }, key, preparedDid, permissions, {
|
|
35768
|
+
const verified = verifyScopedLogin({ ...active }, key, preparedDid, permissions, {
|
|
35769
|
+
expectedOwner: owner,
|
|
35770
|
+
expiry,
|
|
35771
|
+
...namedKeys === void 0 ? {} : { namedKeyRow: namedKeys }
|
|
35772
|
+
});
|
|
35773
|
+
const grant = namedKeys === void 0 ? "none" : namedKeysGrantIn(active.siwe, namedKeys);
|
|
35774
|
+
if (grant === "partial") throw partialKeysGrantError();
|
|
35723
35775
|
const session = {
|
|
35724
35776
|
...verified.session,
|
|
35725
35777
|
...node.replicaStamp ? { replicaStamp: node.replicaStamp } : {},
|
|
@@ -35740,6 +35792,10 @@ async function refreshPreparedOpenKeySession(params) {
|
|
|
35740
35792
|
...replication.allowSecrets === true ? { allowSecrets: true } : {}
|
|
35741
35793
|
}
|
|
35742
35794
|
};
|
|
35795
|
+
if (namedKeys !== void 0) {
|
|
35796
|
+
delete updatedProfile.namedKeys;
|
|
35797
|
+
if (grant === "both") updatedProfile.namedKeys = namedKeys;
|
|
35798
|
+
}
|
|
35743
35799
|
await commitLogin(profileName2, snapshot, {
|
|
35744
35800
|
key,
|
|
35745
35801
|
session,
|
|
@@ -35749,6 +35805,10 @@ async function refreshPreparedOpenKeySession(params) {
|
|
|
35749
35805
|
});
|
|
35750
35806
|
await handoff?.complete(true);
|
|
35751
35807
|
announceFirstOwner(profileName2, expectedOwner, owner);
|
|
35808
|
+
if (namedKeys !== void 0) {
|
|
35809
|
+
if (grant === "both") await readyNamedKeysNetwork(node);
|
|
35810
|
+
else process.stderr.write(keysNotGrantedAfterLogin(profileName2));
|
|
35811
|
+
}
|
|
35752
35812
|
return {
|
|
35753
35813
|
profile: updatedProfile,
|
|
35754
35814
|
delegationData: session,
|
|
@@ -35758,14 +35818,14 @@ async function refreshPreparedOpenKeySession(params) {
|
|
|
35758
35818
|
} catch (error) {
|
|
35759
35819
|
await handoff?.complete(false);
|
|
35760
35820
|
await undoPreparedAttempt(profileName2, node, owner, gateWasActive).catch(() => void 0);
|
|
35761
|
-
throw error;
|
|
35821
|
+
throw namedKeys !== void 0 && isNarrowedRecapMismatch(error) && namedKeysGrantIn(handedBackSiwe, namedKeys) === "partial" ? partialKeysGrantError() : error;
|
|
35762
35822
|
}
|
|
35763
35823
|
}
|
|
35764
35824
|
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
35825
|
`;
|
|
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 \`${
|
|
35826
|
+
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
35827
|
`;
|
|
35768
|
-
var KEYS_FOLDER_NOT_READY = `You're signed in, but your encryption keys aren't set up yet. Try \`${
|
|
35828
|
+
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
35829
|
`;
|
|
35770
35830
|
async function readyNamedKeysNetwork(node) {
|
|
35771
35831
|
try {
|
|
@@ -35792,7 +35852,7 @@ async function refreshPreparedKeysSession(params) {
|
|
|
35792
35852
|
if (expectedOwner && normalizePkhIdentifier(owner) !== normalizePkhIdentifier(expectedOwner)) {
|
|
35793
35853
|
throw new CLIError("OPENKEY_OWNER_MISMATCH", "The verified owner differs from this profile. No approval was started.", ExitCode.PERMISSION_DENIED);
|
|
35794
35854
|
}
|
|
35795
|
-
const namedKeys = parseKeysPrefix(options.keys, owner);
|
|
35855
|
+
const namedKeys = parseKeysPrefix(options.keys, canonicalOwnerDid(owner));
|
|
35796
35856
|
await assertNodeSupportsNamedKeys(host);
|
|
35797
35857
|
if (options.force !== true) await assertShareHistoryAllowsKeyDrop(profileName2, "login");
|
|
35798
35858
|
const node = new TinyCloudNode3({
|
|
@@ -35805,6 +35865,7 @@ async function refreshPreparedKeysSession(params) {
|
|
|
35805
35865
|
});
|
|
35806
35866
|
let handoff;
|
|
35807
35867
|
let preparedDid;
|
|
35868
|
+
let handedBackSiwe;
|
|
35808
35869
|
try {
|
|
35809
35870
|
process.stderr.write(KEYS_APPROVAL_NOTE(namedKeys.prefix));
|
|
35810
35871
|
await node.signInWithOpenKey(async (prepared) => {
|
|
@@ -35815,7 +35876,10 @@ async function refreshPreparedKeysSession(params) {
|
|
|
35815
35876
|
noPopup: options.noPopup,
|
|
35816
35877
|
paste: options.paste
|
|
35817
35878
|
});
|
|
35818
|
-
return handoff.result
|
|
35879
|
+
return handoff.result.then((result) => {
|
|
35880
|
+
handedBackSiwe = result.signedMessage;
|
|
35881
|
+
return result;
|
|
35882
|
+
});
|
|
35819
35883
|
});
|
|
35820
35884
|
const active = node.restorableSession;
|
|
35821
35885
|
if (!active || !preparedDid || node.sessionDid !== preparedDid || active.verificationMethod !== preparedDid) {
|
|
@@ -35849,16 +35913,9 @@ async function refreshPreparedKeysSession(params) {
|
|
|
35849
35913
|
} catch (error) {
|
|
35850
35914
|
await handoff?.complete(false);
|
|
35851
35915
|
await node.replication?.close();
|
|
35852
|
-
throw isNarrowedRecapMismatch(error) ? partialKeysGrantError() : error;
|
|
35916
|
+
throw isNarrowedRecapMismatch(error) && namedKeysGrantIn(handedBackSiwe, namedKeys) === "partial" ? partialKeysGrantError() : error;
|
|
35853
35917
|
}
|
|
35854
35918
|
}
|
|
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
35919
|
async function undoPreparedAttempt(profileName2, node, owner, gateWasActive) {
|
|
35863
35920
|
if (!gateWasActive) {
|
|
35864
35921
|
const undone = await ProfileManager.withLock(profileName2, async () => {
|
|
@@ -35897,14 +35954,7 @@ async function refreshOpenKeySession(profileName2, host, options = {}) {
|
|
|
35897
35954
|
const estimatedExpiry = expiry === void 0 ? void 0 : new Date(Date.now() + expiry.durationMs).toISOString();
|
|
35898
35955
|
assertSessionReplaceable(profileName2, snapshot, expectedOwner, permissions, estimatedExpiry);
|
|
35899
35956
|
}
|
|
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
|
-
}
|
|
35957
|
+
if (options.keys !== void 0 && replication.prefixes.length === 0) {
|
|
35908
35958
|
return refreshPreparedKeysSession({ profileName: profileName2, host, profile, snapshot, options, expectedOwner, expiry });
|
|
35909
35959
|
}
|
|
35910
35960
|
if (replication.prefixes.length > 0) {
|
|
@@ -35982,7 +36032,7 @@ async function refreshOpenKeySession(profileName2, host, options = {}) {
|
|
|
35982
36032
|
}
|
|
35983
36033
|
|
|
35984
36034
|
// src/commands/completion.ts
|
|
35985
|
-
import { CLI_NAME as
|
|
36035
|
+
import { CLI_NAME as CLI_NAME19 } from "@tinycloud/operations/cli-name";
|
|
35986
36036
|
function registerCompletionCommand(program) {
|
|
35987
36037
|
const completion = program.command("completion").description("Generate shell completions");
|
|
35988
36038
|
completion.command("bash").description("Output bash completions").action(() => {
|
|
@@ -35999,7 +36049,7 @@ function registerCompletionCommand(program) {
|
|
|
35999
36049
|
});
|
|
36000
36050
|
}
|
|
36001
36051
|
function generateBashCompletion() {
|
|
36002
|
-
return `# ${
|
|
36052
|
+
return `# ${CLI_NAME19} bash completion
|
|
36003
36053
|
_tc_completions() {
|
|
36004
36054
|
local cur prev commands subcommands
|
|
36005
36055
|
COMPREPLY=()
|
|
@@ -36024,11 +36074,11 @@ _tc_completions() {
|
|
|
36024
36074
|
COMPREPLY=( $(compgen -W "\${subcommands}" -- "\${cur}") )
|
|
36025
36075
|
fi
|
|
36026
36076
|
}
|
|
36027
|
-
complete -F _tc_completions ${
|
|
36077
|
+
complete -F _tc_completions ${CLI_NAME19}
|
|
36028
36078
|
`;
|
|
36029
36079
|
}
|
|
36030
36080
|
function generateZshCompletion() {
|
|
36031
|
-
return `#compdef ${
|
|
36081
|
+
return `#compdef ${CLI_NAME19}
|
|
36032
36082
|
|
|
36033
36083
|
_tc() {
|
|
36034
36084
|
local -a commands
|
|
@@ -36079,50 +36129,50 @@ _tc
|
|
|
36079
36129
|
`;
|
|
36080
36130
|
}
|
|
36081
36131
|
function generateFishCompletion() {
|
|
36082
|
-
return `# ${
|
|
36132
|
+
return `# ${CLI_NAME19} fish completion
|
|
36083
36133
|
set -l commands init auth kv space delegation share node profile completion
|
|
36084
36134
|
|
|
36085
36135
|
# Disable file completion by default
|
|
36086
|
-
complete -c ${
|
|
36136
|
+
complete -c ${CLI_NAME19} -f
|
|
36087
36137
|
|
|
36088
36138
|
# 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 ${
|
|
36139
|
+
complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a init -d "Initialize a new TinyCloud profile"
|
|
36140
|
+
complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a auth -d "Authentication management"
|
|
36141
|
+
complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a kv -d "Key-value store operations"
|
|
36142
|
+
complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a space -d "Space management"
|
|
36143
|
+
complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a delegation -d "Manage delegations"
|
|
36144
|
+
complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a share -d "Share data with others"
|
|
36145
|
+
complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a node -d "Node health and info"
|
|
36146
|
+
complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a profile -d "Profile management"
|
|
36147
|
+
complete -c ${CLI_NAME19} -n "not __fish_seen_subcommand_from $commands" -a completion -d "Generate shell completions"
|
|
36098
36148
|
|
|
36099
36149
|
# 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 ${
|
|
36150
|
+
complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from auth" -a "login logout rotate status whoami"
|
|
36151
|
+
complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from kv" -a "get put delete list head"
|
|
36152
|
+
complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from space" -a "list create info switch"
|
|
36153
|
+
complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from delegation" -a "create list info revoke"
|
|
36154
|
+
complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from share" -a "create receive list revoke"
|
|
36155
|
+
complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from node" -a "health version status"
|
|
36156
|
+
complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from profile" -a "list create show switch delete"
|
|
36157
|
+
complete -c ${CLI_NAME19} -n "__fish_seen_subcommand_from completion" -a "bash zsh fish"
|
|
36108
36158
|
|
|
36109
36159
|
# 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 ${
|
|
36160
|
+
complete -c ${CLI_NAME19} -l profile -s p -d "Profile to use"
|
|
36161
|
+
complete -c ${CLI_NAME19} -l host -s H -d "TinyCloud node URL"
|
|
36162
|
+
complete -c ${CLI_NAME19} -l verbose -s v -d "Enable verbose output"
|
|
36163
|
+
complete -c ${CLI_NAME19} -l no-cache -d "Disable caching"
|
|
36164
|
+
complete -c ${CLI_NAME19} -l quiet -s q -d "Suppress non-essential output"
|
|
36165
|
+
complete -c ${CLI_NAME19} -l replication -d "Enable read-through replication"
|
|
36166
|
+
complete -c ${CLI_NAME19} -l no-replication -d "Disable replication"
|
|
36167
|
+
complete -c ${CLI_NAME19} -l replication-debug -d "Write replication diagnostics to stderr"
|
|
36118
36168
|
`;
|
|
36119
36169
|
}
|
|
36120
36170
|
|
|
36121
36171
|
// src/commands/courier.ts
|
|
36122
|
-
import { CLI_NAME as
|
|
36123
|
-
var TC_OPTIONS_BEFORE_COURIER = `${
|
|
36172
|
+
import { CLI_NAME as CLI_NAME20 } from "@tinycloud/operations/cli-name";
|
|
36173
|
+
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
36174
|
function registerCourierCommand(program) {
|
|
36125
|
-
const courier = program.command("courier").description(`Agent-to-agent threads over TinyCloud; run \`${
|
|
36175
|
+
const courier = program.command("courier").description(`Agent-to-agent threads over TinyCloud; run \`${CLI_NAME20} courier --help\` (beta)`).argument("[args...]").allowUnknownOption().helpOption(false);
|
|
36126
36176
|
courier.action(() => courier.error(TC_OPTIONS_BEFORE_COURIER, { exitCode: 2, code: "tc.courier.options" }));
|
|
36127
36177
|
}
|
|
36128
36178
|
|
|
@@ -36167,7 +36217,7 @@ init_formatter();
|
|
|
36167
36217
|
init_errors();
|
|
36168
36218
|
init_constants();
|
|
36169
36219
|
init_sdk();
|
|
36170
|
-
import { CLI_NAME as
|
|
36220
|
+
import { CLI_NAME as CLI_NAME21 } from "@tinycloud/operations/cli-name";
|
|
36171
36221
|
import { PermissionNotInManifestError } from "@tinycloud/node-sdk";
|
|
36172
36222
|
async function queryDelegations(node, options, until) {
|
|
36173
36223
|
const found = [];
|
|
@@ -36178,7 +36228,7 @@ async function queryDelegations(node, options, until) {
|
|
|
36178
36228
|
if (page.error.code === "PERMISSION_DENIED") {
|
|
36179
36229
|
throw new CLIError(
|
|
36180
36230
|
"PERMISSION_DENIED",
|
|
36181
|
-
`This session cannot read delegations: it lacks tinycloud.delegation/list. Run \`${
|
|
36231
|
+
`This session cannot read delegations: it lacks tinycloud.delegation/list. Run \`${CLI_NAME21} auth login\` again to grant it.`,
|
|
36182
36232
|
ExitCode.PERMISSION_DENIED
|
|
36183
36233
|
);
|
|
36184
36234
|
}
|
|
@@ -36353,7 +36403,7 @@ init_constants();
|
|
|
36353
36403
|
init_formatter();
|
|
36354
36404
|
init_theme();
|
|
36355
36405
|
init_errors();
|
|
36356
|
-
import { CLI_NAME as
|
|
36406
|
+
import { CLI_NAME as CLI_NAME22 } from "@tinycloud/operations/cli-name";
|
|
36357
36407
|
init_session_expired();
|
|
36358
36408
|
init_types();
|
|
36359
36409
|
function registerDoctorCommand(program) {
|
|
@@ -36387,7 +36437,7 @@ function registerDoctorCommand(program) {
|
|
|
36387
36437
|
const profile = await ProfileManager.getProfile(profileName2);
|
|
36388
36438
|
keyDetail = profile.did ? `${profile.did.slice(0, 20)}...` : "key found";
|
|
36389
36439
|
} else {
|
|
36390
|
-
keyDetail = `no key \u2014 run ${
|
|
36440
|
+
keyDetail = `no key \u2014 run ${CLI_NAME22} init`;
|
|
36391
36441
|
}
|
|
36392
36442
|
} catch {
|
|
36393
36443
|
keyDetail = "error reading key";
|
|
@@ -36409,7 +36459,7 @@ function registerDoctorCommand(program) {
|
|
|
36409
36459
|
} else if (state2 === "unverified") {
|
|
36410
36460
|
sessionDetail = `not usable (no signed OpenKey proof) \u2014 ${signInAgainHint(profileName2, resolveProfilePosture(profile))}`;
|
|
36411
36461
|
} else {
|
|
36412
|
-
sessionDetail = sessionOk ? "active" : `no session \u2014 run ${
|
|
36462
|
+
sessionDetail = sessionOk ? "active" : `no session \u2014 run ${CLI_NAME22} auth login`;
|
|
36413
36463
|
}
|
|
36414
36464
|
} catch {
|
|
36415
36465
|
sessionDetail = "error reading session";
|
|
@@ -36437,7 +36487,7 @@ function registerDoctorCommand(program) {
|
|
|
36437
36487
|
try {
|
|
36438
36488
|
const profile = await ProfileManager.getProfile(profileName2);
|
|
36439
36489
|
spaceOk = Boolean(profile.spaceId);
|
|
36440
|
-
spaceDetail = spaceOk ? `${profile.spaceId.slice(0, 16)}...` : `no space \u2014 run ${
|
|
36490
|
+
spaceDetail = spaceOk ? `${profile.spaceId.slice(0, 16)}...` : `no space \u2014 run ${CLI_NAME22} space create`;
|
|
36441
36491
|
} catch {
|
|
36442
36492
|
spaceDetail = "error checking space";
|
|
36443
36493
|
}
|
|
@@ -36627,7 +36677,7 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
|
|
|
36627
36677
|
}
|
|
36628
36678
|
|
|
36629
36679
|
// src/commands/enable.ts
|
|
36630
|
-
import { CLI_NAME as
|
|
36680
|
+
import { CLI_NAME as CLI_NAME23 } from "@tinycloud/operations/cli-name";
|
|
36631
36681
|
init_constants();
|
|
36632
36682
|
init_profiles();
|
|
36633
36683
|
init_errors();
|
|
@@ -36635,7 +36685,7 @@ init_formatter();
|
|
|
36635
36685
|
init_publishing_manifest();
|
|
36636
36686
|
function registerEnableCommand(program) {
|
|
36637
36687
|
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 \`${
|
|
36688
|
+
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
36689
|
try {
|
|
36640
36690
|
const globalOptions = command.optsWithGlobals();
|
|
36641
36691
|
const context = await ProfileManager.resolveContext(globalOptions, { storedSession: "unused" });
|
|
@@ -36670,7 +36720,7 @@ function registerEnableCommand(program) {
|
|
|
36670
36720
|
init_profiles();
|
|
36671
36721
|
init_constants();
|
|
36672
36722
|
init_sdk();
|
|
36673
|
-
import { CLI_NAME as
|
|
36723
|
+
import { CLI_NAME as CLI_NAME24 } from "@tinycloud/operations/cli-name";
|
|
36674
36724
|
import { operationSpaceResolver as operationSpaceResolver2 } from "@tinycloud/operations/delegation-binding";
|
|
36675
36725
|
|
|
36676
36726
|
// src/lib/knowledge-input.ts
|
|
@@ -36936,7 +36986,7 @@ function registerAppsCommand(program) {
|
|
|
36936
36986
|
"after",
|
|
36937
36987
|
`
|
|
36938
36988
|
Examples:
|
|
36939
|
-
$ ${
|
|
36989
|
+
$ ${CLI_NAME24} apps install xyz.tinycloud.sample.healthlog
|
|
36940
36990
|
`
|
|
36941
36991
|
).action(async (appIdInput, _options, cmd) => {
|
|
36942
36992
|
try {
|
|
@@ -37028,7 +37078,7 @@ init_profiles();
|
|
|
37028
37078
|
init_formatter();
|
|
37029
37079
|
init_errors();
|
|
37030
37080
|
init_constants();
|
|
37031
|
-
import { CLI_NAME as
|
|
37081
|
+
import { CLI_NAME as CLI_NAME25 } from "@tinycloud/operations/cli-name";
|
|
37032
37082
|
function registerInitCommand(program) {
|
|
37033
37083
|
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
37084
|
try {
|
|
@@ -37046,7 +37096,7 @@ function registerInitCommand(program) {
|
|
|
37046
37096
|
if (await ProfileManager.profileExists(profileName2)) {
|
|
37047
37097
|
throw new CLIError(
|
|
37048
37098
|
"PROFILE_EXISTS",
|
|
37049
|
-
`Profile "${profileName2}" already exists. Use \`${
|
|
37099
|
+
`Profile "${profileName2}" already exists. Use \`${CLI_NAME25} profile delete ${profileName2}\` first or choose a different name.`,
|
|
37050
37100
|
ExitCode.ERROR
|
|
37051
37101
|
);
|
|
37052
37102
|
}
|
|
@@ -37102,7 +37152,7 @@ init_session_expired();
|
|
|
37102
37152
|
init_constants();
|
|
37103
37153
|
init_profiles();
|
|
37104
37154
|
import { invokeOperation as invokeOperation2 } from "@tinycloud/operations";
|
|
37105
|
-
import { CLI_NAME as
|
|
37155
|
+
import { CLI_NAME as CLI_NAME26 } from "@tinycloud/operations/cli-name";
|
|
37106
37156
|
init_permissions();
|
|
37107
37157
|
init_errors();
|
|
37108
37158
|
init_formatter();
|
|
@@ -37205,7 +37255,7 @@ function knowledgeFailure(result, profile, appId) {
|
|
|
37205
37255
|
case "error": {
|
|
37206
37256
|
if (result.error.code === "SESSION_NOT_FOUND" || result.error.code === "PROFILE_POSTURE_NOT_ALLOWED" && result.context.posture === "unauthenticated") {
|
|
37207
37257
|
return new CLIError("AUTH_REQUIRED", "Not signed in to TinyCloud.", ExitCode.AUTH_REQUIRED, {
|
|
37208
|
-
hint: `Sign in with: ${
|
|
37258
|
+
hint: `Sign in with: ${CLI_NAME26} --profile ${result.context.profile} auth login`
|
|
37209
37259
|
});
|
|
37210
37260
|
}
|
|
37211
37261
|
if (result.error.code === "NODE_UNREACHABLE") {
|
|
@@ -37235,7 +37285,7 @@ function knowledgeFailure(result, profile, appId) {
|
|
|
37235
37285
|
}
|
|
37236
37286
|
}
|
|
37237
37287
|
function missingGrantHint(profile, appId) {
|
|
37238
|
-
const request = `${
|
|
37288
|
+
const request = `${CLI_NAME26} --profile ${profile} auth request`;
|
|
37239
37289
|
const lines = appId === void 0 ? [
|
|
37240
37290
|
` ${request} --preset ${APPS_AGENT_PRESET} --emit req.json (read-only, all installed apps)`,
|
|
37241
37291
|
` ${request} --cap tinycloud.kv:${APPS_AGENT_SPACE}::get,list --emit req.json (all files, no table access)`,
|
|
@@ -37248,7 +37298,7 @@ function missingGrantHint(profile, appId) {
|
|
|
37248
37298
|
return [
|
|
37249
37299
|
"Ask the owner for a grant, narrowest first. Create the request:",
|
|
37250
37300
|
...lines,
|
|
37251
|
-
`The owner runs \`${
|
|
37301
|
+
`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
37302
|
].join("\n");
|
|
37253
37303
|
}
|
|
37254
37304
|
|
|
@@ -37397,9 +37447,12 @@ function registerKvCommand(program) {
|
|
|
37397
37447
|
return handleError(error);
|
|
37398
37448
|
}
|
|
37399
37449
|
});
|
|
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) => {
|
|
37450
|
+
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
37451
|
try {
|
|
37402
37452
|
assertAddressableKey(key);
|
|
37453
|
+
if (options.ifNoneMatch !== void 0 && options.ifNoneMatch !== "*") {
|
|
37454
|
+
throw new CLIError("USAGE_ERROR", "`--if-none-match` only accepts `'*'`.", ExitCode.USAGE_ERROR);
|
|
37455
|
+
}
|
|
37403
37456
|
const globalOpts = cmd.optsWithGlobals();
|
|
37404
37457
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
37405
37458
|
const node = await ensureAuthenticated(ctx);
|
|
@@ -37423,8 +37476,15 @@ function registerKvCommand(program) {
|
|
|
37423
37476
|
}
|
|
37424
37477
|
}
|
|
37425
37478
|
const { kv: kv2, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
37426
|
-
const result = await withSpinner(`Writing ${key}...`, () => kv2.put(
|
|
37479
|
+
const result = await withSpinner(`Writing ${key}...`, () => kv2.put(
|
|
37480
|
+
key,
|
|
37481
|
+
putValue,
|
|
37482
|
+
options.ifNoneMatch === "*" ? { ifNoneMatch: "*" } : void 0
|
|
37483
|
+
));
|
|
37427
37484
|
if (!result.ok) {
|
|
37485
|
+
if (options.ifNoneMatch === "*" && result.error.code === "KV_PRECONDITION_FAILED") {
|
|
37486
|
+
throw new CLIError("KV_ALREADY_EXISTS", `Key "${key}" already exists, so nothing was written.`, ExitCode.PERMISSION_DENIED);
|
|
37487
|
+
}
|
|
37428
37488
|
await throwKvError(result.error, spaceUri, ctx.profile);
|
|
37429
37489
|
}
|
|
37430
37490
|
outputJson({ key, written: true, ...retryFields(result.data?.retry) });
|
|
@@ -37555,7 +37615,7 @@ init_errors();
|
|
|
37555
37615
|
init_sdk();
|
|
37556
37616
|
import { readFile as readFile12 } from "fs/promises";
|
|
37557
37617
|
import { NamedKeyError, createNamedKeyClient } from "@tinycloud/node-sdk/encryption-keys";
|
|
37558
|
-
import { CLI_NAME as
|
|
37618
|
+
import { CLI_NAME as CLI_NAME27 } from "@tinycloud/operations/cli-name";
|
|
37559
37619
|
async function readStdin3() {
|
|
37560
37620
|
const chunks = [];
|
|
37561
37621
|
for await (const chunk of process.stdin) {
|
|
@@ -37579,7 +37639,7 @@ function cliErrorFromNamedKey(error, command, path) {
|
|
|
37579
37639
|
case "KEY_NOT_FOUND":
|
|
37580
37640
|
return new CLIError(
|
|
37581
37641
|
"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 \`${
|
|
37642
|
+
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
37643
|
ExitCode.NOT_FOUND
|
|
37584
37644
|
);
|
|
37585
37645
|
case "KEY_BLOCKED":
|
|
@@ -37589,7 +37649,7 @@ function cliErrorFromNamedKey(error, command, path) {
|
|
|
37589
37649
|
}
|
|
37590
37650
|
if (error.status === 401 || error.status === 403) {
|
|
37591
37651
|
return new CLIError("PERMISSION_DENIED", error.message, ExitCode.PERMISSION_DENIED, {
|
|
37592
|
-
hint: `Sign in with \`${
|
|
37652
|
+
hint: `Sign in with \`${CLI_NAME27} auth login --keys <folder>\` for this key's folder.`
|
|
37593
37653
|
});
|
|
37594
37654
|
}
|
|
37595
37655
|
return new CLIError(error.code, error.message, error.status === void 0 ? ExitCode.ERROR : ExitCode.NODE_ERROR);
|
|
@@ -37609,7 +37669,7 @@ async function grantedPrefix(deps, profileName2) {
|
|
|
37609
37669
|
if (keys === void 0) {
|
|
37610
37670
|
throw new CLIError(
|
|
37611
37671
|
"KEYS_NOT_GRANTED",
|
|
37612
|
-
`Profile "${profileName2}" can't use named keys yet. Sign in with \`${
|
|
37672
|
+
`Profile "${profileName2}" can't use named keys yet. Sign in with \`${CLI_NAME27} --profile ${profileName2} auth login --keys <folder>\`.`,
|
|
37613
37673
|
ExitCode.PERMISSION_DENIED
|
|
37614
37674
|
);
|
|
37615
37675
|
}
|
|
@@ -37619,7 +37679,7 @@ function assertInGrantedPrefix(prefix, path, profileName2) {
|
|
|
37619
37679
|
if (!keyPathInPrefix(prefix, path)) {
|
|
37620
37680
|
throw new CLIError(
|
|
37621
37681
|
"KEY_OUTSIDE_GRANTED_PREFIX",
|
|
37622
|
-
`Key "${path}" is outside the folder "${prefix}/" this sign-in covers. To use it, sign in with \`${
|
|
37682
|
+
`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
37683
|
ExitCode.PERMISSION_DENIED
|
|
37624
37684
|
);
|
|
37625
37685
|
}
|
|
@@ -37647,7 +37707,7 @@ var defaultDeps = {
|
|
|
37647
37707
|
};
|
|
37648
37708
|
function registerKeysCommand(program, deps = defaultDeps) {
|
|
37649
37709
|
const { outputJson: outputJson2, handleError: handleError2 } = deps;
|
|
37650
|
-
const keys = program.command("keys").description(`Named encryption keys (sign in with \`${
|
|
37710
|
+
const keys = program.command("keys").description(`Named encryption keys (sign in with \`${CLI_NAME27} auth login --keys <folder>\` first)`);
|
|
37651
37711
|
keys.command("ensure <path>").description("Create the named key if it doesn't exist yet, then show it").action(async (path, _options, cmd) => {
|
|
37652
37712
|
try {
|
|
37653
37713
|
const ctx = await deps.resolveContext(cmd.optsWithGlobals());
|
|
@@ -37725,7 +37785,7 @@ init_errors();
|
|
|
37725
37785
|
init_constants();
|
|
37726
37786
|
init_theme();
|
|
37727
37787
|
import { resolveManifestKnowledgeRoot as resolveManifestKnowledgeRoot2 } from "@tinycloud/sdk-core";
|
|
37728
|
-
import { CLI_NAME as
|
|
37788
|
+
import { CLI_NAME as CLI_NAME28 } from "@tinycloud/operations/cli-name";
|
|
37729
37789
|
import { readFile as readFile13 } from "fs/promises";
|
|
37730
37790
|
var DEFAULT_APP_SPACE = "applications";
|
|
37731
37791
|
function registerManifestCommand(program) {
|
|
@@ -37733,8 +37793,8 @@ function registerManifestCommand(program) {
|
|
|
37733
37793
|
manifest.command("resolve <source>").description("Resolve a manifest file or URL to its effective space, paths, and DB basenames").addHelpText("after", `
|
|
37734
37794
|
|
|
37735
37795
|
Examples:
|
|
37736
|
-
$ ${
|
|
37737
|
-
$ ${
|
|
37796
|
+
$ ${CLI_NAME28} manifest resolve ./manifest.json
|
|
37797
|
+
$ ${CLI_NAME28} manifest resolve https://app.example.com/manifest.json --json
|
|
37738
37798
|
|
|
37739
37799
|
What it shows:
|
|
37740
37800
|
- app_id, name, manifest_version
|
|
@@ -37744,7 +37804,7 @@ What it shows:
|
|
|
37744
37804
|
|
|
37745
37805
|
This command is read-only and does NOT contact the node \u2014 it just resolves
|
|
37746
37806
|
the manifest against the active profile's address/chain so you know which
|
|
37747
|
-
\`--space\` and \`--db\` values to pass to other ${
|
|
37807
|
+
\`--space\` and \`--db\` values to pass to other ${CLI_NAME28} commands.
|
|
37748
37808
|
`).action(async (source, _options, cmd) => {
|
|
37749
37809
|
try {
|
|
37750
37810
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -37813,7 +37873,7 @@ ${theme.heading("SQL databases")}
|
|
|
37813
37873
|
`);
|
|
37814
37874
|
}
|
|
37815
37875
|
process.stdout.write(theme.muted(`
|
|
37816
|
-
Use with: ${
|
|
37876
|
+
Use with: ${CLI_NAME28} sql query --space ${spaceName} --db <db> "..."
|
|
37817
37877
|
`));
|
|
37818
37878
|
}
|
|
37819
37879
|
if (permissions.length > 0) {
|
|
@@ -37933,7 +37993,7 @@ init_profiles();
|
|
|
37933
37993
|
init_formatter();
|
|
37934
37994
|
init_errors();
|
|
37935
37995
|
init_constants();
|
|
37936
|
-
import { CLI_NAME as
|
|
37996
|
+
import { CLI_NAME as CLI_NAME29 } from "@tinycloud/operations/cli-name";
|
|
37937
37997
|
import { createInterface as createInterface5 } from "readline";
|
|
37938
37998
|
init_theme();
|
|
37939
37999
|
init_types();
|
|
@@ -38079,9 +38139,9 @@ profile at command time. Precedence for every kv/sql command:
|
|
|
38079
38139
|
explicit --space flag > profile defaultSpace > primary space.
|
|
38080
38140
|
|
|
38081
38141
|
Examples:
|
|
38082
|
-
$ ${
|
|
38083
|
-
$ ${
|
|
38084
|
-
$ ${
|
|
38142
|
+
$ ${CLI_NAME29} profile set-default-space applications
|
|
38143
|
+
$ ${CLI_NAME29} profile set-default-space applications --profile cli-test
|
|
38144
|
+
$ ${CLI_NAME29} profile set-default-space --unset
|
|
38085
38145
|
`).action(async (name, options, cmd) => {
|
|
38086
38146
|
try {
|
|
38087
38147
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -38093,7 +38153,7 @@ Examples:
|
|
|
38093
38153
|
if (!options.unset && (name === void 0 || name === "")) {
|
|
38094
38154
|
throw new CLIError(
|
|
38095
38155
|
"USAGE_ERROR",
|
|
38096
|
-
`Provide a space name (e.g. \`${
|
|
38156
|
+
`Provide a space name (e.g. \`${CLI_NAME29} profile set-default-space applications\`) or pass --unset.`,
|
|
38097
38157
|
ExitCode.USAGE_ERROR
|
|
38098
38158
|
);
|
|
38099
38159
|
}
|
|
@@ -38152,7 +38212,7 @@ function parseOperatorType(raw) {
|
|
|
38152
38212
|
|
|
38153
38213
|
// src/commands/replica.ts
|
|
38154
38214
|
init_profile_replicas();
|
|
38155
|
-
import { CLI_NAME as
|
|
38215
|
+
import { CLI_NAME as CLI_NAME30 } from "@tinycloud/operations/cli-name";
|
|
38156
38216
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
38157
38217
|
import { constants as fsConstants5 } from "fs";
|
|
38158
38218
|
import { mkdir as mkdir5, open as open11, readdir as readdir6, rename as rename4, unlink as unlink5, lstat as lstat9 } from "fs/promises";
|
|
@@ -38293,11 +38353,11 @@ var EXIT_BY_CODE = {
|
|
|
38293
38353
|
var READ_FAILURE = {
|
|
38294
38354
|
absent: { code: "KEY_ABSENT", exit: ExitCode.NOT_FOUND, what: "is not in the replica (the replica covers its whole prefix)" },
|
|
38295
38355
|
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 ${
|
|
38356
|
+
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
38357
|
coverage_incomplete: {
|
|
38298
38358
|
code: "COVERAGE_INCOMPLETE",
|
|
38299
38359
|
exit: ExitCode.NOT_FOUND,
|
|
38300
|
-
what: `is not in the replica, which has not finished its first sync; run ${
|
|
38360
|
+
what: `is not in the replica, which has not finished its first sync; run ${CLI_NAME30} replica sync`
|
|
38301
38361
|
},
|
|
38302
38362
|
not_covered: { code: "NOT_COVERED", exit: ExitCode.USAGE_ERROR, what: "is outside the replica's prefix" }
|
|
38303
38363
|
};
|
|
@@ -38415,7 +38475,7 @@ async function existingReplicaName(profile, option) {
|
|
|
38415
38475
|
const names = await replicaNames(profile);
|
|
38416
38476
|
if (names.length === 1) return names[0];
|
|
38417
38477
|
if (names.length === 0) {
|
|
38418
|
-
throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, `Profile "${profile}" has no replicas. Create one with: ${
|
|
38478
|
+
throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, `Profile "${profile}" has no replicas. Create one with: ${CLI_NAME30} replica sync --prefix <prefix>`);
|
|
38419
38479
|
}
|
|
38420
38480
|
throw new CLIError("USAGE_ERROR", `Profile "${profile}" has several replicas (${names.join(", ")}); pass --replica <name>.`, ExitCode.USAGE_ERROR);
|
|
38421
38481
|
}
|
|
@@ -38523,9 +38583,9 @@ async function storedGrants(profile) {
|
|
|
38523
38583
|
function grantHint(space, prefix) {
|
|
38524
38584
|
return [
|
|
38525
38585
|
"A replica needs a device grant with get and sync on its prefix:",
|
|
38526
|
-
` device: ${
|
|
38527
|
-
` owner: ${
|
|
38528
|
-
` device: ${
|
|
38586
|
+
` device: ${CLI_NAME30} auth request --cap tinycloud.kv:${space}:${prefix}:get,list,metadata,sync --expiry 30d --emit req.json`,
|
|
38587
|
+
` owner: ${CLI_NAME30} auth grant req.json > grant.json`,
|
|
38588
|
+
` device: ${CLI_NAME30} auth import grant.json`
|
|
38529
38589
|
].join("\n");
|
|
38530
38590
|
}
|
|
38531
38591
|
function resolveSpace(input, prefix, grants) {
|
|
@@ -38616,7 +38676,7 @@ function describeStatus(status) {
|
|
|
38616
38676
|
formatField("Size", formatBytes(status.bytes)),
|
|
38617
38677
|
formatField("Grant", status.device.delegationCid),
|
|
38618
38678
|
formatField("Last error", status.lastError ? `${status.lastError.code}: ${status.lastError.message}` : null),
|
|
38619
|
-
formatField("Purge", status.purgePending ? `incomplete: content files remain; the next ${
|
|
38679
|
+
formatField("Purge", status.purgePending ? `incomplete: content files remain; the next ${CLI_NAME30} replica command retries` : null)
|
|
38620
38680
|
].join("\n");
|
|
38621
38681
|
}
|
|
38622
38682
|
async function writeFileAtomic(path, bytes) {
|
|
@@ -38780,7 +38840,7 @@ or coverage incomplete; 5 grant expired, revoked or not yet valid; 6 network;
|
|
|
38780
38840
|
(options, cmd) => run(async () => {
|
|
38781
38841
|
const profile = await profileName(cmd);
|
|
38782
38842
|
if (options.purge) {
|
|
38783
|
-
throw new CLIError("REPORT_READ_ONLY", `Replica report is read-only. To remove a named local replica, run ${
|
|
38843
|
+
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
38844
|
}
|
|
38785
38845
|
if (options.clearPending) {
|
|
38786
38846
|
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 +38942,7 @@ or coverage incomplete; 5 grant expired, revoked or not yet valid; 6 network;
|
|
|
38882
38942
|
if (differs !== void 0) {
|
|
38883
38943
|
throw new ReplicaError(
|
|
38884
38944
|
ReplicaErrorCode.CONFIG_MISMATCH,
|
|
38885
|
-
`Replica ${name} already follows ${differs}. Use another --replica name, or ${
|
|
38945
|
+
`Replica ${name} already follows ${differs}. Use another --replica name, or ${CLI_NAME30} replica reset --replica ${name} --purge first.`
|
|
38886
38946
|
);
|
|
38887
38947
|
}
|
|
38888
38948
|
}
|
|
@@ -39047,7 +39107,7 @@ init_theme();
|
|
|
39047
39107
|
init_errors();
|
|
39048
39108
|
init_warnings();
|
|
39049
39109
|
init_constants();
|
|
39050
|
-
import { CLI_NAME as
|
|
39110
|
+
import { CLI_NAME as CLI_NAME32 } from "@tinycloud/operations/cli-name";
|
|
39051
39111
|
import { readFile as readFile14 } from "fs/promises";
|
|
39052
39112
|
import { join as join17 } from "path";
|
|
39053
39113
|
import { homedir, hostname, userInfo } from "os";
|
|
@@ -39258,7 +39318,7 @@ function parseStatusEnvelope(line) {
|
|
|
39258
39318
|
// src/lib/secrets-wait.ts
|
|
39259
39319
|
init_constants();
|
|
39260
39320
|
init_errors();
|
|
39261
|
-
import { CLI_NAME as
|
|
39321
|
+
import { CLI_NAME as CLI_NAME31 } from "@tinycloud/operations/cli-name";
|
|
39262
39322
|
var WAIT_BACKOFF_MS = [3e3, 4500, 6750, 10125];
|
|
39263
39323
|
var WAIT_STEADY_INTERVAL_MS = 15e3;
|
|
39264
39324
|
var DEFAULT_WAIT_TIMEOUT = "5m";
|
|
@@ -39266,7 +39326,7 @@ var MAX_WAIT_TIMEOUT_MS = 9 * 6e4;
|
|
|
39266
39326
|
var MAX_CONSECUTIVE_FAILURES = 5;
|
|
39267
39327
|
var TIMEOUT_RE = /^(0|\d+s|\d+m)$/;
|
|
39268
39328
|
var RETRYABLE_STATUS_ERRORS = { NODE_UNREACHABLE: true, NODE_ERROR: true };
|
|
39269
|
-
var EXPLICIT_KEY_HINT = `${
|
|
39329
|
+
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
39330
|
function abortableSleep(ms2, signal) {
|
|
39271
39331
|
return new Promise((resolve9) => {
|
|
39272
39332
|
if (signal.aborted) {
|
|
@@ -39302,7 +39362,7 @@ async function runSecretsWait(request, deps) {
|
|
|
39302
39362
|
let step = 0;
|
|
39303
39363
|
const cancelled = () => new CLIError(
|
|
39304
39364
|
"CANCELLED",
|
|
39305
|
-
`Interrupted: ${
|
|
39365
|
+
`Interrupted: ${CLI_NAME31} secrets wait stopped before the secrets were ready.`,
|
|
39306
39366
|
ExitCode.CANCELLED,
|
|
39307
39367
|
progress === void 0 ? {} : { progress }
|
|
39308
39368
|
);
|
|
@@ -39446,13 +39506,13 @@ function statusError(error, profile, warnings) {
|
|
|
39446
39506
|
if (error.code === "AUTH_REQUIRED" || error.code === "SESSION_NOT_FOUND") {
|
|
39447
39507
|
const recovery = recoveryOf(error, "sign_in");
|
|
39448
39508
|
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 ${
|
|
39509
|
+
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
39510
|
return new CLIError("AUTH_REQUIRED", error.message, ExitCode.AUTH_REQUIRED, { hint, recovery, ...extra });
|
|
39451
39511
|
}
|
|
39452
39512
|
}
|
|
39453
39513
|
if (error.code === "PROFILE_MIGRATION_REQUIRED") {
|
|
39454
39514
|
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 ${
|
|
39515
|
+
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
39516
|
return new CLIError("PROFILE_MIGRATION_REQUIRED", error.message, ExitCode.ERROR, {
|
|
39457
39517
|
hint,
|
|
39458
39518
|
...recovery === void 0 ? {} : { recovery },
|
|
@@ -39644,11 +39704,11 @@ async function runSecretOperation(params) {
|
|
|
39644
39704
|
}
|
|
39645
39705
|
function secretPermissionReason(action, name) {
|
|
39646
39706
|
const target = name ? ` secret "${name}"` : " secrets";
|
|
39647
|
-
return `Allow \`${
|
|
39707
|
+
return `Allow \`${CLI_NAME32} secrets ${action}${name ? ` ${name}` : ""}\` to access${target} with the required TinyCloud permissions.`;
|
|
39648
39708
|
}
|
|
39649
39709
|
function assertOwnerApprovalPossible(profileName2, profile, action, name, missing = []) {
|
|
39650
39710
|
if (profile.authMethod !== "openkey" || process.stdin.isTTY || process.stderr.isTTY) return;
|
|
39651
|
-
const command = `${
|
|
39711
|
+
const command = `${CLI_NAME32} secrets ${action === "del" ? "delete" : action}${name ? ` ${name}` : ""}`;
|
|
39652
39712
|
throw new CLIError(
|
|
39653
39713
|
"PERMISSION_DENIED",
|
|
39654
39714
|
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 +39719,7 @@ function assertOwnerApprovalPossible(profileName2, profile, action, name, missin
|
|
|
39659
39719
|
);
|
|
39660
39720
|
}
|
|
39661
39721
|
function scopedSecretLoginHint(profileName2) {
|
|
39662
|
-
return `Have the owner approve a scoped login whose manifest names the secret: ${
|
|
39722
|
+
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
39723
|
}
|
|
39664
39724
|
async function runSecretOperationAttempt(label, operation) {
|
|
39665
39725
|
try {
|
|
@@ -39793,7 +39853,7 @@ ${message}`,
|
|
|
39793
39853
|
"AUTH_REQUIRED",
|
|
39794
39854
|
"Not signed in to TinyCloud.",
|
|
39795
39855
|
ExitCode.AUTH_REQUIRED,
|
|
39796
|
-
{ hint: `Sign in with: ${
|
|
39856
|
+
{ hint: `Sign in with: ${CLI_NAME32} --profile ${result.context.profile} auth login` }
|
|
39797
39857
|
);
|
|
39798
39858
|
}
|
|
39799
39859
|
if (result.error.code === "NODE_UNREACHABLE") {
|
|
@@ -40270,7 +40330,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
40270
40330
|
name: "Encryption network",
|
|
40271
40331
|
ok: false,
|
|
40272
40332
|
detail: `${networkName} not found`,
|
|
40273
|
-
hint: `${
|
|
40333
|
+
hint: `${CLI_NAME32} secrets network init ${networkName}`
|
|
40274
40334
|
}
|
|
40275
40335
|
];
|
|
40276
40336
|
let secret;
|
|
@@ -40316,7 +40376,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
40316
40376
|
name: "Secret access",
|
|
40317
40377
|
ok: false,
|
|
40318
40378
|
detail: notFound ? `${resolved.permissionPaths.vault} not found` : result.error.message,
|
|
40319
|
-
hint: notFound ? local ? `${
|
|
40379
|
+
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
40380
|
});
|
|
40321
40381
|
}
|
|
40322
40382
|
} else {
|
|
@@ -40560,7 +40620,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
40560
40620
|
if (effectiveLocal(profile, options)) {
|
|
40561
40621
|
throw new CLIError(
|
|
40562
40622
|
"LOCAL_OWNER_PROFILE",
|
|
40563
|
-
`Profile "${ctx.profile}" uses a local key, which owns its secrets: store them with ${
|
|
40623
|
+
`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
40624
|
ExitCode.USAGE_ERROR
|
|
40565
40625
|
);
|
|
40566
40626
|
}
|
|
@@ -40683,7 +40743,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
40683
40743
|
|
|
40684
40744
|
// src/commands/share.ts
|
|
40685
40745
|
import { Option } from "commander";
|
|
40686
|
-
import { CLI_NAME as
|
|
40746
|
+
import { CLI_NAME as CLI_NAME33 } from "@tinycloud/operations/cli-name";
|
|
40687
40747
|
import { ProfileLockTimeoutError as ProfileLockTimeoutError3 } from "@tinycloud/operations/state";
|
|
40688
40748
|
init_errors();
|
|
40689
40749
|
|
|
@@ -42144,7 +42204,7 @@ function shareCliError(error, operation = "publish") {
|
|
|
42144
42204
|
"SHARE_HISTORY_UNREADABLE",
|
|
42145
42205
|
"sender history cannot be read with the current session key",
|
|
42146
42206
|
1,
|
|
42147
|
-
{ hint: `Run \`${
|
|
42207
|
+
{ hint: `Run \`${CLI_NAME33} share history recover\` to move the unreadable file aside. The old file is kept, not deleted.` }
|
|
42148
42208
|
);
|
|
42149
42209
|
}
|
|
42150
42210
|
if (error instanceof SharePublishAuthorityError) {
|
|
@@ -42152,12 +42212,12 @@ function shareCliError(error, operation = "publish") {
|
|
|
42152
42212
|
const profileName2 = "profileName" in failure ? failure.profileName : void 0;
|
|
42153
42213
|
const localKey = "localKey" in failure && failure.localKey === true;
|
|
42154
42214
|
const profileHint = profileName2 === void 0 ? "" : `--profile ${profileName2} `;
|
|
42155
|
-
const loginHint = localKey ? `\`${
|
|
42215
|
+
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
42216
|
if (failure.kind === "caveated-session") {
|
|
42157
42217
|
const holder = profileName2 === void 0 ? "this session" : `profile ${profileName2}'s session`;
|
|
42158
42218
|
return new CLIError(
|
|
42159
42219
|
"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): \`${
|
|
42220
|
+
`${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
42221
|
5
|
|
42162
42222
|
);
|
|
42163
42223
|
}
|
|
@@ -42258,7 +42318,7 @@ function shareCliError(error, operation = "publish") {
|
|
|
42258
42318
|
if (nodeCode === "ENOENT") return new CLIError("INVALID_ARGUMENT", "share input was not found", 2);
|
|
42259
42319
|
if (nodeCode === "EISDIR") return new CLIError("INVALID_ARGUMENT", "share input must be a Markdown file", 2);
|
|
42260
42320
|
const mapped = typeof nodeCode === "string" && known[nodeCode] !== void 0 ? known[nodeCode] : known[message];
|
|
42261
|
-
const classified = mapped === void 0 ? classifyShareFailure(error, `\`${
|
|
42321
|
+
const classified = mapped === void 0 ? classifyShareFailure(error, `\`${CLI_NAME33} auth login\``) : void 0;
|
|
42262
42322
|
if (classified !== void 0) return Object.assign(new CLIError(classified.code, classified.reason, classified.exitCode), { cause: error });
|
|
42263
42323
|
if (error instanceof TypeError) return new CLIError("INVALID_ARGUMENT", "share input is invalid", 2);
|
|
42264
42324
|
return new CLIError(mapped?.code ?? "INVALID_ARGUMENT", mapped ? mapped.code : "share operation failed", mapped?.exit ?? 2);
|
|
@@ -42668,7 +42728,7 @@ init_constants();
|
|
|
42668
42728
|
init_sdk();
|
|
42669
42729
|
init_host();
|
|
42670
42730
|
init_theme();
|
|
42671
|
-
import { CLI_NAME as
|
|
42731
|
+
import { CLI_NAME as CLI_NAME34 } from "@tinycloud/operations/cli-name";
|
|
42672
42732
|
import { ACCOUNT_REGISTRY_SPACE } from "@tinycloud/node-sdk";
|
|
42673
42733
|
import { randomBytes as randomBytes9 } from "crypto";
|
|
42674
42734
|
import { mkdir as mkdir7, writeFile as writeFile4 } from "fs/promises";
|
|
@@ -42715,7 +42775,7 @@ function registerSpaceCommand(program) {
|
|
|
42715
42775
|
} else {
|
|
42716
42776
|
const profile = await ProfileManager.getProfile(ctx.profile);
|
|
42717
42777
|
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: ${
|
|
42778
|
+
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
42779
|
throw new CLIError(
|
|
42720
42780
|
"PERMISSION_DENIED",
|
|
42721
42781
|
`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 +42812,10 @@ function registerSpaceCommand(program) {
|
|
|
42752
42812
|
|
|
42753
42813
|
A delegate cannot host a space \u2014 only its owner (root authority) can. This
|
|
42754
42814
|
emits a tinycloud.host.request artifact naming the space and its owner so you
|
|
42755
|
-
can hand it to the owner; they run \`${
|
|
42815
|
+
can hand it to the owner; they run \`${CLI_NAME34} space host <name>\` and confirm.
|
|
42756
42816
|
|
|
42757
42817
|
If you ARE the owner of the resolved space, this refuses and tells you to host
|
|
42758
|
-
it directly with \`${
|
|
42818
|
+
it directly with \`${CLI_NAME34} space host <name>\` (no request needed).
|
|
42759
42819
|
`).action(async (name, options, cmd) => {
|
|
42760
42820
|
try {
|
|
42761
42821
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -42766,7 +42826,7 @@ it directly with \`${CLI_NAME33} space host <name>\` (no request needed).
|
|
|
42766
42826
|
if (await isRootAuthority(spaceId, ctx.profile)) {
|
|
42767
42827
|
throw new CLIError(
|
|
42768
42828
|
"ALREADY_ROOT_AUTHORITY",
|
|
42769
|
-
`You are the owner of ${spaceId}. Host it directly: ${
|
|
42829
|
+
`You are the owner of ${spaceId}. Host it directly: ${CLI_NAME34} space host ${spaceName}`,
|
|
42770
42830
|
ExitCode.USAGE_ERROR
|
|
42771
42831
|
);
|
|
42772
42832
|
}
|
|
@@ -42852,7 +42912,7 @@ init_sdk();
|
|
|
42852
42912
|
init_space();
|
|
42853
42913
|
init_host();
|
|
42854
42914
|
init_theme();
|
|
42855
|
-
import { CLI_NAME as
|
|
42915
|
+
import { CLI_NAME as CLI_NAME35 } from "@tinycloud/operations/cli-name";
|
|
42856
42916
|
import { writeFile as writeFile5 } from "fs/promises";
|
|
42857
42917
|
import { resolve as resolve8 } from "path";
|
|
42858
42918
|
async function dbHandle(node, dbName, spaceInput, profileName2) {
|
|
@@ -42876,12 +42936,12 @@ database for simple apps, or pass --db to target a named database. Pass
|
|
|
42876
42936
|
--space to target a non-primary space (e.g. the manifest "applications" space).
|
|
42877
42937
|
|
|
42878
42938
|
Common workflows:
|
|
42879
|
-
$ ${
|
|
42880
|
-
$ ${
|
|
42881
|
-
$ ${
|
|
42882
|
-
$ ${
|
|
42883
|
-
$ ${
|
|
42884
|
-
$ ${
|
|
42939
|
+
$ ${CLI_NAME35} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
|
|
42940
|
+
$ ${CLI_NAME35} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["ship docs"]'
|
|
42941
|
+
$ ${CLI_NAME35} sql query "SELECT id, body FROM notes ORDER BY id"
|
|
42942
|
+
$ ${CLI_NAME35} sql query "SELECT * FROM events WHERE type = ?" --db analytics --params '["signup"]'
|
|
42943
|
+
$ ${CLI_NAME35} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
|
|
42944
|
+
$ ${CLI_NAME35} sql export --db analytics --output analytics.db
|
|
42885
42945
|
|
|
42886
42946
|
Commands:
|
|
42887
42947
|
query Read rows with SELECT statements
|
|
@@ -42898,10 +42958,10 @@ Tips:
|
|
|
42898
42958
|
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
42959
|
|
|
42900
42960
|
Examples:
|
|
42901
|
-
$ ${
|
|
42902
|
-
$ ${
|
|
42903
|
-
$ ${
|
|
42904
|
-
$ ${
|
|
42961
|
+
$ ${CLI_NAME35} sql query "SELECT * FROM notes ORDER BY id"
|
|
42962
|
+
$ ${CLI_NAME35} sql query "SELECT * FROM notes WHERE id = ?" --params '[42]'
|
|
42963
|
+
$ ${CLI_NAME35} sql query "SELECT count(*) AS total FROM events" --db analytics --json
|
|
42964
|
+
$ ${CLI_NAME35} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
|
|
42905
42965
|
|
|
42906
42966
|
Output:
|
|
42907
42967
|
Human output is formatted as a table. Piped output or --json returns
|
|
@@ -42942,11 +43002,11 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
|
|
|
42942
43002
|
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
43003
|
|
|
42944
43004
|
Examples:
|
|
42945
|
-
$ ${
|
|
42946
|
-
$ ${
|
|
42947
|
-
$ ${
|
|
42948
|
-
$ ${
|
|
42949
|
-
$ ${
|
|
43005
|
+
$ ${CLI_NAME35} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
|
|
43006
|
+
$ ${CLI_NAME35} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["first note"]'
|
|
43007
|
+
$ ${CLI_NAME35} sql execute "UPDATE notes SET body = ? WHERE id = ?" --params '["edited", 1]'
|
|
43008
|
+
$ ${CLI_NAME35} sql execute "DROP TABLE old_notes" --db archive
|
|
43009
|
+
$ ${CLI_NAME35} sql execute "DELETE FROM conversation WHERE id = ?" --space applications --db xyz.tinycloud.listen/conversations --params '["abc"]'
|
|
42950
43010
|
|
|
42951
43011
|
Output:
|
|
42952
43012
|
Returns JSON with the changed row count and last inserted row id when available.
|
|
@@ -42975,9 +43035,9 @@ Output:
|
|
|
42975
43035
|
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
43036
|
|
|
42977
43037
|
Examples:
|
|
42978
|
-
$ ${
|
|
42979
|
-
$ ${
|
|
42980
|
-
$ ${
|
|
43038
|
+
$ ${CLI_NAME35} sql export
|
|
43039
|
+
$ ${CLI_NAME35} sql export --db analytics --output analytics.db
|
|
43040
|
+
$ ${CLI_NAME35} sql export --space applications --db xyz.tinycloud.listen/conversations --output listen.db
|
|
42981
43041
|
|
|
42982
43042
|
Output:
|
|
42983
43043
|
Writes the database file locally and returns JSON with the path and size.
|
|
@@ -43010,19 +43070,19 @@ Output:
|
|
|
43010
43070
|
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
43071
|
|
|
43012
43072
|
Examples:
|
|
43013
|
-
$ ${
|
|
43073
|
+
$ ${CLI_NAME35} sql copy --from-db com.tinycloud.conversation-sync/conversations \\
|
|
43014
43074
|
--to-db xyz.tinycloud.listen/conversations \\
|
|
43015
43075
|
--space applications --dry-run
|
|
43016
|
-
$ ${
|
|
43076
|
+
$ ${CLI_NAME35} sql copy --from-space applications --from-db com.foo/data \\
|
|
43017
43077
|
--to-space applications --to-db com.bar/data \\
|
|
43018
43078
|
--table conversation --table participant
|
|
43019
43079
|
|
|
43020
43080
|
Notes:
|
|
43021
43081
|
- 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 \`${
|
|
43082
|
+
- Does NOT create destination tables. Run the target app once (or use \`${CLI_NAME35} sql execute\`)
|
|
43023
43083
|
to materialize the schema before copying.
|
|
43024
43084
|
- One row at a time; suitable for small/medium datasets. Large copies should
|
|
43025
|
-
use \`${
|
|
43085
|
+
use \`${CLI_NAME35} sql export\` + bulk import.
|
|
43026
43086
|
- Authorization: the active session/delegation must cover sql/read on source
|
|
43027
43087
|
AND sql/write on destination. Otherwise the relevant operation will fail.
|
|
43028
43088
|
`).action(async (options, cmd) => {
|
|
@@ -43122,7 +43182,7 @@ init_errors();
|
|
|
43122
43182
|
init_formatter();
|
|
43123
43183
|
init_theme();
|
|
43124
43184
|
init_permissions();
|
|
43125
|
-
import { CLI_NAME as
|
|
43185
|
+
import { CLI_NAME as CLI_NAME36 } from "@tinycloud/operations/cli-name";
|
|
43126
43186
|
import {
|
|
43127
43187
|
NodeWasmBindings as NodeWasmBindings2
|
|
43128
43188
|
} from "@tinycloud/node-sdk";
|
|
@@ -43396,7 +43456,7 @@ function formatStatus(summary) {
|
|
|
43396
43456
|
lines.push(`Default profile: ${theme.value(summary.defaultProfile)}`);
|
|
43397
43457
|
lines.push("");
|
|
43398
43458
|
if (summary.profiles.length === 0) {
|
|
43399
|
-
lines.push(theme.muted(`No profiles configured. Run: ${
|
|
43459
|
+
lines.push(theme.muted(`No profiles configured. Run: ${CLI_NAME36} init`));
|
|
43400
43460
|
return `${lines.join("\n")}
|
|
43401
43461
|
`;
|
|
43402
43462
|
}
|