@tinycloud/cli 1.2.6-beta.4 → 1.3.0-beta.6
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 +10 -3
- package/dist/index.js +41 -27
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +1413 -696
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +5 -5
- package/skills/tc-cli/REFERENCE.md +11 -5
package/README.md
CHANGED
|
@@ -171,9 +171,16 @@ whole space. Whole-space replication requires `kv-sync-root` support and
|
|
|
171
171
|
`--replication-allow-secrets`, because it includes `vault/`. Replication needs
|
|
172
172
|
unrestricted `get` and explicit `sync` on that folder or broader; an exact-key
|
|
173
173
|
grant never qualifies. Bare replica prefixes are rejected; use `notes/`.
|
|
174
|
-
Use `tc replica report` for
|
|
175
|
-
|
|
176
|
-
|
|
174
|
+
Use `tc replica report` for read-only aggregates of persisted replication events
|
|
175
|
+
and identity partitions. Live replica status is unavailable there. Report
|
|
176
|
+
`--purge` and `--clear-pending` refuse with exit 2 `REPORT_READ_ONLY`.
|
|
177
|
+
`tc replica reset --replica <name> --purge` is a partial alternative for a
|
|
178
|
+
named local replica only.
|
|
179
|
+
Flag-owned purge and pending clearing have no equivalent yet (TC-1309).
|
|
180
|
+
On macOS and Windows, `replica reset --purge` and `auth logout` with a legacy
|
|
181
|
+
`replicas/` tree refuse local removal rather than delete through an unpinned
|
|
182
|
+
path; leave that tree intact and retry on a supported platform.
|
|
183
|
+
`auth logout` removes both legacy replicas and the flag-owned
|
|
177
184
|
`replication/` root; `auth logout --keep-replicas` keeps both. B-int release
|
|
178
185
|
acceptance must verify the OpenKey `tc auth request --grant` fallback against
|
|
179
186
|
a real OpenKey using a test account and no production writes: approve the
|
package/dist/index.js
CHANGED
|
@@ -382,7 +382,7 @@ var init_storage = __esm({
|
|
|
382
382
|
// src/output/errors.ts
|
|
383
383
|
import { readFileSync, readdirSync } from "fs";
|
|
384
384
|
import { join } from "path";
|
|
385
|
-
import { ProfileDeletedError, ProfileLockTimeoutError } from "@tinycloud/operations/state";
|
|
385
|
+
import { ProfileDeletedError, ProfileLockTimeoutError, ProfileSymlinkError } from "@tinycloud/operations/state";
|
|
386
386
|
import { isSafeRecovery } from "@tinycloud/operations/recovery";
|
|
387
387
|
import { CLI_NAME as CLI_NAME3 } from "@tinycloud/operations/cli-name";
|
|
388
388
|
import {
|
|
@@ -421,6 +421,9 @@ function wrapError(error) {
|
|
|
421
421
|
if (error instanceof ProfileDeletedError) {
|
|
422
422
|
return new CLIError("PROFILE_NOT_FOUND", message);
|
|
423
423
|
}
|
|
424
|
+
if (error instanceof ProfileSymlinkError) {
|
|
425
|
+
return new CLIError("PROFILE_SYMLINK", message, ExitCode.ERROR);
|
|
426
|
+
}
|
|
424
427
|
if (verdict === void 0 && (message.includes("Not signed in") || message.includes("AUTH_EXPIRED") || message.includes("Session expired"))) {
|
|
425
428
|
return new CLIError("AUTH_REQUIRED", message, ExitCode.AUTH_REQUIRED);
|
|
426
429
|
}
|
|
@@ -5649,12 +5652,14 @@ import { resolveSecretsOwner } from "@tinycloud/operations/secret-request";
|
|
|
5649
5652
|
import {
|
|
5650
5653
|
additionalDelegationsPath as sharedAdditionalDelegationsPath,
|
|
5651
5654
|
authRequestsPath as sharedAuthRequestsPath,
|
|
5655
|
+
profilePath,
|
|
5652
5656
|
profileStoreMetadataPath,
|
|
5653
5657
|
readAdditionalDelegations,
|
|
5654
5658
|
readAuthRequests,
|
|
5655
5659
|
refuseWriteToDeletedProfile,
|
|
5656
5660
|
updateProfileStore,
|
|
5657
5661
|
withProfileLock,
|
|
5662
|
+
withPinnedProfileDirectory,
|
|
5658
5663
|
writeJsonAtomic
|
|
5659
5664
|
} from "@tinycloud/operations/state";
|
|
5660
5665
|
async function replayAdditionalDelegations(node, profile, options) {
|
|
@@ -5669,7 +5674,6 @@ async function replayAdditionalDelegations(node, profile, options) {
|
|
|
5669
5674
|
var init_permissions = __esm({
|
|
5670
5675
|
"src/lib/permissions.ts"() {
|
|
5671
5676
|
"use strict";
|
|
5672
|
-
init_constants();
|
|
5673
5677
|
init_storage2();
|
|
5674
5678
|
init_profiles();
|
|
5675
5679
|
init_errors();
|
|
@@ -7796,7 +7800,7 @@ var init_dist = __esm({
|
|
|
7796
7800
|
// src/lib/replication-config.ts
|
|
7797
7801
|
import { join as join5 } from "path";
|
|
7798
7802
|
import { CLI_NAME as CLI_NAME8 } from "@tinycloud/operations/cli-name";
|
|
7799
|
-
import { profilePath, withProfileLock as withProfileLock2 } from "@tinycloud/operations/state";
|
|
7803
|
+
import { profilePath as profilePath2, withProfileLock as withProfileLock2 } from "@tinycloud/operations/state";
|
|
7800
7804
|
import { sqliteReplicaStorage } from "@tinycloud/node-sdk";
|
|
7801
7805
|
function createCliReplicationConfig(profile, settings, output, enabled) {
|
|
7802
7806
|
if (!enabled || !settings || settings.prefixes.length === 0) return void 0;
|
|
@@ -7814,7 +7818,7 @@ function createCliReplicationConfig(profile, settings, output, enabled) {
|
|
|
7814
7818
|
);
|
|
7815
7819
|
}
|
|
7816
7820
|
}
|
|
7817
|
-
const root =
|
|
7821
|
+
const root = profilePath2(profile);
|
|
7818
7822
|
return {
|
|
7819
7823
|
enabled: true,
|
|
7820
7824
|
prefixes: settings.prefixes,
|
|
@@ -8300,17 +8304,21 @@ var init_host = __esm({
|
|
|
8300
8304
|
});
|
|
8301
8305
|
|
|
8302
8306
|
// src/config/profiles.ts
|
|
8303
|
-
import { chmod as chmod2, lstat, readdir as readdir2, rm as rm2
|
|
8307
|
+
import { chmod as chmod2, lstat, readdir as readdir2, rm as rm2 } from "fs/promises";
|
|
8304
8308
|
import { CLI_NAME as CLI_NAME11 } from "@tinycloud/operations/cli-name";
|
|
8305
8309
|
import { join as join6 } from "path";
|
|
8306
8310
|
import {
|
|
8307
|
-
|
|
8311
|
+
assertRealProfileDirectory,
|
|
8312
|
+
ProfileSymlinkError as ProfileSymlinkError2,
|
|
8313
|
+
namedProfilePath,
|
|
8314
|
+
profilePath as profilePath3,
|
|
8308
8315
|
readSession,
|
|
8309
8316
|
recordProfileDeletion,
|
|
8310
8317
|
refuseWriteToDeletedProfile as refuseWriteToDeletedProfile2,
|
|
8311
8318
|
removeSession,
|
|
8312
8319
|
withProfileLock as withProfileLock3,
|
|
8313
|
-
writeSession
|
|
8320
|
+
writeSession,
|
|
8321
|
+
withPinnedProfileDirectory as withPinnedProfileDirectory2
|
|
8314
8322
|
} from "@tinycloud/operations/state";
|
|
8315
8323
|
var ProfileManager;
|
|
8316
8324
|
var init_profiles = __esm({
|
|
@@ -8352,8 +8360,13 @@ var init_profiles = __esm({
|
|
|
8352
8360
|
}
|
|
8353
8361
|
/** Owner-only profile directory (0700), created or tightened before any write into it. */
|
|
8354
8362
|
static async ensureProfileDir(name) {
|
|
8363
|
+
const profileDir = profilePath3(name);
|
|
8364
|
+
if (profileDir !== join6(PROFILES_DIR, name)) {
|
|
8365
|
+
await chmod2(profileDir, PRIVATE_DIR_MODE);
|
|
8366
|
+
return profileDir;
|
|
8367
|
+
}
|
|
8355
8368
|
await _ProfileManager.ensureConfigDir();
|
|
8356
|
-
|
|
8369
|
+
await assertRealProfileDirectory(name);
|
|
8357
8370
|
await ensureDir(profileDir);
|
|
8358
8371
|
await chmod2(profileDir, PRIVATE_DIR_MODE);
|
|
8359
8372
|
return profileDir;
|
|
@@ -8382,8 +8395,7 @@ var init_profiles = __esm({
|
|
|
8382
8395
|
* Throws CLIError if the profile doesn't exist.
|
|
8383
8396
|
*/
|
|
8384
8397
|
static async getProfile(name) {
|
|
8385
|
-
const
|
|
8386
|
-
const profile = await readJson(profilePath3);
|
|
8398
|
+
const profile = await withPinnedProfileDirectory2(name, (directory) => directory ? readJson(join6(directory, "profile.json")) : Promise.resolve(null));
|
|
8387
8399
|
if (!profile) {
|
|
8388
8400
|
throw new CLIError(
|
|
8389
8401
|
"PROFILE_NOT_FOUND",
|
|
@@ -8403,13 +8415,13 @@ var init_profiles = __esm({
|
|
|
8403
8415
|
}
|
|
8404
8416
|
/** Removes profile.json under the profile lock (rollback of a profile a failed login created). */
|
|
8405
8417
|
static async removeProfileConfig(name) {
|
|
8406
|
-
await _ProfileManager.withLock(name, () => rm2(join6(
|
|
8418
|
+
await _ProfileManager.withLock(name, () => rm2(join6(profilePath3(name), "profile.json"), { force: true }));
|
|
8407
8419
|
}
|
|
8408
8420
|
/**
|
|
8409
8421
|
* Returns true if a profile directory exists.
|
|
8410
8422
|
*/
|
|
8411
8423
|
static async profileExists(name) {
|
|
8412
|
-
return fileExists(join6(
|
|
8424
|
+
return withPinnedProfileDirectory2(name, (directory) => directory ? fileExists(join6(directory, "profile.json")) : Promise.resolve(false));
|
|
8413
8425
|
}
|
|
8414
8426
|
/**
|
|
8415
8427
|
* Returns an array of profile directory names.
|
|
@@ -8433,7 +8445,7 @@ var init_profiles = __esm({
|
|
|
8433
8445
|
static async deleteProfile(name) {
|
|
8434
8446
|
let profileDir;
|
|
8435
8447
|
try {
|
|
8436
|
-
profileDir =
|
|
8448
|
+
profileDir = namedProfilePath(name);
|
|
8437
8449
|
} catch {
|
|
8438
8450
|
throw new CLIError(
|
|
8439
8451
|
"INVALID_PROFILE_NAME",
|
|
@@ -8458,23 +8470,21 @@ var init_profiles = __esm({
|
|
|
8458
8470
|
return;
|
|
8459
8471
|
}
|
|
8460
8472
|
await _ProfileManager.withLock(name, async () => {
|
|
8461
|
-
|
|
8462
|
-
for (const
|
|
8463
|
-
|
|
8473
|
+
const directory = profilePath3(name);
|
|
8474
|
+
for (const file of ["session.json", "key.json"]) await rm2(join6(directory, file), { force: true });
|
|
8475
|
+
for (const entry of await readdir2(directory)) {
|
|
8476
|
+
if (entry !== ".lock" && entry !== "profile.json") await rm2(join6(directory, entry), { recursive: true, force: true });
|
|
8464
8477
|
}
|
|
8465
|
-
await rm2(join6(
|
|
8478
|
+
await rm2(join6(directory, "profile.json"), { force: true });
|
|
8466
8479
|
await recordProfileDeletion(name);
|
|
8467
8480
|
});
|
|
8468
|
-
await rmdir(profileDir).catch((error) => {
|
|
8469
|
-
if (error.code !== "ENOENT" && error.code !== "ENOTEMPTY" && error.code !== "EEXIST") throw error;
|
|
8470
|
-
});
|
|
8471
8481
|
}
|
|
8472
8482
|
// ── Key management ──────────────────────────────────────────────────
|
|
8473
8483
|
/**
|
|
8474
8484
|
* Returns the parsed JWK for a profile, or null if no key exists.
|
|
8475
8485
|
*/
|
|
8476
8486
|
static async getKey(name) {
|
|
8477
|
-
return readJson(join6(
|
|
8487
|
+
return withPinnedProfileDirectory2(name, (directory) => directory ? readJson(join6(directory, "key.json")) : Promise.resolve(null));
|
|
8478
8488
|
}
|
|
8479
8489
|
/**
|
|
8480
8490
|
* Saves a JWK key under the profile lock (0600, in an owner-only profile
|
|
@@ -8489,7 +8499,7 @@ var init_profiles = __esm({
|
|
|
8489
8499
|
}
|
|
8490
8500
|
/** Removes key.json under the profile lock (rollback of a key a failed login created). */
|
|
8491
8501
|
static async removeKey(name) {
|
|
8492
|
-
await _ProfileManager.withLock(name, () => rm2(join6(
|
|
8502
|
+
await _ProfileManager.withLock(name, () => rm2(join6(profilePath3(name), "key.json"), { force: true }));
|
|
8493
8503
|
}
|
|
8494
8504
|
// ── Session management ──────────────────────────────────────────────
|
|
8495
8505
|
/**
|
|
@@ -8516,10 +8526,13 @@ var init_profiles = __esm({
|
|
|
8516
8526
|
* created or tightened to 0700.
|
|
8517
8527
|
*/
|
|
8518
8528
|
static async getCacheDir(name) {
|
|
8519
|
-
const
|
|
8520
|
-
|
|
8521
|
-
|
|
8522
|
-
|
|
8529
|
+
const alreadyPinned = profilePath3(name) !== namedProfilePath(name);
|
|
8530
|
+
return _ProfileManager.withLock(name, async () => {
|
|
8531
|
+
const cacheDir = join6(await _ProfileManager.ensureProfileDir(name), "cache");
|
|
8532
|
+
await ensureDir(cacheDir);
|
|
8533
|
+
await chmod2(cacheDir, PRIVATE_DIR_MODE);
|
|
8534
|
+
return alreadyPinned ? cacheDir : join6(namedProfilePath(name), "cache");
|
|
8535
|
+
});
|
|
8523
8536
|
}
|
|
8524
8537
|
// ── Resolution helpers ──────────────────────────────────────────────
|
|
8525
8538
|
/**
|
|
@@ -8543,7 +8556,8 @@ var init_profiles = __esm({
|
|
|
8543
8556
|
try {
|
|
8544
8557
|
profileConfig = await _ProfileManager.getProfile(profile);
|
|
8545
8558
|
profileHost = profileConfig.host;
|
|
8546
|
-
} catch {
|
|
8559
|
+
} catch (error) {
|
|
8560
|
+
if (error instanceof ProfileSymlinkError2) throw error;
|
|
8547
8561
|
}
|
|
8548
8562
|
const envReplication = process.env.TC_REPLICATION?.toLowerCase();
|
|
8549
8563
|
const envEnabled = envReplication === "1" || envReplication === "true" || envReplication === "on";
|