@tinycloud/cli 0.9.1-beta.4 → 0.9.1-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 +8 -0
- package/dist/index.js +588 -161
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +391 -76
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +1 -1
package/dist/legacy-entry.js
CHANGED
|
@@ -218,6 +218,13 @@ function formatTable(headers, rows) {
|
|
|
218
218
|
);
|
|
219
219
|
return [headerLine, separator, ...dataLines].join("\n");
|
|
220
220
|
}
|
|
221
|
+
function output(data, humanFormatter) {
|
|
222
|
+
if (shouldOutputJson() || !humanFormatter) {
|
|
223
|
+
outputJson(data);
|
|
224
|
+
} else {
|
|
225
|
+
process.stdout.write(humanFormatter() + "\n");
|
|
226
|
+
}
|
|
227
|
+
}
|
|
221
228
|
function formatCheck(ok2, label, detail) {
|
|
222
229
|
const icon = ok2 === "warn" ? theme.warn("\u26A0") : ok2 ? theme.success("\u2713") : theme.error("\u2717");
|
|
223
230
|
const detailStr = detail ? ` ${theme.muted(`(${detail})`)}` : "";
|
|
@@ -6225,7 +6232,7 @@ function eddsa(Point2, cHash, eddsaOpts = {}) {
|
|
|
6225
6232
|
});
|
|
6226
6233
|
const { prehash } = eddsaOpts;
|
|
6227
6234
|
const { BASE, Fp: Fp2, Fn: Fn2 } = Point2;
|
|
6228
|
-
const
|
|
6235
|
+
const randomBytes6 = eddsaOpts.randomBytes || randomBytes;
|
|
6229
6236
|
const adjustScalarBytes2 = eddsaOpts.adjustScalarBytes || ((bytes) => bytes);
|
|
6230
6237
|
const domain = eddsaOpts.domain || ((data, ctx, phflag) => {
|
|
6231
6238
|
_abool2(phflag, "phflag");
|
|
@@ -6307,7 +6314,7 @@ function eddsa(Point2, cHash, eddsaOpts = {}) {
|
|
|
6307
6314
|
signature: 2 * _size,
|
|
6308
6315
|
seed: _size
|
|
6309
6316
|
};
|
|
6310
|
-
function randomSecretKey(seed =
|
|
6317
|
+
function randomSecretKey(seed = randomBytes6(lengths.seed)) {
|
|
6311
6318
|
return _abytes2(seed, lengths.seed, "seed");
|
|
6312
6319
|
}
|
|
6313
6320
|
function keygen(seed) {
|
|
@@ -8989,7 +8996,7 @@ function weierstrass(curveDef) {
|
|
|
8989
8996
|
function prepSig(msgHash, privateKey, opts = defaultSigOpts) {
|
|
8990
8997
|
if (["recovered", "canonical"].some((k) => k in opts))
|
|
8991
8998
|
throw new Error("sign() legacy options not supported");
|
|
8992
|
-
const { hash, randomBytes:
|
|
8999
|
+
const { hash, randomBytes: randomBytes6 } = CURVE;
|
|
8993
9000
|
let { lowS, prehash, extraEntropy: ent } = opts;
|
|
8994
9001
|
if (lowS == null)
|
|
8995
9002
|
lowS = true;
|
|
@@ -9001,7 +9008,7 @@ function weierstrass(curveDef) {
|
|
|
9001
9008
|
const d = normPrivateKeyToScalar(privateKey);
|
|
9002
9009
|
const seedArgs = [int2octets(d), int2octets(h1int)];
|
|
9003
9010
|
if (ent != null && ent !== false) {
|
|
9004
|
-
const e = ent === true ?
|
|
9011
|
+
const e = ent === true ? randomBytes6(Fp2.BYTES) : ent;
|
|
9005
9012
|
seedArgs.push(ensureBytes2("extraEntropy", e));
|
|
9006
9013
|
}
|
|
9007
9014
|
const seed = concatBytes3(...seedArgs);
|
|
@@ -14663,7 +14670,7 @@ var init_dist2 = __esm({
|
|
|
14663
14670
|
async handleErrorResponse(response, operation) {
|
|
14664
14671
|
const errorText = await response.text();
|
|
14665
14672
|
const errorBody = parseServiceErrorBody(errorText);
|
|
14666
|
-
const
|
|
14673
|
+
const errorCode2 = this.mapHttpStatusToErrorCode(
|
|
14667
14674
|
response.status,
|
|
14668
14675
|
errorBody.error
|
|
14669
14676
|
);
|
|
@@ -14681,7 +14688,7 @@ var init_dist2 = __esm({
|
|
|
14681
14688
|
if (resource) meta.resource = resource;
|
|
14682
14689
|
}
|
|
14683
14690
|
return err(
|
|
14684
|
-
serviceError(
|
|
14691
|
+
serviceError(errorCode2, message, "sql", { meta })
|
|
14685
14692
|
);
|
|
14686
14693
|
}
|
|
14687
14694
|
mapHttpStatusToErrorCode(status, serverError) {
|
|
@@ -15011,7 +15018,7 @@ var init_dist2 = __esm({
|
|
|
15011
15018
|
async handleErrorResponse(response, operation) {
|
|
15012
15019
|
const errorText = await response.text();
|
|
15013
15020
|
const errorBody = parseServiceErrorBody(errorText);
|
|
15014
|
-
const
|
|
15021
|
+
const errorCode2 = this.mapHttpStatusToErrorCode(
|
|
15015
15022
|
response.status,
|
|
15016
15023
|
errorBody.error
|
|
15017
15024
|
);
|
|
@@ -15029,7 +15036,7 @@ var init_dist2 = __esm({
|
|
|
15029
15036
|
if (resource) meta.resource = resource;
|
|
15030
15037
|
}
|
|
15031
15038
|
return err(
|
|
15032
|
-
serviceError(
|
|
15039
|
+
serviceError(errorCode2, message, "duckdb", { meta })
|
|
15033
15040
|
);
|
|
15034
15041
|
}
|
|
15035
15042
|
mapHttpStatusToErrorCode(status, serverError) {
|
|
@@ -18210,19 +18217,19 @@ function hex(bytes3) {
|
|
|
18210
18217
|
}
|
|
18211
18218
|
function base32Lower(bytes3) {
|
|
18212
18219
|
const alphabet2 = "abcdefghijklmnopqrstuvwxyz234567";
|
|
18213
|
-
let
|
|
18220
|
+
let output2 = "";
|
|
18214
18221
|
let buffer = 0;
|
|
18215
18222
|
let bits = 0;
|
|
18216
18223
|
for (const byte of bytes3) {
|
|
18217
18224
|
buffer = buffer << 8 | byte;
|
|
18218
18225
|
bits += 8;
|
|
18219
18226
|
while (bits >= 5) {
|
|
18220
|
-
|
|
18227
|
+
output2 += alphabet2[buffer >>> bits - 5 & 31];
|
|
18221
18228
|
bits -= 5;
|
|
18222
18229
|
}
|
|
18223
18230
|
}
|
|
18224
|
-
if (bits > 0)
|
|
18225
|
-
return
|
|
18231
|
+
if (bits > 0) output2 += alphabet2[buffer << 5 - bits & 31];
|
|
18232
|
+
return output2;
|
|
18226
18233
|
}
|
|
18227
18234
|
function assertCanonicalCid(cidString) {
|
|
18228
18235
|
const cid2 = CID.parse(cidString);
|
|
@@ -25367,13 +25374,13 @@ function concat2(arrays, length22) {
|
|
|
25367
25374
|
if (length22 == null) {
|
|
25368
25375
|
length22 = arrays.reduce((acc, curr) => acc + curr.length, 0);
|
|
25369
25376
|
}
|
|
25370
|
-
const
|
|
25377
|
+
const output2 = allocUnsafe(length22);
|
|
25371
25378
|
let offset = 0;
|
|
25372
25379
|
for (const arr of arrays) {
|
|
25373
|
-
|
|
25380
|
+
output2.set(arr, offset);
|
|
25374
25381
|
offset += arr.length;
|
|
25375
25382
|
}
|
|
25376
|
-
return asUint8Array(
|
|
25383
|
+
return asUint8Array(output2);
|
|
25377
25384
|
}
|
|
25378
25385
|
function createCodec(name2, prefix, encode52, decode72) {
|
|
25379
25386
|
return {
|
|
@@ -30963,6 +30970,19 @@ Open this URL in a browser to authenticate:
|
|
|
30963
30970
|
});
|
|
30964
30971
|
}
|
|
30965
30972
|
|
|
30973
|
+
// src/auth/device-auth.ts
|
|
30974
|
+
init_constants();
|
|
30975
|
+
init_profiles();
|
|
30976
|
+
import {
|
|
30977
|
+
createDecipheriv,
|
|
30978
|
+
createHash,
|
|
30979
|
+
createHmac,
|
|
30980
|
+
createPublicKey,
|
|
30981
|
+
diffieHellman,
|
|
30982
|
+
generateKeyPairSync,
|
|
30983
|
+
randomBytes as randomBytes3
|
|
30984
|
+
} from "crypto";
|
|
30985
|
+
|
|
30966
30986
|
// src/auth/local-key.ts
|
|
30967
30987
|
import { TCWSessionManager, importKey, initPanicHook } from "@tinycloud/node-sdk-wasm";
|
|
30968
30988
|
import { PrivateKeySigner } from "@tinycloud/node-sdk";
|
|
@@ -31033,6 +31053,251 @@ async function localKeySignIn(options) {
|
|
|
31033
31053
|
};
|
|
31034
31054
|
}
|
|
31035
31055
|
|
|
31056
|
+
// src/auth/device-auth.ts
|
|
31057
|
+
var SHARE_DEVICE_DELEGATION_SECONDS = 30 * 24 * 60 * 60;
|
|
31058
|
+
var SHARE_DEVICE_PERMISSIONS = [{
|
|
31059
|
+
service: "tinycloud.capabilities",
|
|
31060
|
+
space: "applications",
|
|
31061
|
+
path: "",
|
|
31062
|
+
actions: ["tinycloud.capabilities/read"]
|
|
31063
|
+
}];
|
|
31064
|
+
function digest2(value) {
|
|
31065
|
+
return createHash("sha256").update(value).digest("base64url");
|
|
31066
|
+
}
|
|
31067
|
+
function canonicalOrigin(value, label) {
|
|
31068
|
+
const url = new URL(value);
|
|
31069
|
+
const loopback = url.hostname === "127.0.0.1" || url.hostname === "localhost";
|
|
31070
|
+
if (url.origin !== value || url.protocol !== "https:" && !(loopback && url.protocol === "http:")) {
|
|
31071
|
+
throw new Error(`${label} must be a canonical HTTPS origin`);
|
|
31072
|
+
}
|
|
31073
|
+
return value;
|
|
31074
|
+
}
|
|
31075
|
+
function jsonEqual(left, right) {
|
|
31076
|
+
const canonical = (value) => Array.isArray(value) ? value.map(canonical) : value && typeof value === "object" ? Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, entry]) => [key, canonical(entry)])) : value;
|
|
31077
|
+
return JSON.stringify(canonical(left)) === JSON.stringify(canonical(right));
|
|
31078
|
+
}
|
|
31079
|
+
function validateStart(value) {
|
|
31080
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("OpenKey returned an invalid device authorization response");
|
|
31081
|
+
const result = value;
|
|
31082
|
+
if (typeof result.transactionId !== "string" || !/^[A-Za-z0-9_-]{20,}$/.test(result.transactionId) || typeof result.userCode !== "string" || !/^[A-Z2-9]{4}-[A-Z2-9]{4}$/.test(result.userCode) || typeof result.verificationUri !== "string" || typeof result.verificationUriComplete !== "string" || !Number.isSafeInteger(result.expiresIn) || Number(result.expiresIn) < 60 || !Number.isSafeInteger(result.interval) || Number(result.interval) < 1) throw new Error("OpenKey returned an invalid device authorization response");
|
|
31083
|
+
canonicalOrigin(new URL(result.verificationUri).origin, "verification URI");
|
|
31084
|
+
if (new URL(result.verificationUriComplete).origin !== new URL(result.verificationUri).origin) {
|
|
31085
|
+
throw new Error("OpenKey returned an invalid verification URI");
|
|
31086
|
+
}
|
|
31087
|
+
return result;
|
|
31088
|
+
}
|
|
31089
|
+
async function responseJson(response) {
|
|
31090
|
+
try {
|
|
31091
|
+
return await response.json();
|
|
31092
|
+
} catch {
|
|
31093
|
+
throw new Error(`OpenKey device authorization failed (HTTP ${response.status})`);
|
|
31094
|
+
}
|
|
31095
|
+
}
|
|
31096
|
+
function errorCode(value) {
|
|
31097
|
+
return value && typeof value === "object" && typeof value.error === "string" ? value.error : void 0;
|
|
31098
|
+
}
|
|
31099
|
+
function publicSessionJwk(value) {
|
|
31100
|
+
const publicJwk = publicJwkForDelegation(value);
|
|
31101
|
+
const record = publicJwk;
|
|
31102
|
+
if (record.kty !== "OKP" || record.crv !== "Ed25519" || typeof record.x !== "string") {
|
|
31103
|
+
throw new Error("CLI session key is not a public Ed25519 JWK");
|
|
31104
|
+
}
|
|
31105
|
+
return publicJwk;
|
|
31106
|
+
}
|
|
31107
|
+
function publicRelayJwk(value) {
|
|
31108
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("OpenKey returned an invalid relay key");
|
|
31109
|
+
const jwk = value;
|
|
31110
|
+
if (jwk.kty !== "EC" || jwk.crv !== "P-256" || typeof jwk.x !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(jwk.x) || typeof jwk.y !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(jwk.y) || "d" in jwk) throw new Error("OpenKey returned an invalid relay key");
|
|
31111
|
+
return { kty: "EC", crv: "P-256", x: jwk.x, y: jwk.y };
|
|
31112
|
+
}
|
|
31113
|
+
function decodeCanonicalBase64Url(value, label) {
|
|
31114
|
+
if (typeof value !== "string" || !/^[A-Za-z0-9_-]+$/.test(value)) throw new Error(`OpenKey returned an invalid ${label}`);
|
|
31115
|
+
const decoded = Buffer.from(value, "base64url");
|
|
31116
|
+
if (decoded.toString("base64url") !== value) throw new Error(`OpenKey returned an invalid ${label}`);
|
|
31117
|
+
return decoded;
|
|
31118
|
+
}
|
|
31119
|
+
function deriveRelayKey(sharedSecret, transactionId) {
|
|
31120
|
+
const extracted = createHmac("sha256", Buffer.from(transactionId)).update(sharedSecret).digest();
|
|
31121
|
+
return createHmac("sha256", extracted).update(Buffer.from("openkey-device-relay-v1")).update(Buffer.from([1])).digest();
|
|
31122
|
+
}
|
|
31123
|
+
function decryptRelayResult(envelope, transactionId, privateKey) {
|
|
31124
|
+
if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) throw new Error("OpenKey returned an invalid encrypted relay result");
|
|
31125
|
+
const relay = envelope;
|
|
31126
|
+
if (relay.version !== 1 || relay.algorithm !== "ECDH-P256-A256GCM") throw new Error("OpenKey returned an unsupported encrypted relay result");
|
|
31127
|
+
const peer = createPublicKey({ key: publicRelayJwk(relay.ephemeralPublicJwk), format: "jwk" });
|
|
31128
|
+
const nonce = decodeCanonicalBase64Url(relay.nonce, "relay nonce");
|
|
31129
|
+
const ciphertext = decodeCanonicalBase64Url(relay.ciphertext, "relay ciphertext");
|
|
31130
|
+
if (nonce.length !== 12 || ciphertext.length <= 16) throw new Error("OpenKey returned an invalid encrypted relay result");
|
|
31131
|
+
const sharedSecret = diffieHellman({ privateKey, publicKey: peer });
|
|
31132
|
+
const key = deriveRelayKey(sharedSecret, transactionId);
|
|
31133
|
+
const decipher = createDecipheriv("aes-256-gcm", key, nonce);
|
|
31134
|
+
decipher.setAAD(Buffer.from(transactionId));
|
|
31135
|
+
decipher.setAuthTag(ciphertext.subarray(ciphertext.length - 16));
|
|
31136
|
+
let value;
|
|
31137
|
+
try {
|
|
31138
|
+
value = JSON.parse(Buffer.concat([decipher.update(ciphertext.subarray(0, -16)), decipher.final()]).toString("utf8"));
|
|
31139
|
+
} catch {
|
|
31140
|
+
throw new Error("OpenKey returned an unreadable encrypted relay result");
|
|
31141
|
+
}
|
|
31142
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("OpenKey returned an invalid encrypted relay result");
|
|
31143
|
+
return value;
|
|
31144
|
+
}
|
|
31145
|
+
function assertShareDelegationPermissions(value) {
|
|
31146
|
+
if (!Array.isArray(value) || value.length !== 1) throw new Error("OpenKey returned a delegation outside the requested Share scope");
|
|
31147
|
+
const permission = value[0];
|
|
31148
|
+
if (!permission || permission.service !== "tinycloud.capabilities" && permission.service !== "capabilities" || permission.space !== "applications" && !(typeof permission.space === "string" && permission.space.endsWith(":applications")) || permission.path !== "" || !Array.isArray(permission.actions) || permission.actions.length !== 1 || permission.actions[0] !== "tinycloud.capabilities/read") throw new Error("OpenKey returned a delegation outside the requested Share scope");
|
|
31149
|
+
}
|
|
31150
|
+
function assertApprovedBinding(input) {
|
|
31151
|
+
if (input.binding.transactionId !== input.transactionId || input.binding.sessionDid !== input.sessionDid || input.binding.nodeOrigin !== input.nodeOrigin || input.binding.shareOrigin !== input.shareOrigin || !jsonEqual(input.binding.permissions, SHARE_DEVICE_PERMISSIONS)) throw new Error("OpenKey returned a delegation with the wrong device binding");
|
|
31152
|
+
const expiresAt = Date.parse(input.binding.delegationExpiresAt);
|
|
31153
|
+
if (!Number.isFinite(expiresAt) || expiresAt <= Date.now() || expiresAt > Date.now() + SHARE_DEVICE_DELEGATION_SECONDS * 1e3 + 3e4) {
|
|
31154
|
+
throw new Error("OpenKey returned a delegation outside the requested expiry window");
|
|
31155
|
+
}
|
|
31156
|
+
if (input.delegation.verificationMethod !== input.sessionDid) {
|
|
31157
|
+
throw new Error("OpenKey returned a delegation for a different CLI session DID");
|
|
31158
|
+
}
|
|
31159
|
+
assertShareDelegationPermissions(input.delegation.permissions);
|
|
31160
|
+
const delegationExpiryValue = input.delegation.expiresAt ?? input.delegation.expirationTime ?? input.delegation.expiry;
|
|
31161
|
+
const delegationExpiresAt = typeof delegationExpiryValue === "string" ? Date.parse(delegationExpiryValue) : Number.NaN;
|
|
31162
|
+
if (!Number.isFinite(delegationExpiresAt) || delegationExpiresAt !== expiresAt) {
|
|
31163
|
+
throw new Error("OpenKey returned a delegation outside the approved expiry window");
|
|
31164
|
+
}
|
|
31165
|
+
if (!input.delegation.jwk || typeof input.delegation.jwk !== "object" || !jsonEqual(publicSessionJwk(input.delegation.jwk), input.publicJwk)) {
|
|
31166
|
+
throw new Error("OpenKey returned a delegation for a different CLI session key");
|
|
31167
|
+
}
|
|
31168
|
+
const invalid = validateDelegationCallbackPayload(input.delegation);
|
|
31169
|
+
if (invalid) throw new Error(`OpenKey returned an invalid delegation: ${invalid}`);
|
|
31170
|
+
}
|
|
31171
|
+
async function acquireShareDeviceDelegation(input) {
|
|
31172
|
+
const openkeyHost = canonicalOrigin(input.openkeyHost ?? DEFAULT_OPENKEY_HOST, "OpenKey host");
|
|
31173
|
+
const nodeOrigin = canonicalOrigin(input.nodeOrigin, "TinyCloud node origin");
|
|
31174
|
+
const shareOrigin = canonicalOrigin(input.shareOrigin, "Share origin");
|
|
31175
|
+
const fetchFn = input.fetchFn ?? globalThis.fetch;
|
|
31176
|
+
const deviceSecret = randomBytes3(32).toString("base64url");
|
|
31177
|
+
const codeVerifier = randomBytes3(32).toString("base64url");
|
|
31178
|
+
const relayKeys = generateKeyPairSync("ec", { namedCurve: "prime256v1" });
|
|
31179
|
+
const relayPublicJwk = publicRelayJwk(relayKeys.publicKey.export({ format: "jwk" }));
|
|
31180
|
+
const publicJwk = publicSessionJwk(input.jwk);
|
|
31181
|
+
const startResponse = await fetchFn(`${openkeyHost}/api/device-authorizations`, {
|
|
31182
|
+
method: "POST",
|
|
31183
|
+
credentials: "omit",
|
|
31184
|
+
redirect: "error",
|
|
31185
|
+
referrerPolicy: "no-referrer",
|
|
31186
|
+
headers: { accept: "application/json", "content-type": "application/json" },
|
|
31187
|
+
body: JSON.stringify({
|
|
31188
|
+
deviceSecretHash: digest2(deviceSecret),
|
|
31189
|
+
codeChallenge: digest2(codeVerifier),
|
|
31190
|
+
relayPublicJwk,
|
|
31191
|
+
sessionDid: input.sessionDid,
|
|
31192
|
+
publicJwk,
|
|
31193
|
+
permissions: SHARE_DEVICE_PERMISSIONS,
|
|
31194
|
+
nodeOrigin,
|
|
31195
|
+
shareOrigin,
|
|
31196
|
+
delegationTtlSeconds: SHARE_DEVICE_DELEGATION_SECONDS
|
|
31197
|
+
})
|
|
31198
|
+
});
|
|
31199
|
+
const startValue = await responseJson(startResponse);
|
|
31200
|
+
if (!startResponse.ok) throw new Error(`OpenKey device authorization failed: ${errorCode(startValue) ?? startResponse.status}`);
|
|
31201
|
+
const started = validateStart(startValue);
|
|
31202
|
+
(input.emitInstructions ?? ((value) => {
|
|
31203
|
+
process.stderr.write(`OpenKey device authorization
|
|
31204
|
+
Visit: ${value.verificationUri}
|
|
31205
|
+
Code: ${value.userCode}
|
|
31206
|
+
|
|
31207
|
+
Waiting for approval\u2026
|
|
31208
|
+
`);
|
|
31209
|
+
}))({ verificationUri: started.verificationUri, verificationUriComplete: started.verificationUriComplete, userCode: started.userCode });
|
|
31210
|
+
const deadline = Date.now() + started.expiresIn * 1e3;
|
|
31211
|
+
let interval = started.interval;
|
|
31212
|
+
const wait = input.wait ?? ((milliseconds) => new Promise((resolve4) => setTimeout(resolve4, milliseconds)));
|
|
31213
|
+
while (Date.now() < deadline) {
|
|
31214
|
+
await wait(interval * 1e3);
|
|
31215
|
+
const response = await fetchFn(`${openkeyHost}/api/device-authorizations/token`, {
|
|
31216
|
+
method: "POST",
|
|
31217
|
+
credentials: "omit",
|
|
31218
|
+
redirect: "error",
|
|
31219
|
+
referrerPolicy: "no-referrer",
|
|
31220
|
+
headers: { accept: "application/json", "content-type": "application/json" },
|
|
31221
|
+
body: JSON.stringify({ transactionId: started.transactionId, deviceSecret, codeVerifier })
|
|
31222
|
+
});
|
|
31223
|
+
const value = await responseJson(response);
|
|
31224
|
+
const code3 = errorCode(value);
|
|
31225
|
+
if (response.status === 429 && code3 === "slow_down") {
|
|
31226
|
+
interval += 1;
|
|
31227
|
+
continue;
|
|
31228
|
+
}
|
|
31229
|
+
if (!response.ok) throw new Error(`OpenKey device authorization failed: ${code3 ?? response.status}`);
|
|
31230
|
+
const result = value;
|
|
31231
|
+
if (result.status === "pending") {
|
|
31232
|
+
interval = Math.max(interval, result.interval);
|
|
31233
|
+
continue;
|
|
31234
|
+
}
|
|
31235
|
+
if (result.status !== "approved" || !result.relay || !result.binding) {
|
|
31236
|
+
throw new Error("OpenKey returned an invalid device authorization result");
|
|
31237
|
+
}
|
|
31238
|
+
const delegation = decryptRelayResult(result.relay, started.transactionId, relayKeys.privateKey);
|
|
31239
|
+
assertApprovedBinding({ binding: result.binding, transactionId: started.transactionId, sessionDid: input.sessionDid, nodeOrigin, shareOrigin, publicJwk, delegation });
|
|
31240
|
+
return delegation;
|
|
31241
|
+
}
|
|
31242
|
+
throw new Error("OpenKey device authorization expired before approval");
|
|
31243
|
+
}
|
|
31244
|
+
function mergePrivateJwkIntoSession(session, key) {
|
|
31245
|
+
const sessionJwk = session.jwk;
|
|
31246
|
+
if (!sessionJwk || typeof sessionJwk !== "object") return session;
|
|
31247
|
+
const sessionJwkRecord = sessionJwk;
|
|
31248
|
+
if (typeof sessionJwkRecord.d === "string" && sessionJwkRecord.d.length > 0) return session;
|
|
31249
|
+
const privateParameter = key.d;
|
|
31250
|
+
if (typeof privateParameter !== "string" || privateParameter.length === 0) return session;
|
|
31251
|
+
return { ...session, jwk: { ...sessionJwkRecord, d: privateParameter } };
|
|
31252
|
+
}
|
|
31253
|
+
async function ensureShareDeviceAuthorization(input) {
|
|
31254
|
+
let profile = await ProfileManager.getProfile(input.profileName).catch(() => null);
|
|
31255
|
+
if (profile?.authMethod === "local" && input.allowReplaceLocal !== true) {
|
|
31256
|
+
throw new Error("This profile uses a local owner key. Run `tc auth login --device` explicitly to replace its authentication posture.");
|
|
31257
|
+
}
|
|
31258
|
+
let key = await ProfileManager.getKey(input.profileName);
|
|
31259
|
+
if (!key) {
|
|
31260
|
+
const generated = generateKey2();
|
|
31261
|
+
key = generated.jwk;
|
|
31262
|
+
await ProfileManager.setKey(input.profileName, key);
|
|
31263
|
+
}
|
|
31264
|
+
const sessionDid = keyToDID(key);
|
|
31265
|
+
profile = {
|
|
31266
|
+
...profile,
|
|
31267
|
+
name: input.profileName,
|
|
31268
|
+
host: input.nodeOrigin,
|
|
31269
|
+
chainId: profile?.chainId ?? DEFAULT_CHAIN_ID,
|
|
31270
|
+
spaceName: profile?.spaceName ?? "applications",
|
|
31271
|
+
did: sessionDid,
|
|
31272
|
+
sessionDid,
|
|
31273
|
+
createdAt: profile?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
31274
|
+
posture: "owner-openkey",
|
|
31275
|
+
operatorType: profile?.operatorType ?? "human",
|
|
31276
|
+
authMethod: "openkey",
|
|
31277
|
+
openkeyHost: input.openkeyHost ?? profile?.openkeyHost
|
|
31278
|
+
};
|
|
31279
|
+
await ProfileManager.setProfile(input.profileName, profile);
|
|
31280
|
+
const delegation = await acquireShareDeviceDelegation({
|
|
31281
|
+
sessionDid,
|
|
31282
|
+
jwk: key,
|
|
31283
|
+
nodeOrigin: input.nodeOrigin,
|
|
31284
|
+
shareOrigin: input.shareOrigin,
|
|
31285
|
+
openkeyHost: input.openkeyHost ?? profile.openkeyHost,
|
|
31286
|
+
fetchFn: input.fetchFn,
|
|
31287
|
+
emitInstructions: input.emitInstructions,
|
|
31288
|
+
wait: input.wait
|
|
31289
|
+
});
|
|
31290
|
+
const session = mergePrivateJwkIntoSession(delegation, key);
|
|
31291
|
+
await ProfileManager.setSession(input.profileName, session);
|
|
31292
|
+
const updatedProfile = {
|
|
31293
|
+
...profile,
|
|
31294
|
+
ownerDid: typeof session.ownerDid === "string" ? session.ownerDid : profile.ownerDid,
|
|
31295
|
+
spaceId: typeof session.spaceId === "string" ? session.spaceId : profile.spaceId
|
|
31296
|
+
};
|
|
31297
|
+
await ProfileManager.setProfile(input.profileName, updatedProfile);
|
|
31298
|
+
return { profile: updatedProfile, delegation: session };
|
|
31299
|
+
}
|
|
31300
|
+
|
|
31036
31301
|
// src/commands/auth.ts
|
|
31037
31302
|
init_theme();
|
|
31038
31303
|
function resolveOpenKeyHost(profile) {
|
|
@@ -31040,7 +31305,7 @@ function resolveOpenKeyHost(profile) {
|
|
|
31040
31305
|
}
|
|
31041
31306
|
async function promptAuthMethod() {
|
|
31042
31307
|
if (!isInteractive()) {
|
|
31043
|
-
return "
|
|
31308
|
+
return "openkey";
|
|
31044
31309
|
}
|
|
31045
31310
|
const rl = createInterface2({
|
|
31046
31311
|
input: process.stdin,
|
|
@@ -31066,12 +31331,20 @@ async function promptAuthMethod() {
|
|
|
31066
31331
|
}
|
|
31067
31332
|
function registerAuthCommand(program) {
|
|
31068
31333
|
const auth = program.command("auth").description("Authentication management");
|
|
31069
|
-
auth.command("login").description("Authenticate with TinyCloud").option("--paste", "Use manual paste mode instead of browser callback").option("--no-popup", "Print the OpenKey URL without opening a browser").option("--method <method>", "Authentication method: local or openkey").action(async (options, cmd) => {
|
|
31334
|
+
auth.command("login").description("Authenticate with TinyCloud").option("--device", "Use OpenKey device authorization (recommended for remote/headless use)").option("--paste", "Use manual paste mode instead of browser callback").option("--no-popup", "Print the OpenKey URL without opening a browser").option("--method <method>", "Authentication method: local or openkey").action(async (options, cmd) => {
|
|
31070
31335
|
try {
|
|
31336
|
+
if (options.device && options.paste) {
|
|
31337
|
+
throw new CLIError("INVALID_ARGUMENT", "--device and --paste are mutually exclusive.", ExitCode.USAGE_ERROR);
|
|
31338
|
+
}
|
|
31071
31339
|
const globalOpts = cmd.optsWithGlobals();
|
|
31072
31340
|
const ctx = await ProfileManager.resolveContext(globalOpts);
|
|
31073
31341
|
let method;
|
|
31074
|
-
if (options.method) {
|
|
31342
|
+
if (options.device && options.method === "local") {
|
|
31343
|
+
throw new CLIError("INVALID_ARGUMENT", "--device requires --method openkey.", ExitCode.USAGE_ERROR);
|
|
31344
|
+
}
|
|
31345
|
+
if (options.device) {
|
|
31346
|
+
method = "openkey";
|
|
31347
|
+
} else if (options.method) {
|
|
31075
31348
|
if (options.method !== "local" && options.method !== "openkey") {
|
|
31076
31349
|
throw new CLIError(
|
|
31077
31350
|
"INVALID_METHOD",
|
|
@@ -31088,7 +31361,8 @@ function registerAuthCommand(program) {
|
|
|
31088
31361
|
} else {
|
|
31089
31362
|
await handleOpenKeyAuth(ctx.profile, ctx.host, {
|
|
31090
31363
|
paste: options.paste,
|
|
31091
|
-
noPopup: options.popup === false
|
|
31364
|
+
noPopup: options.popup === false,
|
|
31365
|
+
device: options.device === true || !isInteractive() && options.paste !== true
|
|
31092
31366
|
});
|
|
31093
31367
|
}
|
|
31094
31368
|
} catch (error) {
|
|
@@ -31676,15 +31950,15 @@ async function importRequestBoundDelegation(ctx, artifact) {
|
|
|
31676
31950
|
);
|
|
31677
31951
|
switch (result.status) {
|
|
31678
31952
|
case "ok": {
|
|
31679
|
-
const
|
|
31953
|
+
const output2 = result.output;
|
|
31680
31954
|
outputJson({
|
|
31681
31955
|
imported: true,
|
|
31682
|
-
activated:
|
|
31956
|
+
activated: output2.activated,
|
|
31683
31957
|
kind: "tinycloud.auth.delegation",
|
|
31684
31958
|
requestId: typeof artifact === "object" && artifact !== null && typeof artifact.requestId === "string" ? artifact.requestId : null,
|
|
31685
|
-
delegationCid:
|
|
31686
|
-
permissions:
|
|
31687
|
-
expiry:
|
|
31959
|
+
delegationCid: output2.cid,
|
|
31960
|
+
permissions: output2.effectivePermissions,
|
|
31961
|
+
expiry: output2.expiry
|
|
31688
31962
|
});
|
|
31689
31963
|
return;
|
|
31690
31964
|
}
|
|
@@ -32183,6 +32457,24 @@ async function handleLocalAuth(profileName, host, options = {}) {
|
|
|
32183
32457
|
return { profile: updatedProfile, sessionResult };
|
|
32184
32458
|
}
|
|
32185
32459
|
async function handleOpenKeyAuth(profileName, host, options = {}) {
|
|
32460
|
+
if (options.device) {
|
|
32461
|
+
const result = await ensureShareDeviceAuthorization({
|
|
32462
|
+
profileName,
|
|
32463
|
+
nodeOrigin: host,
|
|
32464
|
+
shareOrigin: "https://share.tinycloud.xyz",
|
|
32465
|
+
openkeyHost: process.env.TC_OPENKEY_HOST,
|
|
32466
|
+
allowReplaceLocal: true
|
|
32467
|
+
});
|
|
32468
|
+
outputJson({
|
|
32469
|
+
authenticated: true,
|
|
32470
|
+
profile: profileName,
|
|
32471
|
+
did: result.profile.did,
|
|
32472
|
+
spaceId: result.profile.spaceId ?? null,
|
|
32473
|
+
authMethod: "openkey",
|
|
32474
|
+
mode: "device"
|
|
32475
|
+
});
|
|
32476
|
+
return;
|
|
32477
|
+
}
|
|
32186
32478
|
const { profile, delegationData } = await refreshOpenKeySession(profileName, host, options);
|
|
32187
32479
|
outputJson({
|
|
32188
32480
|
authenticated: true,
|
|
@@ -32192,25 +32484,6 @@ async function handleOpenKeyAuth(profileName, host, options = {}) {
|
|
|
32192
32484
|
authMethod: "openkey"
|
|
32193
32485
|
});
|
|
32194
32486
|
}
|
|
32195
|
-
function mergePrivateJwkIntoSession(session, key) {
|
|
32196
|
-
const sessionJwk = session.jwk;
|
|
32197
|
-
if (!sessionJwk || typeof sessionJwk !== "object") {
|
|
32198
|
-
return session;
|
|
32199
|
-
}
|
|
32200
|
-
const sessionJwkRecord = sessionJwk;
|
|
32201
|
-
const sessionD = sessionJwkRecord.d;
|
|
32202
|
-
if (typeof sessionD === "string" && sessionD.length > 0) {
|
|
32203
|
-
return session;
|
|
32204
|
-
}
|
|
32205
|
-
const keyD = key.d;
|
|
32206
|
-
if (typeof keyD !== "string" || keyD.length === 0) {
|
|
32207
|
-
return session;
|
|
32208
|
-
}
|
|
32209
|
-
return {
|
|
32210
|
-
...session,
|
|
32211
|
-
jwk: { ...sessionJwkRecord, d: keyD }
|
|
32212
|
-
};
|
|
32213
|
-
}
|
|
32214
32487
|
async function refreshOpenKeySession(profileName, host, options = {}) {
|
|
32215
32488
|
const key = await ProfileManager.getKey(profileName);
|
|
32216
32489
|
if (!key) {
|
|
@@ -32786,6 +33059,36 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
|
|
|
32786
33059
|
});
|
|
32787
33060
|
}
|
|
32788
33061
|
|
|
33062
|
+
// src/commands/enable.ts
|
|
33063
|
+
init_profiles();
|
|
33064
|
+
init_errors();
|
|
33065
|
+
init_formatter();
|
|
33066
|
+
function registerEnableCommand(program) {
|
|
33067
|
+
const enable = program.command("enable").description("Enable a narrowly scoped TinyCloud service");
|
|
33068
|
+
enable.command("share").description("Approve Share publishing through OpenKey device authorization").action(async (_options, command) => {
|
|
33069
|
+
try {
|
|
33070
|
+
const context = await ProfileManager.resolveContext(command.optsWithGlobals());
|
|
33071
|
+
const result = await ensureShareDeviceAuthorization({
|
|
33072
|
+
profileName: context.profile,
|
|
33073
|
+
nodeOrigin: context.host,
|
|
33074
|
+
shareOrigin: "https://share.tinycloud.xyz",
|
|
33075
|
+
openkeyHost: process.env.TC_OPENKEY_HOST,
|
|
33076
|
+
allowReplaceLocal: true
|
|
33077
|
+
});
|
|
33078
|
+
const value = {
|
|
33079
|
+
enabled: true,
|
|
33080
|
+
service: "share",
|
|
33081
|
+
profile: context.profile,
|
|
33082
|
+
sessionDid: result.profile.sessionDid ?? result.profile.did,
|
|
33083
|
+
expiresAt: result.delegation.expiresAt ?? result.delegation.expirationTime ?? result.delegation.expiry
|
|
33084
|
+
};
|
|
33085
|
+
output(value, () => `Share enabled for profile ${context.profile}.`);
|
|
33086
|
+
} catch (error) {
|
|
33087
|
+
handleError(error);
|
|
33088
|
+
}
|
|
33089
|
+
});
|
|
33090
|
+
}
|
|
33091
|
+
|
|
32789
33092
|
// src/commands/init.ts
|
|
32790
33093
|
init_profiles();
|
|
32791
33094
|
init_formatter();
|
|
@@ -34521,7 +34824,7 @@ function receiveJson(result, path) {
|
|
|
34521
34824
|
// src/share/io.ts
|
|
34522
34825
|
import { constants } from "fs";
|
|
34523
34826
|
import { lstat, mkdir as mkdir3, mkdtemp, open as open3, readFile as readFile9, realpath, stat as stat2, link, rename as rename2, rm as rm2, unlink } from "fs/promises";
|
|
34524
|
-
import { randomBytes as
|
|
34827
|
+
import { randomBytes as randomBytes4 } from "crypto";
|
|
34525
34828
|
import { basename as basename2, join as join6, resolve as resolve2, sep } from "path";
|
|
34526
34829
|
var MAX_SHARE_STDIN_BYTES = 100 * 1024 * 1024;
|
|
34527
34830
|
var MAX_SHARE_URL_BYTES = 64 * 1024;
|
|
@@ -34600,7 +34903,7 @@ async function writeShareOutput(directory, filename, bytes, force) {
|
|
|
34600
34903
|
const stagingDirectory = await mkdtemp(join6(stableDirectory, ".tinycloud-share-stage-"));
|
|
34601
34904
|
const stagingInfo = await lstat(stagingDirectory);
|
|
34602
34905
|
if (!stagingInfo.isDirectory() || (stagingInfo.mode & 511) !== 448) throw new Error("OUTPUT_EXISTS");
|
|
34603
|
-
const stagingPath = join6(stagingDirectory, `.tinycloud-share-${
|
|
34906
|
+
const stagingPath = join6(stagingDirectory, `.tinycloud-share-${randomBytes4(16).toString("hex")}.tmp`);
|
|
34604
34907
|
let temporaryPath;
|
|
34605
34908
|
let handle;
|
|
34606
34909
|
try {
|
|
@@ -34653,11 +34956,11 @@ function parseShareTarget(value) {
|
|
|
34653
34956
|
if (value.includes("@")) return { kind: "email", address: value };
|
|
34654
34957
|
throw new CLIError("INVALID_ARGUMENT", "--to must be anyone, a did:, an email address, or domain:example.com", 2);
|
|
34655
34958
|
}
|
|
34656
|
-
function publishServices() {
|
|
34959
|
+
function publishServices(insecureLocalRegistry = false) {
|
|
34657
34960
|
return {
|
|
34658
34961
|
...shareServices.uploadBlob === void 0 ? {} : { uploadBlob: shareServices.uploadBlob },
|
|
34659
34962
|
...shareServices.authorizeUpload === void 0 ? {} : { authorizeUpload: shareServices.authorizeUpload },
|
|
34660
|
-
...shareServices.authorizeUpload === void 0 ? {} : { authorizationOrigin: SHARE_ORIGIN },
|
|
34963
|
+
...shareServices.authorizeUpload === void 0 || insecureLocalRegistry ? {} : { authorizationOrigin: SHARE_ORIGIN },
|
|
34661
34964
|
...shareServices.credentials === void 0 ? {} : { credentials: shareServices.credentials },
|
|
34662
34965
|
...shareServices.fetchFn === void 0 ? {} : { fetchFn: shareServices.fetchFn }
|
|
34663
34966
|
};
|
|
@@ -34703,6 +35006,9 @@ function inputUrl(value, stdin) {
|
|
|
34703
35006
|
if (value === void 0 || value.length === 0) throw new CLIError("INVALID_ARGUMENT", "a share URL or - is required", 2);
|
|
34704
35007
|
return Promise.resolve(value);
|
|
34705
35008
|
}
|
|
35009
|
+
function jsonOutput(options, command) {
|
|
35010
|
+
return options.json === true || command.optsWithGlobals().json === true;
|
|
35011
|
+
}
|
|
34706
35012
|
function expires(value) {
|
|
34707
35013
|
try {
|
|
34708
35014
|
return new Date(Date.now() + parseDuration(value));
|
|
@@ -34753,8 +35059,9 @@ function assertAggregateInputLimit(inputs, maxBytes) {
|
|
|
34753
35059
|
}
|
|
34754
35060
|
function registerShareCommand(program) {
|
|
34755
35061
|
const share = program.command("share").description("Publish and consume TinyCloud Share links");
|
|
34756
|
-
share.command("publish <files...>").description("Publish one or more bounded files as a Share").option("--name <filename>", "Filename for stdin input").option("--to <target>", "Share target", "anyone").option("--notify", "Request idempotent email delivery for addressed targets").option("--expires <duration>", "Share lifetime", "7d").option("--max-bytes <bytes>", "Bound input bytes").option("--media-type <type>", "Media type for a single input").option("--action <actions...>", "Addressed permission: read, list, or edit").option("--prefix", "Publish multiple inputs beneath one addressed prefix").option("--binary", "Allow non-UTF-8 bearer content").option("--inline", "Embed the sealed envelope in the URL fragment").option("--compact", "Use a CID-addressed compact link (default)").option("--json", "Print versioned redacted JSON").option("--registry <url>", "Authenticated registry upload endpoint", DEFAULT_REGISTRY).option("--viewer-origin <origin>", "Canonical HTTPS viewer origin", SHARE_ORIGIN).option("--insecure-registry", "Allow an explicit localhost HTTP registry for hermetic tests").action(async (files, options) => {
|
|
35062
|
+
share.command("publish <files...>").description("Publish one or more bounded files as a Share").option("--name <filename>", "Filename for stdin input").option("--to <target>", "Share target", "anyone").option("--notify", "Request idempotent email delivery for addressed targets").option("--expires <duration>", "Share lifetime", "7d").option("--max-bytes <bytes>", "Bound input bytes").option("--media-type <type>", "Media type for a single input").option("--action <actions...>", "Addressed permission: read, list, or edit").option("--prefix", "Publish multiple inputs beneath one addressed prefix").option("--binary", "Allow non-UTF-8 bearer content").option("--inline", "Embed the sealed envelope in the URL fragment").option("--compact", "Use a CID-addressed compact link (default)").option("--json", "Print versioned redacted JSON").option("--registry <url>", "Authenticated registry upload endpoint", DEFAULT_REGISTRY).option("--viewer-origin <origin>", "Canonical HTTPS viewer origin", SHARE_ORIGIN).option("--insecure-registry", "Allow an explicit localhost HTTP registry for hermetic tests").action(async (files, options, command) => {
|
|
34757
35063
|
try {
|
|
35064
|
+
const json = jsonOutput(options, command);
|
|
34758
35065
|
if (options.inline && options.compact) throw new CLIError("INVALID_ARGUMENT", "--inline and --compact are mutually exclusive", 2);
|
|
34759
35066
|
const maxBytes = byteLimit(options.maxBytes);
|
|
34760
35067
|
if (files.length === 0 || files.includes("-") && files.length > 1) throw new CLIError("INVALID_ARGUMENT", "stdin must be the only publish input", 2);
|
|
@@ -34783,10 +35090,10 @@ function registerShareCommand(program) {
|
|
|
34783
35090
|
allowInsecureRegistry: options.insecureRegistry === true,
|
|
34784
35091
|
notify: options.notify === true,
|
|
34785
35092
|
targetAdapter: shareServices.targetAdapter,
|
|
34786
|
-
...publishServices()
|
|
35093
|
+
...publishServices(options.insecureRegistry === true)
|
|
34787
35094
|
});
|
|
34788
35095
|
if ("state" in result) {
|
|
34789
|
-
if (
|
|
35096
|
+
if (json) {
|
|
34790
35097
|
writeJson2({ protocol: "tinycloud-share", version: 1, authorization: authorizationRequiredJson(result) });
|
|
34791
35098
|
process.exitCode = 6;
|
|
34792
35099
|
return;
|
|
@@ -34799,25 +35106,27 @@ function registerShareCommand(program) {
|
|
|
34799
35106
|
const delivery = await notifyShare({ shareId: record.shareId, recipient: target.address, record, adapter: shareServices.delivery });
|
|
34800
35107
|
if (delivery.state === "partial-failure") process.exitCode = 9;
|
|
34801
35108
|
}
|
|
34802
|
-
if (
|
|
35109
|
+
if (json) writeJson2(redactPublishedShare(result));
|
|
34803
35110
|
else publishHuman(result);
|
|
34804
35111
|
} catch (error) {
|
|
34805
35112
|
handleError(shareCliError(error));
|
|
34806
35113
|
}
|
|
34807
35114
|
});
|
|
34808
|
-
share.command("inspect [url]").description("Verify a share link and print safe metadata").option("--stdin", "Read the complete URL from stdin").option("--json", "Print versioned redacted JSON").option("--registry <url>", "Registry read endpoint", DEFAULT_READ_REGISTRY).option("--viewer-origin <origin>", "Require this canonical Share origin", SHARE_ORIGIN).action(async (url, options) => {
|
|
35115
|
+
share.command("inspect [url]").description("Verify a share link and print safe metadata").option("--stdin", "Read the complete URL from stdin").option("--json", "Print versioned redacted JSON").option("--registry <url>", "Registry read endpoint", DEFAULT_READ_REGISTRY).option("--viewer-origin <origin>", "Require this canonical Share origin", SHARE_ORIGIN).action(async (url, options, command) => {
|
|
34809
35116
|
try {
|
|
35117
|
+
const json = jsonOutput(options, command);
|
|
34810
35118
|
const link2 = await inputUrl(url, options.stdin === true);
|
|
34811
35119
|
const result = await inspectShare(link2, { registryBaseUrl: options.registry, expectedOrigin: options.viewerOrigin, ...fetchServices() });
|
|
34812
|
-
if (
|
|
35120
|
+
if (json) writeJson2(result);
|
|
34813
35121
|
else inspectHuman(result);
|
|
34814
35122
|
} catch (error) {
|
|
34815
35123
|
handleError(shareCliError(error));
|
|
34816
35124
|
}
|
|
34817
35125
|
});
|
|
34818
|
-
share.command("receive [url]").description("Verify and receive a share link").option("--stdin", "Read the complete URL from stdin").option("--output <directory>", "Create the file in this directory").option("--stdout", "Write verified plaintext bytes to stdout").option("--force", "Allow replacing an existing non-symlink output").option("--max-bytes <bytes>", "Bound received content bytes").option("--resume-token <token>", "Resume a previously returned recipient authorization step").option("--authorization-proof-file <path>", "Read the JSON authorization proof from a file").option("--json", "Print versioned redacted JSON").option("--registry <url>", "Registry read endpoint", DEFAULT_READ_REGISTRY).option("--viewer-origin <origin>", "Require this canonical Share origin", SHARE_ORIGIN).option("--legacy", "Read a legacy tc1: link (read-only)").action(async (url, options) => {
|
|
35126
|
+
share.command("receive [url]").description("Verify and receive a share link").option("--stdin", "Read the complete URL from stdin").option("--output <directory>", "Create the file in this directory").option("--stdout", "Write verified plaintext bytes to stdout").option("--force", "Allow replacing an existing non-symlink output").option("--max-bytes <bytes>", "Bound received content bytes").option("--resume-token <token>", "Resume a previously returned recipient authorization step").option("--authorization-proof-file <path>", "Read the JSON authorization proof from a file").option("--json", "Print versioned redacted JSON").option("--registry <url>", "Registry read endpoint", DEFAULT_READ_REGISTRY).option("--viewer-origin <origin>", "Require this canonical Share origin", SHARE_ORIGIN).option("--legacy", "Read a legacy tc1: link (read-only)").action(async (url, options, command) => {
|
|
34819
35127
|
try {
|
|
34820
|
-
|
|
35128
|
+
const json = jsonOutput(options, command);
|
|
35129
|
+
if (options.stdout && json) throw new CLIError("INVALID_ARGUMENT", "--stdout and --json are mutually exclusive", 2);
|
|
34821
35130
|
const maxBytes = byteLimit(options.maxBytes);
|
|
34822
35131
|
const link2 = await inputUrl(url, options.stdin === true);
|
|
34823
35132
|
const proof = await authorizationProof(options);
|
|
@@ -34828,9 +35137,9 @@ function registerShareCommand(program) {
|
|
|
34828
35137
|
process.stdout.write(Buffer.from(bytes));
|
|
34829
35138
|
return;
|
|
34830
35139
|
}
|
|
34831
|
-
const
|
|
34832
|
-
if (
|
|
34833
|
-
else receiveHuman(
|
|
35140
|
+
const output3 = await writeShareOutput(options.output ?? ".", "share.md", bytes, options.force === true);
|
|
35141
|
+
if (json) writeJson2({ protocol: "tinycloud-share", version: 1, legacy: true, path: output3 });
|
|
35142
|
+
else receiveHuman(output3);
|
|
34834
35143
|
return;
|
|
34835
35144
|
}
|
|
34836
35145
|
const result = await receiveShare(link2, {
|
|
@@ -34843,7 +35152,7 @@ function registerShareCommand(program) {
|
|
|
34843
35152
|
...proof === void 0 ? {} : { authorizationProof: proof }
|
|
34844
35153
|
});
|
|
34845
35154
|
if ("state" in result) {
|
|
34846
|
-
if (
|
|
35155
|
+
if (json) {
|
|
34847
35156
|
writeJson2({ protocol: "tinycloud-share", version: 1, authorization: authorizationRequiredJson(result) });
|
|
34848
35157
|
process.exitCode = 6;
|
|
34849
35158
|
return;
|
|
@@ -34854,15 +35163,16 @@ function registerShareCommand(program) {
|
|
|
34854
35163
|
process.stdout.write(Buffer.from(result.bytes));
|
|
34855
35164
|
return;
|
|
34856
35165
|
}
|
|
34857
|
-
const
|
|
34858
|
-
if (
|
|
34859
|
-
else receiveHuman(
|
|
35166
|
+
const output2 = await writeShareOutput(options.output ?? ".", result.metadata.display.filename ?? "share.md", result.bytes, options.force === true);
|
|
35167
|
+
if (json) receiveJson(result, output2);
|
|
35168
|
+
else receiveHuman(output2);
|
|
34860
35169
|
} catch (error) {
|
|
34861
35170
|
handleError(shareCliError(error));
|
|
34862
35171
|
}
|
|
34863
35172
|
});
|
|
34864
|
-
share.command("migrate [url]").description("Read a legacy tc1 link and re-mint a modern Share link").option("--stdin", "Read the complete legacy link from stdin").option("--name <filename>", "Filename for the migrated content", "migrated.md").option("--to <target>", "Modern Share target", "anyone").option("--notify", "Request idempotent email delivery for addressed targets").option("--expires <duration>", "Modern share lifetime", "7d").option("--max-bytes <bytes>", "Bound migrated content bytes").option("--inline", "Embed the sealed envelope in the URL fragment").option("--registry <url>", "Authenticated registry upload endpoint", DEFAULT_REGISTRY).option("--viewer-origin <origin>", "Canonical HTTPS viewer origin", SHARE_ORIGIN).option("--insecure-registry", "Allow an explicit localhost HTTP registry for hermetic tests").option("--json", "Print versioned redacted JSON").action(async (url, options) => {
|
|
35173
|
+
share.command("migrate [url]").description("Read a legacy tc1 link and re-mint a modern Share link").option("--stdin", "Read the complete legacy link from stdin").option("--name <filename>", "Filename for the migrated content", "migrated.md").option("--to <target>", "Modern Share target", "anyone").option("--notify", "Request idempotent email delivery for addressed targets").option("--expires <duration>", "Modern share lifetime", "7d").option("--max-bytes <bytes>", "Bound migrated content bytes").option("--inline", "Embed the sealed envelope in the URL fragment").option("--registry <url>", "Authenticated registry upload endpoint", DEFAULT_REGISTRY).option("--viewer-origin <origin>", "Canonical HTTPS viewer origin", SHARE_ORIGIN).option("--insecure-registry", "Allow an explicit localhost HTTP registry for hermetic tests").option("--json", "Print versioned redacted JSON").action(async (url, options, command) => {
|
|
34865
35174
|
try {
|
|
35175
|
+
const json = jsonOutput(options, command);
|
|
34866
35176
|
if (shareServices.legacyReader === void 0) throw new CLIError("UNSUPPORTED_LINK", "legacy migration requires an installed read-only tc1 adapter", 2);
|
|
34867
35177
|
const link2 = await inputUrl(url, options.stdin === true);
|
|
34868
35178
|
if (!isLegacyShareLink(link2)) throw new CLIError("UNSUPPORTED_LINK", "only tc1: links can be migrated", 2);
|
|
@@ -34884,7 +35194,7 @@ function registerShareCommand(program) {
|
|
|
34884
35194
|
allowInsecureRegistry: options.insecureRegistry === true,
|
|
34885
35195
|
notify: options.notify === true,
|
|
34886
35196
|
targetAdapter: shareServices.targetAdapter,
|
|
34887
|
-
...publishServices()
|
|
35197
|
+
...publishServices(options.insecureRegistry === true)
|
|
34888
35198
|
});
|
|
34889
35199
|
if ("state" in result) throw new CLIError(result.method === "openkey-device" ? "DEVICE_AUTH_REQUIRED" : "CLAIM_REQUIRED", "recipient authorization is required; continue through the configured authority adapter", 6);
|
|
34890
35200
|
const record = await rememberPublishedShare(result);
|
|
@@ -34898,41 +35208,44 @@ function registerShareCommand(program) {
|
|
|
34898
35208
|
return result;
|
|
34899
35209
|
}
|
|
34900
35210
|
});
|
|
34901
|
-
if (
|
|
35211
|
+
if (json) writeJson2({ protocol: "tinycloud-share", version: 1, legacy: true, migrated: redactPublishedShare(migrated.migrated) });
|
|
34902
35212
|
else publishHuman(migrated.migrated);
|
|
34903
35213
|
} catch (error) {
|
|
34904
35214
|
handleError(shareCliError(error));
|
|
34905
35215
|
}
|
|
34906
35216
|
});
|
|
34907
|
-
share.command("list").description("List encrypted sender history without complete bearer URLs").option("--json", "Print versioned redacted JSON").action(async (options) => {
|
|
35217
|
+
share.command("list").description("List encrypted sender history without complete bearer URLs").option("--json", "Print versioned redacted JSON").action(async (options, command) => {
|
|
34908
35218
|
try {
|
|
35219
|
+
const json = jsonOutput(options, command);
|
|
34909
35220
|
if (shareServices.records === void 0) throw new CLIError("AUTH_REQUIRED", "sender history storage is not configured", 3);
|
|
34910
35221
|
const result = await listShares(shareServices.records);
|
|
34911
|
-
if (
|
|
35222
|
+
if (json) writeJson2({ protocol: "tinycloud-share", version: 1, shares: result });
|
|
34912
35223
|
else process.stdout.write(result.map((item) => `${item.shareId} ${item.target} ${item.expiresAt}`).join("\n") + (result.length ? "\n" : ""));
|
|
34913
35224
|
} catch (error) {
|
|
34914
35225
|
handleError(shareCliError(error));
|
|
34915
35226
|
}
|
|
34916
35227
|
});
|
|
34917
|
-
share.command("show <id>").description("Show one redacted sender-history record").option("--reveal-link", "Explicitly include the complete link").option("--json", "Print versioned redacted JSON").action(async (id, options) => {
|
|
35228
|
+
share.command("show <id>").description("Show one redacted sender-history record").option("--reveal-link", "Explicitly include the complete link").option("--json", "Print versioned redacted JSON").action(async (id, options, command) => {
|
|
34918
35229
|
try {
|
|
34919
|
-
|
|
35230
|
+
const json = jsonOutput(options, command);
|
|
35231
|
+
if (options.revealLink && json) throw new CLIError("INVALID_ARGUMENT", "--reveal-link cannot be combined with --json", 2);
|
|
34920
35232
|
if (shareServices.records === void 0) throw new CLIError("AUTH_REQUIRED", "sender history storage is not configured", 3);
|
|
34921
35233
|
const result = await showShare({ storage: shareServices.records, shareId: id, revealLink: options.revealLink === true, link: options.revealLink ? await shareServices.linkFor?.(id) : void 0 });
|
|
34922
|
-
if (
|
|
35234
|
+
if (json) writeJson2({ protocol: "tinycloud-share", version: 1, share: result });
|
|
34923
35235
|
else writeJson2(result);
|
|
34924
35236
|
} catch (error) {
|
|
34925
35237
|
handleError(shareCliError(error));
|
|
34926
35238
|
}
|
|
34927
35239
|
});
|
|
34928
|
-
share.command("notify <id>").description("Retry idempotent delivery without recreating the share").requiredOption("--to <address>", "Recipient email").option("--json", "Print versioned JSON").action(async (id, options) => {
|
|
35240
|
+
share.command("notify <id>").description("Retry idempotent delivery without recreating the share").requiredOption("--to <address>", "Recipient email").option("--json", "Print versioned JSON").action(async (id, options, command) => {
|
|
34929
35241
|
try {
|
|
35242
|
+
const json = jsonOutput(options, command);
|
|
34930
35243
|
if (shareServices.delivery === void 0) throw new CLIError("AUTH_REQUIRED", "delivery authority is not configured", 3);
|
|
34931
35244
|
if (shareServices.records === void 0) throw new CLIError("AUTH_REQUIRED", "sender history storage is not configured", 3);
|
|
34932
35245
|
const record = await shareServices.records.get(id);
|
|
34933
35246
|
if (record === void 0) throw new CLIError("NOT_FOUND", "share not found", 4);
|
|
34934
35247
|
const result = await notifyShare({ shareId: id, recipient: options.to, record, adapter: shareServices.delivery });
|
|
34935
|
-
if (
|
|
35248
|
+
if (json) writeJson2(result);
|
|
34936
35249
|
else process.stdout.write(`${result.state}
|
|
34937
35250
|
`);
|
|
34938
35251
|
if (result.state === "partial-failure") process.exitCode = 9;
|
|
@@ -34940,8 +35253,9 @@ function registerShareCommand(program) {
|
|
|
34940
35253
|
handleError(shareCliError(error));
|
|
34941
35254
|
}
|
|
34942
35255
|
});
|
|
34943
|
-
share.command("revoke <id>").description("Revoke addressed shares; report bearer retention honestly").option("--ancestor", "Revoke the owner delegation ancestry").option("--json", "Print versioned JSON").action(async (id, options) => {
|
|
35256
|
+
share.command("revoke <id>").description("Revoke addressed shares; report bearer retention honestly").option("--ancestor", "Revoke the owner delegation ancestry").option("--json", "Print versioned JSON").action(async (id, options, command) => {
|
|
34944
35257
|
try {
|
|
35258
|
+
const json = jsonOutput(options, command);
|
|
34945
35259
|
if (shareServices.records === void 0) throw new CLIError("AUTH_REQUIRED", "sender history storage is not configured", 3);
|
|
34946
35260
|
const record = shareServices.getRecord ? await shareServices.getRecord(id) : await shareServices.records.get(id);
|
|
34947
35261
|
if (record === void 0) throw new CLIError("NOT_FOUND", "share not found", 4);
|
|
@@ -34949,7 +35263,7 @@ function registerShareCommand(program) {
|
|
|
34949
35263
|
if (result.state === "unsupported") {
|
|
34950
35264
|
throw new CLIError("UNSUPPORTED_TARGET", result.reason, 2);
|
|
34951
35265
|
}
|
|
34952
|
-
if (
|
|
35266
|
+
if (json) writeJson2({ protocol: "tinycloud-share", version: 1, result });
|
|
34953
35267
|
else process.stdout.write(`${result.state}
|
|
34954
35268
|
`);
|
|
34955
35269
|
} catch (error) {
|
|
@@ -34963,7 +35277,7 @@ init_profiles();
|
|
|
34963
35277
|
init_formatter();
|
|
34964
35278
|
init_errors();
|
|
34965
35279
|
init_constants();
|
|
34966
|
-
import { randomBytes as
|
|
35280
|
+
import { randomBytes as randomBytes5 } from "crypto";
|
|
34967
35281
|
import { mkdir as mkdir4, writeFile as writeFile6 } from "fs/promises";
|
|
34968
35282
|
import { dirname as dirname3 } from "path";
|
|
34969
35283
|
init_host();
|
|
@@ -35046,7 +35360,7 @@ it directly with \`tc space host <name>\` (no request needed).
|
|
|
35046
35360
|
const artifact = {
|
|
35047
35361
|
kind: "tinycloud.host.request",
|
|
35048
35362
|
version: 1,
|
|
35049
|
-
requestId: `hostreq_${Date.now().toString(36)}_${
|
|
35363
|
+
requestId: `hostreq_${Date.now().toString(36)}_${randomBytes5(4).toString("hex")}`,
|
|
35050
35364
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
35051
35365
|
spaceName,
|
|
35052
35366
|
spaceId,
|
|
@@ -36128,6 +36442,7 @@ function registerVarsCommand(program) {
|
|
|
36128
36442
|
function registerTinyCloudCommands(program) {
|
|
36129
36443
|
registerInitCommand(program);
|
|
36130
36444
|
registerAuthCommand(program);
|
|
36445
|
+
registerEnableCommand(program);
|
|
36131
36446
|
registerKvCommand(program);
|
|
36132
36447
|
registerSpaceCommand(program);
|
|
36133
36448
|
registerDelegationCommand(program);
|