@tinycloud/cli 1.2.0-beta.1 → 1.2.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -1
- package/dist/index.js +81 -9
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +763 -61
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +4 -4
- package/skills/tc-cli/AUTH.md +19 -0
- package/skills/tc-cli/REFERENCE.md +53 -5
package/README.md
CHANGED
|
@@ -62,9 +62,15 @@ tc kv list
|
|
|
62
62
|
|
|
63
63
|
# Manage network-encrypted secrets on the default network
|
|
64
64
|
tc secrets network init
|
|
65
|
-
tc secrets put ANTHROPIC_API_KEY
|
|
65
|
+
printf '%s' "$ANTHROPIC_API_KEY" | tc secrets put ANTHROPIC_API_KEY --stdin
|
|
66
66
|
tc secrets get ANTHROPIC_API_KEY
|
|
67
67
|
|
|
68
|
+
# Ask the owner for a secret an agent needs, then wait for it
|
|
69
|
+
# (call TinyCloud's CLI by absolute path: a bare `tc` can be iproute2's /usr/sbin/tc)
|
|
70
|
+
TC_BIN="$(npm prefix --global)/bin/tc"
|
|
71
|
+
"$TC_BIN" --profile agent secrets request OPENAI_API_KEY --json # offline; prints the owner's link
|
|
72
|
+
"$TC_BIN" --profile agent secrets wait OPENAI_API_KEY --timeout 9m --json # 0 ready, 8 pending
|
|
73
|
+
|
|
68
74
|
# Manage spaces
|
|
69
75
|
tc space list
|
|
70
76
|
tc space create
|
|
@@ -105,6 +111,9 @@ tc delegation create --to did:pkh:eip155:1:0x...
|
|
|
105
111
|
| `tc secrets network show` | Show a secrets decryption network |
|
|
106
112
|
| `tc secrets network init` | Create or fetch the default secrets network |
|
|
107
113
|
| `tc secrets network grant` | Grant `tinycloud.encryption/decrypt` on a secrets network |
|
|
114
|
+
| `tc secrets request` | Print a Secret Manager link asking the owner to add secrets (offline) |
|
|
115
|
+
| `tc secrets wait` | Wait until secrets exist and are readable (never signs in; needs a stored session; exit 8 pending, 130 interrupted) |
|
|
116
|
+
| `tc secrets manage` | Print or open the Secret Manager page, `{url, opened}` |
|
|
108
117
|
| `tc vars` | Manage environment variables |
|
|
109
118
|
| `tc doctor` | Run diagnostic checks |
|
|
110
119
|
| `tc completion` | Generate shell completions |
|
package/dist/index.js
CHANGED
|
@@ -33,8 +33,12 @@ var init_constants = __esm({
|
|
|
33
33
|
PERMISSION_DENIED: 5,
|
|
34
34
|
NETWORK_ERROR: 6,
|
|
35
35
|
NODE_ERROR: 7,
|
|
36
|
+
/** `tc secrets wait` only: the deadline passed while a secret was still missing. */
|
|
37
|
+
PENDING: 8,
|
|
36
38
|
/** A write was refused because the owner's TinyCloud storage is full. */
|
|
37
|
-
STORAGE_FULL: 10
|
|
39
|
+
STORAGE_FULL: 10,
|
|
40
|
+
/** Interrupted by SIGINT/SIGTERM (`CANCELLED`). */
|
|
41
|
+
CANCELLED: 130
|
|
38
42
|
};
|
|
39
43
|
}
|
|
40
44
|
});
|
|
@@ -119,7 +123,7 @@ function warningLine(warnings) {
|
|
|
119
123
|
return `${subject} not used (${which}). Approve access again when a command needs it; remove the old grant with tc auth grants remove <cid>.`;
|
|
120
124
|
}
|
|
121
125
|
function outputError(code3, message, hint, details = {}) {
|
|
122
|
-
const { meta, warnings = [] } = details;
|
|
126
|
+
const { meta, warnings = [], setup, recovery, progress } = details;
|
|
123
127
|
if (!shouldOutputJson()) {
|
|
124
128
|
process.stderr.write(
|
|
125
129
|
`${theme.error("\u2717")} ${theme.label(code3)}: ${message}
|
|
@@ -138,6 +142,9 @@ function outputError(code3, message, hint, details = {}) {
|
|
|
138
142
|
error: { code: code3, message }
|
|
139
143
|
};
|
|
140
144
|
if (hint) payload.error.hint = hint;
|
|
145
|
+
if (recovery) payload.error.recovery = recovery;
|
|
146
|
+
if (setup) payload.error.setup = setup;
|
|
147
|
+
if (progress) payload.error.progress = progress;
|
|
141
148
|
if (meta) payload.error.meta = meta;
|
|
142
149
|
if (warnings.length > 0) payload.error.warnings = warnings;
|
|
143
150
|
process.stderr.write(JSON.stringify(payload, null, 2) + "\n");
|
|
@@ -267,6 +274,13 @@ var init_storage = __esm({
|
|
|
267
274
|
import { readFileSync, readdirSync } from "fs";
|
|
268
275
|
import { join } from "path";
|
|
269
276
|
import { ProfileDeletedError, ProfileLockTimeoutError } from "@tinycloud/operations/state";
|
|
277
|
+
import { isSafeRecovery } from "@tinycloud/operations/recovery";
|
|
278
|
+
import {
|
|
279
|
+
buildSecretRequestUrl,
|
|
280
|
+
isSafeRequestAccessRecovery,
|
|
281
|
+
isValidSecretName,
|
|
282
|
+
SECRET_REQUEST_MAX_NAMES
|
|
283
|
+
} from "@tinycloud/operations/secret-request";
|
|
270
284
|
import { authorizationVerdictOf, parseCapabilityResource, SERVICE_LONG_TO_SHORT, validatedCapabilityOf } from "@tinycloud/sdk-core";
|
|
271
285
|
function setActiveProfileName(name) {
|
|
272
286
|
activeProfileName = name;
|
|
@@ -342,12 +356,64 @@ function handleError(error) {
|
|
|
342
356
|
meta.resource = capability.resource;
|
|
343
357
|
meta.requiredAction = capability.requiredAction;
|
|
344
358
|
}
|
|
359
|
+
const deniedNames = cliError.metadata?.deniedNames;
|
|
360
|
+
const recovery = safeCliRecovery(cliError.metadata?.recovery, {
|
|
361
|
+
deniedNames: Array.isArray(deniedNames) && deniedNames.every((name) => typeof name === "string") ? deniedNames : []
|
|
362
|
+
});
|
|
363
|
+
const setup = cliError.code === "NOT_FOUND" || cliError.code === "PERMISSION_DENIED" ? safeCliSetup(cliError.metadata?.setup) : void 0;
|
|
364
|
+
const progress = cliError.code === "NETWORK_ERROR" || cliError.code === "CANCELLED" ? safeCliProgress(cliError.metadata?.progress) : void 0;
|
|
345
365
|
outputError(cliError.code, cliError.message, hint, {
|
|
366
|
+
...recovery ? { recovery } : {},
|
|
367
|
+
...setup ? { setup } : {},
|
|
368
|
+
...progress ? { progress } : {},
|
|
346
369
|
...Object.keys(meta).length ? { meta } : {},
|
|
347
370
|
warnings: mergeWarnings(drainWarnings(), operationWarnings(cliError.metadata?.warnings))
|
|
348
371
|
});
|
|
349
372
|
process.exit(cliError.exitCode);
|
|
350
373
|
}
|
|
374
|
+
function safeCliRecovery(value, context) {
|
|
375
|
+
if (typeof value !== "object" || value === null || Array.isArray(value) || !("action" in value)) return void 0;
|
|
376
|
+
const action = value.action;
|
|
377
|
+
switch (action) {
|
|
378
|
+
case "sign_in":
|
|
379
|
+
case "complete_migration":
|
|
380
|
+
return isSafeRecovery(value, action) ? { ...value } : void 0;
|
|
381
|
+
case "request_access":
|
|
382
|
+
return isSafeRequestAccessRecovery(value, context.deniedNames) ? { ...value, names: [...value.names] } : void 0;
|
|
383
|
+
default:
|
|
384
|
+
return void 0;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
function safeCliSetup(value) {
|
|
388
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
389
|
+
const setup = { ...value };
|
|
390
|
+
if (!Object.keys(setup).every((key) => SETUP_KEYS[key] === true)) return void 0;
|
|
391
|
+
const { url, names, scope, owner, action } = setup;
|
|
392
|
+
if (action !== "add_secret" || typeof url !== "string" || typeof owner !== "string") return void 0;
|
|
393
|
+
if (!Array.isArray(names) || !names.every((name) => typeof name === "string")) return void 0;
|
|
394
|
+
if (scope !== void 0 && typeof scope !== "string") return void 0;
|
|
395
|
+
try {
|
|
396
|
+
const link2 = buildSecretRequestUrl({ owner, names, ...scope !== void 0 ? { scope } : {} });
|
|
397
|
+
if (link2.url !== url || link2.owner !== owner || link2.names.length !== names.length) return void 0;
|
|
398
|
+
} catch {
|
|
399
|
+
return void 0;
|
|
400
|
+
}
|
|
401
|
+
return { url, names: [...names], ...scope !== void 0 ? { scope } : {}, owner, action };
|
|
402
|
+
}
|
|
403
|
+
function safeNameList(value) {
|
|
404
|
+
if (!Array.isArray(value) || value.length > SECRET_REQUEST_MAX_NAMES) return void 0;
|
|
405
|
+
if (!value.every((name) => typeof name === "string" && isValidSecretName(name))) return void 0;
|
|
406
|
+
if (new Set(value).size !== value.length) return void 0;
|
|
407
|
+
return [...value];
|
|
408
|
+
}
|
|
409
|
+
function safeCliProgress(value) {
|
|
410
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
411
|
+
if (!("present" in value) || !("pending" in value) || Object.keys(value).length !== 2) return void 0;
|
|
412
|
+
const present = safeNameList(value.present);
|
|
413
|
+
const pending = safeNameList(value.pending);
|
|
414
|
+
if (!present || !pending || present.some((name) => pending.includes(name))) return void 0;
|
|
415
|
+
return { present, pending };
|
|
416
|
+
}
|
|
351
417
|
function buildAuthHint(error) {
|
|
352
418
|
const capability = validatedCapabilityOf({ meta: error.metadata });
|
|
353
419
|
if (!capability) return void 0;
|
|
@@ -409,7 +475,7 @@ function buildNetworkHint() {
|
|
|
409
475
|
lines.push(`Or override per-command with --host or TC_HOST.`);
|
|
410
476
|
return lines.join("\n");
|
|
411
477
|
}
|
|
412
|
-
var activeProfileName, CLIError;
|
|
478
|
+
var activeProfileName, CLIError, SETUP_KEYS;
|
|
413
479
|
var init_errors = __esm({
|
|
414
480
|
"src/output/errors.ts"() {
|
|
415
481
|
"use strict";
|
|
@@ -428,6 +494,7 @@ var init_errors = __esm({
|
|
|
428
494
|
}
|
|
429
495
|
status;
|
|
430
496
|
};
|
|
497
|
+
SETUP_KEYS = { url: true, names: true, scope: true, owner: true, action: true };
|
|
431
498
|
}
|
|
432
499
|
});
|
|
433
500
|
|
|
@@ -5464,6 +5531,8 @@ import {
|
|
|
5464
5531
|
buildPermissionRequestArtifact,
|
|
5465
5532
|
isPermissionRequestArtifact
|
|
5466
5533
|
} from "@tinycloud/operations/artifacts";
|
|
5534
|
+
import { isLocalOwnerProfile } from "@tinycloud/operations/profile";
|
|
5535
|
+
import { resolveSecretsOwner } from "@tinycloud/operations/secret-request";
|
|
5467
5536
|
import {
|
|
5468
5537
|
additionalDelegationsPath as sharedAdditionalDelegationsPath,
|
|
5469
5538
|
authRequestsPath as sharedAuthRequestsPath,
|
|
@@ -5501,14 +5570,17 @@ var init_permissions = __esm({
|
|
|
5501
5570
|
});
|
|
5502
5571
|
|
|
5503
5572
|
// src/auth/session-expired.ts
|
|
5573
|
+
import { signInRecovery } from "@tinycloud/operations/recovery";
|
|
5504
5574
|
function signInAgainHint(profileName, posture) {
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5575
|
+
const delegate = posture === "delegate-session";
|
|
5576
|
+
const { command } = signInRecovery(
|
|
5577
|
+
profileName,
|
|
5578
|
+
delegate ? "delegate-session" : posture === "local-owner-key" ? "local-owner-key" : "owner-openkey"
|
|
5579
|
+
);
|
|
5580
|
+
if (command === void 0) {
|
|
5581
|
+
return delegate ? `Have the owner approve a new scoped login for profile ${JSON.stringify(profileName)} with tc auth login --method openkey --paste --manifest <manifest.json>.` : `Sign in again to profile ${JSON.stringify(profileName)} with tc auth login.`;
|
|
5510
5582
|
}
|
|
5511
|
-
return `
|
|
5583
|
+
return delegate ? `Have the owner approve a new scoped login: ${command}. Pass the owner's code on stdin, newline-terminated.` : `Sign in again with: ${command}`;
|
|
5512
5584
|
}
|
|
5513
5585
|
function sessionExpiredError(profileName, posture) {
|
|
5514
5586
|
return new CLIError(
|