@tinycloud/cli 1.4.0 → 1.5.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +618 -188
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +4 -4
- package/skills/tc-cli/REFERENCE.md +1 -1
package/dist/legacy-entry.js
CHANGED
|
@@ -9049,7 +9049,8 @@ async function assertPendingRemoteLogoutSession(name, expected) {
|
|
|
9049
9049
|
}
|
|
9050
9050
|
async function clearProfileCredentials(name, syncDirectory = false, legacyCleanup = false) {
|
|
9051
9051
|
const profile = await ProfileManager.getProfile(name);
|
|
9052
|
-
|
|
9052
|
+
const { namedKeys: _namedKeys, ...signedOutProfile } = profile;
|
|
9053
|
+
await ProfileManager.setProfileDurable(name, { ...signedOutProfile, authSignedOut: true, ...legacyCleanup ? { legacyReplicaCleanup: true } : {} });
|
|
9053
9054
|
let failed = false;
|
|
9054
9055
|
let error;
|
|
9055
9056
|
try {
|
|
@@ -9481,7 +9482,8 @@ async function bootstrapDelegatedSession(ctx, delegation) {
|
|
|
9481
9482
|
jwk,
|
|
9482
9483
|
verificationMethod: sessionDid
|
|
9483
9484
|
};
|
|
9484
|
-
const
|
|
9485
|
+
const { namedKeys: _namedKeys, ...previousFields } = previousProfile;
|
|
9486
|
+
const profile = { ...previousFields, sessionDid, spaceId: delegation.spaceId };
|
|
9485
9487
|
try {
|
|
9486
9488
|
await ProfileManager.setSession(ctx.profile, session);
|
|
9487
9489
|
await ProfileManager.setProfile(ctx.profile, profile);
|
|
@@ -16454,7 +16456,7 @@ function formatSpace(space) {
|
|
|
16454
16456
|
init_profile_gate();
|
|
16455
16457
|
init_dist();
|
|
16456
16458
|
init_profiles();
|
|
16457
|
-
import { CLI_NAME as
|
|
16459
|
+
import { CLI_NAME as CLI_NAME18 } from "@tinycloud/operations/cli-name";
|
|
16458
16460
|
import { get as httpGet } from "http";
|
|
16459
16461
|
import { get as httpsGet } from "https";
|
|
16460
16462
|
import { spawn } from "child_process";
|
|
@@ -30611,7 +30613,7 @@ function createEncryptedProfileHistory(profileName2, sessionSigner) {
|
|
|
30611
30613
|
throw new ShareHistoryReadError();
|
|
30612
30614
|
}
|
|
30613
30615
|
};
|
|
30614
|
-
const
|
|
30616
|
+
const parseEnvelope2 = (text) => {
|
|
30615
30617
|
let envelope;
|
|
30616
30618
|
try {
|
|
30617
30619
|
envelope = JSON.parse(text);
|
|
@@ -30633,7 +30635,7 @@ function createEncryptedProfileHistory(profileName2, sessionSigner) {
|
|
|
30633
30635
|
const preparedSalt = async (profile) => {
|
|
30634
30636
|
const file = await readIfPresent(await path(profile));
|
|
30635
30637
|
if (file === void 0) return crypto.getRandomValues(new Uint8Array(16));
|
|
30636
|
-
const salt = unb64(
|
|
30638
|
+
const salt = unb64(parseEnvelope2(file.toString("utf8")).kdfSalt);
|
|
30637
30639
|
if (salt.length < 16) throw new ShareHistoryUnreadableError();
|
|
30638
30640
|
return salt;
|
|
30639
30641
|
};
|
|
@@ -30644,7 +30646,7 @@ function createEncryptedProfileHistory(profileName2, sessionSigner) {
|
|
|
30644
30646
|
if (legacy === void 0 || legacy.length <= 12) return [];
|
|
30645
30647
|
return decryptRecords(ready.legacyKey, new Uint8Array(legacy.subarray(0, 12)), new Uint8Array(legacy.subarray(12)));
|
|
30646
30648
|
}
|
|
30647
|
-
const envelope =
|
|
30649
|
+
const envelope = parseEnvelope2(file.toString("utf8"));
|
|
30648
30650
|
if (envelope.kdfSalt !== b64(ready.salt)) throw saltChanged;
|
|
30649
30651
|
const iv = unb64(envelope.iv);
|
|
30650
30652
|
const ciphertext = unb64(envelope.ciphertext);
|
|
@@ -31281,9 +31283,16 @@ async function localKeySignIn(options) {
|
|
|
31281
31283
|
privateKey: options.privateKey,
|
|
31282
31284
|
host: options.host,
|
|
31283
31285
|
autoCreateSpace: true,
|
|
31286
|
+
...options.keyAbilities ? { additionalRawAbilities: options.keyAbilities } : {},
|
|
31284
31287
|
...replication ? { replication } : {}
|
|
31285
31288
|
});
|
|
31286
31289
|
await node.signIn();
|
|
31290
|
+
let networkReady;
|
|
31291
|
+
if (options.keyAbilities && !node.freshEncryptionNetworkCreated) {
|
|
31292
|
+
networkReady = await node.ensureEncryptionNetwork().then(() => true, () => false);
|
|
31293
|
+
} else if (options.keyAbilities) {
|
|
31294
|
+
networkReady = true;
|
|
31295
|
+
}
|
|
31287
31296
|
const address = await new PrivateKeySigner(options.privateKey).getAddress();
|
|
31288
31297
|
const session = node.session;
|
|
31289
31298
|
if (!session) {
|
|
@@ -31299,7 +31308,8 @@ async function localKeySignIn(options) {
|
|
|
31299
31308
|
verificationMethod: session.verificationMethod,
|
|
31300
31309
|
siwe: session.siwe,
|
|
31301
31310
|
signature: session.signature,
|
|
31302
|
-
...node.replicaStamp === void 0 ? {} : { replicaStamp: node.replicaStamp }
|
|
31311
|
+
...node.replicaStamp === void 0 ? {} : { replicaStamp: node.replicaStamp },
|
|
31312
|
+
...networkReady === void 0 ? {} : { networkReady }
|
|
31303
31313
|
};
|
|
31304
31314
|
}
|
|
31305
31315
|
|
|
@@ -31308,6 +31318,7 @@ init_errors();
|
|
|
31308
31318
|
init_constants();
|
|
31309
31319
|
import { NodeWasmBindings, signedCompactUcanExpiry as signedCompactUcanExpiry2 } from "@tinycloud/node-sdk";
|
|
31310
31320
|
import { isLocalOwnerProfile as isLocalOwnerProfile2, ownerSessionLacksProof as ownerSessionLacksProof2 } from "@tinycloud/operations/profile";
|
|
31321
|
+
import { CLI_NAME as CLI_NAME12 } from "@tinycloud/operations/cli-name";
|
|
31311
31322
|
|
|
31312
31323
|
// src/lib/duration.ts
|
|
31313
31324
|
function parseDuration(input) {
|
|
@@ -31343,6 +31354,87 @@ init_manifest();
|
|
|
31343
31354
|
init_raw_encryption();
|
|
31344
31355
|
init_owner_did();
|
|
31345
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
|
|
31346
31438
|
var CAPABILITIES_READ = "tinycloud.capabilities/read";
|
|
31347
31439
|
var CLOCK_SKEW_MS = 3e4;
|
|
31348
31440
|
var SIGNED_RECAP = "signed-recap";
|
|
@@ -31595,6 +31687,35 @@ function verifySignedSession(data, key, sessionDid, expected = {}) {
|
|
|
31595
31687
|
}
|
|
31596
31688
|
return { ownerDid, permissions, expiresAt };
|
|
31597
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
|
+
}
|
|
31598
31719
|
function verifyScopedLogin(data, key, sessionDid, requested, expected = {}) {
|
|
31599
31720
|
const notStored = expected.purpose === "grant" ? "No grant was stored." : "No scoped session was saved.";
|
|
31600
31721
|
let signed;
|
|
@@ -31620,10 +31741,11 @@ function verifyScopedLogin(data, key, sessionDid, requested, expected = {}) {
|
|
|
31620
31741
|
}
|
|
31621
31742
|
const legacyNested = [];
|
|
31622
31743
|
const approved = [];
|
|
31623
|
-
|
|
31744
|
+
const keyRowRest = expected.namedKeyRow === void 0 ? signed.permissions : withoutRequestedKeyRow(signed.permissions, expected.namedKeyRow, signed.ownerDid);
|
|
31745
|
+
for (const permission of keyRowRest) {
|
|
31624
31746
|
(isLegacyNestedDecrypt(permission, requested, signed.ownerDid, spaceId) ? legacyNested : approved).push(permission);
|
|
31625
31747
|
}
|
|
31626
|
-
for (const permission of
|
|
31748
|
+
for (const permission of keyRowRest) {
|
|
31627
31749
|
if (isVerifiedRawEncryptionPermission(permission) && !rawEncryptionOwnerMatches(permission.path, signed.ownerDid)) {
|
|
31628
31750
|
throw new CLIError("OPENKEY_SCOPE_MISMATCH", `OpenKey signed decrypt for a network not owned by the approving identity. ${notStored}`, ExitCode.PERMISSION_DENIED);
|
|
31629
31751
|
}
|
|
@@ -31639,22 +31761,22 @@ function verifyScopedLogin(data, key, sessionDid, requested, expected = {}) {
|
|
|
31639
31761
|
}
|
|
31640
31762
|
|
|
31641
31763
|
// src/auth/login-commit.ts
|
|
31764
|
+
import { lstat as lstat6 } from "fs/promises";
|
|
31642
31765
|
init_constants();
|
|
31643
31766
|
init_profiles();
|
|
31644
31767
|
init_storage();
|
|
31645
31768
|
init_errors();
|
|
31646
31769
|
init_space();
|
|
31647
|
-
import { lstat as lstat6 } from "fs/promises";
|
|
31648
31770
|
import { join as join13 } from "path";
|
|
31649
31771
|
import { REPLICA_LOGOUT_PENDING_FILE as REPLICA_LOGOUT_PENDING_FILE3 } from "@tinycloud/node-sdk";
|
|
31650
|
-
import { CLI_NAME as
|
|
31772
|
+
import { CLI_NAME as CLI_NAME13 } from "@tinycloud/operations/cli-name";
|
|
31651
31773
|
import { isLocalOwnerProfile as isLocalOwnerProfile3, ownerSessionLacksProof as ownerSessionLacksProof3 } from "@tinycloud/operations/profile";
|
|
31652
31774
|
import { ProfileLockTimeoutError as ProfileLockTimeoutError2, profilePath as profilePath7 } from "@tinycloud/operations/state";
|
|
31653
31775
|
function assertNotLocalOwner(profileName2, profile, flow) {
|
|
31654
31776
|
if (profile === null || !isLocalOwnerProfile3(profile)) return;
|
|
31655
31777
|
throw new CLIError(
|
|
31656
31778
|
"LOCAL_OWNER_PROFILE",
|
|
31657
|
-
`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 ...\`.`,
|
|
31658
31780
|
ExitCode.USAGE_ERROR
|
|
31659
31781
|
);
|
|
31660
31782
|
}
|
|
@@ -31689,7 +31811,7 @@ function assertSessionReplaceable(profileName2, snapshot, ownerDid, scope, newEx
|
|
|
31689
31811
|
if (problem !== void 0) {
|
|
31690
31812
|
throw new CLIError(
|
|
31691
31813
|
"PROFILE_STATE_INCONSISTENT",
|
|
31692
|
-
`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.`,
|
|
31693
31815
|
ExitCode.ERROR
|
|
31694
31816
|
);
|
|
31695
31817
|
}
|
|
@@ -31705,7 +31827,7 @@ function assertSessionReplaceable(profileName2, snapshot, ownerDid, scope, newEx
|
|
|
31705
31827
|
const space = typeof session.spaceId === "string" ? session.spaceId : "an unknown space";
|
|
31706
31828
|
throw new CLIError(
|
|
31707
31829
|
"SESSION_IN_USE",
|
|
31708
|
-
`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.`,
|
|
31709
31831
|
ExitCode.USAGE_ERROR
|
|
31710
31832
|
);
|
|
31711
31833
|
}
|
|
@@ -31739,7 +31861,7 @@ async function commitLogin(profileName2, snapshot, commit) {
|
|
|
31739
31861
|
if (canonicalJson(current.profile) !== canonicalJson(snapshot.profile) || canonicalJson(current.key) !== canonicalJson(snapshot.key) || canonicalJson(current.session) !== canonicalJson(snapshot.session)) {
|
|
31740
31862
|
throw new CLIError(
|
|
31741
31863
|
"PROFILE_CHANGED_DURING_LOGIN",
|
|
31742
|
-
`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.`,
|
|
31743
31865
|
ExitCode.ERROR
|
|
31744
31866
|
);
|
|
31745
31867
|
}
|
|
@@ -31758,20 +31880,21 @@ async function commitLogin(profileName2, snapshot, commit) {
|
|
|
31758
31880
|
assertSessionReplaceable(profileName2, current, commit.approved.ownerDid, commit.approved.scope, newExpiresAt);
|
|
31759
31881
|
}
|
|
31760
31882
|
try {
|
|
31883
|
+
const profile = withVerifiedNamedKeys(commit.profile, commit.session);
|
|
31761
31884
|
await ProfileManager.setKey(profileName2, commit.key);
|
|
31762
31885
|
await ProfileManager.setSession(profileName2, commit.session);
|
|
31763
31886
|
if (current.profile?.authSignedOut === true || commit.durable === true) {
|
|
31764
31887
|
await syncAuthCredentialFiles(profilePath7(profileName2));
|
|
31765
|
-
await ProfileManager.setProfileDurable(profileName2, { ...
|
|
31888
|
+
await ProfileManager.setProfileDurable(profileName2, { ...profile, authSignedOut: false });
|
|
31766
31889
|
} else {
|
|
31767
|
-
await ProfileManager.setProfile(profileName2,
|
|
31890
|
+
await ProfileManager.setProfile(profileName2, profile);
|
|
31768
31891
|
}
|
|
31769
31892
|
} catch (error) {
|
|
31770
31893
|
const failures = await restore(profileName2, current);
|
|
31771
31894
|
if (failures.length === 0) throw error;
|
|
31772
31895
|
throw new CLIError(
|
|
31773
31896
|
"PROFILE_STATE_INCONSISTENT",
|
|
31774
|
-
`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.`,
|
|
31775
31898
|
ExitCode.ERROR
|
|
31776
31899
|
);
|
|
31777
31900
|
}
|
|
@@ -31787,7 +31910,7 @@ async function commitLogin(profileName2, snapshot, commit) {
|
|
|
31787
31910
|
|
|
31788
31911
|
// src/auth/owner-key.ts
|
|
31789
31912
|
init_theme();
|
|
31790
|
-
import { CLI_NAME as
|
|
31913
|
+
import { CLI_NAME as CLI_NAME14 } from "@tinycloud/operations/cli-name";
|
|
31791
31914
|
function openKeyPrimaryFlag(delegation) {
|
|
31792
31915
|
return typeof delegation.primary === "boolean" ? delegation.primary : void 0;
|
|
31793
31916
|
}
|
|
@@ -31798,7 +31921,7 @@ function withOwnerKeyPrimary(profile, primary) {
|
|
|
31798
31921
|
function nonPrimaryKeyWarning(ownerDid) {
|
|
31799
31922
|
const address = ownerDid.split(":")[4] ?? ownerDid;
|
|
31800
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.
|
|
31801
|
-
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>
|
|
31802
31925
|
`;
|
|
31803
31926
|
}
|
|
31804
31927
|
function warnIfNotPrimaryKey(ownerDid, primary) {
|
|
@@ -31807,7 +31930,7 @@ function warnIfNotPrimaryKey(ownerDid, primary) {
|
|
|
31807
31930
|
}
|
|
31808
31931
|
function announceFirstOwner(profileName2, expectedOwner, ownerDid) {
|
|
31809
31932
|
if (expectedOwner !== void 0 || ownerDid === void 0) return;
|
|
31810
|
-
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.
|
|
31811
31934
|
`);
|
|
31812
31935
|
}
|
|
31813
31936
|
function ownerLoginPermissions(owner) {
|
|
@@ -32209,6 +32332,7 @@ async function loginWithDeviceAuthorization(input) {
|
|
|
32209
32332
|
operatorType: existing?.operatorType ?? "human",
|
|
32210
32333
|
authMethod: "openkey"
|
|
32211
32334
|
}, result.primary);
|
|
32335
|
+
delete profile.namedKeys;
|
|
32212
32336
|
await commitLogin(input.profileName, snapshot, {
|
|
32213
32337
|
key,
|
|
32214
32338
|
session: result.session,
|
|
@@ -32637,7 +32761,7 @@ init_types();
|
|
|
32637
32761
|
init_constants();
|
|
32638
32762
|
init_permissions();
|
|
32639
32763
|
init_sdk();
|
|
32640
|
-
import { CLI_NAME as
|
|
32764
|
+
import { CLI_NAME as CLI_NAME16 } from "@tinycloud/operations/cli-name";
|
|
32641
32765
|
import { TinyCloudNode as TinyCloudNode2 } from "@tinycloud/node-sdk";
|
|
32642
32766
|
import { DelegationRequestBindingSchema, PermissionEntrySchema } from "@tinycloud/operations/artifacts";
|
|
32643
32767
|
import {
|
|
@@ -32652,7 +32776,7 @@ import {
|
|
|
32652
32776
|
// src/lib/revoke-delegation.ts
|
|
32653
32777
|
init_constants();
|
|
32654
32778
|
init_errors();
|
|
32655
|
-
import { CLI_NAME as
|
|
32779
|
+
import { CLI_NAME as CLI_NAME15 } from "@tinycloud/operations/cli-name";
|
|
32656
32780
|
import { parseSignedCompactUcanAttenuation as parseSignedCompactUcanAttenuation3 } from "@tinycloud/sdk-core";
|
|
32657
32781
|
import { inspectStoredDelegation, withoutBearer as withoutBearer3 } from "@tinycloud/operations/delegation-binding";
|
|
32658
32782
|
init_permissions();
|
|
@@ -32916,7 +33040,7 @@ async function revokeDelegationOnNode(params) {
|
|
|
32916
33040
|
"REVOKE_UNAUTHORIZED",
|
|
32917
33041
|
"The node rejected this revocation: Unauthorized Revoker.",
|
|
32918
33042
|
ExitCode.PERMISSION_DENIED,
|
|
32919
|
-
{ 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}\`.` }
|
|
32920
33044
|
);
|
|
32921
33045
|
}
|
|
32922
33046
|
throw cliErrorFromService(result.error);
|
|
@@ -32999,7 +33123,7 @@ function renderList(result) {
|
|
|
32999
33123
|
}
|
|
33000
33124
|
process.stdout.write(`Profile: ${result.profile}
|
|
33001
33125
|
`);
|
|
33002
|
-
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})
|
|
33003
33127
|
` : "\n"));
|
|
33004
33128
|
process.stdout.write(`Local verification: ${result.localVerification}; node validation and revocation are not checked.
|
|
33005
33129
|
`);
|
|
@@ -33097,7 +33221,7 @@ function registerAuthGrantsCommand(auth, options = {}) {
|
|
|
33097
33221
|
ExitCode.ERROR,
|
|
33098
33222
|
{
|
|
33099
33223
|
causeCode: cause.code,
|
|
33100
|
-
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}`
|
|
33101
33225
|
}
|
|
33102
33226
|
);
|
|
33103
33227
|
}
|
|
@@ -33162,7 +33286,7 @@ init_safe_text();
|
|
|
33162
33286
|
init_raw_encryption();
|
|
33163
33287
|
init_space();
|
|
33164
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";
|
|
33165
|
-
import { CLI_NAME as
|
|
33289
|
+
import { CLI_NAME as CLI_NAME17 } from "@tinycloud/operations/cli-name";
|
|
33166
33290
|
var APPS_AGENT_PRESET = "apps-agent";
|
|
33167
33291
|
var APPS_AGENT_SPACE = "applications";
|
|
33168
33292
|
var APPS_AGENT_EXPIRY_DAYS = 7;
|
|
@@ -33244,7 +33368,7 @@ function lifetimePhrase(ms2) {
|
|
|
33244
33368
|
return `${hours} hour${hours === 1 ? "" : "s"}`;
|
|
33245
33369
|
}
|
|
33246
33370
|
function revokeSentence(lifetimeMs2) {
|
|
33247
|
-
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>\`.`;
|
|
33248
33372
|
}
|
|
33249
33373
|
function appsAgentScopeNotice(mode, appIds = []) {
|
|
33250
33374
|
const variant = APPS_AGENT_VARIANTS[mode];
|
|
@@ -33439,7 +33563,7 @@ function describeGrantScope(requested, requestedExpiry, applicationsSpace, maxLi
|
|
|
33439
33563
|
const durations = [lifetimeMs(requestedExpiry), ...entryLifetimes].filter((ms2) => ms2 !== void 0).map((ms2) => Math.min(ms2, maxLifetimeMs ?? Number.POSITIVE_INFINITY));
|
|
33440
33564
|
const longest = Math.max(...durations);
|
|
33441
33565
|
lines.push(
|
|
33442
|
-
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>\`.`
|
|
33443
33567
|
);
|
|
33444
33568
|
return { text: lines.join("\n"), entries: requested, extras };
|
|
33445
33569
|
}
|
|
@@ -33747,7 +33871,7 @@ async function revokeForLogout(ctx, discardLocalChanges) {
|
|
|
33747
33871
|
"REVOCATION_FAILED",
|
|
33748
33872
|
`Revocation failed for ${cid}: ${result.error.message}. Session, key, and replica files are retained for retry; local reads stay denied.`,
|
|
33749
33873
|
ExitCode.ERROR,
|
|
33750
|
-
{ cid, hint: `Retry \`${
|
|
33874
|
+
{ cid, hint: `Retry \`${CLI_NAME18} auth logout --revoke\` when the node is available.` }
|
|
33751
33875
|
);
|
|
33752
33876
|
}
|
|
33753
33877
|
if (result.data.cid !== cid || result.data.revoked !== true) {
|
|
@@ -33766,7 +33890,7 @@ async function revokeForLogout(ctx, discardLocalChanges) {
|
|
|
33766
33890
|
"REVOCATION_FAILED",
|
|
33767
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.`,
|
|
33768
33892
|
ExitCode.ERROR,
|
|
33769
|
-
{ hint: `Retry \`${
|
|
33893
|
+
{ hint: `Retry \`${CLI_NAME18} auth logout --revoke\` when online authority is available.` }
|
|
33770
33894
|
);
|
|
33771
33895
|
}
|
|
33772
33896
|
}
|
|
@@ -33965,7 +34089,7 @@ async function finishProfileLogout(ctx, options = {}) {
|
|
|
33965
34089
|
function registerAuthCommand(program, options) {
|
|
33966
34090
|
const auth = program.command("auth").description("Authentication management");
|
|
33967
34091
|
registerAuthGrantsCommand(auth, { openKeyAcquisition: options?.openKeyAcquisition });
|
|
33968
|
-
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) => {
|
|
33969
34093
|
try {
|
|
33970
34094
|
if (options2.device && options2.paste) {
|
|
33971
34095
|
throw new CLIError("INVALID_ARGUMENT", "--device and --paste are mutually exclusive.", ExitCode.USAGE_ERROR);
|
|
@@ -33981,6 +34105,9 @@ function registerAuthCommand(program, options) {
|
|
|
33981
34105
|
ExitCode.USAGE_ERROR
|
|
33982
34106
|
);
|
|
33983
34107
|
}
|
|
34108
|
+
if (options2.keys !== void 0 && (options2.device || options2.manifest)) {
|
|
34109
|
+
throw new CLIError("INVALID_ARGUMENT", "`--keys` can't be used with `--device` or `--manifest`.", ExitCode.USAGE_ERROR);
|
|
34110
|
+
}
|
|
33984
34111
|
const globalOpts = cmd.optsWithGlobals();
|
|
33985
34112
|
const ctx = await ProfileManager.resolveContext(globalOpts, { storedSession: "unused" });
|
|
33986
34113
|
await recoverPendingLogout(ctx.profile);
|
|
@@ -34042,6 +34169,8 @@ function registerAuthCommand(program, options) {
|
|
|
34042
34169
|
);
|
|
34043
34170
|
}
|
|
34044
34171
|
method = options2.method;
|
|
34172
|
+
} else if (options2.keys !== void 0) {
|
|
34173
|
+
method = profileConfig?.authMethod === "openkey" || options2.paste || options2.owner ? "openkey" : "local";
|
|
34045
34174
|
} else {
|
|
34046
34175
|
method = scoped ? "openkey" : await promptAuthMethod();
|
|
34047
34176
|
}
|
|
@@ -34054,7 +34183,7 @@ function registerAuthCommand(program, options) {
|
|
|
34054
34183
|
}
|
|
34055
34184
|
if (method === "local") {
|
|
34056
34185
|
buildReplicationLoginRequest(void 0, replicationOptions);
|
|
34057
|
-
await handleLocalAuth(ctx.profile, ctx.host, { replication: replicationOptions });
|
|
34186
|
+
await handleLocalAuth(ctx.profile, ctx.host, { replication: replicationOptions, keys: options2.keys });
|
|
34058
34187
|
await persistReplicationLogin(ctx.profile, replicationOptions);
|
|
34059
34188
|
if (replicationOptions.allowSecrets) writeReplicationSecretsWarning();
|
|
34060
34189
|
} else {
|
|
@@ -34067,7 +34196,8 @@ function registerAuthCommand(program, options) {
|
|
|
34067
34196
|
expectedOwner: owner,
|
|
34068
34197
|
replaceSession: options2.replaceSession === true,
|
|
34069
34198
|
persistHost,
|
|
34070
|
-
force: options2.force === true
|
|
34199
|
+
force: options2.force === true,
|
|
34200
|
+
keys: options2.keys
|
|
34071
34201
|
});
|
|
34072
34202
|
if (!preparedCommitted) await persistReplicationLogin(ctx.profile, replicationOptions);
|
|
34073
34203
|
if (replicationOptions.allowSecrets) writeReplicationSecretsWarning();
|
|
@@ -34117,7 +34247,7 @@ function registerAuthCommand(program, options) {
|
|
|
34117
34247
|
authenticated: false,
|
|
34118
34248
|
replicasRemoved,
|
|
34119
34249
|
replicasKept: options2.keepReplicas === true,
|
|
34120
|
-
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.`,
|
|
34121
34251
|
...online === void 0 ? {} : {
|
|
34122
34252
|
revokedCids: online.plan.cids,
|
|
34123
34253
|
revocationExcluded: online.plan.excluded
|
|
@@ -34262,7 +34392,7 @@ ${appsAgentScopeNotice(preset.mode, preset.appIds)}
|
|
|
34262
34392
|
if (profile.authMethod === "openkey" || options2.device) {
|
|
34263
34393
|
const key = await ProfileManager.getKey(ctx.profile);
|
|
34264
34394
|
if (!key) {
|
|
34265
|
-
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);
|
|
34266
34396
|
}
|
|
34267
34397
|
const openkeyHost = resolveOpenKeyHost(profile);
|
|
34268
34398
|
const grants = [];
|
|
@@ -34282,7 +34412,7 @@ ${appsAgentScopeNotice(preset.mode, preset.appIds)}
|
|
|
34282
34412
|
if (notices) process.stderr.write(`${notices.prompts[index]}
|
|
34283
34413
|
`);
|
|
34284
34414
|
const reason = permissionGrantReason(
|
|
34285
|
-
`Grant requested TinyCloud permissions from \`${
|
|
34415
|
+
`Grant requested TinyCloud permissions from \`${CLI_NAME18} auth request --grant\`.`,
|
|
34286
34416
|
group
|
|
34287
34417
|
);
|
|
34288
34418
|
const request = grantRequestPermissions(group, profile.spaceId ?? profile.spaceName);
|
|
@@ -34392,7 +34522,7 @@ ${appsAgentScopeNotice(preset.mode, preset.appIds)}
|
|
|
34392
34522
|
kind: parsed.kind,
|
|
34393
34523
|
requestId: parsed.requestId,
|
|
34394
34524
|
requested: parsed.requested,
|
|
34395
|
-
next: `${
|
|
34525
|
+
next: `${CLI_NAME18} auth retry ${parsed.requestId}`
|
|
34396
34526
|
});
|
|
34397
34527
|
return;
|
|
34398
34528
|
}
|
|
@@ -35001,7 +35131,7 @@ async function acquireOpenKeyApproval(params) {
|
|
|
35001
35131
|
if (!key) {
|
|
35002
35132
|
throw new CLIError(
|
|
35003
35133
|
"NO_KEY",
|
|
35004
|
-
`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.`,
|
|
35005
35135
|
ExitCode.AUTH_REQUIRED
|
|
35006
35136
|
);
|
|
35007
35137
|
}
|
|
@@ -35074,7 +35204,7 @@ async function ensureDelegationAuthority(params) {
|
|
|
35074
35204
|
if (!key) {
|
|
35075
35205
|
throw new CLIError(
|
|
35076
35206
|
"NO_KEY",
|
|
35077
|
-
`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.`,
|
|
35078
35208
|
ExitCode.AUTH_REQUIRED
|
|
35079
35209
|
);
|
|
35080
35210
|
}
|
|
@@ -35328,12 +35458,13 @@ async function rotateAuthKey(profileName2, host, options = {}) {
|
|
|
35328
35458
|
if (!profile.privateKey) {
|
|
35329
35459
|
throw new CLIError(
|
|
35330
35460
|
"LOCAL_OWNER_KEY_REQUIRED",
|
|
35331
|
-
`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.`,
|
|
35332
35462
|
ExitCode.AUTH_REQUIRED
|
|
35333
35463
|
);
|
|
35334
35464
|
}
|
|
35335
35465
|
const result2 = await handleLocalAuth(profileName2, host, {
|
|
35336
|
-
emitOutput: false
|
|
35466
|
+
emitOutput: false,
|
|
35467
|
+
keys: profile.namedKeys?.prefix
|
|
35337
35468
|
});
|
|
35338
35469
|
outputRotationResult(result2.profile, profileName2, oldDid, "local");
|
|
35339
35470
|
return;
|
|
@@ -35358,8 +35489,9 @@ async function rotateAuthKey(profileName2, host, options = {}) {
|
|
|
35358
35489
|
}
|
|
35359
35490
|
await ProfileManager.setKey(profileName2, jwk);
|
|
35360
35491
|
await ProfileManager.clearSession(profileName2);
|
|
35492
|
+
const { namedKeys: _rotated, ...currentFields } = current;
|
|
35361
35493
|
await ProfileManager.setProfile(profileName2, {
|
|
35362
|
-
...
|
|
35494
|
+
...currentFields,
|
|
35363
35495
|
host,
|
|
35364
35496
|
did,
|
|
35365
35497
|
sessionDid: did,
|
|
@@ -35371,7 +35503,8 @@ async function rotateAuthKey(profileName2, host, options = {}) {
|
|
|
35371
35503
|
const result = await refreshOpenKeySession(profileName2, host, {
|
|
35372
35504
|
paste: options.paste,
|
|
35373
35505
|
noPopup: options.noPopup,
|
|
35374
|
-
permissions
|
|
35506
|
+
permissions,
|
|
35507
|
+
...profile.namedKeys === void 0 ? {} : { keys: profile.namedKeys.prefix, expectedOwner: profile.ownerDid }
|
|
35375
35508
|
});
|
|
35376
35509
|
outputRotationResult(result.profile, profileName2, oldDid, "openkey");
|
|
35377
35510
|
}
|
|
@@ -35420,12 +35553,12 @@ async function persistCurrentLocalSession(profileName2, profile, session, replic
|
|
|
35420
35553
|
signature: session.signature,
|
|
35421
35554
|
...replicaStamp === void 0 ? {} : { replicaStamp }
|
|
35422
35555
|
});
|
|
35423
|
-
if (profile.sessionDid !== session.verificationMethod || profile.spaceId !== session.spaceId) {
|
|
35424
|
-
await ProfileManager.updateProfile(profileName2, (current) => ({
|
|
35556
|
+
if (profile.sessionDid !== session.verificationMethod || profile.spaceId !== session.spaceId || profile.namedKeys !== void 0) {
|
|
35557
|
+
await ProfileManager.updateProfile(profileName2, (current) => withVerifiedNamedKeys({
|
|
35425
35558
|
...current,
|
|
35426
35559
|
sessionDid: session.verificationMethod,
|
|
35427
35560
|
spaceId: session.spaceId
|
|
35428
|
-
}));
|
|
35561
|
+
}, session));
|
|
35429
35562
|
}
|
|
35430
35563
|
});
|
|
35431
35564
|
}
|
|
@@ -35457,11 +35590,14 @@ async function handleLocalAuth(profileName2, host, options = {}) {
|
|
|
35457
35590
|
process.stderr.write(formatField("DID", did) + "\n\n");
|
|
35458
35591
|
}
|
|
35459
35592
|
}
|
|
35593
|
+
const namedKeys = options.keys === void 0 ? void 0 : parseKeysPrefix(options.keys, did);
|
|
35594
|
+
if (namedKeys !== void 0) await assertNodeSupportsNamedKeys(host);
|
|
35460
35595
|
const sessionResult = await withSpinner("Signing in...", async () => {
|
|
35461
35596
|
const replication = options.replication ?? profile?.replication;
|
|
35462
35597
|
return localKeySignIn({
|
|
35463
35598
|
privateKey,
|
|
35464
35599
|
host,
|
|
35600
|
+
...namedKeys === void 0 ? {} : { keyAbilities: keyAbilitiesFor(namedKeys) },
|
|
35465
35601
|
...replication?.prefixes.length ? { profile: profileName2, replication } : {}
|
|
35466
35602
|
});
|
|
35467
35603
|
});
|
|
@@ -35496,9 +35632,12 @@ async function handleLocalAuth(profileName2, host, options = {}) {
|
|
|
35496
35632
|
operatorType: profile?.operatorType ?? "human",
|
|
35497
35633
|
authMethod: "local",
|
|
35498
35634
|
privateKey,
|
|
35499
|
-
address
|
|
35635
|
+
address,
|
|
35636
|
+
...namedKeys === void 0 ? {} : { namedKeys }
|
|
35500
35637
|
};
|
|
35638
|
+
if (namedKeys === void 0) delete updatedProfile.namedKeys;
|
|
35501
35639
|
await commitLogin(profileName2, snapshot, { key, session, profile: updatedProfile });
|
|
35640
|
+
if (namedKeys !== void 0 && sessionResult.networkReady === false) process.stderr.write(KEYS_FOLDER_NOT_READY);
|
|
35502
35641
|
if (options.emitOutput ?? true) {
|
|
35503
35642
|
outputJson({
|
|
35504
35643
|
authenticated: true,
|
|
@@ -35577,6 +35716,8 @@ async function refreshPreparedOpenKeySession(params) {
|
|
|
35577
35716
|
if (expectedOwner && normalizePkhIdentifier(owner) !== normalizePkhIdentifier(expectedOwner)) {
|
|
35578
35717
|
throw new CLIError("OPENKEY_OWNER_MISMATCH", "The verified owner differs from this profile. No approval was started.", ExitCode.PERMISSION_DENIED);
|
|
35579
35718
|
}
|
|
35719
|
+
const namedKeys = options.keys === void 0 ? void 0 : parseKeysPrefix(options.keys, canonicalOwnerDid(owner));
|
|
35720
|
+
if (namedKeys !== void 0) await assertNodeSupportsNamedKeys(host);
|
|
35580
35721
|
if (options.force !== true) await assertShareHistoryAllowsKeyDrop(profileName2, "login");
|
|
35581
35722
|
const replicationConfig = createCliReplicationConfig(profileName2, replication, { debug: false, quiet: true }, true);
|
|
35582
35723
|
if (!replicationConfig) throw new CLIError("V2_REQUIRED", "Replica configuration is unavailable; no approval was started.", ExitCode.AUTH_REQUIRED);
|
|
@@ -35597,12 +35738,15 @@ async function refreshPreparedOpenKeySession(params) {
|
|
|
35597
35738
|
capabilityRequest,
|
|
35598
35739
|
includeAccountRegistryPermissions: false,
|
|
35599
35740
|
autoBootstrapAccount: false,
|
|
35600
|
-
replication: replicationConfig
|
|
35741
|
+
replication: replicationConfig,
|
|
35742
|
+
...namedKeys === void 0 ? {} : { additionalRawAbilities: keyAbilitiesFor(namedKeys) }
|
|
35601
35743
|
});
|
|
35602
35744
|
let handoff;
|
|
35603
35745
|
let preparedDid;
|
|
35746
|
+
let handedBackSiwe;
|
|
35604
35747
|
const gateWasActive = await readGateSnapshot(profilePath8(profileName2)).then((gate) => gate !== void 0 && gate.principalHash !== null, () => true);
|
|
35605
35748
|
try {
|
|
35749
|
+
if (namedKeys !== void 0) process.stderr.write(KEYS_APPROVAL_NOTE(namedKeys.prefix));
|
|
35606
35750
|
await node.signInWithOpenKey(async (prepared) => {
|
|
35607
35751
|
preparedDid = prepared.verificationMethod;
|
|
35608
35752
|
handoff = await startPreparedOpenKeyHandoff({
|
|
@@ -35611,14 +35755,23 @@ async function refreshPreparedOpenKeySession(params) {
|
|
|
35611
35755
|
noPopup: options.noPopup,
|
|
35612
35756
|
paste: options.paste
|
|
35613
35757
|
});
|
|
35614
|
-
return handoff.result
|
|
35758
|
+
return handoff.result.then((result) => {
|
|
35759
|
+
handedBackSiwe = result.signedMessage;
|
|
35760
|
+
return result;
|
|
35761
|
+
});
|
|
35615
35762
|
});
|
|
35616
35763
|
const active = node.restorableSession;
|
|
35617
35764
|
if (!active || !preparedDid || node.sessionDid !== preparedDid || active.verificationMethod !== preparedDid) {
|
|
35618
35765
|
throw new CLIError("OPENKEY_SESSION_MISMATCH", "OpenKey signed a different SDK session key; no profile or session was saved.", ExitCode.PERMISSION_DENIED);
|
|
35619
35766
|
}
|
|
35620
35767
|
const key = active.jwk;
|
|
35621
|
-
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();
|
|
35622
35775
|
const session = {
|
|
35623
35776
|
...verified.session,
|
|
35624
35777
|
...node.replicaStamp ? { replicaStamp: node.replicaStamp } : {},
|
|
@@ -35639,6 +35792,10 @@ async function refreshPreparedOpenKeySession(params) {
|
|
|
35639
35792
|
...replication.allowSecrets === true ? { allowSecrets: true } : {}
|
|
35640
35793
|
}
|
|
35641
35794
|
};
|
|
35795
|
+
if (namedKeys !== void 0) {
|
|
35796
|
+
delete updatedProfile.namedKeys;
|
|
35797
|
+
if (grant === "both") updatedProfile.namedKeys = namedKeys;
|
|
35798
|
+
}
|
|
35642
35799
|
await commitLogin(profileName2, snapshot, {
|
|
35643
35800
|
key,
|
|
35644
35801
|
session,
|
|
@@ -35648,6 +35805,10 @@ async function refreshPreparedOpenKeySession(params) {
|
|
|
35648
35805
|
});
|
|
35649
35806
|
await handoff?.complete(true);
|
|
35650
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
|
+
}
|
|
35651
35812
|
return {
|
|
35652
35813
|
profile: updatedProfile,
|
|
35653
35814
|
delegationData: session,
|
|
@@ -35657,7 +35818,102 @@ async function refreshPreparedOpenKeySession(params) {
|
|
|
35657
35818
|
} catch (error) {
|
|
35658
35819
|
await handoff?.complete(false);
|
|
35659
35820
|
await undoPreparedAttempt(profileName2, node, owner, gateWasActive).catch(() => void 0);
|
|
35660
|
-
throw error;
|
|
35821
|
+
throw namedKeys !== void 0 && isNarrowedRecapMismatch(error) && namedKeysGrantIn(handedBackSiwe, namedKeys) === "partial" ? partialKeysGrantError() : error;
|
|
35822
|
+
}
|
|
35823
|
+
}
|
|
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.
|
|
35825
|
+
`;
|
|
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.
|
|
35827
|
+
`;
|
|
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.
|
|
35829
|
+
`;
|
|
35830
|
+
async function readyNamedKeysNetwork(node) {
|
|
35831
|
+
try {
|
|
35832
|
+
await node.ensureEncryptionNetwork();
|
|
35833
|
+
} catch {
|
|
35834
|
+
process.stderr.write(KEYS_FOLDER_NOT_READY);
|
|
35835
|
+
}
|
|
35836
|
+
}
|
|
35837
|
+
function isNarrowedRecapMismatch(error) {
|
|
35838
|
+
const text = error instanceof Error ? `${error.code ?? ""} ${error.message}` : String(error);
|
|
35839
|
+
return text.includes("narrowed_recap_mismatch");
|
|
35840
|
+
}
|
|
35841
|
+
async function refreshPreparedKeysSession(params) {
|
|
35842
|
+
const { profileName: profileName2, host, profile, snapshot, options, expectedOwner, expiry } = params;
|
|
35843
|
+
const storedOwner = snapshot.session && snapshot.key && profile.sessionDid ? verifySignedSession(snapshot.session, snapshot.key, profile.sessionDid).ownerDid : void 0;
|
|
35844
|
+
const owner = options.expectedOwner ? canonicalOwnerDid(options.expectedOwner) : storedOwner ?? (profile.ownerDid ? canonicalOwnerDid(profile.ownerDid) : void 0);
|
|
35845
|
+
if (!owner) {
|
|
35846
|
+
throw new CLIError(
|
|
35847
|
+
"OWNER_REQUIRED",
|
|
35848
|
+
"The first OpenKey sign-in with `--keys` needs your account: add `--owner did:pkh:eip155:<chain>:<address>`. Nothing was started.",
|
|
35849
|
+
ExitCode.AUTH_REQUIRED
|
|
35850
|
+
);
|
|
35851
|
+
}
|
|
35852
|
+
if (expectedOwner && normalizePkhIdentifier(owner) !== normalizePkhIdentifier(expectedOwner)) {
|
|
35853
|
+
throw new CLIError("OPENKEY_OWNER_MISMATCH", "The verified owner differs from this profile. No approval was started.", ExitCode.PERMISSION_DENIED);
|
|
35854
|
+
}
|
|
35855
|
+
const namedKeys = parseKeysPrefix(options.keys, canonicalOwnerDid(owner));
|
|
35856
|
+
await assertNodeSupportsNamedKeys(host);
|
|
35857
|
+
if (options.force !== true) await assertShareHistoryAllowsKeyDrop(profileName2, "login");
|
|
35858
|
+
const node = new TinyCloudNode3({
|
|
35859
|
+
host,
|
|
35860
|
+
signer: ownerIdentitySigner(owner),
|
|
35861
|
+
additionalRawAbilities: keyAbilitiesFor(namedKeys),
|
|
35862
|
+
includeAccountRegistryPermissions: false,
|
|
35863
|
+
autoBootstrapAccount: false,
|
|
35864
|
+
...expiry ? { sessionExpirationMs: expiry.durationMs } : {}
|
|
35865
|
+
});
|
|
35866
|
+
let handoff;
|
|
35867
|
+
let preparedDid;
|
|
35868
|
+
let handedBackSiwe;
|
|
35869
|
+
try {
|
|
35870
|
+
process.stderr.write(KEYS_APPROVAL_NOTE(namedKeys.prefix));
|
|
35871
|
+
await node.signInWithOpenKey(async (prepared) => {
|
|
35872
|
+
preparedDid = prepared.verificationMethod;
|
|
35873
|
+
handoff = await startPreparedOpenKeyHandoff({
|
|
35874
|
+
prepared,
|
|
35875
|
+
openKeyHost: resolveOpenKeyHost(profile),
|
|
35876
|
+
noPopup: options.noPopup,
|
|
35877
|
+
paste: options.paste
|
|
35878
|
+
});
|
|
35879
|
+
return handoff.result.then((result) => {
|
|
35880
|
+
handedBackSiwe = result.signedMessage;
|
|
35881
|
+
return result;
|
|
35882
|
+
});
|
|
35883
|
+
});
|
|
35884
|
+
const active = node.restorableSession;
|
|
35885
|
+
if (!active || !preparedDid || node.sessionDid !== preparedDid || active.verificationMethod !== preparedDid) {
|
|
35886
|
+
throw new CLIError("OPENKEY_SESSION_MISMATCH", "OpenKey signed a different SDK session key; no profile or session was saved.", ExitCode.PERMISSION_DENIED);
|
|
35887
|
+
}
|
|
35888
|
+
const key = active.jwk;
|
|
35889
|
+
const signed = verifySignedSession({ ...active }, key, preparedDid, { expectedOwner: owner, expiry });
|
|
35890
|
+
const grant = namedKeysGrantIn(active.siwe, namedKeys);
|
|
35891
|
+
if (grant === "partial") throw partialKeysGrantError();
|
|
35892
|
+
const session = { ...withVerifiedAuthority({ ...active, jwk: key }, signed), authMethod: "openkey" };
|
|
35893
|
+
const updatedProfile = {
|
|
35894
|
+
...profile,
|
|
35895
|
+
host: options.persistHost === true || !profile.host ? host : profile.host,
|
|
35896
|
+
did: preparedDid,
|
|
35897
|
+
sessionDid: preparedDid,
|
|
35898
|
+
posture: profile.posture ?? "owner-openkey",
|
|
35899
|
+
operatorType: profile.operatorType ?? "human",
|
|
35900
|
+
authMethod: "openkey",
|
|
35901
|
+
ownerDid: signed.ownerDid,
|
|
35902
|
+
spaceId: active.spaceId
|
|
35903
|
+
};
|
|
35904
|
+
delete updatedProfile.namedKeys;
|
|
35905
|
+
if (grant === "both") updatedProfile.namedKeys = namedKeys;
|
|
35906
|
+
await commitLogin(profileName2, snapshot, { key, session, profile: updatedProfile });
|
|
35907
|
+
await handoff?.complete(true);
|
|
35908
|
+
if (grant === "both") await readyNamedKeysNetwork(node);
|
|
35909
|
+
if (grant === "none") {
|
|
35910
|
+
process.stderr.write(keysNotGrantedAfterLogin(profileName2));
|
|
35911
|
+
}
|
|
35912
|
+
return { profile: updatedProfile, delegationData: session, declined: [], legacyNested: [] };
|
|
35913
|
+
} catch (error) {
|
|
35914
|
+
await handoff?.complete(false);
|
|
35915
|
+
await node.replication?.close();
|
|
35916
|
+
throw isNarrowedRecapMismatch(error) && namedKeysGrantIn(handedBackSiwe, namedKeys) === "partial" ? partialKeysGrantError() : error;
|
|
35661
35917
|
}
|
|
35662
35918
|
}
|
|
35663
35919
|
async function undoPreparedAttempt(profileName2, node, owner, gateWasActive) {
|
|
@@ -35698,6 +35954,9 @@ async function refreshOpenKeySession(profileName2, host, options = {}) {
|
|
|
35698
35954
|
const estimatedExpiry = expiry === void 0 ? void 0 : new Date(Date.now() + expiry.durationMs).toISOString();
|
|
35699
35955
|
assertSessionReplaceable(profileName2, snapshot, expectedOwner, permissions, estimatedExpiry);
|
|
35700
35956
|
}
|
|
35957
|
+
if (options.keys !== void 0 && replication.prefixes.length === 0) {
|
|
35958
|
+
return refreshPreparedKeysSession({ profileName: profileName2, host, profile, snapshot, options, expectedOwner, expiry });
|
|
35959
|
+
}
|
|
35701
35960
|
if (replication.prefixes.length > 0) {
|
|
35702
35961
|
return refreshPreparedOpenKeySession({
|
|
35703
35962
|
profileName: profileName2,
|
|
@@ -35760,6 +36019,7 @@ async function refreshOpenKeySession(profileName2, host, options = {}) {
|
|
|
35760
36019
|
...typeof sanitizedSession.spaceId === "string" ? { spaceId: sanitizedSession.spaceId } : {},
|
|
35761
36020
|
...verifiedOwner === void 0 ? {} : { ownerDid: verifiedOwner }
|
|
35762
36021
|
}, ownerKeyPrimary);
|
|
36022
|
+
delete updatedProfile.namedKeys;
|
|
35763
36023
|
await commitLogin(profileName2, snapshot, {
|
|
35764
36024
|
key,
|
|
35765
36025
|
session: sanitizedSession,
|
|
@@ -35772,7 +36032,7 @@ async function refreshOpenKeySession(profileName2, host, options = {}) {
|
|
|
35772
36032
|
}
|
|
35773
36033
|
|
|
35774
36034
|
// src/commands/completion.ts
|
|
35775
|
-
import { CLI_NAME as
|
|
36035
|
+
import { CLI_NAME as CLI_NAME19 } from "@tinycloud/operations/cli-name";
|
|
35776
36036
|
function registerCompletionCommand(program) {
|
|
35777
36037
|
const completion = program.command("completion").description("Generate shell completions");
|
|
35778
36038
|
completion.command("bash").description("Output bash completions").action(() => {
|
|
@@ -35789,7 +36049,7 @@ function registerCompletionCommand(program) {
|
|
|
35789
36049
|
});
|
|
35790
36050
|
}
|
|
35791
36051
|
function generateBashCompletion() {
|
|
35792
|
-
return `# ${
|
|
36052
|
+
return `# ${CLI_NAME19} bash completion
|
|
35793
36053
|
_tc_completions() {
|
|
35794
36054
|
local cur prev commands subcommands
|
|
35795
36055
|
COMPREPLY=()
|
|
@@ -35814,11 +36074,11 @@ _tc_completions() {
|
|
|
35814
36074
|
COMPREPLY=( $(compgen -W "\${subcommands}" -- "\${cur}") )
|
|
35815
36075
|
fi
|
|
35816
36076
|
}
|
|
35817
|
-
complete -F _tc_completions ${
|
|
36077
|
+
complete -F _tc_completions ${CLI_NAME19}
|
|
35818
36078
|
`;
|
|
35819
36079
|
}
|
|
35820
36080
|
function generateZshCompletion() {
|
|
35821
|
-
return `#compdef ${
|
|
36081
|
+
return `#compdef ${CLI_NAME19}
|
|
35822
36082
|
|
|
35823
36083
|
_tc() {
|
|
35824
36084
|
local -a commands
|
|
@@ -35869,50 +36129,50 @@ _tc
|
|
|
35869
36129
|
`;
|
|
35870
36130
|
}
|
|
35871
36131
|
function generateFishCompletion() {
|
|
35872
|
-
return `# ${
|
|
36132
|
+
return `# ${CLI_NAME19} fish completion
|
|
35873
36133
|
set -l commands init auth kv space delegation share node profile completion
|
|
35874
36134
|
|
|
35875
36135
|
# Disable file completion by default
|
|
35876
|
-
complete -c ${
|
|
36136
|
+
complete -c ${CLI_NAME19} -f
|
|
35877
36137
|
|
|
35878
36138
|
# Top-level commands
|
|
35879
|
-
complete -c ${
|
|
35880
|
-
complete -c ${
|
|
35881
|
-
complete -c ${
|
|
35882
|
-
complete -c ${
|
|
35883
|
-
complete -c ${
|
|
35884
|
-
complete -c ${
|
|
35885
|
-
complete -c ${
|
|
35886
|
-
complete -c ${
|
|
35887
|
-
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"
|
|
35888
36148
|
|
|
35889
36149
|
# Subcommands
|
|
35890
|
-
complete -c ${
|
|
35891
|
-
complete -c ${
|
|
35892
|
-
complete -c ${
|
|
35893
|
-
complete -c ${
|
|
35894
|
-
complete -c ${
|
|
35895
|
-
complete -c ${
|
|
35896
|
-
complete -c ${
|
|
35897
|
-
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"
|
|
35898
36158
|
|
|
35899
36159
|
# Global options
|
|
35900
|
-
complete -c ${
|
|
35901
|
-
complete -c ${
|
|
35902
|
-
complete -c ${
|
|
35903
|
-
complete -c ${
|
|
35904
|
-
complete -c ${
|
|
35905
|
-
complete -c ${
|
|
35906
|
-
complete -c ${
|
|
35907
|
-
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"
|
|
35908
36168
|
`;
|
|
35909
36169
|
}
|
|
35910
36170
|
|
|
35911
36171
|
// src/commands/courier.ts
|
|
35912
|
-
import { CLI_NAME as
|
|
35913
|
-
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>.`;
|
|
35914
36174
|
function registerCourierCommand(program) {
|
|
35915
|
-
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);
|
|
35916
36176
|
courier.action(() => courier.error(TC_OPTIONS_BEFORE_COURIER, { exitCode: 2, code: "tc.courier.options" }));
|
|
35917
36177
|
}
|
|
35918
36178
|
|
|
@@ -35957,7 +36217,7 @@ init_formatter();
|
|
|
35957
36217
|
init_errors();
|
|
35958
36218
|
init_constants();
|
|
35959
36219
|
init_sdk();
|
|
35960
|
-
import { CLI_NAME as
|
|
36220
|
+
import { CLI_NAME as CLI_NAME21 } from "@tinycloud/operations/cli-name";
|
|
35961
36221
|
import { PermissionNotInManifestError } from "@tinycloud/node-sdk";
|
|
35962
36222
|
async function queryDelegations(node, options, until) {
|
|
35963
36223
|
const found = [];
|
|
@@ -35968,7 +36228,7 @@ async function queryDelegations(node, options, until) {
|
|
|
35968
36228
|
if (page.error.code === "PERMISSION_DENIED") {
|
|
35969
36229
|
throw new CLIError(
|
|
35970
36230
|
"PERMISSION_DENIED",
|
|
35971
|
-
`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.`,
|
|
35972
36232
|
ExitCode.PERMISSION_DENIED
|
|
35973
36233
|
);
|
|
35974
36234
|
}
|
|
@@ -36143,7 +36403,7 @@ init_constants();
|
|
|
36143
36403
|
init_formatter();
|
|
36144
36404
|
init_theme();
|
|
36145
36405
|
init_errors();
|
|
36146
|
-
import { CLI_NAME as
|
|
36406
|
+
import { CLI_NAME as CLI_NAME22 } from "@tinycloud/operations/cli-name";
|
|
36147
36407
|
init_session_expired();
|
|
36148
36408
|
init_types();
|
|
36149
36409
|
function registerDoctorCommand(program) {
|
|
@@ -36177,7 +36437,7 @@ function registerDoctorCommand(program) {
|
|
|
36177
36437
|
const profile = await ProfileManager.getProfile(profileName2);
|
|
36178
36438
|
keyDetail = profile.did ? `${profile.did.slice(0, 20)}...` : "key found";
|
|
36179
36439
|
} else {
|
|
36180
|
-
keyDetail = `no key \u2014 run ${
|
|
36440
|
+
keyDetail = `no key \u2014 run ${CLI_NAME22} init`;
|
|
36181
36441
|
}
|
|
36182
36442
|
} catch {
|
|
36183
36443
|
keyDetail = "error reading key";
|
|
@@ -36199,7 +36459,7 @@ function registerDoctorCommand(program) {
|
|
|
36199
36459
|
} else if (state2 === "unverified") {
|
|
36200
36460
|
sessionDetail = `not usable (no signed OpenKey proof) \u2014 ${signInAgainHint(profileName2, resolveProfilePosture(profile))}`;
|
|
36201
36461
|
} else {
|
|
36202
|
-
sessionDetail = sessionOk ? "active" : `no session \u2014 run ${
|
|
36462
|
+
sessionDetail = sessionOk ? "active" : `no session \u2014 run ${CLI_NAME22} auth login`;
|
|
36203
36463
|
}
|
|
36204
36464
|
} catch {
|
|
36205
36465
|
sessionDetail = "error reading session";
|
|
@@ -36227,7 +36487,7 @@ function registerDoctorCommand(program) {
|
|
|
36227
36487
|
try {
|
|
36228
36488
|
const profile = await ProfileManager.getProfile(profileName2);
|
|
36229
36489
|
spaceOk = Boolean(profile.spaceId);
|
|
36230
|
-
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`;
|
|
36231
36491
|
} catch {
|
|
36232
36492
|
spaceDetail = "error checking space";
|
|
36233
36493
|
}
|
|
@@ -36417,7 +36677,7 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
|
|
|
36417
36677
|
}
|
|
36418
36678
|
|
|
36419
36679
|
// src/commands/enable.ts
|
|
36420
|
-
import { CLI_NAME as
|
|
36680
|
+
import { CLI_NAME as CLI_NAME23 } from "@tinycloud/operations/cli-name";
|
|
36421
36681
|
init_constants();
|
|
36422
36682
|
init_profiles();
|
|
36423
36683
|
init_errors();
|
|
@@ -36425,7 +36685,7 @@ init_formatter();
|
|
|
36425
36685
|
init_publishing_manifest();
|
|
36426
36686
|
function registerEnableCommand(program) {
|
|
36427
36687
|
const enable = program.command("enable").description("Enable a narrowly scoped TinyCloud service");
|
|
36428
|
-
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) => {
|
|
36429
36689
|
try {
|
|
36430
36690
|
const globalOptions = command.optsWithGlobals();
|
|
36431
36691
|
const context = await ProfileManager.resolveContext(globalOptions, { storedSession: "unused" });
|
|
@@ -36460,7 +36720,7 @@ function registerEnableCommand(program) {
|
|
|
36460
36720
|
init_profiles();
|
|
36461
36721
|
init_constants();
|
|
36462
36722
|
init_sdk();
|
|
36463
|
-
import { CLI_NAME as
|
|
36723
|
+
import { CLI_NAME as CLI_NAME24 } from "@tinycloud/operations/cli-name";
|
|
36464
36724
|
import { operationSpaceResolver as operationSpaceResolver2 } from "@tinycloud/operations/delegation-binding";
|
|
36465
36725
|
|
|
36466
36726
|
// src/lib/knowledge-input.ts
|
|
@@ -36726,7 +36986,7 @@ function registerAppsCommand(program) {
|
|
|
36726
36986
|
"after",
|
|
36727
36987
|
`
|
|
36728
36988
|
Examples:
|
|
36729
|
-
$ ${
|
|
36989
|
+
$ ${CLI_NAME24} apps install xyz.tinycloud.sample.healthlog
|
|
36730
36990
|
`
|
|
36731
36991
|
).action(async (appIdInput, _options, cmd) => {
|
|
36732
36992
|
try {
|
|
@@ -36818,7 +37078,7 @@ init_profiles();
|
|
|
36818
37078
|
init_formatter();
|
|
36819
37079
|
init_errors();
|
|
36820
37080
|
init_constants();
|
|
36821
|
-
import { CLI_NAME as
|
|
37081
|
+
import { CLI_NAME as CLI_NAME25 } from "@tinycloud/operations/cli-name";
|
|
36822
37082
|
function registerInitCommand(program) {
|
|
36823
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) => {
|
|
36824
37084
|
try {
|
|
@@ -36836,7 +37096,7 @@ function registerInitCommand(program) {
|
|
|
36836
37096
|
if (await ProfileManager.profileExists(profileName2)) {
|
|
36837
37097
|
throw new CLIError(
|
|
36838
37098
|
"PROFILE_EXISTS",
|
|
36839
|
-
`Profile "${profileName2}" already exists. Use \`${
|
|
37099
|
+
`Profile "${profileName2}" already exists. Use \`${CLI_NAME25} profile delete ${profileName2}\` first or choose a different name.`,
|
|
36840
37100
|
ExitCode.ERROR
|
|
36841
37101
|
);
|
|
36842
37102
|
}
|
|
@@ -36892,7 +37152,7 @@ init_session_expired();
|
|
|
36892
37152
|
init_constants();
|
|
36893
37153
|
init_profiles();
|
|
36894
37154
|
import { invokeOperation as invokeOperation2 } from "@tinycloud/operations";
|
|
36895
|
-
import { CLI_NAME as
|
|
37155
|
+
import { CLI_NAME as CLI_NAME26 } from "@tinycloud/operations/cli-name";
|
|
36896
37156
|
init_permissions();
|
|
36897
37157
|
init_errors();
|
|
36898
37158
|
init_formatter();
|
|
@@ -36995,7 +37255,7 @@ function knowledgeFailure(result, profile, appId) {
|
|
|
36995
37255
|
case "error": {
|
|
36996
37256
|
if (result.error.code === "SESSION_NOT_FOUND" || result.error.code === "PROFILE_POSTURE_NOT_ALLOWED" && result.context.posture === "unauthenticated") {
|
|
36997
37257
|
return new CLIError("AUTH_REQUIRED", "Not signed in to TinyCloud.", ExitCode.AUTH_REQUIRED, {
|
|
36998
|
-
hint: `Sign in with: ${
|
|
37258
|
+
hint: `Sign in with: ${CLI_NAME26} --profile ${result.context.profile} auth login`
|
|
36999
37259
|
});
|
|
37000
37260
|
}
|
|
37001
37261
|
if (result.error.code === "NODE_UNREACHABLE") {
|
|
@@ -37025,7 +37285,7 @@ function knowledgeFailure(result, profile, appId) {
|
|
|
37025
37285
|
}
|
|
37026
37286
|
}
|
|
37027
37287
|
function missingGrantHint(profile, appId) {
|
|
37028
|
-
const request = `${
|
|
37288
|
+
const request = `${CLI_NAME26} --profile ${profile} auth request`;
|
|
37029
37289
|
const lines = appId === void 0 ? [
|
|
37030
37290
|
` ${request} --preset ${APPS_AGENT_PRESET} --emit req.json (read-only, all installed apps)`,
|
|
37031
37291
|
` ${request} --cap tinycloud.kv:${APPS_AGENT_SPACE}::get,list --emit req.json (all files, no table access)`,
|
|
@@ -37038,7 +37298,7 @@ function missingGrantHint(profile, appId) {
|
|
|
37038
37298
|
return [
|
|
37039
37299
|
"Ask the owner for a grant, narrowest first. Create the request:",
|
|
37040
37300
|
...lines,
|
|
37041
|
-
`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\`.`
|
|
37042
37302
|
].join("\n");
|
|
37043
37303
|
}
|
|
37044
37304
|
|
|
@@ -37337,6 +37597,175 @@ function registerKvCommand(program) {
|
|
|
37337
37597
|
});
|
|
37338
37598
|
}
|
|
37339
37599
|
|
|
37600
|
+
// src/commands/keys.ts
|
|
37601
|
+
init_profiles();
|
|
37602
|
+
init_constants();
|
|
37603
|
+
init_formatter();
|
|
37604
|
+
init_errors();
|
|
37605
|
+
init_sdk();
|
|
37606
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
37607
|
+
import { NamedKeyError, createNamedKeyClient } from "@tinycloud/node-sdk/encryption-keys";
|
|
37608
|
+
import { CLI_NAME as CLI_NAME27 } from "@tinycloud/operations/cli-name";
|
|
37609
|
+
async function readStdin3() {
|
|
37610
|
+
const chunks = [];
|
|
37611
|
+
for await (const chunk of process.stdin) {
|
|
37612
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
37613
|
+
}
|
|
37614
|
+
return Buffer.concat(chunks);
|
|
37615
|
+
}
|
|
37616
|
+
async function readInput(file) {
|
|
37617
|
+
return file === void 0 ? readStdin3() : readFile12(file);
|
|
37618
|
+
}
|
|
37619
|
+
function cliErrorFromNamedKey(error, command, path) {
|
|
37620
|
+
if (!(error instanceof NamedKeyError)) return error;
|
|
37621
|
+
switch (error.code) {
|
|
37622
|
+
case "NAMED_KEYS_UNSUPPORTED":
|
|
37623
|
+
return new CLIError("NAMED_KEYS_UNAVAILABLE", "This node doesn't support named keys.", ExitCode.NODE_ERROR);
|
|
37624
|
+
case "INVALID_KEY_TARGET":
|
|
37625
|
+
case "KEY_PATH_INVALID":
|
|
37626
|
+
return new CLIError("INVALID_KEY_PATH", "That key name isn't valid.", ExitCode.USAGE_ERROR);
|
|
37627
|
+
case "INVALID_ENVELOPE":
|
|
37628
|
+
return new CLIError("INVALID_ENVELOPE", "The envelope is damaged or incomplete.", ExitCode.USAGE_ERROR);
|
|
37629
|
+
case "KEY_NOT_FOUND":
|
|
37630
|
+
return new CLIError(
|
|
37631
|
+
"NOT_FOUND",
|
|
37632
|
+
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}\`.`,
|
|
37633
|
+
ExitCode.NOT_FOUND
|
|
37634
|
+
);
|
|
37635
|
+
case "KEY_BLOCKED":
|
|
37636
|
+
return new CLIError("KEY_BLOCKED", `Key ${path} is blocked, so it can't be used.`, ExitCode.PERMISSION_DENIED);
|
|
37637
|
+
case "DECRYPT_FAILED":
|
|
37638
|
+
return new CLIError("DECRYPT_FAILED", `Couldn't decrypt this envelope with key ${path}.`, ExitCode.ERROR);
|
|
37639
|
+
}
|
|
37640
|
+
if (error.status === 401 || error.status === 403) {
|
|
37641
|
+
return new CLIError("PERMISSION_DENIED", error.message, ExitCode.PERMISSION_DENIED, {
|
|
37642
|
+
hint: `Sign in with \`${CLI_NAME27} auth login --keys <folder>\` for this key's folder.`
|
|
37643
|
+
});
|
|
37644
|
+
}
|
|
37645
|
+
return new CLIError(error.code, error.message, error.status === void 0 ? ExitCode.ERROR : ExitCode.NODE_ERROR);
|
|
37646
|
+
}
|
|
37647
|
+
async function withNetworkSetup(node, call) {
|
|
37648
|
+
try {
|
|
37649
|
+
return await call();
|
|
37650
|
+
} catch (error) {
|
|
37651
|
+
if (!(error instanceof NamedKeyError) || error.code !== "NETWORK_NOT_FOUND") throw error;
|
|
37652
|
+
await node.ensureEncryptionNetwork();
|
|
37653
|
+
return call();
|
|
37654
|
+
}
|
|
37655
|
+
}
|
|
37656
|
+
async function grantedPrefix(deps, profileName2) {
|
|
37657
|
+
const profile = await deps.getProfile(profileName2);
|
|
37658
|
+
const keys = profile.namedKeys;
|
|
37659
|
+
if (keys === void 0) {
|
|
37660
|
+
throw new CLIError(
|
|
37661
|
+
"KEYS_NOT_GRANTED",
|
|
37662
|
+
`Profile "${profileName2}" can't use named keys yet. Sign in with \`${CLI_NAME27} --profile ${profileName2} auth login --keys <folder>\`.`,
|
|
37663
|
+
ExitCode.PERMISSION_DENIED
|
|
37664
|
+
);
|
|
37665
|
+
}
|
|
37666
|
+
return keys.prefix;
|
|
37667
|
+
}
|
|
37668
|
+
function assertInGrantedPrefix(prefix, path, profileName2) {
|
|
37669
|
+
if (!keyPathInPrefix(prefix, path)) {
|
|
37670
|
+
throw new CLIError(
|
|
37671
|
+
"KEY_OUTSIDE_GRANTED_PREFIX",
|
|
37672
|
+
`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>\`.`,
|
|
37673
|
+
ExitCode.PERMISSION_DENIED
|
|
37674
|
+
);
|
|
37675
|
+
}
|
|
37676
|
+
}
|
|
37677
|
+
function parseEnvelope(bytes) {
|
|
37678
|
+
let parsed;
|
|
37679
|
+
try {
|
|
37680
|
+
parsed = JSON.parse(bytes.toString("utf8"));
|
|
37681
|
+
} catch {
|
|
37682
|
+
throw new CLIError("INVALID_ENVELOPE", "The input isn't an envelope (expected JSON).", ExitCode.USAGE_ERROR);
|
|
37683
|
+
}
|
|
37684
|
+
const envelope = parsed;
|
|
37685
|
+
if (typeof envelope !== "object" || envelope === null || envelope.v !== 2 || typeof envelope.keyPath !== "string") {
|
|
37686
|
+
throw new CLIError("INVALID_ENVELOPE", 'The input isn\'t a named-key envelope (it needs `"v": 2` and a `keyPath`).', ExitCode.USAGE_ERROR);
|
|
37687
|
+
}
|
|
37688
|
+
return envelope;
|
|
37689
|
+
}
|
|
37690
|
+
var defaultDeps = {
|
|
37691
|
+
resolveContext: (options) => ProfileManager.resolveContext(options),
|
|
37692
|
+
getProfile: (name) => ProfileManager.getProfile(name),
|
|
37693
|
+
authenticate: (ctx) => ensureAuthenticated(ctx),
|
|
37694
|
+
createClient: createNamedKeyClient,
|
|
37695
|
+
outputJson,
|
|
37696
|
+
handleError
|
|
37697
|
+
};
|
|
37698
|
+
function registerKeysCommand(program, deps = defaultDeps) {
|
|
37699
|
+
const { outputJson: outputJson2, handleError: handleError2 } = deps;
|
|
37700
|
+
const keys = program.command("keys").description(`Named encryption keys (sign in with \`${CLI_NAME27} auth login --keys <folder>\` first)`);
|
|
37701
|
+
keys.command("ensure <path>").description("Create the named key if it doesn't exist yet, then show it").action(async (path, _options, cmd) => {
|
|
37702
|
+
try {
|
|
37703
|
+
const ctx = await deps.resolveContext(cmd.optsWithGlobals());
|
|
37704
|
+
assertInGrantedPrefix(await grantedPrefix(deps, ctx.profile), path, ctx.profile);
|
|
37705
|
+
const node = await deps.authenticate(ctx);
|
|
37706
|
+
const client = deps.createClient(node);
|
|
37707
|
+
const descriptor = await withSpinner(`Getting or creating key ${path}\u2026`, () => withNetworkSetup(node, () => client.ensureKey(path))).catch((error) => {
|
|
37708
|
+
throw cliErrorFromNamedKey(error, "ensure", path);
|
|
37709
|
+
});
|
|
37710
|
+
outputJson2({ ...descriptor });
|
|
37711
|
+
} catch (error) {
|
|
37712
|
+
await handleError2(error);
|
|
37713
|
+
}
|
|
37714
|
+
});
|
|
37715
|
+
keys.command("encrypt <path>").description("Encrypt data with a named key and output the encrypted envelope as JSON").option("--in <file>", "Read the data to encrypt from a file (default: standard input)").option("--out <file>", "Write the envelope to a file (default: standard output)").action(async (path, options, cmd) => {
|
|
37716
|
+
try {
|
|
37717
|
+
const ctx = await deps.resolveContext(cmd.optsWithGlobals());
|
|
37718
|
+
assertInGrantedPrefix(await grantedPrefix(deps, ctx.profile), path, ctx.profile);
|
|
37719
|
+
if (options.out) await validatePrivateOutput(options.out, "--out");
|
|
37720
|
+
const plaintext = await readInput(options.in);
|
|
37721
|
+
const node = await deps.authenticate(ctx);
|
|
37722
|
+
const client = deps.createClient(node);
|
|
37723
|
+
const envelope = await withSpinner(`Encrypting with ${path}\u2026`, () => withNetworkSetup(node, () => client.encrypt(path, plaintext))).catch((error) => {
|
|
37724
|
+
throw cliErrorFromNamedKey(error, "encrypt", path);
|
|
37725
|
+
});
|
|
37726
|
+
const json = JSON.stringify(envelope);
|
|
37727
|
+
if (options.out) {
|
|
37728
|
+
await writePrivateOutput(options.out, new TextEncoder().encode(json));
|
|
37729
|
+
outputJson2({ keyPath: envelope.keyPath, networkId: envelope.networkId, written: options.out });
|
|
37730
|
+
return;
|
|
37731
|
+
}
|
|
37732
|
+
process.stdout.write(json + "\n");
|
|
37733
|
+
} catch (error) {
|
|
37734
|
+
await handleError2(error);
|
|
37735
|
+
}
|
|
37736
|
+
});
|
|
37737
|
+
keys.command("decrypt").description("Decrypt an envelope made with a named key and output the original data").option("--in <file>", "Read the envelope from a file (default: standard input)").option("--out <file>", "Write the decrypted data to a file (default: standard output)").action(async (options, cmd) => {
|
|
37738
|
+
try {
|
|
37739
|
+
const ctx = await deps.resolveContext(cmd.optsWithGlobals());
|
|
37740
|
+
const prefix = await grantedPrefix(deps, ctx.profile);
|
|
37741
|
+
if (options.out) await validatePrivateOutput(options.out, "--out");
|
|
37742
|
+
const envelope = parseEnvelope(await readInput(options.in));
|
|
37743
|
+
assertInGrantedPrefix(prefix, envelope.keyPath, ctx.profile);
|
|
37744
|
+
const profile = await deps.getProfile(ctx.profile);
|
|
37745
|
+
if (profile.ownerDid !== void 0 && envelope.networkId !== namedKeysNetworkId(profile.ownerDid)) {
|
|
37746
|
+
throw new CLIError(
|
|
37747
|
+
"KEY_OUTSIDE_GRANTED_PREFIX",
|
|
37748
|
+
"This envelope was made with a different encryption network than this profile's, so this profile can't decrypt it.",
|
|
37749
|
+
ExitCode.PERMISSION_DENIED
|
|
37750
|
+
);
|
|
37751
|
+
}
|
|
37752
|
+
const node = await deps.authenticate(ctx);
|
|
37753
|
+
const client = deps.createClient(node);
|
|
37754
|
+
const plaintext = await withSpinner("Decrypting\u2026", () => client.decrypt(envelope)).catch((error) => {
|
|
37755
|
+
throw cliErrorFromNamedKey(error, "decrypt", envelope.keyPath);
|
|
37756
|
+
});
|
|
37757
|
+
if (options.out) {
|
|
37758
|
+
await writePrivateOutput(options.out, plaintext);
|
|
37759
|
+
outputJson2({ keyPath: envelope.keyPath, written: options.out });
|
|
37760
|
+
return;
|
|
37761
|
+
}
|
|
37762
|
+
process.stdout.write(plaintext);
|
|
37763
|
+
} catch (error) {
|
|
37764
|
+
await handleError2(error);
|
|
37765
|
+
}
|
|
37766
|
+
});
|
|
37767
|
+
}
|
|
37768
|
+
|
|
37340
37769
|
// src/commands/manifest.ts
|
|
37341
37770
|
init_profiles();
|
|
37342
37771
|
init_sdk();
|
|
@@ -37346,16 +37775,16 @@ init_errors();
|
|
|
37346
37775
|
init_constants();
|
|
37347
37776
|
init_theme();
|
|
37348
37777
|
import { resolveManifestKnowledgeRoot as resolveManifestKnowledgeRoot2 } from "@tinycloud/sdk-core";
|
|
37349
|
-
import { CLI_NAME as
|
|
37350
|
-
import { readFile as
|
|
37778
|
+
import { CLI_NAME as CLI_NAME28 } from "@tinycloud/operations/cli-name";
|
|
37779
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
37351
37780
|
var DEFAULT_APP_SPACE = "applications";
|
|
37352
37781
|
function registerManifestCommand(program) {
|
|
37353
37782
|
const manifest = program.command("manifest").description("Inspect TinyCloud app manifests");
|
|
37354
37783
|
manifest.command("resolve <source>").description("Resolve a manifest file or URL to its effective space, paths, and DB basenames").addHelpText("after", `
|
|
37355
37784
|
|
|
37356
37785
|
Examples:
|
|
37357
|
-
$ ${
|
|
37358
|
-
$ ${
|
|
37786
|
+
$ ${CLI_NAME28} manifest resolve ./manifest.json
|
|
37787
|
+
$ ${CLI_NAME28} manifest resolve https://app.example.com/manifest.json --json
|
|
37359
37788
|
|
|
37360
37789
|
What it shows:
|
|
37361
37790
|
- app_id, name, manifest_version
|
|
@@ -37365,7 +37794,7 @@ What it shows:
|
|
|
37365
37794
|
|
|
37366
37795
|
This command is read-only and does NOT contact the node \u2014 it just resolves
|
|
37367
37796
|
the manifest against the active profile's address/chain so you know which
|
|
37368
|
-
\`--space\` and \`--db\` values to pass to other ${
|
|
37797
|
+
\`--space\` and \`--db\` values to pass to other ${CLI_NAME28} commands.
|
|
37369
37798
|
`).action(async (source, _options, cmd) => {
|
|
37370
37799
|
try {
|
|
37371
37800
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -37434,7 +37863,7 @@ ${theme.heading("SQL databases")}
|
|
|
37434
37863
|
`);
|
|
37435
37864
|
}
|
|
37436
37865
|
process.stdout.write(theme.muted(`
|
|
37437
|
-
Use with: ${
|
|
37866
|
+
Use with: ${CLI_NAME28} sql query --space ${spaceName} --db <db> "..."
|
|
37438
37867
|
`));
|
|
37439
37868
|
}
|
|
37440
37869
|
if (permissions.length > 0) {
|
|
@@ -37461,7 +37890,7 @@ async function loadManifestSource2(source) {
|
|
|
37461
37890
|
}
|
|
37462
37891
|
return response.text();
|
|
37463
37892
|
}
|
|
37464
|
-
return
|
|
37893
|
+
return readFile13(source, "utf8");
|
|
37465
37894
|
}
|
|
37466
37895
|
function prefixWithAppId(path, appId) {
|
|
37467
37896
|
const slash = path.indexOf("/");
|
|
@@ -37554,7 +37983,7 @@ init_profiles();
|
|
|
37554
37983
|
init_formatter();
|
|
37555
37984
|
init_errors();
|
|
37556
37985
|
init_constants();
|
|
37557
|
-
import { CLI_NAME as
|
|
37986
|
+
import { CLI_NAME as CLI_NAME29 } from "@tinycloud/operations/cli-name";
|
|
37558
37987
|
import { createInterface as createInterface5 } from "readline";
|
|
37559
37988
|
init_theme();
|
|
37560
37989
|
init_types();
|
|
@@ -37700,9 +38129,9 @@ profile at command time. Precedence for every kv/sql command:
|
|
|
37700
38129
|
explicit --space flag > profile defaultSpace > primary space.
|
|
37701
38130
|
|
|
37702
38131
|
Examples:
|
|
37703
|
-
$ ${
|
|
37704
|
-
$ ${
|
|
37705
|
-
$ ${
|
|
38132
|
+
$ ${CLI_NAME29} profile set-default-space applications
|
|
38133
|
+
$ ${CLI_NAME29} profile set-default-space applications --profile cli-test
|
|
38134
|
+
$ ${CLI_NAME29} profile set-default-space --unset
|
|
37706
38135
|
`).action(async (name, options, cmd) => {
|
|
37707
38136
|
try {
|
|
37708
38137
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -37714,7 +38143,7 @@ Examples:
|
|
|
37714
38143
|
if (!options.unset && (name === void 0 || name === "")) {
|
|
37715
38144
|
throw new CLIError(
|
|
37716
38145
|
"USAGE_ERROR",
|
|
37717
|
-
`Provide a space name (e.g. \`${
|
|
38146
|
+
`Provide a space name (e.g. \`${CLI_NAME29} profile set-default-space applications\`) or pass --unset.`,
|
|
37718
38147
|
ExitCode.USAGE_ERROR
|
|
37719
38148
|
);
|
|
37720
38149
|
}
|
|
@@ -37773,7 +38202,7 @@ function parseOperatorType(raw) {
|
|
|
37773
38202
|
|
|
37774
38203
|
// src/commands/replica.ts
|
|
37775
38204
|
init_profile_replicas();
|
|
37776
|
-
import { CLI_NAME as
|
|
38205
|
+
import { CLI_NAME as CLI_NAME30 } from "@tinycloud/operations/cli-name";
|
|
37777
38206
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
37778
38207
|
import { constants as fsConstants5 } from "fs";
|
|
37779
38208
|
import { mkdir as mkdir5, open as open11, readdir as readdir6, rename as rename4, unlink as unlink5, lstat as lstat9 } from "fs/promises";
|
|
@@ -37914,11 +38343,11 @@ var EXIT_BY_CODE = {
|
|
|
37914
38343
|
var READ_FAILURE = {
|
|
37915
38344
|
absent: { code: "KEY_ABSENT", exit: ExitCode.NOT_FOUND, what: "is not in the replica (the replica covers its whole prefix)" },
|
|
37916
38345
|
deleted: { code: "KEY_DELETED", exit: ExitCode.NOT_FOUND, what: "was deleted at the source" },
|
|
37917
|
-
content_missing: { code: "CONTENT_MISSING", exit: ExitCode.NOT_FOUND, what: `is known but its content has not been fetched yet; run ${
|
|
38346
|
+
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` },
|
|
37918
38347
|
coverage_incomplete: {
|
|
37919
38348
|
code: "COVERAGE_INCOMPLETE",
|
|
37920
38349
|
exit: ExitCode.NOT_FOUND,
|
|
37921
|
-
what: `is not in the replica, which has not finished its first sync; run ${
|
|
38350
|
+
what: `is not in the replica, which has not finished its first sync; run ${CLI_NAME30} replica sync`
|
|
37922
38351
|
},
|
|
37923
38352
|
not_covered: { code: "NOT_COVERED", exit: ExitCode.USAGE_ERROR, what: "is outside the replica's prefix" }
|
|
37924
38353
|
};
|
|
@@ -38036,7 +38465,7 @@ async function existingReplicaName(profile, option) {
|
|
|
38036
38465
|
const names = await replicaNames(profile);
|
|
38037
38466
|
if (names.length === 1) return names[0];
|
|
38038
38467
|
if (names.length === 0) {
|
|
38039
|
-
throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, `Profile "${profile}" has no replicas. Create one with: ${
|
|
38468
|
+
throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, `Profile "${profile}" has no replicas. Create one with: ${CLI_NAME30} replica sync --prefix <prefix>`);
|
|
38040
38469
|
}
|
|
38041
38470
|
throw new CLIError("USAGE_ERROR", `Profile "${profile}" has several replicas (${names.join(", ")}); pass --replica <name>.`, ExitCode.USAGE_ERROR);
|
|
38042
38471
|
}
|
|
@@ -38144,9 +38573,9 @@ async function storedGrants(profile) {
|
|
|
38144
38573
|
function grantHint(space, prefix) {
|
|
38145
38574
|
return [
|
|
38146
38575
|
"A replica needs a device grant with get and sync on its prefix:",
|
|
38147
|
-
` device: ${
|
|
38148
|
-
` owner: ${
|
|
38149
|
-
` device: ${
|
|
38576
|
+
` device: ${CLI_NAME30} auth request --cap tinycloud.kv:${space}:${prefix}:get,list,metadata,sync --expiry 30d --emit req.json`,
|
|
38577
|
+
` owner: ${CLI_NAME30} auth grant req.json > grant.json`,
|
|
38578
|
+
` device: ${CLI_NAME30} auth import grant.json`
|
|
38150
38579
|
].join("\n");
|
|
38151
38580
|
}
|
|
38152
38581
|
function resolveSpace(input, prefix, grants) {
|
|
@@ -38237,7 +38666,7 @@ function describeStatus(status) {
|
|
|
38237
38666
|
formatField("Size", formatBytes(status.bytes)),
|
|
38238
38667
|
formatField("Grant", status.device.delegationCid),
|
|
38239
38668
|
formatField("Last error", status.lastError ? `${status.lastError.code}: ${status.lastError.message}` : null),
|
|
38240
|
-
formatField("Purge", status.purgePending ? `incomplete: content files remain; the next ${
|
|
38669
|
+
formatField("Purge", status.purgePending ? `incomplete: content files remain; the next ${CLI_NAME30} replica command retries` : null)
|
|
38241
38670
|
].join("\n");
|
|
38242
38671
|
}
|
|
38243
38672
|
async function writeFileAtomic(path, bytes) {
|
|
@@ -38401,7 +38830,7 @@ or coverage incomplete; 5 grant expired, revoked or not yet valid; 6 network;
|
|
|
38401
38830
|
(options, cmd) => run(async () => {
|
|
38402
38831
|
const profile = await profileName(cmd);
|
|
38403
38832
|
if (options.purge) {
|
|
38404
|
-
throw new CLIError("REPORT_READ_ONLY", `Replica report is read-only. To remove a named local replica, run ${
|
|
38833
|
+
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);
|
|
38405
38834
|
}
|
|
38406
38835
|
if (options.clearPending) {
|
|
38407
38836
|
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);
|
|
@@ -38503,7 +38932,7 @@ or coverage incomplete; 5 grant expired, revoked or not yet valid; 6 network;
|
|
|
38503
38932
|
if (differs !== void 0) {
|
|
38504
38933
|
throw new ReplicaError(
|
|
38505
38934
|
ReplicaErrorCode.CONFIG_MISMATCH,
|
|
38506
|
-
`Replica ${name} already follows ${differs}. Use another --replica name, or ${
|
|
38935
|
+
`Replica ${name} already follows ${differs}. Use another --replica name, or ${CLI_NAME30} replica reset --replica ${name} --purge first.`
|
|
38507
38936
|
);
|
|
38508
38937
|
}
|
|
38509
38938
|
}
|
|
@@ -38668,8 +39097,8 @@ init_theme();
|
|
|
38668
39097
|
init_errors();
|
|
38669
39098
|
init_warnings();
|
|
38670
39099
|
init_constants();
|
|
38671
|
-
import { CLI_NAME as
|
|
38672
|
-
import { readFile as
|
|
39100
|
+
import { CLI_NAME as CLI_NAME32 } from "@tinycloud/operations/cli-name";
|
|
39101
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
38673
39102
|
import { join as join17 } from "path";
|
|
38674
39103
|
import { homedir, hostname, userInfo } from "os";
|
|
38675
39104
|
import { appSecretScope } from "@tinycloud/sdk-core";
|
|
@@ -38879,7 +39308,7 @@ function parseStatusEnvelope(line) {
|
|
|
38879
39308
|
// src/lib/secrets-wait.ts
|
|
38880
39309
|
init_constants();
|
|
38881
39310
|
init_errors();
|
|
38882
|
-
import { CLI_NAME as
|
|
39311
|
+
import { CLI_NAME as CLI_NAME31 } from "@tinycloud/operations/cli-name";
|
|
38883
39312
|
var WAIT_BACKOFF_MS = [3e3, 4500, 6750, 10125];
|
|
38884
39313
|
var WAIT_STEADY_INTERVAL_MS = 15e3;
|
|
38885
39314
|
var DEFAULT_WAIT_TIMEOUT = "5m";
|
|
@@ -38887,7 +39316,7 @@ var MAX_WAIT_TIMEOUT_MS = 9 * 6e4;
|
|
|
38887
39316
|
var MAX_CONSECUTIVE_FAILURES = 5;
|
|
38888
39317
|
var TIMEOUT_RE = /^(0|\d+s|\d+m)$/;
|
|
38889
39318
|
var RETRYABLE_STATUS_ERRORS = { NODE_UNREACHABLE: true, NODE_ERROR: true };
|
|
38890
|
-
var EXPLICIT_KEY_HINT = `${
|
|
39319
|
+
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.`;
|
|
38891
39320
|
function abortableSleep(ms2, signal) {
|
|
38892
39321
|
return new Promise((resolve9) => {
|
|
38893
39322
|
if (signal.aborted) {
|
|
@@ -38923,7 +39352,7 @@ async function runSecretsWait(request, deps) {
|
|
|
38923
39352
|
let step = 0;
|
|
38924
39353
|
const cancelled = () => new CLIError(
|
|
38925
39354
|
"CANCELLED",
|
|
38926
|
-
`Interrupted: ${
|
|
39355
|
+
`Interrupted: ${CLI_NAME31} secrets wait stopped before the secrets were ready.`,
|
|
38927
39356
|
ExitCode.CANCELLED,
|
|
38928
39357
|
progress === void 0 ? {} : { progress }
|
|
38929
39358
|
);
|
|
@@ -39067,13 +39496,13 @@ function statusError(error, profile, warnings) {
|
|
|
39067
39496
|
if (error.code === "AUTH_REQUIRED" || error.code === "SESSION_NOT_FOUND") {
|
|
39068
39497
|
const recovery = recoveryOf(error, "sign_in");
|
|
39069
39498
|
if (recovery !== void 0) {
|
|
39070
|
-
const hint = recovery.command !== void 0 ? `Sign in once with: ${recovery.command}, then retry.` : `Sign in to profile "${profile}" once with ${
|
|
39499
|
+
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.`;
|
|
39071
39500
|
return new CLIError("AUTH_REQUIRED", error.message, ExitCode.AUTH_REQUIRED, { hint, recovery, ...extra });
|
|
39072
39501
|
}
|
|
39073
39502
|
}
|
|
39074
39503
|
if (error.code === "PROFILE_MIGRATION_REQUIRED") {
|
|
39075
39504
|
const recovery = recoveryOf(error, "complete_migration");
|
|
39076
|
-
const hint = recovery?.command !== void 0 ? `Run ${recovery.command} once to finish this profile's one-time upgrade, then retry wait.` : `Run ${
|
|
39505
|
+
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.`;
|
|
39077
39506
|
return new CLIError("PROFILE_MIGRATION_REQUIRED", error.message, ExitCode.ERROR, {
|
|
39078
39507
|
hint,
|
|
39079
39508
|
...recovery === void 0 ? {} : { recovery },
|
|
@@ -39099,7 +39528,7 @@ var SECRET_KV_ABILITIES = {
|
|
|
39099
39528
|
del: secretCapabilityAction("del"),
|
|
39100
39529
|
list: secretCapabilityAction("list")
|
|
39101
39530
|
};
|
|
39102
|
-
async function
|
|
39531
|
+
async function readStdin4() {
|
|
39103
39532
|
const chunks = [];
|
|
39104
39533
|
for await (const chunk of process.stdin) {
|
|
39105
39534
|
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
@@ -39265,11 +39694,11 @@ async function runSecretOperation(params) {
|
|
|
39265
39694
|
}
|
|
39266
39695
|
function secretPermissionReason(action, name) {
|
|
39267
39696
|
const target = name ? ` secret "${name}"` : " secrets";
|
|
39268
|
-
return `Allow \`${
|
|
39697
|
+
return `Allow \`${CLI_NAME32} secrets ${action}${name ? ` ${name}` : ""}\` to access${target} with the required TinyCloud permissions.`;
|
|
39269
39698
|
}
|
|
39270
39699
|
function assertOwnerApprovalPossible(profileName2, profile, action, name, missing = []) {
|
|
39271
39700
|
if (profile.authMethod !== "openkey" || process.stdin.isTTY || process.stderr.isTTY) return;
|
|
39272
|
-
const command = `${
|
|
39701
|
+
const command = `${CLI_NAME32} secrets ${action === "del" ? "delete" : action}${name ? ` ${name}` : ""}`;
|
|
39273
39702
|
throw new CLIError(
|
|
39274
39703
|
"PERMISSION_DENIED",
|
|
39275
39704
|
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.`,
|
|
@@ -39280,7 +39709,7 @@ function assertOwnerApprovalPossible(profileName2, profile, action, name, missin
|
|
|
39280
39709
|
);
|
|
39281
39710
|
}
|
|
39282
39711
|
function scopedSecretLoginHint(profileName2) {
|
|
39283
|
-
return `Have the owner approve a scoped login whose manifest names the secret: ${
|
|
39712
|
+
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.`;
|
|
39284
39713
|
}
|
|
39285
39714
|
async function runSecretOperationAttempt(label, operation) {
|
|
39286
39715
|
try {
|
|
@@ -39414,7 +39843,7 @@ ${message}`,
|
|
|
39414
39843
|
"AUTH_REQUIRED",
|
|
39415
39844
|
"Not signed in to TinyCloud.",
|
|
39416
39845
|
ExitCode.AUTH_REQUIRED,
|
|
39417
|
-
{ hint: `Sign in with: ${
|
|
39846
|
+
{ hint: `Sign in with: ${CLI_NAME32} --profile ${result.context.profile} auth login` }
|
|
39418
39847
|
);
|
|
39419
39848
|
}
|
|
39420
39849
|
if (result.error.code === "NODE_UNREACHABLE") {
|
|
@@ -39563,7 +39992,7 @@ function permissionsFromDelegation2(delegation) {
|
|
|
39563
39992
|
}
|
|
39564
39993
|
async function loadDelegationCandidates(source) {
|
|
39565
39994
|
try {
|
|
39566
|
-
const raw = JSON.parse(await
|
|
39995
|
+
const raw = JSON.parse(await readFile14(source, "utf8"));
|
|
39567
39996
|
return normalizeDelegationCandidates(raw, source);
|
|
39568
39997
|
} catch (error) {
|
|
39569
39998
|
if (error?.code !== "ENOENT") {
|
|
@@ -39583,7 +40012,7 @@ async function loadDelegationCandidates(source) {
|
|
|
39583
40012
|
}
|
|
39584
40013
|
try {
|
|
39585
40014
|
const importedPath = join17(resolveProfilesDir(), source, "additional-delegations.json");
|
|
39586
|
-
const raw = JSON.parse(await
|
|
40015
|
+
const raw = JSON.parse(await readFile14(importedPath, "utf8"));
|
|
39587
40016
|
return normalizeDelegationCandidates(raw, source);
|
|
39588
40017
|
} catch (error) {
|
|
39589
40018
|
if (error?.code === "ENOENT") {
|
|
@@ -39891,7 +40320,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
39891
40320
|
name: "Encryption network",
|
|
39892
40321
|
ok: false,
|
|
39893
40322
|
detail: `${networkName} not found`,
|
|
39894
|
-
hint: `${
|
|
40323
|
+
hint: `${CLI_NAME32} secrets network init ${networkName}`
|
|
39895
40324
|
}
|
|
39896
40325
|
];
|
|
39897
40326
|
let secret;
|
|
@@ -39937,7 +40366,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
39937
40366
|
name: "Secret access",
|
|
39938
40367
|
ok: false,
|
|
39939
40368
|
detail: notFound ? `${resolved.permissionPaths.vault} not found` : result.error.message,
|
|
39940
|
-
hint: notFound ? local ? `${
|
|
40369
|
+
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}.`
|
|
39941
40370
|
});
|
|
39942
40371
|
}
|
|
39943
40372
|
} else {
|
|
@@ -40090,9 +40519,9 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
40090
40519
|
throw new CLIError("USAGE_ERROR", "Provide only one of: value argument, --file, or --stdin", ExitCode.USAGE_ERROR);
|
|
40091
40520
|
}
|
|
40092
40521
|
if (options.file) {
|
|
40093
|
-
secretValue = await
|
|
40522
|
+
secretValue = await readFile14(options.file, "utf-8");
|
|
40094
40523
|
} else if (options.stdin) {
|
|
40095
|
-
secretValue = (await
|
|
40524
|
+
secretValue = (await readStdin4()).toString("utf-8");
|
|
40096
40525
|
} else {
|
|
40097
40526
|
secretValue = value;
|
|
40098
40527
|
}
|
|
@@ -40181,7 +40610,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
40181
40610
|
if (effectiveLocal(profile, options)) {
|
|
40182
40611
|
throw new CLIError(
|
|
40183
40612
|
"LOCAL_OWNER_PROFILE",
|
|
40184
|
-
`Profile "${ctx.profile}" uses a local key, which owns its secrets: store them with ${
|
|
40613
|
+
`Profile "${ctx.profile}" uses a local key, which owns its secrets: store them with ${CLI_NAME32} secrets put NAME --stdin instead of requesting them.`,
|
|
40185
40614
|
ExitCode.USAGE_ERROR
|
|
40186
40615
|
);
|
|
40187
40616
|
}
|
|
@@ -40304,7 +40733,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
40304
40733
|
|
|
40305
40734
|
// src/commands/share.ts
|
|
40306
40735
|
import { Option } from "commander";
|
|
40307
|
-
import { CLI_NAME as
|
|
40736
|
+
import { CLI_NAME as CLI_NAME33 } from "@tinycloud/operations/cli-name";
|
|
40308
40737
|
import { ProfileLockTimeoutError as ProfileLockTimeoutError3 } from "@tinycloud/operations/state";
|
|
40309
40738
|
init_errors();
|
|
40310
40739
|
|
|
@@ -40399,7 +40828,7 @@ function receiveJson(result, path) {
|
|
|
40399
40828
|
|
|
40400
40829
|
// src/share/io.ts
|
|
40401
40830
|
import { constants } from "fs";
|
|
40402
|
-
import { lstat as lstat10, mkdir as mkdir6, mkdtemp, open as open13, readFile as
|
|
40831
|
+
import { lstat as lstat10, mkdir as mkdir6, mkdtemp, open as open13, readFile as readFile15, realpath as realpath2, stat as stat6, link as link3, rename as rename5, rm as rm6, unlink as unlink6 } from "fs/promises";
|
|
40403
40832
|
import { randomBytes as randomBytes8 } from "crypto";
|
|
40404
40833
|
import { basename as basename4, join as join18, resolve as resolve6, sep as sep3 } from "path";
|
|
40405
40834
|
init_errors();
|
|
@@ -40446,7 +40875,7 @@ async function readShareInput(input, name, limit = MAX_SHARE_STDIN_BYTES) {
|
|
|
40446
40875
|
const path = resolve6(input);
|
|
40447
40876
|
const info = await stat6(path);
|
|
40448
40877
|
if (!info.isFile() || info.size > limit) throw new Error("MAX_BYTES_EXCEEDED");
|
|
40449
|
-
const bytes = new Uint8Array(await
|
|
40878
|
+
const bytes = new Uint8Array(await readFile15(path));
|
|
40450
40879
|
if (bytes.byteLength > limit) throw new Error("MAX_BYTES_EXCEEDED");
|
|
40451
40880
|
return { bytes, filename };
|
|
40452
40881
|
}
|
|
@@ -41765,7 +42194,7 @@ function shareCliError(error, operation = "publish") {
|
|
|
41765
42194
|
"SHARE_HISTORY_UNREADABLE",
|
|
41766
42195
|
"sender history cannot be read with the current session key",
|
|
41767
42196
|
1,
|
|
41768
|
-
{ hint: `Run \`${
|
|
42197
|
+
{ hint: `Run \`${CLI_NAME33} share history recover\` to move the unreadable file aside. The old file is kept, not deleted.` }
|
|
41769
42198
|
);
|
|
41770
42199
|
}
|
|
41771
42200
|
if (error instanceof SharePublishAuthorityError) {
|
|
@@ -41773,12 +42202,12 @@ function shareCliError(error, operation = "publish") {
|
|
|
41773
42202
|
const profileName2 = "profileName" in failure ? failure.profileName : void 0;
|
|
41774
42203
|
const localKey = "localKey" in failure && failure.localKey === true;
|
|
41775
42204
|
const profileHint = profileName2 === void 0 ? "" : `--profile ${profileName2} `;
|
|
41776
|
-
const loginHint = localKey ? `\`${
|
|
42205
|
+
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\`)`;
|
|
41777
42206
|
if (failure.kind === "caveated-session") {
|
|
41778
42207
|
const holder = profileName2 === void 0 ? "this session" : `profile ${profileName2}'s session`;
|
|
41779
42208
|
return new CLIError(
|
|
41780
42209
|
"PERMISSION_DENIED",
|
|
41781
|
-
`${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): \`${
|
|
42210
|
+
`${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\``,
|
|
41782
42211
|
5
|
|
41783
42212
|
);
|
|
41784
42213
|
}
|
|
@@ -41879,7 +42308,7 @@ function shareCliError(error, operation = "publish") {
|
|
|
41879
42308
|
if (nodeCode === "ENOENT") return new CLIError("INVALID_ARGUMENT", "share input was not found", 2);
|
|
41880
42309
|
if (nodeCode === "EISDIR") return new CLIError("INVALID_ARGUMENT", "share input must be a Markdown file", 2);
|
|
41881
42310
|
const mapped = typeof nodeCode === "string" && known[nodeCode] !== void 0 ? known[nodeCode] : known[message];
|
|
41882
|
-
const classified = mapped === void 0 ? classifyShareFailure(error, `\`${
|
|
42311
|
+
const classified = mapped === void 0 ? classifyShareFailure(error, `\`${CLI_NAME33} auth login\``) : void 0;
|
|
41883
42312
|
if (classified !== void 0) return Object.assign(new CLIError(classified.code, classified.reason, classified.exitCode), { cause: error });
|
|
41884
42313
|
if (error instanceof TypeError) return new CLIError("INVALID_ARGUMENT", "share input is invalid", 2);
|
|
41885
42314
|
return new CLIError(mapped?.code ?? "INVALID_ARGUMENT", mapped ? mapped.code : "share operation failed", mapped?.exit ?? 2);
|
|
@@ -42289,7 +42718,7 @@ init_constants();
|
|
|
42289
42718
|
init_sdk();
|
|
42290
42719
|
init_host();
|
|
42291
42720
|
init_theme();
|
|
42292
|
-
import { CLI_NAME as
|
|
42721
|
+
import { CLI_NAME as CLI_NAME34 } from "@tinycloud/operations/cli-name";
|
|
42293
42722
|
import { ACCOUNT_REGISTRY_SPACE } from "@tinycloud/node-sdk";
|
|
42294
42723
|
import { randomBytes as randomBytes9 } from "crypto";
|
|
42295
42724
|
import { mkdir as mkdir7, writeFile as writeFile4 } from "fs/promises";
|
|
@@ -42336,7 +42765,7 @@ function registerSpaceCommand(program) {
|
|
|
42336
42765
|
} else {
|
|
42337
42766
|
const profile = await ProfileManager.getProfile(ctx.profile);
|
|
42338
42767
|
const cap = "tinycloud.kv:account:spaces/:get,list";
|
|
42339
|
-
const hint = resolveProfilePosture(profile) === "delegate-session" ? `Ask the space owner to grant it. Create the request with: ${
|
|
42768
|
+
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.`;
|
|
42340
42769
|
throw new CLIError(
|
|
42341
42770
|
"PERMISSION_DENIED",
|
|
42342
42771
|
`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.`,
|
|
@@ -42373,10 +42802,10 @@ function registerSpaceCommand(program) {
|
|
|
42373
42802
|
|
|
42374
42803
|
A delegate cannot host a space \u2014 only its owner (root authority) can. This
|
|
42375
42804
|
emits a tinycloud.host.request artifact naming the space and its owner so you
|
|
42376
|
-
can hand it to the owner; they run \`${
|
|
42805
|
+
can hand it to the owner; they run \`${CLI_NAME34} space host <name>\` and confirm.
|
|
42377
42806
|
|
|
42378
42807
|
If you ARE the owner of the resolved space, this refuses and tells you to host
|
|
42379
|
-
it directly with \`${
|
|
42808
|
+
it directly with \`${CLI_NAME34} space host <name>\` (no request needed).
|
|
42380
42809
|
`).action(async (name, options, cmd) => {
|
|
42381
42810
|
try {
|
|
42382
42811
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -42387,7 +42816,7 @@ it directly with \`${CLI_NAME32} space host <name>\` (no request needed).
|
|
|
42387
42816
|
if (await isRootAuthority(spaceId, ctx.profile)) {
|
|
42388
42817
|
throw new CLIError(
|
|
42389
42818
|
"ALREADY_ROOT_AUTHORITY",
|
|
42390
|
-
`You are the owner of ${spaceId}. Host it directly: ${
|
|
42819
|
+
`You are the owner of ${spaceId}. Host it directly: ${CLI_NAME34} space host ${spaceName}`,
|
|
42391
42820
|
ExitCode.USAGE_ERROR
|
|
42392
42821
|
);
|
|
42393
42822
|
}
|
|
@@ -42473,7 +42902,7 @@ init_sdk();
|
|
|
42473
42902
|
init_space();
|
|
42474
42903
|
init_host();
|
|
42475
42904
|
init_theme();
|
|
42476
|
-
import { CLI_NAME as
|
|
42905
|
+
import { CLI_NAME as CLI_NAME35 } from "@tinycloud/operations/cli-name";
|
|
42477
42906
|
import { writeFile as writeFile5 } from "fs/promises";
|
|
42478
42907
|
import { resolve as resolve8 } from "path";
|
|
42479
42908
|
async function dbHandle(node, dbName, spaceInput, profileName2) {
|
|
@@ -42497,12 +42926,12 @@ database for simple apps, or pass --db to target a named database. Pass
|
|
|
42497
42926
|
--space to target a non-primary space (e.g. the manifest "applications" space).
|
|
42498
42927
|
|
|
42499
42928
|
Common workflows:
|
|
42500
|
-
$ ${
|
|
42501
|
-
$ ${
|
|
42502
|
-
$ ${
|
|
42503
|
-
$ ${
|
|
42504
|
-
$ ${
|
|
42505
|
-
$ ${
|
|
42929
|
+
$ ${CLI_NAME35} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
|
|
42930
|
+
$ ${CLI_NAME35} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["ship docs"]'
|
|
42931
|
+
$ ${CLI_NAME35} sql query "SELECT id, body FROM notes ORDER BY id"
|
|
42932
|
+
$ ${CLI_NAME35} sql query "SELECT * FROM events WHERE type = ?" --db analytics --params '["signup"]'
|
|
42933
|
+
$ ${CLI_NAME35} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
|
|
42934
|
+
$ ${CLI_NAME35} sql export --db analytics --output analytics.db
|
|
42506
42935
|
|
|
42507
42936
|
Commands:
|
|
42508
42937
|
query Read rows with SELECT statements
|
|
@@ -42519,10 +42948,10 @@ Tips:
|
|
|
42519
42948
|
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", `
|
|
42520
42949
|
|
|
42521
42950
|
Examples:
|
|
42522
|
-
$ ${
|
|
42523
|
-
$ ${
|
|
42524
|
-
$ ${
|
|
42525
|
-
$ ${
|
|
42951
|
+
$ ${CLI_NAME35} sql query "SELECT * FROM notes ORDER BY id"
|
|
42952
|
+
$ ${CLI_NAME35} sql query "SELECT * FROM notes WHERE id = ?" --params '[42]'
|
|
42953
|
+
$ ${CLI_NAME35} sql query "SELECT count(*) AS total FROM events" --db analytics --json
|
|
42954
|
+
$ ${CLI_NAME35} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
|
|
42526
42955
|
|
|
42527
42956
|
Output:
|
|
42528
42957
|
Human output is formatted as a table. Piped output or --json returns
|
|
@@ -42563,11 +42992,11 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
|
|
|
42563
42992
|
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", `
|
|
42564
42993
|
|
|
42565
42994
|
Examples:
|
|
42566
|
-
$ ${
|
|
42567
|
-
$ ${
|
|
42568
|
-
$ ${
|
|
42569
|
-
$ ${
|
|
42570
|
-
$ ${
|
|
42995
|
+
$ ${CLI_NAME35} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
|
|
42996
|
+
$ ${CLI_NAME35} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["first note"]'
|
|
42997
|
+
$ ${CLI_NAME35} sql execute "UPDATE notes SET body = ? WHERE id = ?" --params '["edited", 1]'
|
|
42998
|
+
$ ${CLI_NAME35} sql execute "DROP TABLE old_notes" --db archive
|
|
42999
|
+
$ ${CLI_NAME35} sql execute "DELETE FROM conversation WHERE id = ?" --space applications --db xyz.tinycloud.listen/conversations --params '["abc"]'
|
|
42571
43000
|
|
|
42572
43001
|
Output:
|
|
42573
43002
|
Returns JSON with the changed row count and last inserted row id when available.
|
|
@@ -42596,9 +43025,9 @@ Output:
|
|
|
42596
43025
|
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", `
|
|
42597
43026
|
|
|
42598
43027
|
Examples:
|
|
42599
|
-
$ ${
|
|
42600
|
-
$ ${
|
|
42601
|
-
$ ${
|
|
43028
|
+
$ ${CLI_NAME35} sql export
|
|
43029
|
+
$ ${CLI_NAME35} sql export --db analytics --output analytics.db
|
|
43030
|
+
$ ${CLI_NAME35} sql export --space applications --db xyz.tinycloud.listen/conversations --output listen.db
|
|
42602
43031
|
|
|
42603
43032
|
Output:
|
|
42604
43033
|
Writes the database file locally and returns JSON with the path and size.
|
|
@@ -42631,19 +43060,19 @@ Output:
|
|
|
42631
43060
|
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", `
|
|
42632
43061
|
|
|
42633
43062
|
Examples:
|
|
42634
|
-
$ ${
|
|
43063
|
+
$ ${CLI_NAME35} sql copy --from-db com.tinycloud.conversation-sync/conversations \\
|
|
42635
43064
|
--to-db xyz.tinycloud.listen/conversations \\
|
|
42636
43065
|
--space applications --dry-run
|
|
42637
|
-
$ ${
|
|
43066
|
+
$ ${CLI_NAME35} sql copy --from-space applications --from-db com.foo/data \\
|
|
42638
43067
|
--to-space applications --to-db com.bar/data \\
|
|
42639
43068
|
--table conversation --table participant
|
|
42640
43069
|
|
|
42641
43070
|
Notes:
|
|
42642
43071
|
- Refuses to run when (resolved space, db) is identical for source and destination.
|
|
42643
|
-
- Does NOT create destination tables. Run the target app once (or use \`${
|
|
43072
|
+
- Does NOT create destination tables. Run the target app once (or use \`${CLI_NAME35} sql execute\`)
|
|
42644
43073
|
to materialize the schema before copying.
|
|
42645
43074
|
- One row at a time; suitable for small/medium datasets. Large copies should
|
|
42646
|
-
use \`${
|
|
43075
|
+
use \`${CLI_NAME35} sql export\` + bulk import.
|
|
42647
43076
|
- Authorization: the active session/delegation must cover sql/read on source
|
|
42648
43077
|
AND sql/write on destination. Otherwise the relevant operation will fail.
|
|
42649
43078
|
`).action(async (options, cmd) => {
|
|
@@ -42743,7 +43172,7 @@ init_errors();
|
|
|
42743
43172
|
init_formatter();
|
|
42744
43173
|
init_theme();
|
|
42745
43174
|
init_permissions();
|
|
42746
|
-
import { CLI_NAME as
|
|
43175
|
+
import { CLI_NAME as CLI_NAME36 } from "@tinycloud/operations/cli-name";
|
|
42747
43176
|
import {
|
|
42748
43177
|
NodeWasmBindings as NodeWasmBindings2
|
|
42749
43178
|
} from "@tinycloud/node-sdk";
|
|
@@ -43017,7 +43446,7 @@ function formatStatus(summary) {
|
|
|
43017
43446
|
lines.push(`Default profile: ${theme.value(summary.defaultProfile)}`);
|
|
43018
43447
|
lines.push("");
|
|
43019
43448
|
if (summary.profiles.length === 0) {
|
|
43020
|
-
lines.push(theme.muted(`No profiles configured. Run: ${
|
|
43449
|
+
lines.push(theme.muted(`No profiles configured. Run: ${CLI_NAME36} init`));
|
|
43021
43450
|
return `${lines.join("\n")}
|
|
43022
43451
|
`;
|
|
43023
43452
|
}
|
|
@@ -43173,14 +43602,14 @@ function registerUpgradeCommand(program) {
|
|
|
43173
43602
|
}
|
|
43174
43603
|
|
|
43175
43604
|
// src/commands/vault.ts
|
|
43176
|
-
import { readFile as
|
|
43605
|
+
import { readFile as readFile16 } from "fs/promises";
|
|
43177
43606
|
init_profiles();
|
|
43178
43607
|
init_formatter();
|
|
43179
43608
|
init_errors();
|
|
43180
43609
|
init_constants();
|
|
43181
43610
|
init_sdk();
|
|
43182
43611
|
import { PrivateKeySigner as PrivateKeySigner2 } from "@tinycloud/node-sdk";
|
|
43183
|
-
async function
|
|
43612
|
+
async function readStdin5() {
|
|
43184
43613
|
const chunks = [];
|
|
43185
43614
|
for await (const chunk of process.stdin) {
|
|
43186
43615
|
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
@@ -43235,9 +43664,9 @@ function registerVaultCommand(program) {
|
|
|
43235
43664
|
throw new CLIError("USAGE_ERROR", "Provide only one of: value argument, --file, or --stdin", ExitCode.USAGE_ERROR);
|
|
43236
43665
|
}
|
|
43237
43666
|
if (options.file) {
|
|
43238
|
-
putValue = new Uint8Array(await
|
|
43667
|
+
putValue = new Uint8Array(await readFile16(options.file));
|
|
43239
43668
|
} else if (options.stdin) {
|
|
43240
|
-
putValue = new Uint8Array(await
|
|
43669
|
+
putValue = new Uint8Array(await readStdin5());
|
|
43241
43670
|
} else {
|
|
43242
43671
|
putValue = value;
|
|
43243
43672
|
}
|
|
@@ -43350,14 +43779,14 @@ function registerVaultCommand(program) {
|
|
|
43350
43779
|
}
|
|
43351
43780
|
|
|
43352
43781
|
// src/commands/vars.ts
|
|
43353
|
-
import { readFile as
|
|
43782
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
43354
43783
|
init_profiles();
|
|
43355
43784
|
init_formatter();
|
|
43356
43785
|
init_errors();
|
|
43357
43786
|
init_constants();
|
|
43358
43787
|
init_sdk();
|
|
43359
43788
|
var VARIABLES_PREFIX = "variables/";
|
|
43360
|
-
async function
|
|
43789
|
+
async function readStdin6() {
|
|
43361
43790
|
const chunks = [];
|
|
43362
43791
|
for await (const chunk of process.stdin) {
|
|
43363
43792
|
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
@@ -43455,9 +43884,9 @@ function registerVarsCommand(program) {
|
|
|
43455
43884
|
throw new CLIError("USAGE_ERROR", "Provide only one of: value argument, --file, or --stdin", ExitCode.USAGE_ERROR);
|
|
43456
43885
|
}
|
|
43457
43886
|
if (options.file) {
|
|
43458
|
-
varValue = await
|
|
43887
|
+
varValue = await readFile17(options.file, "utf-8");
|
|
43459
43888
|
} else if (options.stdin) {
|
|
43460
|
-
varValue = (await
|
|
43889
|
+
varValue = (await readStdin6()).toString("utf-8");
|
|
43461
43890
|
} else {
|
|
43462
43891
|
varValue = value;
|
|
43463
43892
|
}
|
|
@@ -43500,6 +43929,7 @@ function registerTinyCloudCommands(program) {
|
|
|
43500
43929
|
registerAuthCommand(program);
|
|
43501
43930
|
registerEnableCommand(program);
|
|
43502
43931
|
registerKvCommand(program);
|
|
43932
|
+
registerKeysCommand(program);
|
|
43503
43933
|
registerReplicaCommand(program);
|
|
43504
43934
|
registerCourierCommand(program);
|
|
43505
43935
|
registerSpaceCommand(program);
|