@tinycloud/cli 1.5.0-beta.2 → 1.5.0-beta.21
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/README.md +59 -0
- package/dist/index.js +21 -5
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +693 -216
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +9 -9
- package/skills/tc-cli/REFERENCE.md +24 -1
- package/skills/tc-cli/SKILL.md +2 -0
package/dist/legacy-entry.js
CHANGED
|
@@ -174,6 +174,35 @@ var init_storage = __esm({
|
|
|
174
174
|
}
|
|
175
175
|
});
|
|
176
176
|
|
|
177
|
+
// src/config/types.ts
|
|
178
|
+
function isCLIProfilePosture(value) {
|
|
179
|
+
return typeof value === "string" && CLI_PROFILE_POSTURES.includes(value);
|
|
180
|
+
}
|
|
181
|
+
function isCLIOperatorType(value) {
|
|
182
|
+
return typeof value === "string" && CLI_OPERATOR_TYPES.includes(value);
|
|
183
|
+
}
|
|
184
|
+
function resolveProfilePosture(profile) {
|
|
185
|
+
if (isCLIProfilePosture(profile.posture)) return profile.posture;
|
|
186
|
+
if (profile.authMethod === "local") return "local-owner-key";
|
|
187
|
+
return "owner-openkey";
|
|
188
|
+
}
|
|
189
|
+
function resolveProfileOperatorType(profile) {
|
|
190
|
+
if (isCLIOperatorType(profile.operatorType)) return profile.operatorType;
|
|
191
|
+
return "human";
|
|
192
|
+
}
|
|
193
|
+
var CLI_PROFILE_POSTURES, CLI_OPERATOR_TYPES;
|
|
194
|
+
var init_types = __esm({
|
|
195
|
+
"src/config/types.ts"() {
|
|
196
|
+
"use strict";
|
|
197
|
+
CLI_PROFILE_POSTURES = [
|
|
198
|
+
"owner-openkey",
|
|
199
|
+
"delegate-session",
|
|
200
|
+
"local-owner-key"
|
|
201
|
+
];
|
|
202
|
+
CLI_OPERATOR_TYPES = ["human", "agent"];
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
177
206
|
// src/lib/replication-registry.ts
|
|
178
207
|
function stopForSignal(signal) {
|
|
179
208
|
void closeReplication().finally(() => {
|
|
@@ -903,35 +932,6 @@ var init_errors = __esm({
|
|
|
903
932
|
}
|
|
904
933
|
});
|
|
905
934
|
|
|
906
|
-
// src/config/types.ts
|
|
907
|
-
function isCLIProfilePosture(value) {
|
|
908
|
-
return typeof value === "string" && CLI_PROFILE_POSTURES.includes(value);
|
|
909
|
-
}
|
|
910
|
-
function isCLIOperatorType(value) {
|
|
911
|
-
return typeof value === "string" && CLI_OPERATOR_TYPES.includes(value);
|
|
912
|
-
}
|
|
913
|
-
function resolveProfilePosture(profile) {
|
|
914
|
-
if (isCLIProfilePosture(profile.posture)) return profile.posture;
|
|
915
|
-
if (profile.authMethod === "local") return "local-owner-key";
|
|
916
|
-
return "owner-openkey";
|
|
917
|
-
}
|
|
918
|
-
function resolveProfileOperatorType(profile) {
|
|
919
|
-
if (isCLIOperatorType(profile.operatorType)) return profile.operatorType;
|
|
920
|
-
return "human";
|
|
921
|
-
}
|
|
922
|
-
var CLI_PROFILE_POSTURES, CLI_OPERATOR_TYPES;
|
|
923
|
-
var init_types = __esm({
|
|
924
|
-
"src/config/types.ts"() {
|
|
925
|
-
"use strict";
|
|
926
|
-
CLI_PROFILE_POSTURES = [
|
|
927
|
-
"owner-openkey",
|
|
928
|
-
"delegate-session",
|
|
929
|
-
"local-owner-key"
|
|
930
|
-
];
|
|
931
|
-
CLI_OPERATOR_TYPES = ["human", "agent"];
|
|
932
|
-
}
|
|
933
|
-
});
|
|
934
|
-
|
|
935
935
|
// src/lib/space.ts
|
|
936
936
|
import { CLI_NAME as CLI_NAME3 } from "@tinycloud/operations/cli-name";
|
|
937
937
|
function invalidSpaceError(input) {
|
|
@@ -1857,7 +1857,8 @@ var init_sha256 = __esm({
|
|
|
1857
1857
|
});
|
|
1858
1858
|
|
|
1859
1859
|
// ../sdk-core/src/manifest.ts
|
|
1860
|
-
import { resolveSecretPath, SECRET_NAME_RE } from "@tinycloud/sdk-services";
|
|
1860
|
+
import { resolveSecretPath, SECRET_NAME_RE, parseNetworkId } from "@tinycloud/sdk-services";
|
|
1861
|
+
import { parseEncryptionResource } from "@tinycloud/sdk-services/encryption/resource";
|
|
1861
1862
|
function parseExpiry(duration) {
|
|
1862
1863
|
if (typeof duration !== "string" || duration.length === 0) {
|
|
1863
1864
|
throw new ManifestValidationError(
|
|
@@ -1900,6 +1901,14 @@ function expandEncryptionPermissionEntry(entry) {
|
|
|
1900
1901
|
`tinycloud.encryption entries require path to be a networkId URN (got ${JSON.stringify(entry.path)})`
|
|
1901
1902
|
);
|
|
1902
1903
|
}
|
|
1904
|
+
if (entry.path.includes("/")) {
|
|
1905
|
+
throw new ManifestValidationError("named keys go in manifest.keys");
|
|
1906
|
+
}
|
|
1907
|
+
try {
|
|
1908
|
+
parseNetworkId(entry.path);
|
|
1909
|
+
} catch {
|
|
1910
|
+
throw new ManifestValidationError("tinycloud.encryption entries require a valid networkId URN");
|
|
1911
|
+
}
|
|
1903
1912
|
const normalizedActions = [];
|
|
1904
1913
|
for (const action of entry.actions) {
|
|
1905
1914
|
if (action === "decrypt" || action === "tinycloud.encryption/decrypt") {
|
|
@@ -2004,8 +2013,52 @@ function validateManifest(input) {
|
|
|
2004
2013
|
if (m.secrets !== void 0) {
|
|
2005
2014
|
validateManifestSecrets(m.secrets);
|
|
2006
2015
|
}
|
|
2016
|
+
if (Object.prototype.hasOwnProperty.call(m, "keys_app_id") && (typeof m.keys_app_id !== "string" || !KEY_APP_ID.test(m.keys_app_id))) {
|
|
2017
|
+
throw new ManifestValidationError("manifest.keys_app_id is invalid");
|
|
2018
|
+
}
|
|
2019
|
+
validateManifestKeys(m.keys_app_id ?? m.app_id, m.keys);
|
|
2007
2020
|
return m;
|
|
2008
2021
|
}
|
|
2022
|
+
function validateManifestKeys(appId, keys) {
|
|
2023
|
+
if (keys === void 0) return;
|
|
2024
|
+
if (!Array.isArray(keys) || keys.length > 32) {
|
|
2025
|
+
throw new ManifestValidationError("manifest.keys must contain at most 32 entries");
|
|
2026
|
+
}
|
|
2027
|
+
if (keys.length === 0) return;
|
|
2028
|
+
if (!KEY_APP_ID.test(appId)) {
|
|
2029
|
+
throw new ManifestValidationError("manifest.keys requires a valid app_id");
|
|
2030
|
+
}
|
|
2031
|
+
const paths = /* @__PURE__ */ new Set();
|
|
2032
|
+
let prefixSeen = false;
|
|
2033
|
+
for (const [index, value] of keys.entries()) {
|
|
2034
|
+
const label = `manifest.keys[${index}]`;
|
|
2035
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
2036
|
+
throw new ManifestValidationError(`${label} must be an object`);
|
|
2037
|
+
}
|
|
2038
|
+
const entry = value;
|
|
2039
|
+
const fields = Object.keys(entry);
|
|
2040
|
+
const hasPath = Object.prototype.hasOwnProperty.call(entry, "path");
|
|
2041
|
+
const hasPrefix = Object.prototype.hasOwnProperty.call(entry, "prefix");
|
|
2042
|
+
if (hasPath === hasPrefix || fields.some((field) => !["path", "prefix", "abilities"].includes(field))) {
|
|
2043
|
+
throw new ManifestValidationError(`${label} must have one path or prefix and abilities`);
|
|
2044
|
+
}
|
|
2045
|
+
if (!Array.isArray(entry.abilities) || entry.abilities.length === 0 || new Set(entry.abilities).size !== entry.abilities.length || entry.abilities.some((ability) => typeof ability !== "string" || !KEY_ABILITIES.includes(ability))) {
|
|
2046
|
+
throw new ManifestValidationError(`${label}.abilities is invalid`);
|
|
2047
|
+
}
|
|
2048
|
+
if (hasPath) {
|
|
2049
|
+
const path = entry.path;
|
|
2050
|
+
if (typeof path !== "string" || path.length > 255 || !KEY_PATH.test(path) || !path.startsWith(`apps/${appId}/`) || paths.has(path)) {
|
|
2051
|
+
throw new ManifestValidationError(`${label}.path is invalid`);
|
|
2052
|
+
}
|
|
2053
|
+
paths.add(path);
|
|
2054
|
+
} else {
|
|
2055
|
+
if (entry.prefix !== `apps/${appId}/` || prefixSeen) {
|
|
2056
|
+
throw new ManifestValidationError(`${label}.prefix is invalid`);
|
|
2057
|
+
}
|
|
2058
|
+
prefixSeen = true;
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2009
2062
|
function resolveManifestKnowledgeRoot(knowledge) {
|
|
2010
2063
|
if (knowledge === void 0) {
|
|
2011
2064
|
return void 0;
|
|
@@ -2405,7 +2458,7 @@ function withCapabilitiesReadForSpaces(resources) {
|
|
|
2405
2458
|
...[...spaces].map(capabilitiesReadPermission)
|
|
2406
2459
|
]);
|
|
2407
2460
|
}
|
|
2408
|
-
var import_ms, DEFAULT_KNOWLEDGE_ROOT, ManifestValidationError, DEFAULT_EXPIRY, DEFAULT_DEFAULTS, DEFAULT_MANIFEST_VERSION, DEFAULT_MANIFEST_SPACE, SECRETS_SPACE, VAULT_PERMISSION_SERVICE, SERVICE_SHORT_TO_LONG, ENCRYPTION_PERMISSION_SERVICE, ENCRYPTION_MANIFEST_SPACE, SERVICE_LONG_TO_SHORT2, DEFAULT_STANDARD_ENTRIES, DEFAULT_ADMIN_ENTRIES, DEFAULT_ALL_ENTRIES;
|
|
2461
|
+
var import_ms, DEFAULT_KNOWLEDGE_ROOT, ManifestValidationError, DEFAULT_EXPIRY, DEFAULT_DEFAULTS, DEFAULT_MANIFEST_VERSION, DEFAULT_MANIFEST_SPACE, SECRETS_SPACE, VAULT_PERMISSION_SERVICE, SERVICE_SHORT_TO_LONG, ENCRYPTION_PERMISSION_SERVICE, ENCRYPTION_MANIFEST_SPACE, SERVICE_LONG_TO_SHORT2, DEFAULT_STANDARD_ENTRIES, DEFAULT_ADMIN_ENTRIES, DEFAULT_ALL_ENTRIES, KEY_SEGMENT, KEY_APP_ID, KEY_PATH, KEY_ABILITIES;
|
|
2409
2462
|
var init_manifest = __esm({
|
|
2410
2463
|
"../sdk-core/src/manifest.ts"() {
|
|
2411
2464
|
"use strict";
|
|
@@ -2488,6 +2541,10 @@ var init_manifest = __esm({
|
|
|
2488
2541
|
actions: ["read", "write"]
|
|
2489
2542
|
}
|
|
2490
2543
|
];
|
|
2544
|
+
KEY_SEGMENT = "[a-z0-9][a-z0-9._-]{0,63}";
|
|
2545
|
+
KEY_APP_ID = new RegExp(`^${KEY_SEGMENT}$`);
|
|
2546
|
+
KEY_PATH = new RegExp(`^${KEY_SEGMENT}(?:/${KEY_SEGMENT}){2,7}$`);
|
|
2547
|
+
KEY_ABILITIES = ["key.create", "decrypt", "key.block", "key.unblock"];
|
|
2491
2548
|
}
|
|
2492
2549
|
});
|
|
2493
2550
|
|
|
@@ -5203,6 +5260,9 @@ function assertReadable(authority, now) {
|
|
|
5203
5260
|
function contentHash(bytes) {
|
|
5204
5261
|
return bytesToHex(blake3(bytes));
|
|
5205
5262
|
}
|
|
5263
|
+
function repairId(entry) {
|
|
5264
|
+
return `${entry.hash}\0${entry.key}`;
|
|
5265
|
+
}
|
|
5206
5266
|
function sameEtag(a, b) {
|
|
5207
5267
|
return a.replace(/^"|"$/g, "") === b.replace(/^"|"$/g, "");
|
|
5208
5268
|
}
|
|
@@ -5518,8 +5578,8 @@ function fetched(key, result, request) {
|
|
|
5518
5578
|
}
|
|
5519
5579
|
return { bytes: result.data.data, etag: result.data.headers.etag ?? "" };
|
|
5520
5580
|
}
|
|
5521
|
-
function kvSyncTransport(
|
|
5522
|
-
const host = options.host ??
|
|
5581
|
+
function kvSyncTransport(kv2, options = {}) {
|
|
5582
|
+
const host = options.host ?? kv2.host;
|
|
5523
5583
|
const fetchFn = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
5524
5584
|
return {
|
|
5525
5585
|
async syncPage({ prefix, cursor, limit, retentionGrant, signal }) {
|
|
@@ -5538,7 +5598,7 @@ function kvSyncTransport(kv, options = {}) {
|
|
|
5538
5598
|
);
|
|
5539
5599
|
}
|
|
5540
5600
|
}
|
|
5541
|
-
const result = await
|
|
5601
|
+
const result = await kv2.changes({
|
|
5542
5602
|
prefix,
|
|
5543
5603
|
limit,
|
|
5544
5604
|
...cursor === void 0 ? {} : { cursor },
|
|
@@ -5553,7 +5613,7 @@ function kvSyncTransport(kv, options = {}) {
|
|
|
5553
5613
|
if (keys.length === 0) return contents;
|
|
5554
5614
|
const signal = options2?.signal;
|
|
5555
5615
|
const ordered = [...keys].sort(compareKeyBytes);
|
|
5556
|
-
const batch = await
|
|
5616
|
+
const batch = await kv2.batchGet(ordered, { binary: true, ...signal === void 0 ? {} : { signal } });
|
|
5557
5617
|
if (batch.ok) {
|
|
5558
5618
|
for (const item of batch.data.results) {
|
|
5559
5619
|
const result = item.result;
|
|
@@ -5566,13 +5626,13 @@ function kvSyncTransport(kv, options = {}) {
|
|
|
5566
5626
|
for (const key of keys) {
|
|
5567
5627
|
if (contents.has(key)) continue;
|
|
5568
5628
|
const request = `Failed to get key ${JSON.stringify(key)}`;
|
|
5569
|
-
contents.set(key, fetched(key, await
|
|
5629
|
+
contents.set(key, fetched(key, await kv2.get(key, { binary: true, ...signal === void 0 ? {} : { signal } }), request));
|
|
5570
5630
|
}
|
|
5571
5631
|
return contents;
|
|
5572
5632
|
}
|
|
5573
5633
|
};
|
|
5574
5634
|
}
|
|
5575
|
-
var ReplicaErrorCode, REPLICA_ERROR, ReplicaError, RFC3339, DEFAULT_PAGE_LIMIT, DEFAULT_LEASE_TTL_MS, FETCH_CHUNK, REPAIR_BATCH, RETENTION_REVOKED, PENDING_REFUSALS, Replica, BASE58, BASE32, featureCache, REVOKED_CODES, REVOKED_BODY, encoder;
|
|
5635
|
+
var ReplicaErrorCode, REPLICA_ERROR, ReplicaError, RFC3339, DEFAULT_PAGE_LIMIT, DEFAULT_LEASE_TTL_MS, FETCH_CHUNK, REPAIR_BATCH, REPAIR_MAX_TRIES, REPAIR_BACKOFF_BASE_MS, RETENTION_REVOKED, PENDING_REFUSALS, Replica, BASE58, BASE32, featureCache, REVOKED_CODES, REVOKED_BODY, encoder;
|
|
5576
5636
|
var init_dist = __esm({
|
|
5577
5637
|
"../replica/dist/index.js"() {
|
|
5578
5638
|
"use strict";
|
|
@@ -5660,6 +5720,8 @@ var init_dist = __esm({
|
|
|
5660
5720
|
DEFAULT_LEASE_TTL_MS = 12e4;
|
|
5661
5721
|
FETCH_CHUNK = 100;
|
|
5662
5722
|
REPAIR_BATCH = 100;
|
|
5723
|
+
REPAIR_MAX_TRIES = 5;
|
|
5724
|
+
REPAIR_BACKOFF_BASE_MS = 6e4;
|
|
5663
5725
|
RETENTION_REVOKED = /* @__PURE__ */ new Set(["retention-grant-revoked", "retention-grant-ancestor-revoked"]);
|
|
5664
5726
|
PENDING_REFUSALS = /* @__PURE__ */ new Set([
|
|
5665
5727
|
ReplicaErrorCode.GRANT_UNAUTHORIZED,
|
|
@@ -5672,6 +5734,9 @@ var init_dist = __esm({
|
|
|
5672
5734
|
#now;
|
|
5673
5735
|
#leaseTtlMs;
|
|
5674
5736
|
#onCommit;
|
|
5737
|
+
#contentFetch;
|
|
5738
|
+
/** Failed repair fetches of this session, by key and ETag hash. Never persisted: a new `Replica` retries afresh. */
|
|
5739
|
+
#repairTries = /* @__PURE__ */ new Map();
|
|
5675
5740
|
constructor(options) {
|
|
5676
5741
|
this.#store = options.store;
|
|
5677
5742
|
const transport = options.transport;
|
|
@@ -5679,6 +5744,7 @@ var init_dist = __esm({
|
|
|
5679
5744
|
this.#now = options.now ?? Date.now;
|
|
5680
5745
|
this.#leaseTtlMs = options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS;
|
|
5681
5746
|
this.#onCommit = options.onCommit;
|
|
5747
|
+
this.#contentFetch = options.contentFetch ?? "eager";
|
|
5682
5748
|
}
|
|
5683
5749
|
async #state() {
|
|
5684
5750
|
const state2 = await this.#store.open();
|
|
@@ -6007,6 +6073,7 @@ var init_dist = __esm({
|
|
|
6007
6073
|
changes[candidate.index].content = true;
|
|
6008
6074
|
return false;
|
|
6009
6075
|
});
|
|
6076
|
+
if (this.#contentFetch === "lazy") return { changes, blobs, fetched: 0 };
|
|
6010
6077
|
let fetched2 = 0;
|
|
6011
6078
|
for (let start = 0; start < needed.length; start += FETCH_CHUNK) {
|
|
6012
6079
|
const chunk = needed.slice(start, start + FETCH_CHUNK);
|
|
@@ -6026,7 +6093,15 @@ var init_dist = __esm({
|
|
|
6026
6093
|
return { changes, blobs, fetched: fetched2 };
|
|
6027
6094
|
}
|
|
6028
6095
|
async #repair(attempt, cursor, nodeDid, coverage, retentionGrantCid) {
|
|
6029
|
-
|
|
6096
|
+
if (this.#contentFetch === "lazy") return 0;
|
|
6097
|
+
const all = await this.#store.pendingRepairs(Number.MAX_SAFE_INTEGER);
|
|
6098
|
+
const now = this.#now();
|
|
6099
|
+
const pendingIds = new Set(all.map((entry) => repairId(entry)));
|
|
6100
|
+
for (const id of this.#repairTries.keys()) if (!pendingIds.has(id)) this.#repairTries.delete(id);
|
|
6101
|
+
const pending = all.filter((entry) => {
|
|
6102
|
+
const state2 = this.#repairTries.get(repairId(entry));
|
|
6103
|
+
return state2 === void 0 || state2.tries < REPAIR_MAX_TRIES && now >= state2.nextAttemptAt;
|
|
6104
|
+
});
|
|
6030
6105
|
let repaired = 0;
|
|
6031
6106
|
for (let start = 0; start < pending.length; start += REPAIR_BATCH) {
|
|
6032
6107
|
const chunk = pending.slice(start, start + REPAIR_BATCH);
|
|
@@ -6038,7 +6113,14 @@ var init_dist = __esm({
|
|
|
6038
6113
|
const changes = [];
|
|
6039
6114
|
for (const entry of chunk) {
|
|
6040
6115
|
const bytes = acceptContent(entry, contents.get(entry.key));
|
|
6041
|
-
|
|
6116
|
+
const id = repairId(entry);
|
|
6117
|
+
if (bytes === void 0) {
|
|
6118
|
+
const tries = (this.#repairTries.get(id)?.tries ?? 0) + 1;
|
|
6119
|
+
const wait = tries === 1 ? 0 : REPAIR_BACKOFF_BASE_MS * 2 ** (tries - 2);
|
|
6120
|
+
this.#repairTries.set(id, { tries, nextAttemptAt: this.#now() + wait });
|
|
6121
|
+
continue;
|
|
6122
|
+
}
|
|
6123
|
+
this.#repairTries.delete(id);
|
|
6042
6124
|
blobs.set(entry.hash, bytes);
|
|
6043
6125
|
changes.push({ ...entry, content: true });
|
|
6044
6126
|
}
|
|
@@ -9760,6 +9842,7 @@ var init_profiles = __esm({
|
|
|
9760
9842
|
"use strict";
|
|
9761
9843
|
init_constants();
|
|
9762
9844
|
init_storage();
|
|
9845
|
+
init_types();
|
|
9763
9846
|
init_errors();
|
|
9764
9847
|
ProfileManager = class _ProfileManager {
|
|
9765
9848
|
// ── Initialization ──────────────────────────────────────────────────
|
|
@@ -10041,6 +10124,7 @@ var init_profiles = __esm({
|
|
|
10041
10124
|
quiet: options.quiet ?? false,
|
|
10042
10125
|
replication,
|
|
10043
10126
|
replicationDebug: options.replicationDebug === true || process.env.TC_REPLICATION_DEBUG === "1",
|
|
10127
|
+
...profileConfig === void 0 ? {} : { posture: resolveProfilePosture(profileConfig) },
|
|
10044
10128
|
...localDiscovery === void 0 ? {} : { localDiscovery }
|
|
10045
10129
|
};
|
|
10046
10130
|
}
|
|
@@ -15794,7 +15878,7 @@ async function refusal(response) {
|
|
|
15794
15878
|
async function prepareLinkRecord(input) {
|
|
15795
15879
|
const apiOrigin2 = checkApiOrigin(input.apiOrigin);
|
|
15796
15880
|
const nowMs = toEpochMs(input.now);
|
|
15797
|
-
const expiresMs = input.expiresAt.getTime();
|
|
15881
|
+
const expiresMs = Math.floor(input.expiresAt.getTime() / 1e3) * 1e3;
|
|
15798
15882
|
if (!Number.isFinite(nowMs) || !Number.isFinite(expiresMs)) throw new LinkError("invalid_argument", "now and expiresAt must be valid dates");
|
|
15799
15883
|
let blob;
|
|
15800
15884
|
let key;
|
|
@@ -29060,12 +29144,12 @@ async function verifyEnvelopeV3(envelope, options) {
|
|
|
29060
29144
|
const nativeProjection2 = sortedCapabilities.map((capability) => capability.kind === "encryption" ? { service: "tinycloud.encryption", space: capability.resource, path: capability.resource, actions: [capability.action] } : { service: "tinycloud.kv", space: capability.resource.slice(0, capability.resource.indexOf("/kv/")), path: capability.resource.split("/kv/")[1], actions: [...capability.actions], caveat: { type: "xyz.tinycloud.resource/selector", kind: capability.selector, value: capability.resource } }).sort((left, right) => canonicalize2(left).localeCompare(canonicalize2(right)));
|
|
29061
29145
|
const nativeProjectionHashHex = hex(sha2562(new TextEncoder().encode(`${NATIVE_PROJECTION_V1_DOMAIN}${canonicalize2(nativeProjection2)}`)));
|
|
29062
29146
|
const expectedAttenuation = Object.fromEntries(sortedCapabilities.map((capability) => capability.kind === "encryption" ? [capability.resource, { [capability.action]: [{}] }] : [capability.resource, Object.fromEntries(capability.actions.map((action) => [action, [{ kind: capability.selector, type: "xyz.tinycloud.resource/selector", value: capability.resource }]]))]));
|
|
29063
|
-
const
|
|
29147
|
+
const kv2 = policy.capabilityCeiling.find((capability) => capability.kind === "kv");
|
|
29064
29148
|
const expectedKvActions = parsed.actions.flatMap((action) => action === "read" ? ["tinycloud.kv/get", "tinycloud.kv/metadata"] : action === "list" ? ["tinycloud.kv/list"] : ["tinycloud.kv/put"]);
|
|
29065
29149
|
const marker = parsed.contentSource.kvResource.indexOf("/kv/");
|
|
29066
29150
|
const resourceSpace = marker < 1 ? "" : parsed.contentSource.kvResource.slice(0, marker);
|
|
29067
29151
|
const resourcePath = marker < 1 ? "" : parsed.contentSource.kvResource.slice(marker + 4);
|
|
29068
|
-
if (parsed.shareId !== parsed.contentSource.shareId || resourceSpace !== parsed.target.spaceId || resourcePath !== parsed.resource.path.replace(/\/$/, "") || parsed.resource.kind !== parsed.contentSource.selector ||
|
|
29152
|
+
if (parsed.shareId !== parsed.contentSource.shareId || resourceSpace !== parsed.target.spaceId || resourcePath !== parsed.resource.path.replace(/\/$/, "") || parsed.resource.kind !== parsed.contentSource.selector || kv2?.kind !== "kv" || expectedKvActions.some((action) => !kv2.actions.includes(action)) || parsed.encryptionNetwork !== parsed.contentSource.encryptionNetwork || parsed.contentSource.keyVersion <= 0 || policy.expiresAt !== void 0 && Date.parse(parsed.expiry) > Date.parse(policy.expiresAt)) return false;
|
|
29069
29153
|
const binding = parsed.attestedEnforcerBinding;
|
|
29070
29154
|
const { signature: bindingSignature, ...unsignedBinding } = binding;
|
|
29071
29155
|
const expectedBindingDigestHex = hex(sha2562(new TextEncoder().encode(canonicalize2({ enforcerDid: binding.enforcerDid, nodeAudience: binding.nodeAudience }))));
|
|
@@ -32441,10 +32525,10 @@ function hasKvAction(permission, action) {
|
|
|
32441
32525
|
return permission.actions.some((entry) => entry === action || entry === `tinycloud.kv/${action}`);
|
|
32442
32526
|
}
|
|
32443
32527
|
function readTarget(permissions) {
|
|
32444
|
-
const
|
|
32445
|
-
const list =
|
|
32528
|
+
const kv2 = (permissions ?? []).filter((permission) => permission.service === "tinycloud.kv" || permission.service === "kv");
|
|
32529
|
+
const list = kv2.find((permission) => hasKvAction(permission, "list"));
|
|
32446
32530
|
if (list) return { op: "list", prefix: list.path };
|
|
32447
|
-
const get =
|
|
32531
|
+
const get = kv2.find((permission) => hasKvAction(permission, "get"));
|
|
32448
32532
|
if (!get) return void 0;
|
|
32449
32533
|
return { op: "get", key: get.path === "" || get.path.endsWith("/") ? `${get.path}.tinycloud-space-probe` : get.path };
|
|
32450
32534
|
}
|
|
@@ -32456,8 +32540,8 @@ function kvSpaceProbe(ctx, permissions, restore2 = restoreProfileSessionNode) {
|
|
|
32456
32540
|
if (!target) return "unreadable";
|
|
32457
32541
|
restored ??= await restore2(ctx);
|
|
32458
32542
|
if ("unavailable" in restored) return "unreadable";
|
|
32459
|
-
const
|
|
32460
|
-
const result = target.op === "list" ? await
|
|
32543
|
+
const kv2 = restored.node.kv;
|
|
32544
|
+
const result = target.op === "list" ? await kv2.list({ prefix: target.prefix, limit: 1 }) : await kv2.get(target.key);
|
|
32461
32545
|
if (result.ok) return "hosted";
|
|
32462
32546
|
const status = result.error.meta?.status;
|
|
32463
32547
|
if (status === 404 && UNHOSTED.test(result.error.message)) return "unhosted";
|
|
@@ -33002,7 +33086,8 @@ async function revokeDelegationOnNode(params) {
|
|
|
33002
33086
|
throw new CLIError(
|
|
33003
33087
|
"RAW_RECAP_RESOURCE_UNSUPPORTED",
|
|
33004
33088
|
`Cannot revoke "${cid}": this CLI profile has no record of this grant and the node's record does not match the signed-in owner.`,
|
|
33005
|
-
ExitCode.PERMISSION_DENIED
|
|
33089
|
+
ExitCode.PERMISSION_DENIED,
|
|
33090
|
+
profile.authMethod === "openkey" ? { hint: "This permission can't be revoked from the CLI yet. Revoke it at https://account.tinycloud.xyz/delegations." } : void 0
|
|
33006
33091
|
);
|
|
33007
33092
|
}
|
|
33008
33093
|
fallbackSpaceId = fallback.space;
|
|
@@ -33827,7 +33912,7 @@ async function prepareProfileRemoteLogout(ctx, expectedSessionCid, discardLocalC
|
|
|
33827
33912
|
"REVOCATION_AUTHORITY_UNAVAILABLE",
|
|
33828
33913
|
"OpenKey owner CID revocation requires a verified signed revoke-control grant or wallet proof. No CID was revoked; session, key and replica files remain for retry, while local replica reads stay denied.",
|
|
33829
33914
|
ExitCode.AUTH_REQUIRED,
|
|
33830
|
-
{ hint:
|
|
33915
|
+
{ hint: `This sign-in can't be revoked from the CLI yet. Revoke it at https://account.tinycloud.xyz/delegations. To sign out on this computer only, run \`${CLI_NAME18} auth logout\`.` }
|
|
33831
33916
|
);
|
|
33832
33917
|
}
|
|
33833
33918
|
const signerDid = addressToDID(
|
|
@@ -35986,7 +36071,7 @@ async function refreshOpenKeySession(profileName2, host, options = {}) {
|
|
|
35986
36071
|
// owner is verified below.
|
|
35987
36072
|
permissions: permissions ?? ownerLoginPermissions(expectedOwner),
|
|
35988
36073
|
expiry: openKeyExpiry,
|
|
35989
|
-
...permissions ? { reason: "Allow this TinyCloud CLI profile to use the permissions in the requested manifest." } : {}
|
|
36074
|
+
...permissions ? { reason: options.reason ?? "Allow this TinyCloud CLI profile to use the permissions in the requested manifest." } : {}
|
|
35990
36075
|
});
|
|
35991
36076
|
let sanitizedSession;
|
|
35992
36077
|
let verifiedOwner;
|
|
@@ -36254,6 +36339,52 @@ function listedDelegation(d) {
|
|
|
36254
36339
|
resources
|
|
36255
36340
|
};
|
|
36256
36341
|
}
|
|
36342
|
+
var ISSUED_ORDER_NOTE = "Sorted by delegation ID, not by date.";
|
|
36343
|
+
function issuedFailure(error, host) {
|
|
36344
|
+
if (error.code === "UNSUPPORTED_OPERATION") {
|
|
36345
|
+
return new CLIError(
|
|
36346
|
+
"ISSUED_DELEGATIONS_UNAVAILABLE",
|
|
36347
|
+
`${host} can't list the delegations you issued yet.`,
|
|
36348
|
+
ExitCode.NODE_ERROR
|
|
36349
|
+
);
|
|
36350
|
+
}
|
|
36351
|
+
if (error.code === "CHAIN_TOO_DEEP") {
|
|
36352
|
+
return new CLIError(
|
|
36353
|
+
"CHAIN_TOO_DEEP",
|
|
36354
|
+
"The node can't check this delegation chain right now because it's too long. Try again later.",
|
|
36355
|
+
ExitCode.NODE_ERROR,
|
|
36356
|
+
{ status: 503 }
|
|
36357
|
+
);
|
|
36358
|
+
}
|
|
36359
|
+
if (error.code === "INVALID_CURSOR") {
|
|
36360
|
+
return new CLIError(
|
|
36361
|
+
"INVALID_CURSOR",
|
|
36362
|
+
"The node couldn't continue the list. Run the command again.",
|
|
36363
|
+
ExitCode.NODE_ERROR,
|
|
36364
|
+
{ status: 400 }
|
|
36365
|
+
);
|
|
36366
|
+
}
|
|
36367
|
+
return cliErrorFromService(error);
|
|
36368
|
+
}
|
|
36369
|
+
async function revokeIssuedDelegation(node, ctx, cid) {
|
|
36370
|
+
const result = await node.delegationManager.revokeIssued(cid);
|
|
36371
|
+
if (result.ok) return { cid: result.data.cid, revoked: true };
|
|
36372
|
+
if (result.error.code === "PERMISSION_DENIED") {
|
|
36373
|
+
throw new CLIError(
|
|
36374
|
+
"REVOKE_UNAUTHORIZED",
|
|
36375
|
+
`The node refused this revocation: ${result.error.message}.`,
|
|
36376
|
+
ExitCode.PERMISSION_DENIED,
|
|
36377
|
+
{
|
|
36378
|
+
status: result.error.meta?.status,
|
|
36379
|
+
hint: `A profile can revoke only the delegations its own key issued. See them with \`${CLI_NAME21} --profile ${ctx.profile} delegation list --granted\`.`
|
|
36380
|
+
}
|
|
36381
|
+
);
|
|
36382
|
+
}
|
|
36383
|
+
if (result.error.code === "NOT_FOUND") {
|
|
36384
|
+
throw new CLIError("TARGET_NOT_FOUND", `The node has no delegation "${cid}".`, ExitCode.NOT_FOUND);
|
|
36385
|
+
}
|
|
36386
|
+
throw issuedFailure(result.error, ctx.host);
|
|
36387
|
+
}
|
|
36257
36388
|
async function confirmCurrentSessionRevoke(yes) {
|
|
36258
36389
|
if (yes) return;
|
|
36259
36390
|
const warning = "This is your current sign-in session; revoking it signs you out";
|
|
@@ -36342,6 +36473,16 @@ function registerDelegationCommand(program, configuration = {}) {
|
|
|
36342
36473
|
if (options.granted && options.received) {
|
|
36343
36474
|
throw new CLIError("USAGE_ERROR", "Use only one of --granted or --received.", ExitCode.USAGE_ERROR);
|
|
36344
36475
|
}
|
|
36476
|
+
if (options.granted && ctx.posture === "delegate-session" && node.address === void 0) {
|
|
36477
|
+
const issued = await node.delegationManager.queryAllIssued({ space: options.space });
|
|
36478
|
+
if (!issued.ok) throw issuedFailure(issued.error, ctx.host);
|
|
36479
|
+
outputJson({
|
|
36480
|
+
delegations: issued.data.map(listedDelegation),
|
|
36481
|
+
count: issued.data.length,
|
|
36482
|
+
note: ISSUED_ORDER_NOTE
|
|
36483
|
+
});
|
|
36484
|
+
return;
|
|
36485
|
+
}
|
|
36345
36486
|
const delegations = await queryDelegations(node, {
|
|
36346
36487
|
direction: options.granted ? "granted" : options.received ? "received" : "all",
|
|
36347
36488
|
space: options.space
|
|
@@ -36372,11 +36513,16 @@ function registerDelegationCommand(program, configuration = {}) {
|
|
|
36372
36513
|
try {
|
|
36373
36514
|
const globalOpts = cmd.optsWithGlobals();
|
|
36374
36515
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
36516
|
+
const delegateCandidate = ctx.posture === "delegate-session" ? await ensureAuthenticated(ctx) : void 0;
|
|
36517
|
+
if (delegateCandidate !== void 0 && delegateCandidate.address === void 0) {
|
|
36518
|
+
outputJson(await revokeIssuedDelegation(delegateCandidate, ctx, cid));
|
|
36519
|
+
return;
|
|
36520
|
+
}
|
|
36375
36521
|
const session = await ProfileManager.getSession(ctx.profile);
|
|
36376
36522
|
const currentSession = session && "delegationCid" in session && session.delegationCid === cid;
|
|
36377
36523
|
if (currentSession) await confirmCurrentSessionRevoke(options.yes === true);
|
|
36378
36524
|
const prepared = currentSession ? await prepareProfileRemoteLogout(ctx, cid) : void 0;
|
|
36379
|
-
const node = prepared?.node ?? await ensureAuthenticated(ctx);
|
|
36525
|
+
const node = prepared?.node ?? delegateCandidate ?? await ensureAuthenticated(ctx);
|
|
36380
36526
|
const profile = prepared?.profile ?? await ProfileManager.getProfile(ctx.profile);
|
|
36381
36527
|
const revoked = await revokeDelegationOnNode({
|
|
36382
36528
|
ctx,
|
|
@@ -37008,7 +37154,7 @@ Examples:
|
|
|
37008
37154
|
}
|
|
37009
37155
|
});
|
|
37010
37156
|
}
|
|
37011
|
-
function appsInstallIo(
|
|
37157
|
+
function appsInstallIo(kv2, db) {
|
|
37012
37158
|
return {
|
|
37013
37159
|
async fetchBytes(url) {
|
|
37014
37160
|
const response = await fetch(url, { redirect: "error" });
|
|
@@ -37016,7 +37162,7 @@ function appsInstallIo(kv, db) {
|
|
|
37016
37162
|
return new Uint8Array(await response.arrayBuffer());
|
|
37017
37163
|
},
|
|
37018
37164
|
async kvGet(key) {
|
|
37019
|
-
const result = await
|
|
37165
|
+
const result = await kv2.get(key, { binary: true });
|
|
37020
37166
|
if (result.ok) {
|
|
37021
37167
|
const bytes = result.data.data;
|
|
37022
37168
|
if (bytes instanceof Uint8Array) return bytes;
|
|
@@ -37026,7 +37172,7 @@ function appsInstallIo(kv, db) {
|
|
|
37026
37172
|
throw new Error(result.error.code ?? "KV_GET_FAILED");
|
|
37027
37173
|
},
|
|
37028
37174
|
async kvCreate(key, bytes) {
|
|
37029
|
-
const result = await
|
|
37175
|
+
const result = await kv2.put(key, bytes, { ifNoneMatch: "*" });
|
|
37030
37176
|
if (result.ok) return "created";
|
|
37031
37177
|
if (result.error.code === "KV_PRECONDITION_FAILED") return "exists";
|
|
37032
37178
|
throw new Error(result.error.code ?? "KV_PUT_FAILED");
|
|
@@ -37079,6 +37225,25 @@ init_formatter();
|
|
|
37079
37225
|
init_errors();
|
|
37080
37226
|
init_constants();
|
|
37081
37227
|
import { CLI_NAME as CLI_NAME25 } from "@tinycloud/operations/cli-name";
|
|
37228
|
+
async function createKeyOnlyProfile(profileName2, host, defaultSpace) {
|
|
37229
|
+
await ProfileManager.ensureConfigDir();
|
|
37230
|
+
const { jwk, did } = await generateKey2();
|
|
37231
|
+
await ProfileManager.setKey(profileName2, jwk);
|
|
37232
|
+
await ProfileManager.setProfile(profileName2, {
|
|
37233
|
+
name: profileName2,
|
|
37234
|
+
host,
|
|
37235
|
+
chainId: DEFAULT_CHAIN_ID,
|
|
37236
|
+
spaceName: "default",
|
|
37237
|
+
did,
|
|
37238
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
37239
|
+
...defaultSpace ? { defaultSpace } : {}
|
|
37240
|
+
});
|
|
37241
|
+
const config = await ProfileManager.getConfig();
|
|
37242
|
+
if (profileName2 === "default" || !await ProfileManager.profileExists(config.defaultProfile)) {
|
|
37243
|
+
await ProfileManager.setConfig({ ...config, defaultProfile: profileName2 });
|
|
37244
|
+
}
|
|
37245
|
+
return { did };
|
|
37246
|
+
}
|
|
37082
37247
|
function registerInitCommand(program) {
|
|
37083
37248
|
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) => {
|
|
37084
37249
|
try {
|
|
@@ -37100,25 +37265,7 @@ function registerInitCommand(program) {
|
|
|
37100
37265
|
ExitCode.ERROR
|
|
37101
37266
|
);
|
|
37102
37267
|
}
|
|
37103
|
-
await
|
|
37104
|
-
const { jwk, did } = await withSpinner("Generating key...", async () => {
|
|
37105
|
-
return generateKey2();
|
|
37106
|
-
});
|
|
37107
|
-
await ProfileManager.setKey(profileName2, jwk);
|
|
37108
|
-
const profileConfig = {
|
|
37109
|
-
name: profileName2,
|
|
37110
|
-
host,
|
|
37111
|
-
chainId: DEFAULT_CHAIN_ID,
|
|
37112
|
-
spaceName: "default",
|
|
37113
|
-
did,
|
|
37114
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
37115
|
-
...defaultSpace ? { defaultSpace } : {}
|
|
37116
|
-
};
|
|
37117
|
-
await ProfileManager.setProfile(profileName2, profileConfig);
|
|
37118
|
-
const config = await ProfileManager.getConfig();
|
|
37119
|
-
if (profileName2 === "default" || !await ProfileManager.profileExists(config.defaultProfile)) {
|
|
37120
|
-
await ProfileManager.setConfig({ ...config, defaultProfile: profileName2 });
|
|
37121
|
-
}
|
|
37268
|
+
const { did } = await withSpinner("Generating key...", () => createKeyOnlyProfile(profileName2, host, defaultSpace));
|
|
37122
37269
|
if (options.keyOnly) {
|
|
37123
37270
|
outputJson({
|
|
37124
37271
|
profile: profileName2,
|
|
@@ -37393,25 +37540,25 @@ async function readStdin2() {
|
|
|
37393
37540
|
}
|
|
37394
37541
|
async function kvHandle(node, spaceInput, profileName2) {
|
|
37395
37542
|
const spaceUri = await resolveSpaceUri(spaceInput, profileName2);
|
|
37396
|
-
const
|
|
37397
|
-
return { kv, spaceUri };
|
|
37543
|
+
const kv2 = spaceUri ? node.kvForSpace(spaceUri) : node.kv;
|
|
37544
|
+
return { kv: kv2, spaceUri };
|
|
37398
37545
|
}
|
|
37399
37546
|
function retryFields(retry) {
|
|
37400
37547
|
return retry ? { attempts: retry.attempts, backoffMs: retry.backoffMs } : {};
|
|
37401
37548
|
}
|
|
37402
37549
|
function registerKvCommand(program) {
|
|
37403
|
-
const
|
|
37404
|
-
|
|
37550
|
+
const kv2 = program.command("kv").description("Key-value store operations");
|
|
37551
|
+
kv2.command("get <key>").description("Get a value by key").option("--raw", "Output raw value (no JSON wrapping)").option("-o, --output <file>", "Write value to file").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").action(async (key, options, cmd) => {
|
|
37405
37552
|
try {
|
|
37406
37553
|
assertAddressableKey(key);
|
|
37407
37554
|
const globalOpts = cmd.optsWithGlobals();
|
|
37408
37555
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
37409
37556
|
const node = await ensureAuthenticated(ctx);
|
|
37410
|
-
const { kv:
|
|
37557
|
+
const { kv: kv3, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
37411
37558
|
const wantBytes = !!options.output || !!options.raw;
|
|
37412
37559
|
const result = await withSpinner(
|
|
37413
37560
|
`Getting ${key}...`,
|
|
37414
|
-
() =>
|
|
37561
|
+
() => kv3.get(key, wantBytes ? { binary: true } : void 0)
|
|
37415
37562
|
);
|
|
37416
37563
|
if (!result.ok) {
|
|
37417
37564
|
const hosted = await unhostedSpaceError(result.error, spaceUri, ctx.profile);
|
|
@@ -37447,7 +37594,7 @@ function registerKvCommand(program) {
|
|
|
37447
37594
|
return handleError(error);
|
|
37448
37595
|
}
|
|
37449
37596
|
});
|
|
37450
|
-
|
|
37597
|
+
kv2.command("put <key> [value]").description("Set a value").option("--file <path>", "Read value from file").option("--stdin", "Read value from stdin").option("--if-none-match <value>", "`--if-none-match '*'`: Write only if the key doesn't exist yet; never overwrite").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").action(async (key, value, options, cmd) => {
|
|
37451
37598
|
try {
|
|
37452
37599
|
assertAddressableKey(key);
|
|
37453
37600
|
if (options.ifNoneMatch !== void 0 && options.ifNoneMatch !== "*") {
|
|
@@ -37475,8 +37622,8 @@ function registerKvCommand(program) {
|
|
|
37475
37622
|
putValue = value;
|
|
37476
37623
|
}
|
|
37477
37624
|
}
|
|
37478
|
-
const { kv:
|
|
37479
|
-
const result = await withSpinner(`Writing ${key}...`, () =>
|
|
37625
|
+
const { kv: kv3, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
37626
|
+
const result = await withSpinner(`Writing ${key}...`, () => kv3.put(
|
|
37480
37627
|
key,
|
|
37481
37628
|
putValue,
|
|
37482
37629
|
options.ifNoneMatch === "*" ? { ifNoneMatch: "*" } : void 0
|
|
@@ -37492,14 +37639,14 @@ function registerKvCommand(program) {
|
|
|
37492
37639
|
return handleError(error);
|
|
37493
37640
|
}
|
|
37494
37641
|
});
|
|
37495
|
-
|
|
37642
|
+
kv2.command("delete <key>").description("Delete a key").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").action(async (key, options, cmd) => {
|
|
37496
37643
|
try {
|
|
37497
37644
|
assertAddressableKey(key);
|
|
37498
37645
|
const globalOpts = cmd.optsWithGlobals();
|
|
37499
37646
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
37500
37647
|
const node = await ensureAuthenticated(ctx);
|
|
37501
|
-
const { kv:
|
|
37502
|
-
const result = await withSpinner(`Deleting ${key}...`, () =>
|
|
37648
|
+
const { kv: kv3, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
37649
|
+
const result = await withSpinner(`Deleting ${key}...`, () => kv3.delete(key));
|
|
37503
37650
|
if (!result.ok) {
|
|
37504
37651
|
await throwKvError(result.error, spaceUri, ctx.profile);
|
|
37505
37652
|
}
|
|
@@ -37508,7 +37655,7 @@ function registerKvCommand(program) {
|
|
|
37508
37655
|
return handleError(error);
|
|
37509
37656
|
}
|
|
37510
37657
|
});
|
|
37511
|
-
|
|
37658
|
+
kv2.command("list").description("List keys").option("--prefix <prefix>", "Filter by key prefix").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").option("--limit <n>", "Keys per page, 1-1000 (the node's default otherwise)").option("--cursor <cursor>", "Continue from a previous page's nextCursor").option("--all", "Follow nextCursor until the listing is complete").action(async (options, cmd) => {
|
|
37512
37659
|
try {
|
|
37513
37660
|
if (options.prefix) assertAddressableKey(options.prefix);
|
|
37514
37661
|
const limit = options.limit === void 0 ? void 0 : Number(options.limit);
|
|
@@ -37518,13 +37665,13 @@ function registerKvCommand(program) {
|
|
|
37518
37665
|
const globalOpts = cmd.optsWithGlobals();
|
|
37519
37666
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
37520
37667
|
const node = await ensureAuthenticated(ctx);
|
|
37521
|
-
const { kv:
|
|
37668
|
+
const { kv: kv3, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
37522
37669
|
const keyList = [];
|
|
37523
37670
|
let cursor = options.cursor;
|
|
37524
37671
|
let truncated;
|
|
37525
37672
|
const seenCursors = /* @__PURE__ */ new Set();
|
|
37526
37673
|
for (; ; ) {
|
|
37527
|
-
const result = await withSpinner("Listing keys...", () =>
|
|
37674
|
+
const result = await withSpinner("Listing keys...", () => kv3.list({
|
|
37528
37675
|
...options.prefix ? { prefix: options.prefix } : {},
|
|
37529
37676
|
...limit === void 0 ? {} : { limit },
|
|
37530
37677
|
...cursor === void 0 ? {} : { cursor }
|
|
@@ -37579,14 +37726,14 @@ function registerKvCommand(program) {
|
|
|
37579
37726
|
return handleError(error);
|
|
37580
37727
|
}
|
|
37581
37728
|
});
|
|
37582
|
-
|
|
37729
|
+
kv2.command("head <key>").description("Get metadata for a key (no body)").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").action(async (key, options, cmd) => {
|
|
37583
37730
|
try {
|
|
37584
37731
|
assertAddressableKey(key);
|
|
37585
37732
|
const globalOpts = cmd.optsWithGlobals();
|
|
37586
37733
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
37587
37734
|
const node = await ensureAuthenticated(ctx);
|
|
37588
|
-
const { kv:
|
|
37589
|
-
const result = await withSpinner(`Checking ${key}...`, () =>
|
|
37735
|
+
const { kv: kv3, spaceUri } = await kvHandle(node, options.space, ctx.profile);
|
|
37736
|
+
const result = await withSpinner(`Checking ${key}...`, () => kv3.head(key));
|
|
37590
37737
|
if (!result.ok) {
|
|
37591
37738
|
const hosted = await unhostedSpaceError(result.error, spaceUri, ctx.profile);
|
|
37592
37739
|
if (hosted) throw hosted;
|
|
@@ -37988,12 +38135,129 @@ function registerNodeCommand(program) {
|
|
|
37988
38135
|
});
|
|
37989
38136
|
}
|
|
37990
38137
|
|
|
38138
|
+
// src/commands/onboard.ts
|
|
38139
|
+
import { CLI_NAME as CLI_NAME29 } from "@tinycloud/operations/cli-name";
|
|
38140
|
+
init_constants();
|
|
38141
|
+
init_profiles();
|
|
38142
|
+
init_types();
|
|
38143
|
+
init_permissions();
|
|
38144
|
+
init_sdk();
|
|
38145
|
+
init_errors();
|
|
38146
|
+
init_formatter();
|
|
38147
|
+
var ONBOARD_EXPIRY = "30d";
|
|
38148
|
+
var kv = (actions) => actions.map((action) => `tinycloud.kv/${action}`);
|
|
38149
|
+
var ONBOARD_STEPS = [
|
|
38150
|
+
{
|
|
38151
|
+
space: "applications",
|
|
38152
|
+
asks: "Read and change your apps' data for 30 days.",
|
|
38153
|
+
permissions: [{ service: "tinycloud.kv", space: "applications", path: "", actions: kv(["get", "list", "put", "del", "metadata"]) }]
|
|
38154
|
+
},
|
|
38155
|
+
{
|
|
38156
|
+
space: "account",
|
|
38157
|
+
asks: "See which spaces and apps you have, read-only, for 30 days.",
|
|
38158
|
+
permissions: [
|
|
38159
|
+
{ service: "tinycloud.kv", space: "account", path: "spaces/", actions: kv(["get", "list"]) },
|
|
38160
|
+
{ service: "tinycloud.kv", space: "account", path: "applications/", actions: kv(["get", "list"]) }
|
|
38161
|
+
]
|
|
38162
|
+
}
|
|
38163
|
+
];
|
|
38164
|
+
var ApprovalNeeded = class extends Error {
|
|
38165
|
+
constructor(approvalUrl) {
|
|
38166
|
+
super("approval needed");
|
|
38167
|
+
this.approvalUrl = approvalUrl;
|
|
38168
|
+
}
|
|
38169
|
+
};
|
|
38170
|
+
function registerOnboardCommand(program) {
|
|
38171
|
+
program.command("onboard").description(`Set up this computer to act for its owner: a profile, the owner's OpenKey sign-in (or new account), and a starting grant for 30 days (read and change the applications space; read which spaces and apps exist). Each run does the next approval: it prints the approval link and exits ${ExitCode.PENDING}; run it again with the code OpenKey shows. Exits 0 when onboarding is complete.`).option("--code <code>", "The code OpenKey showed after the owner approved the last link").action(async (options, cmd) => {
|
|
38172
|
+
try {
|
|
38173
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
38174
|
+
const ctx = await ProfileManager.resolveContext(globalOpts, { storedSession: "unused" });
|
|
38175
|
+
if (!await ProfileManager.profileExists(ctx.profile)) await createKeyOnlyProfile(ctx.profile, ctx.host);
|
|
38176
|
+
let profile = await ProfileManager.getProfile(ctx.profile);
|
|
38177
|
+
if (resolveProfilePosture(profile) !== "owner-openkey") {
|
|
38178
|
+
throw new CLIError(
|
|
38179
|
+
"PROFILE_NOT_ONBOARDABLE",
|
|
38180
|
+
`Profile "${ctx.profile}" doesn't sign in with OpenKey, so onboarding can't use it. Run \`${CLI_NAME29} --profile <new name> onboard\` instead.`,
|
|
38181
|
+
ExitCode.USAGE_ERROR
|
|
38182
|
+
);
|
|
38183
|
+
}
|
|
38184
|
+
let code5 = typeof options.code === "string" && options.code.trim() !== "" ? options.code.trim() : void 0;
|
|
38185
|
+
const acquire = async (did, flow = {}) => {
|
|
38186
|
+
if (code5 === void 0) throw new ApprovalNeeded(buildAuthUrl(did, flow));
|
|
38187
|
+
const pasted = code5;
|
|
38188
|
+
code5 = void 0;
|
|
38189
|
+
return delegationFromInput(pasted, did, flow);
|
|
38190
|
+
};
|
|
38191
|
+
for (const [index, step] of ONBOARD_STEPS.entries()) {
|
|
38192
|
+
const reason = `TinyCloud onboarding, step ${index + 1} of ${ONBOARD_STEPS.length}: ${step.asks}`;
|
|
38193
|
+
try {
|
|
38194
|
+
const session2 = await ProfileManager.getSession(ctx.profile);
|
|
38195
|
+
if (!sessionStateIsUsable(storedSessionState(session2, profile).state)) {
|
|
38196
|
+
const { delegationData } = await refreshOpenKeySession(ctx.profile, ctx.host, {
|
|
38197
|
+
permissions: step.permissions,
|
|
38198
|
+
expiry: ONBOARD_EXPIRY,
|
|
38199
|
+
reason,
|
|
38200
|
+
persistHost: globalOpts.host !== void 0,
|
|
38201
|
+
openKeyAcquisition: acquire
|
|
38202
|
+
});
|
|
38203
|
+
await ensureSpaceSetup({ profile: ctx.profile, host: ctx.host }, delegationData, delegationData.permissions);
|
|
38204
|
+
profile = await ProfileManager.getProfile(ctx.profile);
|
|
38205
|
+
continue;
|
|
38206
|
+
}
|
|
38207
|
+
await ensureDelegationAuthority({
|
|
38208
|
+
ctx,
|
|
38209
|
+
profile,
|
|
38210
|
+
node: await ensureAuthenticated(ctx),
|
|
38211
|
+
requested: await resolvePermissionSpaces(step.permissions, ctx.profile),
|
|
38212
|
+
expiryOption: ONBOARD_EXPIRY,
|
|
38213
|
+
reason,
|
|
38214
|
+
yes: true,
|
|
38215
|
+
openKeyAcquisition: acquire
|
|
38216
|
+
});
|
|
38217
|
+
} catch (error) {
|
|
38218
|
+
if (!(error instanceof ApprovalNeeded)) throw error;
|
|
38219
|
+
const next = `${CLI_NAME29} --profile ${ctx.profile} onboard --code <code>`;
|
|
38220
|
+
output({
|
|
38221
|
+
onboarded: false,
|
|
38222
|
+
profile: ctx.profile,
|
|
38223
|
+
step: index + 1,
|
|
38224
|
+
steps: ONBOARD_STEPS.length,
|
|
38225
|
+
asks: step.asks,
|
|
38226
|
+
approvalUrl: error.approvalUrl,
|
|
38227
|
+
next
|
|
38228
|
+
}, () => [
|
|
38229
|
+
`Step ${index + 1} of ${ONBOARD_STEPS.length}: ${step.asks}`,
|
|
38230
|
+
`Open this link, sign in or create an account, and approve:`,
|
|
38231
|
+
` ${error.approvalUrl}`,
|
|
38232
|
+
`Then run: ${next}`
|
|
38233
|
+
].join("\n"));
|
|
38234
|
+
process.exitCode = ExitCode.PENDING;
|
|
38235
|
+
return;
|
|
38236
|
+
}
|
|
38237
|
+
}
|
|
38238
|
+
profile = await ProfileManager.getProfile(ctx.profile);
|
|
38239
|
+
const session = await ProfileManager.getSession(ctx.profile);
|
|
38240
|
+
output({
|
|
38241
|
+
onboarded: true,
|
|
38242
|
+
profile: ctx.profile,
|
|
38243
|
+
ownerDid: profile.ownerDid ?? null,
|
|
38244
|
+
host: ctx.host,
|
|
38245
|
+
granted: ONBOARD_STEPS.map(({ space, asks }) => ({ space, asks })),
|
|
38246
|
+
expiresAt: storedSessionState(session, profile).expiresAt
|
|
38247
|
+
}, () => `Onboarded: profile ${ctx.profile} acts for ${profile.ownerDid ?? "the owner"}.
|
|
38248
|
+
${ONBOARD_STEPS.map(({ asks }) => ` ${asks}`).join("\n")}`);
|
|
38249
|
+
} catch (error) {
|
|
38250
|
+
return handleError(error);
|
|
38251
|
+
}
|
|
38252
|
+
});
|
|
38253
|
+
}
|
|
38254
|
+
|
|
37991
38255
|
// src/commands/profile.ts
|
|
37992
38256
|
init_profiles();
|
|
37993
38257
|
init_formatter();
|
|
37994
38258
|
init_errors();
|
|
37995
38259
|
init_constants();
|
|
37996
|
-
import { CLI_NAME as
|
|
38260
|
+
import { CLI_NAME as CLI_NAME30 } from "@tinycloud/operations/cli-name";
|
|
37997
38261
|
import { createInterface as createInterface5 } from "readline";
|
|
37998
38262
|
init_theme();
|
|
37999
38263
|
init_types();
|
|
@@ -38139,9 +38403,9 @@ profile at command time. Precedence for every kv/sql command:
|
|
|
38139
38403
|
explicit --space flag > profile defaultSpace > primary space.
|
|
38140
38404
|
|
|
38141
38405
|
Examples:
|
|
38142
|
-
$ ${
|
|
38143
|
-
$ ${
|
|
38144
|
-
$ ${
|
|
38406
|
+
$ ${CLI_NAME30} profile set-default-space applications
|
|
38407
|
+
$ ${CLI_NAME30} profile set-default-space applications --profile cli-test
|
|
38408
|
+
$ ${CLI_NAME30} profile set-default-space --unset
|
|
38145
38409
|
`).action(async (name, options, cmd) => {
|
|
38146
38410
|
try {
|
|
38147
38411
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -38153,7 +38417,7 @@ Examples:
|
|
|
38153
38417
|
if (!options.unset && (name === void 0 || name === "")) {
|
|
38154
38418
|
throw new CLIError(
|
|
38155
38419
|
"USAGE_ERROR",
|
|
38156
|
-
`Provide a space name (e.g. \`${
|
|
38420
|
+
`Provide a space name (e.g. \`${CLI_NAME30} profile set-default-space applications\`) or pass --unset.`,
|
|
38157
38421
|
ExitCode.USAGE_ERROR
|
|
38158
38422
|
);
|
|
38159
38423
|
}
|
|
@@ -38212,7 +38476,7 @@ function parseOperatorType(raw) {
|
|
|
38212
38476
|
|
|
38213
38477
|
// src/commands/replica.ts
|
|
38214
38478
|
init_profile_replicas();
|
|
38215
|
-
import { CLI_NAME as
|
|
38479
|
+
import { CLI_NAME as CLI_NAME31 } from "@tinycloud/operations/cli-name";
|
|
38216
38480
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
38217
38481
|
import { constants as fsConstants5 } from "fs";
|
|
38218
38482
|
import { mkdir as mkdir5, open as open11, readdir as readdir6, rename as rename4, unlink as unlink5, lstat as lstat9 } from "fs/promises";
|
|
@@ -38353,11 +38617,11 @@ var EXIT_BY_CODE = {
|
|
|
38353
38617
|
var READ_FAILURE = {
|
|
38354
38618
|
absent: { code: "KEY_ABSENT", exit: ExitCode.NOT_FOUND, what: "is not in the replica (the replica covers its whole prefix)" },
|
|
38355
38619
|
deleted: { code: "KEY_DELETED", exit: ExitCode.NOT_FOUND, what: "was deleted at the source" },
|
|
38356
|
-
content_missing: { code: "CONTENT_MISSING", exit: ExitCode.NOT_FOUND, what: `is known but its content has not been fetched yet; run ${
|
|
38620
|
+
content_missing: { code: "CONTENT_MISSING", exit: ExitCode.NOT_FOUND, what: `is known but its content has not been fetched yet; run ${CLI_NAME31} replica sync` },
|
|
38357
38621
|
coverage_incomplete: {
|
|
38358
38622
|
code: "COVERAGE_INCOMPLETE",
|
|
38359
38623
|
exit: ExitCode.NOT_FOUND,
|
|
38360
|
-
what: `is not in the replica, which has not finished its first sync; run ${
|
|
38624
|
+
what: `is not in the replica, which has not finished its first sync; run ${CLI_NAME31} replica sync`
|
|
38361
38625
|
},
|
|
38362
38626
|
not_covered: { code: "NOT_COVERED", exit: ExitCode.USAGE_ERROR, what: "is outside the replica's prefix" }
|
|
38363
38627
|
};
|
|
@@ -38475,7 +38739,7 @@ async function existingReplicaName(profile, option) {
|
|
|
38475
38739
|
const names = await replicaNames(profile);
|
|
38476
38740
|
if (names.length === 1) return names[0];
|
|
38477
38741
|
if (names.length === 0) {
|
|
38478
|
-
throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, `Profile "${profile}" has no replicas. Create one with: ${
|
|
38742
|
+
throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, `Profile "${profile}" has no replicas. Create one with: ${CLI_NAME31} replica sync --prefix <prefix>`);
|
|
38479
38743
|
}
|
|
38480
38744
|
throw new CLIError("USAGE_ERROR", `Profile "${profile}" has several replicas (${names.join(", ")}); pass --replica <name>.`, ExitCode.USAGE_ERROR);
|
|
38481
38745
|
}
|
|
@@ -38583,9 +38847,9 @@ async function storedGrants(profile) {
|
|
|
38583
38847
|
function grantHint(space, prefix) {
|
|
38584
38848
|
return [
|
|
38585
38849
|
"A replica needs a device grant with get and sync on its prefix:",
|
|
38586
|
-
` device: ${
|
|
38587
|
-
` owner: ${
|
|
38588
|
-
` device: ${
|
|
38850
|
+
` device: ${CLI_NAME31} auth request --cap tinycloud.kv:${space}:${prefix}:get,list,metadata,sync --expiry 30d --emit req.json`,
|
|
38851
|
+
` owner: ${CLI_NAME31} auth grant req.json > grant.json`,
|
|
38852
|
+
` device: ${CLI_NAME31} auth import grant.json`
|
|
38589
38853
|
].join("\n");
|
|
38590
38854
|
}
|
|
38591
38855
|
function resolveSpace(input, prefix, grants) {
|
|
@@ -38641,9 +38905,9 @@ async function grantTransports(input) {
|
|
|
38641
38905
|
fetch: globalThis.fetch.bind(globalThis),
|
|
38642
38906
|
hosts: [input.host]
|
|
38643
38907
|
});
|
|
38644
|
-
const
|
|
38645
|
-
|
|
38646
|
-
context.registerService("kv",
|
|
38908
|
+
const kv2 = new KVService({});
|
|
38909
|
+
kv2.initialize(context);
|
|
38910
|
+
context.registerService("kv", kv2);
|
|
38647
38911
|
context.setSession({
|
|
38648
38912
|
delegationHeader: { Authorization: new TextDecoder().decode(grant.bytes) },
|
|
38649
38913
|
delegationCid: grant.cid,
|
|
@@ -38651,7 +38915,7 @@ async function grantTransports(input) {
|
|
|
38651
38915
|
verificationMethod: input.deviceDid,
|
|
38652
38916
|
jwk: input.jwk
|
|
38653
38917
|
});
|
|
38654
|
-
const client =
|
|
38918
|
+
const client = kv2;
|
|
38655
38919
|
return kvSyncTransport(client);
|
|
38656
38920
|
};
|
|
38657
38921
|
}
|
|
@@ -38676,7 +38940,7 @@ function describeStatus(status) {
|
|
|
38676
38940
|
formatField("Size", formatBytes(status.bytes)),
|
|
38677
38941
|
formatField("Grant", status.device.delegationCid),
|
|
38678
38942
|
formatField("Last error", status.lastError ? `${status.lastError.code}: ${status.lastError.message}` : null),
|
|
38679
|
-
formatField("Purge", status.purgePending ? `incomplete: content files remain; the next ${
|
|
38943
|
+
formatField("Purge", status.purgePending ? `incomplete: content files remain; the next ${CLI_NAME31} replica command retries` : null)
|
|
38680
38944
|
].join("\n");
|
|
38681
38945
|
}
|
|
38682
38946
|
async function writeFileAtomic(path, bytes) {
|
|
@@ -38840,7 +39104,7 @@ or coverage incomplete; 5 grant expired, revoked or not yet valid; 6 network;
|
|
|
38840
39104
|
(options, cmd) => run(async () => {
|
|
38841
39105
|
const profile = await profileName(cmd);
|
|
38842
39106
|
if (options.purge) {
|
|
38843
|
-
throw new CLIError("REPORT_READ_ONLY", `Replica report is read-only. To remove a named local replica, run ${
|
|
39107
|
+
throw new CLIError("REPORT_READ_ONLY", `Replica report is read-only. To remove a named local replica, run ${CLI_NAME31} replica reset --replica <name> --purge; flag-owned purge has no equivalent yet (TC-1309).`, ExitCode.USAGE_ERROR);
|
|
38844
39108
|
}
|
|
38845
39109
|
if (options.clearPending) {
|
|
38846
39110
|
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);
|
|
@@ -38942,7 +39206,7 @@ or coverage incomplete; 5 grant expired, revoked or not yet valid; 6 network;
|
|
|
38942
39206
|
if (differs !== void 0) {
|
|
38943
39207
|
throw new ReplicaError(
|
|
38944
39208
|
ReplicaErrorCode.CONFIG_MISMATCH,
|
|
38945
|
-
`Replica ${name} already follows ${differs}. Use another --replica name, or ${
|
|
39209
|
+
`Replica ${name} already follows ${differs}. Use another --replica name, or ${CLI_NAME31} replica reset --replica ${name} --purge first.`
|
|
38946
39210
|
);
|
|
38947
39211
|
}
|
|
38948
39212
|
}
|
|
@@ -39107,10 +39371,10 @@ init_theme();
|
|
|
39107
39371
|
init_errors();
|
|
39108
39372
|
init_warnings();
|
|
39109
39373
|
init_constants();
|
|
39110
|
-
import { CLI_NAME as
|
|
39374
|
+
import { CLI_NAME as CLI_NAME33 } from "@tinycloud/operations/cli-name";
|
|
39111
39375
|
import { readFile as readFile14 } from "fs/promises";
|
|
39112
39376
|
import { join as join17 } from "path";
|
|
39113
|
-
import { homedir, hostname, userInfo } from "os";
|
|
39377
|
+
import { constants as osConstants, homedir, hostname, userInfo } from "os";
|
|
39114
39378
|
import { appSecretScope } from "@tinycloud/sdk-core";
|
|
39115
39379
|
import { invokeOperation as invokeOperation3 } from "@tinycloud/operations";
|
|
39116
39380
|
import { isLocalOwnerProfile as isLocalOwnerProfile4, ownerSessionLacksProof as ownerSessionLacksProof5 } from "@tinycloud/operations/profile";
|
|
@@ -39131,6 +39395,7 @@ import {
|
|
|
39131
39395
|
} from "@tinycloud/operations/secret-capabilities";
|
|
39132
39396
|
import { invokeSecretsGetWithLocalAuthorityRetry } from "@tinycloud/operations/cli-runtime";
|
|
39133
39397
|
init_sdk();
|
|
39398
|
+
init_replication_registry();
|
|
39134
39399
|
init_session_expired();
|
|
39135
39400
|
init_space();
|
|
39136
39401
|
|
|
@@ -39196,14 +39461,14 @@ function runProbeChild(request, options = {}) {
|
|
|
39196
39461
|
const args = options.argv ?? probeChildArgv(request);
|
|
39197
39462
|
const budgetMs = options.budgetMs ?? ATTEMPT_BUDGET_MS;
|
|
39198
39463
|
const killGraceMs = options.killGraceMs ?? KILL_GRACE_MS;
|
|
39199
|
-
const
|
|
39464
|
+
const spawn3 = options.spawn ?? nodeSpawn;
|
|
39200
39465
|
const signal = options.signal;
|
|
39201
39466
|
return new Promise((resolve9) => {
|
|
39202
39467
|
if (signal?.aborted) {
|
|
39203
39468
|
resolve9({ kind: "cancelled" });
|
|
39204
39469
|
return;
|
|
39205
39470
|
}
|
|
39206
|
-
const child =
|
|
39471
|
+
const child = spawn3(command, args, { env: process.env, stdio: ["ignore", "pipe", "pipe"] });
|
|
39207
39472
|
let outcome;
|
|
39208
39473
|
let stdout = Buffer.alloc(0);
|
|
39209
39474
|
let stderr = Buffer.alloc(0);
|
|
@@ -39315,10 +39580,79 @@ function parseStatusEnvelope(line) {
|
|
|
39315
39580
|
return void 0;
|
|
39316
39581
|
}
|
|
39317
39582
|
|
|
39583
|
+
// src/lib/run-command.ts
|
|
39584
|
+
import { execFileSync, spawn as spawn2 } from "child_process";
|
|
39585
|
+
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
39586
|
+
var FORWARDED_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT"];
|
|
39587
|
+
var TERMINAL_SIGNALS = { SIGINT: true, SIGQUIT: true, SIGHUP: true };
|
|
39588
|
+
function psGroupArgs(pid) {
|
|
39589
|
+
return ["-o", "pgid=", "-o", "tpgid=", "-p", String(pid)];
|
|
39590
|
+
}
|
|
39591
|
+
function parsePsGroups(output2) {
|
|
39592
|
+
const columns = output2.trim().split(/\s+/);
|
|
39593
|
+
if (columns.length !== 2) return void 0;
|
|
39594
|
+
const group = Number(columns[0]);
|
|
39595
|
+
const foreground = Number(columns[1]);
|
|
39596
|
+
return Number.isSafeInteger(group) && Number.isSafeInteger(foreground) ? { group, foreground } : void 0;
|
|
39597
|
+
}
|
|
39598
|
+
function processGroups() {
|
|
39599
|
+
try {
|
|
39600
|
+
const stat8 = readFileSync2("/proc/self/stat", "utf8");
|
|
39601
|
+
const fields = stat8.slice(stat8.lastIndexOf(")") + 2).split(" ");
|
|
39602
|
+
return { group: Number(fields[2]), foreground: Number(fields[5]) };
|
|
39603
|
+
} catch {
|
|
39604
|
+
}
|
|
39605
|
+
try {
|
|
39606
|
+
const ps = existsSync("/bin/ps") ? "/bin/ps" : "/usr/bin/ps";
|
|
39607
|
+
return parsePsGroups(execFileSync(ps, psGroupArgs(process.pid), {
|
|
39608
|
+
env: {},
|
|
39609
|
+
encoding: "utf8",
|
|
39610
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
39611
|
+
timeout: 2e3
|
|
39612
|
+
}));
|
|
39613
|
+
} catch {
|
|
39614
|
+
return void 0;
|
|
39615
|
+
}
|
|
39616
|
+
}
|
|
39617
|
+
function inTerminalForeground() {
|
|
39618
|
+
const groups = processGroups();
|
|
39619
|
+
return groups !== void 0 && groups.foreground > 0 && groups.group === groups.foreground;
|
|
39620
|
+
}
|
|
39621
|
+
function runCommand(argv, env) {
|
|
39622
|
+
return new Promise((resolve9) => {
|
|
39623
|
+
let child;
|
|
39624
|
+
try {
|
|
39625
|
+
child = spawn2(argv[0], argv.slice(1), { stdio: "inherit", env, shell: false });
|
|
39626
|
+
} catch (error) {
|
|
39627
|
+
const code5 = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
39628
|
+
resolve9({ kind: "not_started", ...code5 === void 0 ? {} : { errno: code5 } });
|
|
39629
|
+
return;
|
|
39630
|
+
}
|
|
39631
|
+
const forward = (signal) => {
|
|
39632
|
+
if (TERMINAL_SIGNALS[signal] && inTerminalForeground()) return;
|
|
39633
|
+
child.kill(signal);
|
|
39634
|
+
};
|
|
39635
|
+
for (const signal of FORWARDED_SIGNALS) process.on(signal, forward);
|
|
39636
|
+
const settle = (outcome) => {
|
|
39637
|
+
for (const signal of FORWARDED_SIGNALS) process.off(signal, forward);
|
|
39638
|
+
resolve9(outcome);
|
|
39639
|
+
};
|
|
39640
|
+
child.on("error", (error) => {
|
|
39641
|
+
if (child.pid === void 0) settle({ kind: "not_started", ...error.code === void 0 ? {} : { errno: error.code } });
|
|
39642
|
+
});
|
|
39643
|
+
child.once("exit", (code5, signal) => {
|
|
39644
|
+
settle(signal !== null ? { kind: "signaled", signal } : { kind: "exited", code: code5 ?? 1 });
|
|
39645
|
+
});
|
|
39646
|
+
});
|
|
39647
|
+
}
|
|
39648
|
+
|
|
39649
|
+
// src/commands/secrets.ts
|
|
39650
|
+
init_safe_text();
|
|
39651
|
+
|
|
39318
39652
|
// src/lib/secrets-wait.ts
|
|
39319
39653
|
init_constants();
|
|
39320
39654
|
init_errors();
|
|
39321
|
-
import { CLI_NAME as
|
|
39655
|
+
import { CLI_NAME as CLI_NAME32 } from "@tinycloud/operations/cli-name";
|
|
39322
39656
|
var WAIT_BACKOFF_MS = [3e3, 4500, 6750, 10125];
|
|
39323
39657
|
var WAIT_STEADY_INTERVAL_MS = 15e3;
|
|
39324
39658
|
var DEFAULT_WAIT_TIMEOUT = "5m";
|
|
@@ -39326,7 +39660,7 @@ var MAX_WAIT_TIMEOUT_MS = 9 * 6e4;
|
|
|
39326
39660
|
var MAX_CONSECUTIVE_FAILURES = 5;
|
|
39327
39661
|
var TIMEOUT_RE = /^(0|\d+s|\d+m)$/;
|
|
39328
39662
|
var RETRYABLE_STATUS_ERRORS = { NODE_UNREACHABLE: true, NODE_ERROR: true };
|
|
39329
|
-
var EXPLICIT_KEY_HINT = `${
|
|
39663
|
+
var EXPLICIT_KEY_HINT = `${CLI_NAME32} secrets wait never signs in, and an explicit private key has no stored session. Use a local profile signed in once with ${CLI_NAME32} --profile <profile> auth login --method local, without TC_PRIVATE_KEY.`;
|
|
39330
39664
|
function abortableSleep(ms2, signal) {
|
|
39331
39665
|
return new Promise((resolve9) => {
|
|
39332
39666
|
if (signal.aborted) {
|
|
@@ -39362,7 +39696,7 @@ async function runSecretsWait(request, deps) {
|
|
|
39362
39696
|
let step = 0;
|
|
39363
39697
|
const cancelled = () => new CLIError(
|
|
39364
39698
|
"CANCELLED",
|
|
39365
|
-
`Interrupted: ${
|
|
39699
|
+
`Interrupted: ${CLI_NAME32} secrets wait stopped before the secrets were ready.`,
|
|
39366
39700
|
ExitCode.CANCELLED,
|
|
39367
39701
|
progress === void 0 ? {} : { progress }
|
|
39368
39702
|
);
|
|
@@ -39506,13 +39840,13 @@ function statusError(error, profile, warnings) {
|
|
|
39506
39840
|
if (error.code === "AUTH_REQUIRED" || error.code === "SESSION_NOT_FOUND") {
|
|
39507
39841
|
const recovery = recoveryOf(error, "sign_in");
|
|
39508
39842
|
if (recovery !== void 0) {
|
|
39509
|
-
const hint = recovery.command !== void 0 ? `Sign in once with: ${recovery.command}, then retry.` : `Sign in to profile "${profile}" once with ${
|
|
39843
|
+
const hint = recovery.command !== void 0 ? `Sign in once with: ${recovery.command}, then retry.` : `Sign in to profile "${profile}" once with ${CLI_NAME32} auth login, then retry.`;
|
|
39510
39844
|
return new CLIError("AUTH_REQUIRED", error.message, ExitCode.AUTH_REQUIRED, { hint, recovery, ...extra });
|
|
39511
39845
|
}
|
|
39512
39846
|
}
|
|
39513
39847
|
if (error.code === "PROFILE_MIGRATION_REQUIRED") {
|
|
39514
39848
|
const recovery = recoveryOf(error, "complete_migration");
|
|
39515
|
-
const hint = recovery?.command !== void 0 ? `Run ${recovery.command} once to finish this profile's one-time upgrade, then retry wait.` : `Run ${
|
|
39849
|
+
const hint = recovery?.command !== void 0 ? `Run ${recovery.command} once to finish this profile's one-time upgrade, then retry wait.` : `Run ${CLI_NAME32} auth caps once with profile "${profile}" to finish its one-time upgrade, then retry wait.`;
|
|
39516
39850
|
return new CLIError("PROFILE_MIGRATION_REQUIRED", error.message, ExitCode.ERROR, {
|
|
39517
39851
|
hint,
|
|
39518
39852
|
...recovery === void 0 ? {} : { recovery },
|
|
@@ -39704,11 +40038,11 @@ async function runSecretOperation(params) {
|
|
|
39704
40038
|
}
|
|
39705
40039
|
function secretPermissionReason(action, name) {
|
|
39706
40040
|
const target = name ? ` secret "${name}"` : " secrets";
|
|
39707
|
-
return `Allow \`${
|
|
40041
|
+
return `Allow \`${CLI_NAME33} secrets ${action}${name ? ` ${name}` : ""}\` to access${target} with the required TinyCloud permissions.`;
|
|
39708
40042
|
}
|
|
39709
40043
|
function assertOwnerApprovalPossible(profileName2, profile, action, name, missing = []) {
|
|
39710
40044
|
if (profile.authMethod !== "openkey" || process.stdin.isTTY || process.stderr.isTTY) return;
|
|
39711
|
-
const command = `${
|
|
40045
|
+
const command = `${CLI_NAME33} secrets ${action === "del" ? "delete" : action}${name ? ` ${name}` : ""}`;
|
|
39712
40046
|
throw new CLIError(
|
|
39713
40047
|
"PERMISSION_DENIED",
|
|
39714
40048
|
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.`,
|
|
@@ -39719,7 +40053,7 @@ function assertOwnerApprovalPossible(profileName2, profile, action, name, missin
|
|
|
39719
40053
|
);
|
|
39720
40054
|
}
|
|
39721
40055
|
function scopedSecretLoginHint(profileName2) {
|
|
39722
|
-
return `Have the owner approve a scoped login whose manifest names the secret: ${
|
|
40056
|
+
return `Have the owner approve a scoped login whose manifest names the secret: ${CLI_NAME33} --profile ${profileName2} auth login --method openkey --paste --manifest <manifest.json>. Pass the owner's code on stdin, newline-terminated.`;
|
|
39723
40057
|
}
|
|
39724
40058
|
async function runSecretOperationAttempt(label, operation) {
|
|
39725
40059
|
try {
|
|
@@ -39853,7 +40187,7 @@ ${message}`,
|
|
|
39853
40187
|
"AUTH_REQUIRED",
|
|
39854
40188
|
"Not signed in to TinyCloud.",
|
|
39855
40189
|
ExitCode.AUTH_REQUIRED,
|
|
39856
|
-
{ hint: `Sign in with: ${
|
|
40190
|
+
{ hint: `Sign in with: ${CLI_NAME33} --profile ${result.context.profile} auth login` }
|
|
39857
40191
|
);
|
|
39858
40192
|
}
|
|
39859
40193
|
if (result.error.code === "NODE_UNREACHABLE") {
|
|
@@ -40269,6 +40603,153 @@ function outputSecretDoctor(result) {
|
|
|
40269
40603
|
process.stdout.write(theme.warn(`${failed} secrets check${failed > 1 ? "s" : ""} need attention.`) + "\n");
|
|
40270
40604
|
}
|
|
40271
40605
|
}
|
|
40606
|
+
async function resolveSecretReadTarget(ctx, name, options) {
|
|
40607
|
+
const scopeOptions = resolveSecretScope(options);
|
|
40608
|
+
const legacySpaceUri = await resolveSecretSpace(options.space, ctx.profile);
|
|
40609
|
+
const resolved = resolveSecretPath2(name, scopeOptions);
|
|
40610
|
+
return { name, scopeOptions, scope: resolved.scope, legacySpaceUri, secretPath: resolved.permissionPaths.vault };
|
|
40611
|
+
}
|
|
40612
|
+
async function readSecret(params) {
|
|
40613
|
+
const { ctx, target, options } = params;
|
|
40614
|
+
const { name } = target;
|
|
40615
|
+
if (options.delegation) {
|
|
40616
|
+
const delegated = await resolveDelegatedSecretSource(
|
|
40617
|
+
options.delegation,
|
|
40618
|
+
target.secretPath,
|
|
40619
|
+
target.legacySpaceUri ?? SECRETS_SPACE2
|
|
40620
|
+
);
|
|
40621
|
+
const effectiveHost = params.host ?? delegated.delegation.host ?? ctx.host;
|
|
40622
|
+
const delegatedCtx = { ...ctx, host: effectiveHost };
|
|
40623
|
+
const local = effectiveLocal(await ProfileManager.getProfile(ctx.profile).catch(() => null), options);
|
|
40624
|
+
const node = await ensureSecretsNode(delegatedCtx, options);
|
|
40625
|
+
const value = await withSpinner(
|
|
40626
|
+
`Getting secret ${name}...`,
|
|
40627
|
+
() => readDelegatedSecretValue({
|
|
40628
|
+
node,
|
|
40629
|
+
delegation: delegated.delegation,
|
|
40630
|
+
delegationCid: delegated.delegation.cid,
|
|
40631
|
+
permissions: delegated.permissions,
|
|
40632
|
+
secretPath: target.secretPath,
|
|
40633
|
+
space: target.legacySpaceUri ?? SECRETS_SPACE2,
|
|
40634
|
+
name,
|
|
40635
|
+
...target.scope === void 0 ? {} : { scope: target.scope },
|
|
40636
|
+
local
|
|
40637
|
+
})
|
|
40638
|
+
);
|
|
40639
|
+
return { value };
|
|
40640
|
+
}
|
|
40641
|
+
const privateKey = authOptions(options)?.privateKey;
|
|
40642
|
+
const spaceUri = privateKey !== void 0 && options.space !== void 0 && !options.space.startsWith("tinycloud:") ? options.space : target.legacySpaceUri;
|
|
40643
|
+
const result = await invokeCanonicalSecretGet({
|
|
40644
|
+
ctx,
|
|
40645
|
+
name,
|
|
40646
|
+
...target.scopeOptions?.scope === void 0 ? {} : { scope: target.scopeOptions.scope },
|
|
40647
|
+
...spaceUri === void 0 ? {} : { space: spaceUri },
|
|
40648
|
+
options,
|
|
40649
|
+
label: `Getting secret ${name}...`,
|
|
40650
|
+
openKeyAcquisition: params.openKeyAcquisition
|
|
40651
|
+
});
|
|
40652
|
+
if (result.status !== "ok") {
|
|
40653
|
+
try {
|
|
40654
|
+
throwCanonicalSecretGetError(result, name);
|
|
40655
|
+
} catch (error) {
|
|
40656
|
+
throw withOperationWarnings(error, result.warnings);
|
|
40657
|
+
}
|
|
40658
|
+
}
|
|
40659
|
+
return { value: result.output.value, warnings: result.warnings };
|
|
40660
|
+
}
|
|
40661
|
+
var ENV_VAR_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
40662
|
+
function argvAfterSeparator(cmd) {
|
|
40663
|
+
let root = cmd;
|
|
40664
|
+
while (root.parent) root = root.parent;
|
|
40665
|
+
const raw = "rawArgs" in root ? root.rawArgs : void 0;
|
|
40666
|
+
if (!Array.isArray(raw)) return void 0;
|
|
40667
|
+
const args = raw.map(String);
|
|
40668
|
+
const withValue = (flag) => root.options.some((option) => option.required && (option.long === flag || option.short === flag));
|
|
40669
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
40670
|
+
const arg = args[index];
|
|
40671
|
+
if (arg === "--") return args.slice(index + 1);
|
|
40672
|
+
if (arg.startsWith("--")) {
|
|
40673
|
+
if (withValue(arg)) index += 1;
|
|
40674
|
+
continue;
|
|
40675
|
+
}
|
|
40676
|
+
if (arg.length < 2 || !arg.startsWith("-")) continue;
|
|
40677
|
+
for (let char = 1; char < arg.length; char += 1) {
|
|
40678
|
+
const option = root.options.find((candidate) => candidate.short === `-${arg[char]}`);
|
|
40679
|
+
if (option === void 0) break;
|
|
40680
|
+
if (option.required) {
|
|
40681
|
+
if (char === arg.length - 1) index += 1;
|
|
40682
|
+
break;
|
|
40683
|
+
}
|
|
40684
|
+
}
|
|
40685
|
+
}
|
|
40686
|
+
return void 0;
|
|
40687
|
+
}
|
|
40688
|
+
function secretRunPlan(cmd, operands, maps) {
|
|
40689
|
+
const usage3 = `${CLI_NAME33} secrets run NAME... [--map NAME=ENV_VAR] -- <command> [args...]`;
|
|
40690
|
+
const after = argvAfterSeparator(cmd);
|
|
40691
|
+
if (after === void 0 || after.length > operands.length) {
|
|
40692
|
+
throw new CLIError("USAGE_ERROR", `Put the command after \`--\`: ${usage3}`, ExitCode.USAGE_ERROR);
|
|
40693
|
+
}
|
|
40694
|
+
const [program, ...args] = after;
|
|
40695
|
+
if (program === void 0) {
|
|
40696
|
+
throw new CLIError("USAGE_ERROR", `Add the command to run after \`--\`: ${usage3}`, ExitCode.USAGE_ERROR);
|
|
40697
|
+
}
|
|
40698
|
+
const exports = [];
|
|
40699
|
+
for (const name of operands.slice(0, operands.length - after.length)) {
|
|
40700
|
+
if (!ENV_VAR_RE.test(name)) {
|
|
40701
|
+
throw new CLIError(
|
|
40702
|
+
"USAGE_ERROR",
|
|
40703
|
+
`${JSON.stringify(name)} can't be used as an environment variable name. Use \`--map ${name}=ENV_VAR\` to choose one.`,
|
|
40704
|
+
ExitCode.USAGE_ERROR
|
|
40705
|
+
);
|
|
40706
|
+
}
|
|
40707
|
+
exports.push({ name, variable: name });
|
|
40708
|
+
}
|
|
40709
|
+
for (const map of maps) {
|
|
40710
|
+
const equals7 = map.indexOf("=");
|
|
40711
|
+
const variable = map.slice(equals7 + 1);
|
|
40712
|
+
if (equals7 <= 0 || !ENV_VAR_RE.test(variable)) {
|
|
40713
|
+
throw new CLIError(
|
|
40714
|
+
"USAGE_ERROR",
|
|
40715
|
+
`Invalid \`--map ${JSON.stringify(map)}\`: use NAME=ENV_VAR, where ENV_VAR starts with a letter or _ and uses only letters, digits and _.`,
|
|
40716
|
+
ExitCode.USAGE_ERROR
|
|
40717
|
+
);
|
|
40718
|
+
}
|
|
40719
|
+
exports.push({ name: map.slice(0, equals7), variable });
|
|
40720
|
+
}
|
|
40721
|
+
if (exports.length === 0) {
|
|
40722
|
+
throw new CLIError("USAGE_ERROR", `Name at least one secret: ${usage3}`, ExitCode.USAGE_ERROR);
|
|
40723
|
+
}
|
|
40724
|
+
const variables = /* @__PURE__ */ new Set();
|
|
40725
|
+
for (const { variable } of exports) {
|
|
40726
|
+
if (variables.has(variable)) {
|
|
40727
|
+
throw new CLIError("USAGE_ERROR", `Two secrets are mapped to ${variable}. Give each its own variable.`, ExitCode.USAGE_ERROR);
|
|
40728
|
+
}
|
|
40729
|
+
variables.add(variable);
|
|
40730
|
+
}
|
|
40731
|
+
return { command: [program, ...args], exports };
|
|
40732
|
+
}
|
|
40733
|
+
var RERAISED_SIGNALS = {
|
|
40734
|
+
SIGINT: true,
|
|
40735
|
+
SIGTERM: true,
|
|
40736
|
+
SIGHUP: true,
|
|
40737
|
+
SIGKILL: true,
|
|
40738
|
+
SIGALRM: true,
|
|
40739
|
+
SIGUSR2: true
|
|
40740
|
+
};
|
|
40741
|
+
var TC_CREDENTIAL_VARIABLES = ["TC_PRIVATE_KEY"];
|
|
40742
|
+
function exitAsCommand(outcome, program) {
|
|
40743
|
+
if (outcome.kind === "exited") return process.exit(outcome.code);
|
|
40744
|
+
if (outcome.kind === "signaled") {
|
|
40745
|
+
if (RERAISED_SIGNALS[outcome.signal]) process.kill(process.pid, outcome.signal);
|
|
40746
|
+
return process.exit(128 + (osConstants.signals[outcome.signal] ?? 0));
|
|
40747
|
+
}
|
|
40748
|
+
const reason = outcome.errno === "ENOENT" ? "command not found" : outcome.errno === "EACCES" ? "permission denied" : `cannot run (${outcome.errno ?? "unknown error"})`;
|
|
40749
|
+
process.stderr.write(`${CLI_NAME33} secrets run: ${safeInline(program)}: ${reason}
|
|
40750
|
+
`);
|
|
40751
|
+
return process.exit(outcome.errno === "ENOENT" ? 127 : 126);
|
|
40752
|
+
}
|
|
40272
40753
|
function registerSecretsCommand(program, openKeyAcquisition) {
|
|
40273
40754
|
const secrets = program.command("secrets").description("Encrypted secrets management");
|
|
40274
40755
|
const network = secrets.command("network").description("Manage the default secrets encryption network");
|
|
@@ -40330,7 +40811,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
40330
40811
|
name: "Encryption network",
|
|
40331
40812
|
ok: false,
|
|
40332
40813
|
detail: `${networkName} not found`,
|
|
40333
|
-
hint: `${
|
|
40814
|
+
hint: `${CLI_NAME33} secrets network init ${networkName}`
|
|
40334
40815
|
}
|
|
40335
40816
|
];
|
|
40336
40817
|
let secret;
|
|
@@ -40376,7 +40857,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
40376
40857
|
name: "Secret access",
|
|
40377
40858
|
ok: false,
|
|
40378
40859
|
detail: notFound ? `${resolved.permissionPaths.vault} not found` : result.error.message,
|
|
40379
|
-
hint: notFound ? local ? `${
|
|
40860
|
+
hint: notFound ? local ? `${CLI_NAME33} secrets put ${resolved.name}${formatSecretScopeFlag(scopeOptions)} --stdin` : `${CLI_NAME33} secrets request ${resolved.name}${formatSecretScopeFlag(scopeOptions)}` : `Ask the owner profile to grant ${secretCapabilityAction("get")} and ${SECRET_DECRYPT_CAPABILITY}.`
|
|
40380
40861
|
});
|
|
40381
40862
|
}
|
|
40382
40863
|
} else {
|
|
@@ -40436,66 +40917,23 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
40436
40917
|
try {
|
|
40437
40918
|
const globalOpts = cmd.optsWithGlobals();
|
|
40438
40919
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
40439
|
-
const
|
|
40440
|
-
const legacySpaceUri = await resolveSecretSpace(options.space, ctx.profile);
|
|
40441
|
-
const secretPath = resolveSecretPath2(name, scopeOptions).permissionPaths.vault;
|
|
40920
|
+
const target = await resolveSecretReadTarget(ctx, name, options);
|
|
40442
40921
|
if (options.output) await validatePrivateOutput(options.output, "Secret output");
|
|
40922
|
+
const secret = await readSecret({ ctx, host: globalOpts.host, target, options, openKeyAcquisition });
|
|
40923
|
+
const value = secret.value;
|
|
40443
40924
|
if (options.delegation) {
|
|
40444
|
-
const delegated = await resolveDelegatedSecretSource(
|
|
40445
|
-
options.delegation,
|
|
40446
|
-
secretPath,
|
|
40447
|
-
legacySpaceUri ?? SECRETS_SPACE2
|
|
40448
|
-
);
|
|
40449
|
-
const effectiveHost = globalOpts.host ?? delegated.delegation.host ?? ctx.host;
|
|
40450
|
-
const delegatedCtx = { ...ctx, host: effectiveHost };
|
|
40451
|
-
const local = effectiveLocal(await ProfileManager.getProfile(ctx.profile).catch(() => null), options);
|
|
40452
|
-
const scope = resolveSecretPath2(name, scopeOptions).scope;
|
|
40453
|
-
const node = await ensureSecretsNode(delegatedCtx, options);
|
|
40454
|
-
const value2 = await withSpinner(
|
|
40455
|
-
`Getting secret ${name}...`,
|
|
40456
|
-
() => readDelegatedSecretValue({
|
|
40457
|
-
node,
|
|
40458
|
-
delegation: delegated.delegation,
|
|
40459
|
-
delegationCid: delegated.delegation.cid,
|
|
40460
|
-
permissions: delegated.permissions,
|
|
40461
|
-
secretPath,
|
|
40462
|
-
space: legacySpaceUri ?? SECRETS_SPACE2,
|
|
40463
|
-
name,
|
|
40464
|
-
...scope === void 0 ? {} : { scope },
|
|
40465
|
-
local
|
|
40466
|
-
})
|
|
40467
|
-
);
|
|
40468
40925
|
if (options.output) {
|
|
40469
|
-
await writePrivateOutput(options.output,
|
|
40926
|
+
await writePrivateOutput(options.output, value, "Secret output");
|
|
40470
40927
|
outputJson({ name, written: options.output });
|
|
40471
40928
|
return;
|
|
40472
40929
|
}
|
|
40473
40930
|
if (options.raw) {
|
|
40474
|
-
process.stdout.write(
|
|
40931
|
+
process.stdout.write(value);
|
|
40475
40932
|
return;
|
|
40476
40933
|
}
|
|
40477
|
-
outputJson({ name, value
|
|
40934
|
+
outputJson({ name, value });
|
|
40478
40935
|
return;
|
|
40479
40936
|
}
|
|
40480
|
-
const privateKey = authOptions(options)?.privateKey;
|
|
40481
|
-
const spaceUri = privateKey !== void 0 && options.space !== void 0 && !options.space.startsWith("tinycloud:") ? options.space : legacySpaceUri;
|
|
40482
|
-
const result = await invokeCanonicalSecretGet({
|
|
40483
|
-
ctx,
|
|
40484
|
-
name,
|
|
40485
|
-
...scopeOptions?.scope === void 0 ? {} : { scope: scopeOptions.scope },
|
|
40486
|
-
...spaceUri === void 0 ? {} : { space: spaceUri },
|
|
40487
|
-
options,
|
|
40488
|
-
label: `Getting secret ${name}...`,
|
|
40489
|
-
openKeyAcquisition
|
|
40490
|
-
});
|
|
40491
|
-
if (result.status !== "ok") {
|
|
40492
|
-
try {
|
|
40493
|
-
throwCanonicalSecretGetError(result, name);
|
|
40494
|
-
} catch (error) {
|
|
40495
|
-
throw withOperationWarnings(error, result.warnings);
|
|
40496
|
-
}
|
|
40497
|
-
}
|
|
40498
|
-
const value = result.output.value;
|
|
40499
40937
|
try {
|
|
40500
40938
|
if (options.output) {
|
|
40501
40939
|
await writePrivateOutput(options.output, value, "Secret output");
|
|
@@ -40506,9 +40944,47 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
40506
40944
|
outputJson({ name, value });
|
|
40507
40945
|
}
|
|
40508
40946
|
} catch (error) {
|
|
40509
|
-
throw withOperationWarnings(error,
|
|
40947
|
+
throw withOperationWarnings(error, secret.warnings);
|
|
40510
40948
|
}
|
|
40511
|
-
outputWarnings(operationWarnings(
|
|
40949
|
+
outputWarnings(operationWarnings(secret.warnings));
|
|
40950
|
+
} catch (error) {
|
|
40951
|
+
return handleError(error);
|
|
40952
|
+
}
|
|
40953
|
+
});
|
|
40954
|
+
secrets.command("run [names...]").description("Run a command with secrets in its environment").usage("[options] [names...] -- <command> [args...]").option(
|
|
40955
|
+
"--map <NAME=ENV_VAR>",
|
|
40956
|
+
"Give secret NAME to the command as environment variable ENV_VAR (repeatable)",
|
|
40957
|
+
(value, previous) => [...previous ?? [], value]
|
|
40958
|
+
).option("--scope <scope>", "Logical secret scope").option("--space <name|uri>", "Target a non-default secrets space (short name or full URI)").option("--delegation <source>", "Delegation file path or imported profile name").option("--private-key <hex>", "Ethereum private key (or set TC_PRIVATE_KEY)").action(async (operands, options, cmd) => {
|
|
40959
|
+
try {
|
|
40960
|
+
const plan = secretRunPlan(cmd, operands, options.map ?? []);
|
|
40961
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
40962
|
+
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
40963
|
+
const targets = [];
|
|
40964
|
+
for (const name of new Set(plan.exports.map((entry) => entry.name))) {
|
|
40965
|
+
targets.push(await resolveSecretReadTarget(ctx, name, options));
|
|
40966
|
+
}
|
|
40967
|
+
const values = /* @__PURE__ */ new Map();
|
|
40968
|
+
let warnings = [];
|
|
40969
|
+
for (const target of targets) {
|
|
40970
|
+
const secret = await readSecret({ ctx, host: globalOpts.host, target, options, openKeyAcquisition });
|
|
40971
|
+
if (secret.value.includes("\0")) {
|
|
40972
|
+
throw new CLIError(
|
|
40973
|
+
"SECRET_NOT_EXPORTABLE",
|
|
40974
|
+
`Secret "${target.name}" contains a NUL character, so it can't be put in an environment variable. The command wasn't started.`,
|
|
40975
|
+
ExitCode.ERROR
|
|
40976
|
+
);
|
|
40977
|
+
}
|
|
40978
|
+
values.set(target.name, secret.value);
|
|
40979
|
+
warnings = mergeWarnings(warnings, secret.warnings ?? []);
|
|
40980
|
+
}
|
|
40981
|
+
outputWarnings(operationWarnings(warnings));
|
|
40982
|
+
await closeReplication();
|
|
40983
|
+
const env = { ...process.env };
|
|
40984
|
+
for (const variable of TC_CREDENTIAL_VARIABLES) delete env[variable];
|
|
40985
|
+
for (const entry of plan.exports) env[entry.variable] = values.get(entry.name);
|
|
40986
|
+
values.clear();
|
|
40987
|
+
exitAsCommand(await runCommand(plan.command, env), plan.command[0]);
|
|
40512
40988
|
} catch (error) {
|
|
40513
40989
|
return handleError(error);
|
|
40514
40990
|
}
|
|
@@ -40620,7 +41096,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
40620
41096
|
if (effectiveLocal(profile, options)) {
|
|
40621
41097
|
throw new CLIError(
|
|
40622
41098
|
"LOCAL_OWNER_PROFILE",
|
|
40623
|
-
`Profile "${ctx.profile}" uses a local key, which owns its secrets: store them with ${
|
|
41099
|
+
`Profile "${ctx.profile}" uses a local key, which owns its secrets: store them with ${CLI_NAME33} secrets put NAME --stdin instead of requesting them.`,
|
|
40624
41100
|
ExitCode.USAGE_ERROR
|
|
40625
41101
|
);
|
|
40626
41102
|
}
|
|
@@ -40743,7 +41219,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
40743
41219
|
|
|
40744
41220
|
// src/commands/share.ts
|
|
40745
41221
|
import { Option } from "commander";
|
|
40746
|
-
import { CLI_NAME as
|
|
41222
|
+
import { CLI_NAME as CLI_NAME34 } from "@tinycloud/operations/cli-name";
|
|
40747
41223
|
import { ProfileLockTimeoutError as ProfileLockTimeoutError3 } from "@tinycloud/operations/state";
|
|
40748
41224
|
init_errors();
|
|
40749
41225
|
|
|
@@ -40970,7 +41446,7 @@ async function writeShareOutput(directory, filename, bytes, force) {
|
|
|
40970
41446
|
init_profiles();
|
|
40971
41447
|
|
|
40972
41448
|
// src/share/html-folder.ts
|
|
40973
|
-
import { constants as constants2, existsSync } from "fs";
|
|
41449
|
+
import { constants as constants2, existsSync as existsSync2 } from "fs";
|
|
40974
41450
|
import { lstat as lstat11, open as open14, readdir as readdir7, realpath as realpath3, stat as stat7 } from "fs/promises";
|
|
40975
41451
|
import { basename as basename5, dirname as dirname9, isAbsolute, join as join19, relative as relativePath, resolve as resolve7, sep as sep4 } from "path";
|
|
40976
41452
|
|
|
@@ -41908,7 +42384,7 @@ function isRaceError(error) {
|
|
|
41908
42384
|
}
|
|
41909
42385
|
var PROC_FD = "/proc/self/fd";
|
|
41910
42386
|
async function linuxDescriptorPath(fd) {
|
|
41911
|
-
if (process.platform !== "linux" || !
|
|
42387
|
+
if (process.platform !== "linux" || !existsSync2(PROC_FD)) return void 0;
|
|
41912
42388
|
return realpath3(`${PROC_FD}/${fd}`);
|
|
41913
42389
|
}
|
|
41914
42390
|
async function parentChainIsRealDirectories(root, path) {
|
|
@@ -42204,7 +42680,7 @@ function shareCliError(error, operation = "publish") {
|
|
|
42204
42680
|
"SHARE_HISTORY_UNREADABLE",
|
|
42205
42681
|
"sender history cannot be read with the current session key",
|
|
42206
42682
|
1,
|
|
42207
|
-
{ hint: `Run \`${
|
|
42683
|
+
{ hint: `Run \`${CLI_NAME34} share history recover\` to move the unreadable file aside. The old file is kept, not deleted.` }
|
|
42208
42684
|
);
|
|
42209
42685
|
}
|
|
42210
42686
|
if (error instanceof SharePublishAuthorityError) {
|
|
@@ -42212,12 +42688,12 @@ function shareCliError(error, operation = "publish") {
|
|
|
42212
42688
|
const profileName2 = "profileName" in failure ? failure.profileName : void 0;
|
|
42213
42689
|
const localKey = "localKey" in failure && failure.localKey === true;
|
|
42214
42690
|
const profileHint = profileName2 === void 0 ? "" : `--profile ${profileName2} `;
|
|
42215
|
-
const loginHint = localKey ? `\`${
|
|
42691
|
+
const loginHint = localKey ? `\`${CLI_NAME34} ${profileHint}auth login --method local\`` : `\`${CLI_NAME34} ${profileHint}auth login --device --manifest builtin:share-publishing\` (or \`${CLI_NAME34} ${profileHint}enable share\`)`;
|
|
42216
42692
|
if (failure.kind === "caveated-session") {
|
|
42217
42693
|
const holder = profileName2 === void 0 ? "this session" : `profile ${profileName2}'s session`;
|
|
42218
42694
|
return new CLIError(
|
|
42219
42695
|
"PERMISSION_DENIED",
|
|
42220
|
-
`${holder} carries signed restrictions (caveats) on the authority an anyone-with-link share needs, so it cannot create the share link; nothing was shared. Approve Share publishing without restrictions on a new, dedicated profile (any unused name): \`${
|
|
42696
|
+
`${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_NAME34} init --name publisher --key-only && ${CLI_NAME34} --profile publisher enable share\``,
|
|
42221
42697
|
5
|
|
42222
42698
|
);
|
|
42223
42699
|
}
|
|
@@ -42318,7 +42794,7 @@ function shareCliError(error, operation = "publish") {
|
|
|
42318
42794
|
if (nodeCode === "ENOENT") return new CLIError("INVALID_ARGUMENT", "share input was not found", 2);
|
|
42319
42795
|
if (nodeCode === "EISDIR") return new CLIError("INVALID_ARGUMENT", "share input must be a Markdown file", 2);
|
|
42320
42796
|
const mapped = typeof nodeCode === "string" && known[nodeCode] !== void 0 ? known[nodeCode] : known[message];
|
|
42321
|
-
const classified = mapped === void 0 ? classifyShareFailure(error, `\`${
|
|
42797
|
+
const classified = mapped === void 0 ? classifyShareFailure(error, `\`${CLI_NAME34} auth login\``) : void 0;
|
|
42322
42798
|
if (classified !== void 0) return Object.assign(new CLIError(classified.code, classified.reason, classified.exitCode), { cause: error });
|
|
42323
42799
|
if (error instanceof TypeError) return new CLIError("INVALID_ARGUMENT", "share input is invalid", 2);
|
|
42324
42800
|
return new CLIError(mapped?.code ?? "INVALID_ARGUMENT", mapped ? mapped.code : "share operation failed", mapped?.exit ?? 2);
|
|
@@ -42728,7 +43204,7 @@ init_constants();
|
|
|
42728
43204
|
init_sdk();
|
|
42729
43205
|
init_host();
|
|
42730
43206
|
init_theme();
|
|
42731
|
-
import { CLI_NAME as
|
|
43207
|
+
import { CLI_NAME as CLI_NAME35 } from "@tinycloud/operations/cli-name";
|
|
42732
43208
|
import { ACCOUNT_REGISTRY_SPACE } from "@tinycloud/node-sdk";
|
|
42733
43209
|
import { randomBytes as randomBytes9 } from "crypto";
|
|
42734
43210
|
import { mkdir as mkdir7, writeFile as writeFile4 } from "fs/promises";
|
|
@@ -42775,7 +43251,7 @@ function registerSpaceCommand(program) {
|
|
|
42775
43251
|
} else {
|
|
42776
43252
|
const profile = await ProfileManager.getProfile(ctx.profile);
|
|
42777
43253
|
const cap = "tinycloud.kv:account:spaces/:get,list";
|
|
42778
|
-
const hint = resolveProfilePosture(profile) === "delegate-session" ? `Ask the space owner to grant it. Create the request with: ${
|
|
43254
|
+
const hint = resolveProfilePosture(profile) === "delegate-session" ? `Ask the space owner to grant it. Create the request with: ${CLI_NAME35} --profile ${ctx.profile} auth request --cap ${cap} --emit` : `Grant it to this session with: ${CLI_NAME35} --profile ${ctx.profile} auth request --grant --cap ${cap}. The default OpenKey owner sign-in covers only the default space, so it includes neither.`;
|
|
42779
43255
|
throw new CLIError(
|
|
42780
43256
|
"PERMISSION_DENIED",
|
|
42781
43257
|
`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.`,
|
|
@@ -42812,10 +43288,10 @@ function registerSpaceCommand(program) {
|
|
|
42812
43288
|
|
|
42813
43289
|
A delegate cannot host a space \u2014 only its owner (root authority) can. This
|
|
42814
43290
|
emits a tinycloud.host.request artifact naming the space and its owner so you
|
|
42815
|
-
can hand it to the owner; they run \`${
|
|
43291
|
+
can hand it to the owner; they run \`${CLI_NAME35} space host <name>\` and confirm.
|
|
42816
43292
|
|
|
42817
43293
|
If you ARE the owner of the resolved space, this refuses and tells you to host
|
|
42818
|
-
it directly with \`${
|
|
43294
|
+
it directly with \`${CLI_NAME35} space host <name>\` (no request needed).
|
|
42819
43295
|
`).action(async (name, options, cmd) => {
|
|
42820
43296
|
try {
|
|
42821
43297
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -42826,7 +43302,7 @@ it directly with \`${CLI_NAME34} space host <name>\` (no request needed).
|
|
|
42826
43302
|
if (await isRootAuthority(spaceId, ctx.profile)) {
|
|
42827
43303
|
throw new CLIError(
|
|
42828
43304
|
"ALREADY_ROOT_AUTHORITY",
|
|
42829
|
-
`You are the owner of ${spaceId}. Host it directly: ${
|
|
43305
|
+
`You are the owner of ${spaceId}. Host it directly: ${CLI_NAME35} space host ${spaceName}`,
|
|
42830
43306
|
ExitCode.USAGE_ERROR
|
|
42831
43307
|
);
|
|
42832
43308
|
}
|
|
@@ -42912,7 +43388,7 @@ init_sdk();
|
|
|
42912
43388
|
init_space();
|
|
42913
43389
|
init_host();
|
|
42914
43390
|
init_theme();
|
|
42915
|
-
import { CLI_NAME as
|
|
43391
|
+
import { CLI_NAME as CLI_NAME36 } from "@tinycloud/operations/cli-name";
|
|
42916
43392
|
import { writeFile as writeFile5 } from "fs/promises";
|
|
42917
43393
|
import { resolve as resolve8 } from "path";
|
|
42918
43394
|
async function dbHandle(node, dbName, spaceInput, profileName2) {
|
|
@@ -42936,12 +43412,12 @@ database for simple apps, or pass --db to target a named database. Pass
|
|
|
42936
43412
|
--space to target a non-primary space (e.g. the manifest "applications" space).
|
|
42937
43413
|
|
|
42938
43414
|
Common workflows:
|
|
42939
|
-
$ ${
|
|
42940
|
-
$ ${
|
|
42941
|
-
$ ${
|
|
42942
|
-
$ ${
|
|
42943
|
-
$ ${
|
|
42944
|
-
$ ${
|
|
43415
|
+
$ ${CLI_NAME36} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
|
|
43416
|
+
$ ${CLI_NAME36} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["ship docs"]'
|
|
43417
|
+
$ ${CLI_NAME36} sql query "SELECT id, body FROM notes ORDER BY id"
|
|
43418
|
+
$ ${CLI_NAME36} sql query "SELECT * FROM events WHERE type = ?" --db analytics --params '["signup"]'
|
|
43419
|
+
$ ${CLI_NAME36} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
|
|
43420
|
+
$ ${CLI_NAME36} sql export --db analytics --output analytics.db
|
|
42945
43421
|
|
|
42946
43422
|
Commands:
|
|
42947
43423
|
query Read rows with SELECT statements
|
|
@@ -42958,10 +43434,10 @@ Tips:
|
|
|
42958
43434
|
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", `
|
|
42959
43435
|
|
|
42960
43436
|
Examples:
|
|
42961
|
-
$ ${
|
|
42962
|
-
$ ${
|
|
42963
|
-
$ ${
|
|
42964
|
-
$ ${
|
|
43437
|
+
$ ${CLI_NAME36} sql query "SELECT * FROM notes ORDER BY id"
|
|
43438
|
+
$ ${CLI_NAME36} sql query "SELECT * FROM notes WHERE id = ?" --params '[42]'
|
|
43439
|
+
$ ${CLI_NAME36} sql query "SELECT count(*) AS total FROM events" --db analytics --json
|
|
43440
|
+
$ ${CLI_NAME36} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
|
|
42965
43441
|
|
|
42966
43442
|
Output:
|
|
42967
43443
|
Human output is formatted as a table. Piped output or --json returns
|
|
@@ -43002,11 +43478,11 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
|
|
|
43002
43478
|
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", `
|
|
43003
43479
|
|
|
43004
43480
|
Examples:
|
|
43005
|
-
$ ${
|
|
43006
|
-
$ ${
|
|
43007
|
-
$ ${
|
|
43008
|
-
$ ${
|
|
43009
|
-
$ ${
|
|
43481
|
+
$ ${CLI_NAME36} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
|
|
43482
|
+
$ ${CLI_NAME36} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["first note"]'
|
|
43483
|
+
$ ${CLI_NAME36} sql execute "UPDATE notes SET body = ? WHERE id = ?" --params '["edited", 1]'
|
|
43484
|
+
$ ${CLI_NAME36} sql execute "DROP TABLE old_notes" --db archive
|
|
43485
|
+
$ ${CLI_NAME36} sql execute "DELETE FROM conversation WHERE id = ?" --space applications --db xyz.tinycloud.listen/conversations --params '["abc"]'
|
|
43010
43486
|
|
|
43011
43487
|
Output:
|
|
43012
43488
|
Returns JSON with the changed row count and last inserted row id when available.
|
|
@@ -43035,9 +43511,9 @@ Output:
|
|
|
43035
43511
|
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", `
|
|
43036
43512
|
|
|
43037
43513
|
Examples:
|
|
43038
|
-
$ ${
|
|
43039
|
-
$ ${
|
|
43040
|
-
$ ${
|
|
43514
|
+
$ ${CLI_NAME36} sql export
|
|
43515
|
+
$ ${CLI_NAME36} sql export --db analytics --output analytics.db
|
|
43516
|
+
$ ${CLI_NAME36} sql export --space applications --db xyz.tinycloud.listen/conversations --output listen.db
|
|
43041
43517
|
|
|
43042
43518
|
Output:
|
|
43043
43519
|
Writes the database file locally and returns JSON with the path and size.
|
|
@@ -43070,19 +43546,19 @@ Output:
|
|
|
43070
43546
|
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", `
|
|
43071
43547
|
|
|
43072
43548
|
Examples:
|
|
43073
|
-
$ ${
|
|
43549
|
+
$ ${CLI_NAME36} sql copy --from-db com.tinycloud.conversation-sync/conversations \\
|
|
43074
43550
|
--to-db xyz.tinycloud.listen/conversations \\
|
|
43075
43551
|
--space applications --dry-run
|
|
43076
|
-
$ ${
|
|
43552
|
+
$ ${CLI_NAME36} sql copy --from-space applications --from-db com.foo/data \\
|
|
43077
43553
|
--to-space applications --to-db com.bar/data \\
|
|
43078
43554
|
--table conversation --table participant
|
|
43079
43555
|
|
|
43080
43556
|
Notes:
|
|
43081
43557
|
- Refuses to run when (resolved space, db) is identical for source and destination.
|
|
43082
|
-
- Does NOT create destination tables. Run the target app once (or use \`${
|
|
43558
|
+
- Does NOT create destination tables. Run the target app once (or use \`${CLI_NAME36} sql execute\`)
|
|
43083
43559
|
to materialize the schema before copying.
|
|
43084
43560
|
- One row at a time; suitable for small/medium datasets. Large copies should
|
|
43085
|
-
use \`${
|
|
43561
|
+
use \`${CLI_NAME36} sql export\` + bulk import.
|
|
43086
43562
|
- Authorization: the active session/delegation must cover sql/read on source
|
|
43087
43563
|
AND sql/write on destination. Otherwise the relevant operation will fail.
|
|
43088
43564
|
`).action(async (options, cmd) => {
|
|
@@ -43182,7 +43658,7 @@ init_errors();
|
|
|
43182
43658
|
init_formatter();
|
|
43183
43659
|
init_theme();
|
|
43184
43660
|
init_permissions();
|
|
43185
|
-
import { CLI_NAME as
|
|
43661
|
+
import { CLI_NAME as CLI_NAME37 } from "@tinycloud/operations/cli-name";
|
|
43186
43662
|
import {
|
|
43187
43663
|
NodeWasmBindings as NodeWasmBindings2
|
|
43188
43664
|
} from "@tinycloud/node-sdk";
|
|
@@ -43456,7 +43932,7 @@ function formatStatus(summary) {
|
|
|
43456
43932
|
lines.push(`Default profile: ${theme.value(summary.defaultProfile)}`);
|
|
43457
43933
|
lines.push("");
|
|
43458
43934
|
if (summary.profiles.length === 0) {
|
|
43459
|
-
lines.push(theme.muted(`No profiles configured. Run: ${
|
|
43935
|
+
lines.push(theme.muted(`No profiles configured. Run: ${CLI_NAME37} init`));
|
|
43460
43936
|
return `${lines.join("\n")}
|
|
43461
43937
|
`;
|
|
43462
43938
|
}
|
|
@@ -43545,11 +44021,11 @@ function plural(count, label) {
|
|
|
43545
44021
|
init_theme();
|
|
43546
44022
|
init_errors();
|
|
43547
44023
|
import { execSync } from "child_process";
|
|
43548
|
-
import { readFileSync as
|
|
44024
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
43549
44025
|
var PACKAGE_NAME = "@tinycloud/cli";
|
|
43550
44026
|
function getCurrentVersion() {
|
|
43551
44027
|
const pkg = JSON.parse(
|
|
43552
|
-
|
|
44028
|
+
readFileSync3(new URL("../package.json", import.meta.url), "utf-8")
|
|
43553
44029
|
);
|
|
43554
44030
|
return pkg.version;
|
|
43555
44031
|
}
|
|
@@ -43935,6 +44411,7 @@ function registerVarsCommand(program) {
|
|
|
43935
44411
|
// src/command-registry.ts
|
|
43936
44412
|
function registerTinyCloudCommands(program) {
|
|
43937
44413
|
registerInitCommand(program);
|
|
44414
|
+
registerOnboardCommand(program);
|
|
43938
44415
|
registerContextCommand(program);
|
|
43939
44416
|
registerAuthCommand(program);
|
|
43940
44417
|
registerEnableCommand(program);
|