@tinycloud/cli 0.9.1-beta.5 → 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.
@@ -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 randomBytes5 = eddsaOpts.randomBytes || randomBytes;
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 = randomBytes5(lengths.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: randomBytes5 } = CURVE;
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 ? randomBytes5(Fp2.BYTES) : ent;
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 errorCode = this.mapHttpStatusToErrorCode(
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(errorCode, message, "sql", { meta })
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 errorCode = this.mapHttpStatusToErrorCode(
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(errorCode, message, "duckdb", { meta })
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 output = "";
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
- output += alphabet2[buffer >>> bits - 5 & 31];
18227
+ output2 += alphabet2[buffer >>> bits - 5 & 31];
18221
18228
  bits -= 5;
18222
18229
  }
18223
18230
  }
18224
- if (bits > 0) output += alphabet2[buffer << 5 - bits & 31];
18225
- return output;
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 output = allocUnsafe(length22);
25377
+ const output2 = allocUnsafe(length22);
25371
25378
  let offset = 0;
25372
25379
  for (const arr of arrays) {
25373
- output.set(arr, offset);
25380
+ output2.set(arr, offset);
25374
25381
  offset += arr.length;
25375
25382
  }
25376
- return asUint8Array(output);
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 "local";
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 output = result.output;
31953
+ const output2 = result.output;
31680
31954
  outputJson({
31681
31955
  imported: true,
31682
- activated: output.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: output.cid,
31686
- permissions: output.effectivePermissions,
31687
- expiry: output.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 randomBytes3 } from "crypto";
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-${randomBytes3(16).toString("hex")}.tmp`);
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
  };
@@ -34787,7 +35090,7 @@ function registerShareCommand(program) {
34787
35090
  allowInsecureRegistry: options.insecureRegistry === true,
34788
35091
  notify: options.notify === true,
34789
35092
  targetAdapter: shareServices.targetAdapter,
34790
- ...publishServices()
35093
+ ...publishServices(options.insecureRegistry === true)
34791
35094
  });
34792
35095
  if ("state" in result) {
34793
35096
  if (json) {
@@ -34834,9 +35137,9 @@ function registerShareCommand(program) {
34834
35137
  process.stdout.write(Buffer.from(bytes));
34835
35138
  return;
34836
35139
  }
34837
- const output2 = await writeShareOutput(options.output ?? ".", "share.md", bytes, options.force === true);
34838
- if (json) writeJson2({ protocol: "tinycloud-share", version: 1, legacy: true, path: output2 });
34839
- else receiveHuman(output2);
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);
34840
35143
  return;
34841
35144
  }
34842
35145
  const result = await receiveShare(link2, {
@@ -34860,9 +35163,9 @@ function registerShareCommand(program) {
34860
35163
  process.stdout.write(Buffer.from(result.bytes));
34861
35164
  return;
34862
35165
  }
34863
- const output = await writeShareOutput(options.output ?? ".", result.metadata.display.filename ?? "share.md", result.bytes, options.force === true);
34864
- if (json) receiveJson(result, output);
34865
- else receiveHuman(output);
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);
34866
35169
  } catch (error) {
34867
35170
  handleError(shareCliError(error));
34868
35171
  }
@@ -34891,7 +35194,7 @@ function registerShareCommand(program) {
34891
35194
  allowInsecureRegistry: options.insecureRegistry === true,
34892
35195
  notify: options.notify === true,
34893
35196
  targetAdapter: shareServices.targetAdapter,
34894
- ...publishServices()
35197
+ ...publishServices(options.insecureRegistry === true)
34895
35198
  });
34896
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);
34897
35200
  const record = await rememberPublishedShare(result);
@@ -34974,7 +35277,7 @@ init_profiles();
34974
35277
  init_formatter();
34975
35278
  init_errors();
34976
35279
  init_constants();
34977
- import { randomBytes as randomBytes4 } from "crypto";
35280
+ import { randomBytes as randomBytes5 } from "crypto";
34978
35281
  import { mkdir as mkdir4, writeFile as writeFile6 } from "fs/promises";
34979
35282
  import { dirname as dirname3 } from "path";
34980
35283
  init_host();
@@ -35057,7 +35360,7 @@ it directly with \`tc space host <name>\` (no request needed).
35057
35360
  const artifact = {
35058
35361
  kind: "tinycloud.host.request",
35059
35362
  version: 1,
35060
- requestId: `hostreq_${Date.now().toString(36)}_${randomBytes4(4).toString("hex")}`,
35363
+ requestId: `hostreq_${Date.now().toString(36)}_${randomBytes5(4).toString("hex")}`,
35061
35364
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
35062
35365
  spaceName,
35063
35366
  spaceId,
@@ -36139,6 +36442,7 @@ function registerVarsCommand(program) {
36139
36442
  function registerTinyCloudCommands(program) {
36140
36443
  registerInitCommand(program);
36141
36444
  registerAuthCommand(program);
36445
+ registerEnableCommand(program);
36142
36446
  registerKvCommand(program);
36143
36447
  registerSpaceCommand(program);
36144
36448
  registerDelegationCommand(program);