@better-update/cli 0.24.1 → 0.25.0
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.mjs +210 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/dist/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { execFile, spawn, spawnSync } from "node:child_process";
|
|
4
4
|
import { defineCommand, runMain } from "citty";
|
|
5
|
-
import { Console, Context, Data, Deferred, Duration, Effect, Either, Layer, Match, Option, ParseResult, Schedule, Schema } from "effect";
|
|
5
|
+
import { Clock, Console, Context, Data, Deferred, Duration, Effect, Either, Layer, Match, Option, ParseResult, Schedule, Schema } from "effect";
|
|
6
6
|
import { Command, FetchHttpClient, FileSystem, Headers as Headers$1, HttpApi, HttpApiClient, HttpApiEndpoint, HttpApiGroup, HttpApiMiddleware, HttpApiSchema, HttpApiSecurity, HttpClient, HttpClientRequest, OpenApi, Path } from "@effect/platform";
|
|
7
7
|
import { NodeContext } from "@effect/platform-node";
|
|
8
8
|
import path from "node:path";
|
|
@@ -12,6 +12,7 @@ import { autocomplete, cancel, confirm, isCancel, multiselect, password, select,
|
|
|
12
12
|
import { open, readFile, writeFile } from "node:fs/promises";
|
|
13
13
|
import { X509Certificate, createHash, createSign, createVerify, randomBytes, randomUUID } from "node:crypto";
|
|
14
14
|
import { accessSync, chmodSync, constants, createReadStream, existsSync, promises, readFileSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { Entry } from "@napi-rs/keyring";
|
|
15
16
|
import { once } from "node:events";
|
|
16
17
|
import { createServer } from "node:http";
|
|
17
18
|
import { maxBy, uniqBy } from "es-toolkit";
|
|
@@ -33,7 +34,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
33
34
|
|
|
34
35
|
//#endregion
|
|
35
36
|
//#region package.json
|
|
36
|
-
var version = "0.
|
|
37
|
+
var version = "0.25.0";
|
|
37
38
|
|
|
38
39
|
//#endregion
|
|
39
40
|
//#region src/lib/interactive-mode.ts
|
|
@@ -67,7 +68,7 @@ const cookieSecurity = HttpApiSecurity.apiKey({
|
|
|
67
68
|
in: "cookie"
|
|
68
69
|
});
|
|
69
70
|
var Authentication = class extends HttpApiMiddleware.Tag()("api/Authentication", {
|
|
70
|
-
failure: Unauthorized,
|
|
71
|
+
failure: Schema.Union(Unauthorized, Forbidden),
|
|
71
72
|
provides: AuthContext,
|
|
72
73
|
security: {
|
|
73
74
|
bearer: bearerSecurity,
|
|
@@ -130,6 +131,52 @@ const UploadHeaders = Schema.Record({
|
|
|
130
131
|
value: Schema.String
|
|
131
132
|
});
|
|
132
133
|
|
|
134
|
+
//#endregion
|
|
135
|
+
//#region ../../packages/api/src/domain/admin.ts
|
|
136
|
+
/**
|
|
137
|
+
* A platform user as seen by a superadmin on the dashboard `/admin` page.
|
|
138
|
+
* `role` is the GLOBAL Better Auth admin-plugin role (e.g. "admin"), distinct
|
|
139
|
+
* from per-organization membership roles. `approved` is the dev-phase gate.
|
|
140
|
+
*/
|
|
141
|
+
const AdminUser = Schema.Struct({
|
|
142
|
+
id: Schema.String,
|
|
143
|
+
name: Schema.String,
|
|
144
|
+
email: Schema.String,
|
|
145
|
+
role: Schema.NullOr(Schema.String),
|
|
146
|
+
approved: Schema.Boolean,
|
|
147
|
+
banned: Schema.Boolean,
|
|
148
|
+
createdAt: DateTimeString
|
|
149
|
+
});
|
|
150
|
+
const AdminUserStatus = Schema.Literal("all", "pending", "approved");
|
|
151
|
+
const ListAdminUsersParams = Schema.Struct({
|
|
152
|
+
search: Schema.optional(Schema.String),
|
|
153
|
+
status: Schema.optional(AdminUserStatus),
|
|
154
|
+
...PaginationParams.fields
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region ../../packages/api/src/groups/admin.ts
|
|
159
|
+
const userIdParam = HttpApiSchema.param("userId", Schema.String);
|
|
160
|
+
/**
|
|
161
|
+
* Platform administration, restricted to superadmins (Better Auth admin-plugin
|
|
162
|
+
* `role = "admin"`). The dev-phase approval gate lives here: superadmins list
|
|
163
|
+
* users and approve/revoke their access. All endpoints fail `Forbidden` for
|
|
164
|
+
* non-superadmins.
|
|
165
|
+
*/
|
|
166
|
+
var AdminGroup = class extends HttpApiGroup.make("admin").add(HttpApiEndpoint.get("listUsers", "/api/admin/users").setUrlParams(ListAdminUsersParams).addSuccess(pageResult(AdminUser)).annotateContext(OpenApi.annotations({
|
|
167
|
+
title: "List users",
|
|
168
|
+
description: "List platform users with approval status (superadmin only)"
|
|
169
|
+
}))).add(HttpApiEndpoint.post("approveUser")`/api/admin/users/${userIdParam}/approve`.addSuccess(AdminUser).annotateContext(OpenApi.annotations({
|
|
170
|
+
title: "Approve user",
|
|
171
|
+
description: "Grant a user access to the app (superadmin only)"
|
|
172
|
+
}))).add(HttpApiEndpoint.post("revokeUser")`/api/admin/users/${userIdParam}/revoke`.addSuccess(AdminUser).annotateContext(OpenApi.annotations({
|
|
173
|
+
title: "Revoke user approval",
|
|
174
|
+
description: "Revoke a user's access to the app (superadmin only)"
|
|
175
|
+
}))).addError(Forbidden).addError(NotFound).annotateContext(OpenApi.annotations({
|
|
176
|
+
title: "Admin",
|
|
177
|
+
description: "Superadmin platform administration"
|
|
178
|
+
})) {};
|
|
179
|
+
|
|
133
180
|
//#endregion
|
|
134
181
|
//#region ../../packages/api/src/domain/analytics.ts
|
|
135
182
|
const PeriodLiteral = Schema.Literal("1d", "7d", "30d", "90d");
|
|
@@ -2108,7 +2155,7 @@ var WebhooksGroup = class extends HttpApiGroup.make("webhooks").add(HttpApiEndpo
|
|
|
2108
2155
|
|
|
2109
2156
|
//#endregion
|
|
2110
2157
|
//#region ../../packages/api/src/api.ts
|
|
2111
|
-
var ManagementApi = class extends HttpApi.make("management-api").add(ProjectsGroup).add(BranchesGroup).add(ChannelsGroup).add(UpdatesGroup).add(AssetsGroup).add(AnalyticsGroup).add(BuildsGroup).add(EnvVarsGroup).add(FingerprintsGroup).add(AuditLogsGroup).add(DevicesGroup).add(AppleTeamsGroup).add(AppleDistributionCertificatesGroup).add(ApplePushKeysGroup).add(AscApiKeysGroup).add(AppleProvisioningProfilesGroup).add(GoogleServiceAccountKeysGroup).add(IosBundleConfigurationsGroup).add(IosAppMetadataGroup).add(SubmissionsGroup).add(AndroidApplicationIdentifiersGroup).add(AndroidUploadKeystoresGroup).add(AndroidBuildCredentialsGroup).add(BuildCredentialsGroup).add(UserEncryptionKeysGroup).add(OrgVaultGroup).add(MeGroup).add(WebhooksGroup).middleware(Authentication).annotateContext(OpenApi.annotations({
|
|
2158
|
+
var ManagementApi = class extends HttpApi.make("management-api").add(ProjectsGroup).add(BranchesGroup).add(ChannelsGroup).add(UpdatesGroup).add(AssetsGroup).add(AnalyticsGroup).add(BuildsGroup).add(EnvVarsGroup).add(FingerprintsGroup).add(AuditLogsGroup).add(DevicesGroup).add(AppleTeamsGroup).add(AppleDistributionCertificatesGroup).add(ApplePushKeysGroup).add(AscApiKeysGroup).add(AppleProvisioningProfilesGroup).add(GoogleServiceAccountKeysGroup).add(IosBundleConfigurationsGroup).add(IosAppMetadataGroup).add(SubmissionsGroup).add(AndroidApplicationIdentifiersGroup).add(AndroidUploadKeystoresGroup).add(AndroidBuildCredentialsGroup).add(BuildCredentialsGroup).add(UserEncryptionKeysGroup).add(OrgVaultGroup).add(MeGroup).add(WebhooksGroup).add(AdminGroup).middleware(Authentication).annotateContext(OpenApi.annotations({
|
|
2112
2159
|
title: "Better Update Management API",
|
|
2113
2160
|
version: "1.0.0",
|
|
2114
2161
|
description: "Management API for OTA update publishing, deployment, and analytics"
|
|
@@ -2883,6 +2930,81 @@ const UpdateAssetUploaderLive = Layer.effect(UpdateAssetUploader, Effect.gen(fun
|
|
|
2883
2930
|
}) };
|
|
2884
2931
|
}));
|
|
2885
2932
|
|
|
2933
|
+
//#endregion
|
|
2934
|
+
//#region src/services/vault-cache.ts
|
|
2935
|
+
/**
|
|
2936
|
+
* "Unlock once, reuse" for the credential vault — the analog of macOS
|
|
2937
|
+
* `security unlock-keychain`. The first vault operation in a session prompts for
|
|
2938
|
+
* the device passphrase, unwraps the vault key, and stows it in the OS keychain
|
|
2939
|
+
* (`@napi-rs/keyring`: macOS Keychain / Windows Credential Manager / Linux
|
|
2940
|
+
* libsecret) with a short TTL; subsequent commands read it back and skip the
|
|
2941
|
+
* prompt + Argon2id derivation entirely until it expires.
|
|
2942
|
+
*
|
|
2943
|
+
* What is cached is the unwrapped **vault key**, never the passphrase or the age
|
|
2944
|
+
* private key — so the blast radius of a leaked keychain entry is one vault
|
|
2945
|
+
* version's credentials, and only until the TTL lapses.
|
|
2946
|
+
*/
|
|
2947
|
+
/** How long a cached vault key stays valid before a fresh passphrase is required. */
|
|
2948
|
+
const VAULT_CACHE_TTL_MS = 900 * 1e3;
|
|
2949
|
+
/** Keychain service name; the account is the recipient's public key. */
|
|
2950
|
+
const KEYCHAIN_SERVICE = "better-update-vault";
|
|
2951
|
+
const isCachedVaultEntry = (value) => isRecord(value) && typeof value["vaultKey"] === "string" && typeof value["vaultVersion"] === "number" && typeof value["keyId"] === "string" && typeof value["exp"] === "number";
|
|
2952
|
+
/** Serialize an unlocked vault into a keychain blob, stamping a TTL from `now`. */
|
|
2953
|
+
const encodeCacheEntry = (vault, now, ttlMs = VAULT_CACHE_TTL_MS) => JSON.stringify({
|
|
2954
|
+
vaultKey: toBase64(vault.vaultKey),
|
|
2955
|
+
vaultVersion: vault.vaultVersion,
|
|
2956
|
+
keyId: vault.keyId,
|
|
2957
|
+
exp: now + ttlMs
|
|
2958
|
+
});
|
|
2959
|
+
/**
|
|
2960
|
+
* Parse a keychain blob back into an unlocked vault, or `undefined` when it is
|
|
2961
|
+
* malformed or has expired as of `now` — so an expired entry reads exactly like
|
|
2962
|
+
* a missing one (and is evicted by the caller).
|
|
2963
|
+
*/
|
|
2964
|
+
const decodeCacheEntry = (raw, now) => {
|
|
2965
|
+
const parsed = safeJsonParse(raw);
|
|
2966
|
+
if (!isCachedVaultEntry(parsed) || now >= parsed.exp) return;
|
|
2967
|
+
return {
|
|
2968
|
+
vault: {
|
|
2969
|
+
vaultKey: fromBase64(parsed.vaultKey),
|
|
2970
|
+
vaultVersion: parsed.vaultVersion,
|
|
2971
|
+
keyId: parsed.keyId
|
|
2972
|
+
},
|
|
2973
|
+
remainingMs: parsed.exp - now
|
|
2974
|
+
};
|
|
2975
|
+
};
|
|
2976
|
+
var VaultCache = class extends Context.Tag("cli/VaultCache")() {};
|
|
2977
|
+
const VaultCacheLive = Layer.effect(VaultCache, Effect.gen(function* () {
|
|
2978
|
+
const runtime = yield* CliRuntime;
|
|
2979
|
+
const cacheDisabled = Effect.gen(function* () {
|
|
2980
|
+
const flag = yield* runtime.getEnv("BETTER_UPDATE_NO_CACHE");
|
|
2981
|
+
return flag !== void 0 && flag.length > 0 && flag !== "0" && flag !== "false";
|
|
2982
|
+
});
|
|
2983
|
+
const readRaw = (publicKey) => Effect.try(() => new Entry(KEYCHAIN_SERVICE, publicKey).getPassword()).pipe(Effect.catchAll(() => Effect.succeed(null)));
|
|
2984
|
+
const writeRaw = (publicKey, blob) => Effect.try(() => {
|
|
2985
|
+
new Entry(KEYCHAIN_SERVICE, publicKey).setPassword(blob);
|
|
2986
|
+
}).pipe(Effect.ignore);
|
|
2987
|
+
const deleteRaw = (publicKey) => Effect.try(() => new Entry(KEYCHAIN_SERVICE, publicKey).deletePassword()).pipe(Effect.ignore);
|
|
2988
|
+
return {
|
|
2989
|
+
get: (publicKey) => Effect.gen(function* () {
|
|
2990
|
+
if (yield* cacheDisabled) return;
|
|
2991
|
+
const raw = yield* readRaw(publicKey);
|
|
2992
|
+
if (raw === null) return;
|
|
2993
|
+
const decoded = decodeCacheEntry(raw, yield* Clock.currentTimeMillis);
|
|
2994
|
+
if (decoded === void 0) {
|
|
2995
|
+
yield* deleteRaw(publicKey);
|
|
2996
|
+
return;
|
|
2997
|
+
}
|
|
2998
|
+
return decoded;
|
|
2999
|
+
}),
|
|
3000
|
+
set: (publicKey, vault) => Effect.gen(function* () {
|
|
3001
|
+
if (yield* cacheDisabled) return;
|
|
3002
|
+
yield* writeRaw(publicKey, encodeCacheEntry(vault, yield* Clock.currentTimeMillis));
|
|
3003
|
+
}),
|
|
3004
|
+
clear: (publicKey) => deleteRaw(publicKey)
|
|
3005
|
+
};
|
|
3006
|
+
}));
|
|
3007
|
+
|
|
2886
3008
|
//#endregion
|
|
2887
3009
|
//#region src/services/version-check.ts
|
|
2888
3010
|
const NPM_REGISTRY_URL = "https://registry.npmjs.org/@better-update/cli/latest";
|
|
@@ -2933,7 +3055,7 @@ const VersionCheckLive = Layer.effect(VersionCheck, Effect.gen(function* () {
|
|
|
2933
3055
|
//#endregion
|
|
2934
3056
|
//#region src/app-layer.ts
|
|
2935
3057
|
const CliPlatformLayer = Layer.mergeAll(CliRuntimeLive, NodeContext.layer, FetchHttpClient.layer);
|
|
2936
|
-
const CliStoreLayer = Layer.mergeAll(AuthStoreLive, ConfigStoreLive, AppleSessionStoreLive, IdentityStoreLive).pipe(Layer.provide(CliPlatformLayer));
|
|
3058
|
+
const CliStoreLayer = Layer.mergeAll(AuthStoreLive, ConfigStoreLive, AppleSessionStoreLive, IdentityStoreLive, VaultCacheLive).pipe(Layer.provide(CliPlatformLayer));
|
|
2937
3059
|
const CliAdapterDependencies = Layer.mergeAll(CliPlatformLayer, CliStoreLayer);
|
|
2938
3060
|
const ApiClientLayer = ApiClientLive.pipe(Layer.provide(CliAdapterDependencies));
|
|
2939
3061
|
const AppleAuthLayer = AppleAuthLive.pipe(Layer.provide(CliAdapterDependencies));
|
|
@@ -18327,6 +18449,25 @@ const grantRecipient = (args) => Effect.gen(function* () {
|
|
|
18327
18449
|
const resolveVaultPassphrase = Effect.gen(function* () {
|
|
18328
18450
|
return (yield* activeRecipient).source === "file" ? yield* promptPassword("Passphrase to unlock this device's identity:") : void 0;
|
|
18329
18451
|
});
|
|
18452
|
+
/**
|
|
18453
|
+
* Unlock the org vault key for an interactive command, reusing a cached vault key
|
|
18454
|
+
* from the OS keychain when one is present and unexpired — so the device
|
|
18455
|
+
* passphrase is prompted at most once per cache TTL rather than on every command
|
|
18456
|
+
* (`better-update credentials unlock` / `lock` drive that session explicitly).
|
|
18457
|
+
* The CI `BETTER_UPDATE_IDENTITY` key carries no passphrase and is never cached:
|
|
18458
|
+
* it skips straight to the raw unwrap. On a cache miss the full unlock runs —
|
|
18459
|
+
* prompt, Argon2id, fetch + unwrap — and the result is cached for next time.
|
|
18460
|
+
*/
|
|
18461
|
+
const unlockVaultKeyInteractive = (api) => Effect.gen(function* () {
|
|
18462
|
+
const recipient = yield* activeRecipient;
|
|
18463
|
+
if (recipient.source !== "file") return yield* unlockVaultKey(api, void 0);
|
|
18464
|
+
const cache = yield* VaultCache;
|
|
18465
|
+
const cached = yield* cache.get(recipient.publicKey);
|
|
18466
|
+
if (cached !== void 0) return cached.vault;
|
|
18467
|
+
const vault = yield* unlockVaultKey(api, yield* promptPassword("Passphrase to unlock this device's identity:"));
|
|
18468
|
+
yield* cache.set(recipient.publicKey, vault);
|
|
18469
|
+
return vault;
|
|
18470
|
+
}).pipe(Effect.provide(VaultCacheLive));
|
|
18330
18471
|
/** Look up a registered recipient by its key id or full `SHA256:` fingerprint. */
|
|
18331
18472
|
const findRecipient = (api, selector) => Effect.gen(function* () {
|
|
18332
18473
|
const { items } = yield* api.userEncryptionKeys.list();
|
|
@@ -18374,11 +18515,15 @@ const openVaultSession = (api, passphrase) => Effect.gen(function* () {
|
|
|
18374
18515
|
};
|
|
18375
18516
|
});
|
|
18376
18517
|
/**
|
|
18377
|
-
* {@link openVaultSession} that
|
|
18378
|
-
*
|
|
18518
|
+
* {@link openVaultSession} that unlocks the vault key interactively — reusing the
|
|
18519
|
+
* OS-keychain-cached key when one is live (no prompt), prompting for the device
|
|
18520
|
+
* passphrase only on a cache miss, and none at all for the CI env key.
|
|
18379
18521
|
*/
|
|
18380
18522
|
const openVaultSessionInteractive = (api) => Effect.gen(function* () {
|
|
18381
|
-
return
|
|
18523
|
+
return {
|
|
18524
|
+
orgId: yield* getActiveOrgId(api),
|
|
18525
|
+
vault: yield* unlockVaultKeyInteractive(api)
|
|
18526
|
+
};
|
|
18382
18527
|
});
|
|
18383
18528
|
/** Reshape a sealed envelope into the `{ id, …opaque fields }` an upload body carries. */
|
|
18384
18529
|
const toUploadEnvelope = (envelope) => ({
|
|
@@ -26056,13 +26201,11 @@ const currentRecipients = (api) => Effect.gen(function* () {
|
|
|
26056
26201
|
//#endregion
|
|
26057
26202
|
//#region src/commands/credentials/vault-session.ts
|
|
26058
26203
|
/**
|
|
26059
|
-
* Unlock the vault key
|
|
26060
|
-
*
|
|
26061
|
-
*
|
|
26204
|
+
* Unlock the vault key for an interactive command: reuse the OS-keychain-cached
|
|
26205
|
+
* key when live, prompt for the device passphrase only on a cache miss, and none
|
|
26206
|
+
* at all for the CI `BETTER_UPDATE_IDENTITY` env key.
|
|
26062
26207
|
*/
|
|
26063
|
-
const unlockVaultInteractively = (api) =>
|
|
26064
|
-
return yield* unlockVaultKey(api, yield* resolveVaultPassphrase);
|
|
26065
|
-
});
|
|
26208
|
+
const unlockVaultInteractively = (api) => unlockVaultKeyInteractive(api);
|
|
26066
26209
|
/** Resolve a recipient selector (key id or fingerprint) from a flag, prompting if absent. */
|
|
26067
26210
|
const resolveSelector = (flag, message) => Effect.gen(function* () {
|
|
26068
26211
|
if (flag && flag.trim().length > 0) return flag.trim();
|
|
@@ -27694,6 +27837,56 @@ const revokeCommand = defineCommand({
|
|
|
27694
27837
|
subCommands: { "distribution-certificate": distributionCertificateCommand }
|
|
27695
27838
|
});
|
|
27696
27839
|
|
|
27840
|
+
//#endregion
|
|
27841
|
+
//#region src/commands/credentials/session.ts
|
|
27842
|
+
/** Whole minutes left, rounded up so "<1 min remaining" still reads as 1. */
|
|
27843
|
+
const remainingMinutes = (remainingMs) => Math.max(1, Math.ceil(remainingMs / 6e4));
|
|
27844
|
+
const unlockCommand = defineCommand({
|
|
27845
|
+
meta: {
|
|
27846
|
+
name: "unlock",
|
|
27847
|
+
description: "Unlock the credential vault and cache the key in your OS keychain, so later commands don't re-prompt"
|
|
27848
|
+
},
|
|
27849
|
+
run: async () => runEffect(Effect.gen(function* () {
|
|
27850
|
+
const recipient = yield* activeRecipient;
|
|
27851
|
+
if (recipient.source !== "file") {
|
|
27852
|
+
yield* printHuman("Active identity is the BETTER_UPDATE_IDENTITY (CI) key — it has no passphrase and isn't cached.");
|
|
27853
|
+
return;
|
|
27854
|
+
}
|
|
27855
|
+
const api = yield* apiClient;
|
|
27856
|
+
const cache = yield* VaultCache;
|
|
27857
|
+
yield* cache.clear(recipient.publicKey);
|
|
27858
|
+
yield* unlockVaultKeyInteractive(api);
|
|
27859
|
+
const cached = yield* cache.get(recipient.publicKey);
|
|
27860
|
+
yield* printHuman(`Vault unlocked${cached === void 0 ? " (no OS keychain available — commands will keep prompting)" : ` for ~${remainingMinutes(cached.remainingMs)} min; run \`better-update credentials lock\` to clear it`}.`);
|
|
27861
|
+
}))
|
|
27862
|
+
});
|
|
27863
|
+
const lockCommand = defineCommand({
|
|
27864
|
+
meta: {
|
|
27865
|
+
name: "lock",
|
|
27866
|
+
description: "Forget the cached vault key — the next credential command will prompt again"
|
|
27867
|
+
},
|
|
27868
|
+
run: async () => runEffect(Effect.gen(function* () {
|
|
27869
|
+
const recipient = yield* activeRecipient;
|
|
27870
|
+
yield* (yield* VaultCache).clear(recipient.publicKey);
|
|
27871
|
+
yield* printHuman("Vault locked — the cached key was cleared from your OS keychain.");
|
|
27872
|
+
}))
|
|
27873
|
+
});
|
|
27874
|
+
const statusCommand$1 = defineCommand({
|
|
27875
|
+
meta: {
|
|
27876
|
+
name: "status",
|
|
27877
|
+
description: "Show whether the vault is currently unlocked (cached) and for how much longer"
|
|
27878
|
+
},
|
|
27879
|
+
run: async () => runEffect(Effect.gen(function* () {
|
|
27880
|
+
const recipient = yield* activeRecipient;
|
|
27881
|
+
if (recipient.source !== "file") {
|
|
27882
|
+
yield* printHuman("Active identity is the BETTER_UPDATE_IDENTITY (CI) key — caching not used.");
|
|
27883
|
+
return;
|
|
27884
|
+
}
|
|
27885
|
+
const cached = yield* (yield* VaultCache).get(recipient.publicKey);
|
|
27886
|
+
yield* printHuman(cached === void 0 ? "Locked — the next credential command will prompt for your passphrase." : `Unlocked — cached vault key expires in ~${remainingMinutes(cached.remainingMs)} min.`);
|
|
27887
|
+
}))
|
|
27888
|
+
});
|
|
27889
|
+
|
|
27697
27890
|
//#endregion
|
|
27698
27891
|
//#region src/commands/credentials/sync/helpers.ts
|
|
27699
27892
|
const SYNC_EXIT_EXTRAS = {
|
|
@@ -28553,6 +28746,9 @@ const credentialsCommand = defineCommand({
|
|
|
28553
28746
|
identity: identityCommand,
|
|
28554
28747
|
access: accessCommand,
|
|
28555
28748
|
device: deviceCommand,
|
|
28749
|
+
unlock: unlockCommand,
|
|
28750
|
+
lock: lockCommand,
|
|
28751
|
+
status: statusCommand$1,
|
|
28556
28752
|
list: listCommand$3,
|
|
28557
28753
|
view: viewCommand$1,
|
|
28558
28754
|
download: downloadCommand,
|