@tinycloud/cli 1.3.0-beta.36 → 1.3.0-beta.38

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.
@@ -37,7 +37,7 @@ import {
37
37
  tinycloudConfigPath,
38
38
  tinycloudHomePath
39
39
  } from "@tinycloud/operations/state";
40
- var CONFIG_DIR, PROFILES_DIR, CONFIG_FILE, DEFAULT_HOST, DEFAULT_OPENKEY_HOST, DEFAULT_OPENKEY_DEVICE_API_HOST, DEFAULT_SHARE_ORIGIN, DEFAULT_PROFILE, DEFAULT_CHAIN_ID, PROFILE_COMMIT_LOCK_TIMEOUT_MS, ExitCode;
40
+ var CONFIG_DIR, PROFILES_DIR, CONFIG_FILE, DEFAULT_HOST, DEFAULT_OPENKEY_HOST, DEFAULT_OPENKEY_DEVICE_API_HOST, DEFAULT_SHARE_ORIGIN, DEFAULT_PROFILE, DEFAULT_CHAIN_ID, PROFILE_COMMIT_LOCK_TIMEOUT_MS, ExitCode, OPENKEY_SPACE_SETUP_RETRY_HINT;
41
41
  var init_constants = __esm({
42
42
  "src/config/constants.ts"() {
43
43
  "use strict";
@@ -67,6 +67,7 @@ var init_constants = __esm({
67
67
  /** Interrupted by SIGINT/SIGTERM (`CANCELLED`). */
68
68
  CANCELLED: 130
69
69
  };
70
+ OPENKEY_SPACE_SETUP_RETRY_HINT = "Wait a minute and retry; approving again in OpenKey re-runs the setup.";
70
71
  }
71
72
  });
72
73
 
@@ -281,6 +282,40 @@ var init_replication_registry = __esm({
281
282
  }
282
283
  });
283
284
 
285
+ // src/output/safe-text.ts
286
+ function escape(match) {
287
+ return `\\u{${match.codePointAt(0).toString(16)}}`;
288
+ }
289
+ function safeBlock(text) {
290
+ return text.replace(UNSAFE_BLOCK, escape);
291
+ }
292
+ function safeInline(text) {
293
+ return text.replace(UNSAFE_INLINE, escape);
294
+ }
295
+ function safeClipped(text, max = ECHO_FIELD_MAX) {
296
+ const escaped = Array.from(safeInline(text));
297
+ return escaped.length <= max ? escaped.join("") : `${escaped.slice(0, max).join("")}\u2026`;
298
+ }
299
+ function safeClippedList(items) {
300
+ const shown = items.slice(0, ECHO_LIST_MAX).map((item) => `"${safeClipped(item)}"`).join(", ");
301
+ return items.length > ECHO_LIST_MAX ? `${shown} \u2026 and ${items.length - ECHO_LIST_MAX} more` : shown;
302
+ }
303
+ function safeJsonStringify(value, space) {
304
+ const text = JSON.stringify(value, null, space);
305
+ return text === void 0 ? text : text.replace(JSON_UNSAFE, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
306
+ }
307
+ var UNSAFE_BLOCK, UNSAFE_INLINE, ECHO_FIELD_MAX, ECHO_LIST_MAX, JSON_UNSAFE;
308
+ var init_safe_text = __esm({
309
+ "src/output/safe-text.ts"() {
310
+ "use strict";
311
+ UNSAFE_BLOCK = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f\u061c\u200b-\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069\ufeff]/g;
312
+ UNSAFE_INLINE = /[\u0000-\u001f\u007f-\u009f\u061c\u200b-\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069\ufeff]/g;
313
+ ECHO_FIELD_MAX = 80;
314
+ ECHO_LIST_MAX = 5;
315
+ JSON_UNSAFE = /[\u007f-\u009f\u061c\u200b-\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069\ufeff]/g;
316
+ }
317
+ });
318
+
284
319
  // src/output/theme.ts
285
320
  import chalk from "chalk";
286
321
  var TC_PALETTE, theme;
@@ -363,7 +398,7 @@ var init_warnings = __esm({
363
398
  import ora from "ora";
364
399
  import { CLI_NAME } from "@tinycloud/operations/cli-name";
365
400
  function outputJson(data) {
366
- process.stdout.write(JSON.stringify(data, null, 2) + "\n");
401
+ process.stdout.write(safeJsonStringify(data, 2) + "\n");
367
402
  }
368
403
  function warningLine(warnings) {
369
404
  const which = warnings.map((warning) => `${warning.grantCid ?? "unidentified"}: ${warning.reason}`).join(", ");
@@ -374,12 +409,12 @@ function outputError(code4, message, hint, details = {}) {
374
409
  const { meta, warnings = [], setup, recovery, progress } = details;
375
410
  if (!shouldOutputJson()) {
376
411
  process.stderr.write(
377
- `${theme.error("\u2717")} ${theme.label(code4)}: ${message}
412
+ `${theme.error("\u2717")} ${theme.label(safeInline(code4))}: ${safeBlock(message)}
378
413
  `
379
414
  );
380
415
  if (hint) {
381
416
  for (const line of hint.split("\n")) {
382
- process.stderr.write(` ${theme.hint(line)}
417
+ process.stderr.write(` ${theme.hint(safeBlock(line))}
383
418
  `);
384
419
  }
385
420
  }
@@ -395,13 +430,13 @@ function outputError(code4, message, hint, details = {}) {
395
430
  if (progress) payload.error.progress = progress;
396
431
  if (meta) payload.error.meta = meta;
397
432
  if (warnings.length > 0) payload.error.warnings = warnings;
398
- process.stderr.write(JSON.stringify(payload, null, 2) + "\n");
433
+ process.stderr.write(safeJsonStringify(payload, 2) + "\n");
399
434
  }
400
435
  }
401
436
  function outputWarnings(warnings) {
402
437
  const combined = mergeWarnings(drainWarnings(), operationWarnings(warnings));
403
438
  if (combined.length === 0) return;
404
- process.stderr.write(shouldOutputJson() ? `${JSON.stringify({ warnings: combined })}
439
+ process.stderr.write(shouldOutputJson() ? `${safeJsonStringify({ warnings: combined })}
405
440
  ` : `${theme.warn("!")} ${warningLine(combined)}
406
441
  `);
407
442
  }
@@ -479,6 +514,7 @@ var WARNING_CODE, WARNING_REASON, DELEGATION_CID, JSON_REQUESTED;
479
514
  var init_formatter = __esm({
480
515
  "src/output/formatter.ts"() {
481
516
  "use strict";
517
+ init_safe_text();
482
518
  init_theme();
483
519
  init_warnings();
484
520
  WARNING_CODE = /^[A-Z][A-Z_]{0,63}$/;
@@ -881,6 +917,13 @@ var init_types = __esm({
881
917
 
882
918
  // src/lib/space.ts
883
919
  import { CLI_NAME as CLI_NAME3 } from "@tinycloud/operations/cli-name";
920
+ function invalidSpaceError(input) {
921
+ return new CLIError(
922
+ "INVALID_SPACE",
923
+ `Invalid space "${safeClipped(input)}". Use a short name ([A-Za-z0-9_-]) or a full tinycloud:... URI.`,
924
+ ExitCode.USAGE_ERROR
925
+ );
926
+ }
884
927
  function canonicalizeAddress(address) {
885
928
  const trimmed = address.trim();
886
929
  return trimmed.startsWith("0x") ? `0x${trimmed.slice(2).toLowerCase()}` : trimmed.toLowerCase();
@@ -944,22 +987,10 @@ async function resolveSpaceUri(input, profileName2, options = {}) {
944
987
  if (!effective) return void 0;
945
988
  if (effective.startsWith("tinycloud:")) {
946
989
  const parsed = parseSpaceUri(effective);
947
- if (!parsed) {
948
- throw new CLIError(
949
- "INVALID_SPACE",
950
- `Invalid space "${effective}". Use a short name ([A-Za-z0-9_-]) or a full tinycloud:... URI.`,
951
- ExitCode.USAGE_ERROR
952
- );
953
- }
990
+ if (!parsed) throw invalidSpaceError(effective);
954
991
  return buildSpaceUri(parsed.owner, parsed.name);
955
992
  }
956
- if (!/^[A-Za-z0-9_-]+$/.test(effective)) {
957
- throw new CLIError(
958
- "INVALID_SPACE",
959
- `Invalid space "${effective}". Use a short name ([A-Za-z0-9_-]) or a full tinycloud:... URI.`,
960
- ExitCode.USAGE_ERROR
961
- );
962
- }
993
+ if (!/^[A-Za-z0-9_-]+$/.test(effective)) throw invalidSpaceError(effective);
963
994
  const session = await ProfileManager.getSession(profileName2);
964
995
  const address = resolveAddress(profile, session);
965
996
  const chainId = resolveChainId(profile, session);
@@ -970,6 +1001,7 @@ var init_space = __esm({
970
1001
  "use strict";
971
1002
  init_errors();
972
1003
  init_constants();
1004
+ init_safe_text();
973
1005
  init_profiles();
974
1006
  }
975
1007
  });
@@ -9604,17 +9636,25 @@ async function isRootAuthority(spaceUri, profileName2) {
9604
9636
  const selfAddr = await resolveLocalAddress(profile, profileName2);
9605
9637
  return selfAddr !== null && selfAddr === ownerAddr;
9606
9638
  }
9639
+ async function primarySpaceUri(profileName2) {
9640
+ const session = await ProfileManager.getSession(profileName2);
9641
+ if (typeof session?.spaceId === "string" && session.spaceId !== "") return session.spaceId;
9642
+ const profile = await ProfileManager.getProfile(profileName2);
9643
+ return typeof profile.spaceId === "string" && profile.spaceId !== "" ? profile.spaceId : void 0;
9644
+ }
9607
9645
  function spaceNameFromUri(spaceUri) {
9608
9646
  return spaceUri.slice(spaceUri.lastIndexOf(":") + 1);
9609
9647
  }
9610
- async function unhostedSpaceError(error, spaceUri, profileName2) {
9611
- if (!spaceUri) return null;
9648
+ async function unhostedSpaceError(error, requestedSpaceUri, profileName2) {
9612
9649
  const status = error.meta?.status;
9613
9650
  const isUnhosted = status === 404 && /space not found/i.test(error.message);
9614
9651
  if (!isUnhosted) return null;
9652
+ const spaceUri = requestedSpaceUri ?? await primarySpaceUri(profileName2);
9653
+ if (!spaceUri) return null;
9615
9654
  const spaceName = spaceNameFromUri(spaceUri);
9616
9655
  const owner = await isRootAuthority(spaceUri, profileName2);
9617
- const hint = owner ? [
9656
+ const openKeyOwner = owner && resolveProfilePosture(await ProfileManager.getProfile(profileName2)) === "owner-openkey";
9657
+ const hint = openKeyOwner ? `OpenKey is still setting up your space. ${OPENKEY_SPACE_SETUP_RETRY_HINT}` : owner ? [
9618
9658
  "You are the owner. Host it once:",
9619
9659
  ` ${CLI_NAME8} space host ${spaceName}`,
9620
9660
  "Then retry."
@@ -10353,7 +10393,7 @@ function formatSpace(space) {
10353
10393
  init_profile_gate();
10354
10394
  init_dist();
10355
10395
  init_profiles();
10356
- import { CLI_NAME as CLI_NAME15 } from "@tinycloud/operations/cli-name";
10396
+ import { CLI_NAME as CLI_NAME16 } from "@tinycloud/operations/cli-name";
10357
10397
  import { get as httpGet } from "http";
10358
10398
  import { get as httpsGet } from "https";
10359
10399
  import { spawn } from "child_process";
@@ -11886,6 +11926,132 @@ async function loginWithDeviceAuthorization(input) {
11886
11926
  return { profile, result };
11887
11927
  }
11888
11928
 
11929
+ // src/auth/space-setup.ts
11930
+ init_constants();
11931
+ init_sdk();
11932
+ init_errors();
11933
+ var SPACE_SETUP_TIMEOUT_MS = 15e4;
11934
+ var POLL_DELAYS_MS = [2e3, 3e3, 5e3, 8e3, 13e3, 15e3];
11935
+ var PROGRESS_INTERVAL_MS = 15e3;
11936
+ var SPACE_ERROR_MAX_LENGTH = 500;
11937
+ function spaceSetupReport(delegation) {
11938
+ const rawError = typeof delegation.spaceError === "string" ? delegation.spaceError : void 0;
11939
+ const error = rawError?.replace(/[\u0000-\u001f\u007f-\u009f\s]+/g, " ").trim().slice(0, SPACE_ERROR_MAX_LENGTH);
11940
+ const ready = delegation.spaceReady;
11941
+ if (ready === true) return void 0;
11942
+ const pending = delegation.spacePending === true || ready === null;
11943
+ if (!pending && !error && ready !== false) return void 0;
11944
+ return {
11945
+ ready: ready === false || ready === null ? ready : void 0,
11946
+ pending,
11947
+ ...error ? { error } : {}
11948
+ };
11949
+ }
11950
+ async function awaitSpaceSetup(report, deps) {
11951
+ const write = deps.write ?? ((line) => {
11952
+ process.stderr.write(`${line}
11953
+ `);
11954
+ });
11955
+ const sleep = deps.sleep ?? ((ms2) => new Promise((resolve8) => setTimeout(resolve8, ms2)));
11956
+ const now = deps.now ?? Date.now;
11957
+ const timeoutMs = deps.timeoutMs ?? SPACE_SETUP_TIMEOUT_MS;
11958
+ const started = now();
11959
+ const deadline = started + timeoutMs;
11960
+ const probe = async () => deps.probe().catch(() => "transient");
11961
+ const unreadable = () => write(
11962
+ `Warning: this login cannot read your TinyCloud space to check it${report.error ? `; OpenKey reported: ${report.error}` : report.pending ? "; OpenKey is still setting it up" : ""}. If the first storage command fails with "Space not found", OpenKey is still setting up your space. ${OPENKEY_SPACE_SETUP_RETRY_HINT}`
11963
+ );
11964
+ let result = await probe();
11965
+ if (result === "hosted") {
11966
+ if (!report.pending && report.error) write(`Warning: OpenKey reported a problem finishing your account setup: ${report.error}`);
11967
+ return;
11968
+ }
11969
+ if (result === "unreadable") {
11970
+ unreadable();
11971
+ return;
11972
+ }
11973
+ if (!report.pending) {
11974
+ if (result === "transient") {
11975
+ write(`Warning: could not check whether your TinyCloud space is hosted${report.error ? `; OpenKey reported: ${report.error}` : ""}. ${OPENKEY_SPACE_SETUP_RETRY_HINT}`);
11976
+ return;
11977
+ }
11978
+ if (report.error) {
11979
+ throw new CLIError(
11980
+ "SPACE_SETUP_FAILED",
11981
+ `OpenKey could not finish setting up your space: ${report.error} Your login is saved, but storage commands will fail with "Space not found" until the space is hosted.`,
11982
+ ExitCode.ERROR,
11983
+ { hint: "Approve again in OpenKey to re-run the setup, then retry." }
11984
+ );
11985
+ }
11986
+ write(
11987
+ 'Warning: OpenKey did not set up your TinyCloud space (spaceReady: false), so storage commands will fail with "Space not found". Turn on Auto-Sign in OpenKey and approve again, or host the space from a wallet profile.'
11988
+ );
11989
+ return;
11990
+ }
11991
+ write(
11992
+ "Setting up your space\u2026 (OpenKey is hosting it; this can take a minute or two)" + (report.error ? ` OpenKey reported: ${report.error} It is still retrying.` : "")
11993
+ );
11994
+ let lastProgress = started;
11995
+ for (let attempt = 0; ; attempt++) {
11996
+ const current = now();
11997
+ if (current >= deadline) break;
11998
+ await sleep(Math.min(POLL_DELAYS_MS[Math.min(attempt, POLL_DELAYS_MS.length - 1)], deadline - current));
11999
+ result = await probe();
12000
+ if (result === "hosted") {
12001
+ write("Your space is ready.");
12002
+ return;
12003
+ }
12004
+ if (result === "unreadable") {
12005
+ unreadable();
12006
+ return;
12007
+ }
12008
+ const elapsed = now();
12009
+ if (elapsed < deadline && elapsed - lastProgress >= PROGRESS_INTERVAL_MS) {
12010
+ lastProgress = elapsed;
12011
+ write(`Still setting up your space\u2026 (${Math.round((elapsed - started) / 1e3)}s)`);
12012
+ }
12013
+ }
12014
+ throw new CLIError(
12015
+ "SPACE_SETUP_PENDING",
12016
+ `OpenKey is still setting up your space after ${Math.round(timeoutMs / 1e3)} seconds.${report.error ? ` OpenKey's last report: ${report.error}` : ""} Your login is saved, but storage commands will fail with "Space not found" until the space is hosted.`,
12017
+ ExitCode.ERROR,
12018
+ { hint: OPENKEY_SPACE_SETUP_RETRY_HINT }
12019
+ );
12020
+ }
12021
+ function hasKvAction(permission, action) {
12022
+ return permission.actions.some((entry) => entry === action || entry === `tinycloud.kv/${action}`);
12023
+ }
12024
+ function readTarget(permissions) {
12025
+ const kv = (permissions ?? []).filter((permission) => permission.service === "tinycloud.kv" || permission.service === "kv");
12026
+ const list = kv.find((permission) => hasKvAction(permission, "list"));
12027
+ if (list) return { op: "list", prefix: list.path };
12028
+ const get = kv.find((permission) => hasKvAction(permission, "get"));
12029
+ if (!get) return void 0;
12030
+ return { op: "get", key: get.path === "" || get.path.endsWith("/") ? `${get.path}.tinycloud-space-probe` : get.path };
12031
+ }
12032
+ var UNHOSTED = /space not found/i;
12033
+ function kvSpaceProbe(ctx, permissions, restore2 = restoreProfileSessionNode) {
12034
+ const target = readTarget(permissions);
12035
+ let restored;
12036
+ return async () => {
12037
+ if (!target) return "unreadable";
12038
+ restored ??= await restore2(ctx);
12039
+ if ("unavailable" in restored) return "unreadable";
12040
+ const kv = restored.node.kv;
12041
+ const result = target.op === "list" ? await kv.list({ prefix: target.prefix, limit: 1 }) : await kv.get(target.key);
12042
+ if (result.ok) return "hosted";
12043
+ const status = result.error.meta?.status;
12044
+ if (status === 404 && UNHOSTED.test(result.error.message)) return "unhosted";
12045
+ if (status === 401 || status === 403) return "unreadable";
12046
+ return result.error.code === "KV_NOT_FOUND" || status === 404 ? "hosted" : "transient";
12047
+ };
12048
+ }
12049
+ async function ensureSpaceSetup(ctx, delegation, permissions) {
12050
+ const report = spaceSetupReport(delegation);
12051
+ if (!report) return;
12052
+ await awaitSpaceSetup(report, { probe: kvSpaceProbe(ctx, permissions) });
12053
+ }
12054
+
11889
12055
  // src/commands/auth.ts
11890
12056
  init_raw_encryption();
11891
12057
  init_owner_did();
@@ -12212,7 +12378,7 @@ async function cidBoundOwnerSpace(record3, ownerDid, node, host) {
12212
12378
  try {
12213
12379
  const signed = parseSignedCompactUcanAttenuation2(withoutBearer2(authorization), delegation.cid);
12214
12380
  for (const resource of Object.keys(signed.payload.att)) {
12215
- const space = /^(tinycloud:[^/]+)\/[^/]+\/.*$/.exec(resource)?.[1];
12381
+ const space = /^(tinycloud:[^/]+)\/[^/]+(?:\/.*)?$/.exec(resource)?.[1];
12216
12382
  if (space && ownerDidFromSpace(space) && normalizeDid(ownerDidFromSpace(space)) === normalizeDid(ownerDid)) {
12217
12383
  return space;
12218
12384
  }
@@ -12615,6 +12781,290 @@ function approvalNotices(groups) {
12615
12781
  };
12616
12782
  }
12617
12783
 
12784
+ // src/lib/apps-agent-preset.ts
12785
+ init_constants();
12786
+ init_errors();
12787
+ init_safe_text();
12788
+ init_raw_encryption();
12789
+ init_space();
12790
+ import { DEFAULT_EXPIRY as DEFAULT_EXPIRY2, parseExpiry as parseExpiry3, SERVICE_LONG_TO_SHORT as SERVICE_LONG_TO_SHORT3, VAULT_PERMISSION_SERVICE as VAULT_PERMISSION_SERVICE2 } from "@tinycloud/sdk-core";
12791
+ import { CLI_NAME as CLI_NAME15 } from "@tinycloud/operations/cli-name";
12792
+ var APPS_AGENT_PRESET = "apps-agent";
12793
+ var APPS_AGENT_SPACE = "applications";
12794
+ var APPS_AGENT_EXPIRY_DAYS = 7;
12795
+ var APPS_AGENT_EXPIRY = `${APPS_AGENT_EXPIRY_DAYS}d`;
12796
+ var APPS_AGENT_MAX_APPS = 5;
12797
+ var DAY_MS2 = 864e5;
12798
+ var APPS_AGENT_VARIANTS = {
12799
+ read: {
12800
+ grants: [
12801
+ { service: "tinycloud.kv", actions: ["tinycloud.kv/get", "tinycloud.kv/list"] },
12802
+ { service: "tinycloud.sql", actions: ["tinycloud.sql/read"] }
12803
+ ],
12804
+ perAppOnly: false,
12805
+ changesStructure: false
12806
+ },
12807
+ write: {
12808
+ grants: [
12809
+ { service: "tinycloud.kv", actions: ["tinycloud.kv/get", "tinycloud.kv/list", "tinycloud.kv/put"] },
12810
+ { service: "tinycloud.sql", actions: ["tinycloud.sql/read", "tinycloud.sql/write"] }
12811
+ ],
12812
+ perAppOnly: true,
12813
+ changesStructure: true
12814
+ }
12815
+ };
12816
+ var ACTION_WORDS = {
12817
+ "tinycloud.kv/get": "read files",
12818
+ "tinycloud.kv/list": "list files",
12819
+ "tinycloud.kv/put": "create and overwrite files",
12820
+ "tinycloud.kv/del": "delete files",
12821
+ "tinycloud.kv/metadata": "read file metadata",
12822
+ "tinycloud.sql/read": "read table rows",
12823
+ "tinycloud.sql/write": "add, change and delete table rows",
12824
+ "tinycloud.sql/schema": "create, change and drop tables",
12825
+ "tinycloud.sql/admin": "do anything in the database",
12826
+ "tinycloud.delegation/create": "can give other people or agents access on your behalf",
12827
+ "tinycloud.delegation/revoke": "can cancel access you gave",
12828
+ "tinycloud.delegation/*": "can do everything the delegation permissions allow, including giving access on to others",
12829
+ "tinycloud.kv/*": "do anything with files (read, write, delete)",
12830
+ "tinycloud.sql/*": "do anything in the database",
12831
+ "tinycloud.capabilities/read": "see which permissions this key holds"
12832
+ };
12833
+ var STRUCTURE_ACTIONS = /* @__PURE__ */ new Set([
12834
+ "tinycloud.sql/write",
12835
+ "tinycloud.sql/schema",
12836
+ "tinycloud.sql/admin",
12837
+ "tinycloud.sql/*"
12838
+ ]);
12839
+ var APP_ID = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
12840
+ var APP_ID_RULE = "an app id looks like xyz.example.app: letters, digits, dots, hyphens and underscores, no slashes or spaces";
12841
+ function isAppId(value) {
12842
+ return value.length <= 200 && APP_ID.test(value);
12843
+ }
12844
+ function appsAgentPermissions(mode, appIds = []) {
12845
+ const paths = appIds.length === 0 ? [""] : appIds.map((appId) => `${appId}/`);
12846
+ return APPS_AGENT_VARIANTS[mode].grants.flatMap(({ service, actions }) => paths.map((path) => ({ service, space: APPS_AGENT_SPACE, path, actions: [...actions] })));
12847
+ }
12848
+ function quoteApps(appIds) {
12849
+ return appIds.map((appId) => `\`${appId}\``).join(", ");
12850
+ }
12851
+ function lifetimePhrase(ms2) {
12852
+ if (ms2 >= DAY_MS2) {
12853
+ const days = Math.ceil(ms2 / DAY_MS2);
12854
+ return `${days} day${days === 1 ? "" : "s"}`;
12855
+ }
12856
+ const hours = Math.max(1, Math.ceil(ms2 / 36e5));
12857
+ return `${hours} hour${hours === 1 ? "" : "s"}`;
12858
+ }
12859
+ function revokeSentence(lifetimeMs2) {
12860
+ return `It lasts up to ${lifetimePhrase(lifetimeMs2)}; revoke it any time with \`${CLI_NAME15} delegation revoke <id>\`.`;
12861
+ }
12862
+ function appsAgentScopeNotice(mode, appIds = []) {
12863
+ const variant = APPS_AGENT_VARIANTS[mode];
12864
+ const reads = variant.grants.some(({ actions }) => actions.includes("tinycloud.kv/put")) ? "read it and add and change its files and table rows" : "list and read its files and read its table rows";
12865
+ const scope = appIds.length === 0 ? `ALL your installed apps' data in the \`${APPS_AGENT_SPACE}\` space` : `the data of ${quoteApps(appIds)} (everything under ${appIds.map((appId) => `\`${appId}/\``).join(", ")}) in the \`${APPS_AGENT_SPACE}\` space`;
12866
+ const lines = [
12867
+ variant.changesStructure ? `This gives the agent access to ${scope}: it can ${reads}.` : `This gives the agent READ-ONLY access to ${scope}: it can ${reads}.`
12868
+ ];
12869
+ if (variant.changesStructure) {
12870
+ lines.push(
12871
+ `Because of how TinyCloud permissions work today, it can also change or delete ${appIds.length === 1 ? "this app's" : "these apps'"} tables (structure) and data.`,
12872
+ "It cannot touch other apps, other spaces or secrets."
12873
+ );
12874
+ } else {
12875
+ lines.push("It cannot create, change or delete anything, and it cannot touch other spaces or secrets.");
12876
+ }
12877
+ lines.push(revokeSentence(APPS_AGENT_EXPIRY_DAYS * DAY_MS2));
12878
+ return lines.join("\n");
12879
+ }
12880
+ function usage(message) {
12881
+ return new CLIError("INVALID_ARGUMENT", message, ExitCode.USAGE_ERROR);
12882
+ }
12883
+ function resolveAppsAgentRequest(options) {
12884
+ const apps = options.app ?? [];
12885
+ if (options.preset === void 0) {
12886
+ if (apps.length > 0 || options.write === true) {
12887
+ throw usage(`--app and --write require --preset ${APPS_AGENT_PRESET}.`);
12888
+ }
12889
+ return void 0;
12890
+ }
12891
+ if (options.preset !== APPS_AGENT_PRESET) {
12892
+ throw usage(`Unknown --preset "${safeClipped(options.preset)}". Available: ${APPS_AGENT_PRESET}.`);
12893
+ }
12894
+ if ((options.cap?.length ?? 0) > 0 || options.permission !== void 0 || options.manifest !== void 0 || options.expiry !== void 0) {
12895
+ throw usage(
12896
+ `--preset ${APPS_AGENT_PRESET} is the whole request (${APPS_AGENT_EXPIRY}); it cannot be combined with --cap, --permission, --manifest or --expiry.`
12897
+ );
12898
+ }
12899
+ if (options.device === true) {
12900
+ throw usage(`--preset ${APPS_AGENT_PRESET} includes SQL, which device approval cannot grant. Use browser or --paste approval.`);
12901
+ }
12902
+ const mode = options.write === true ? "write" : "read";
12903
+ const appIds = [...new Set(apps)];
12904
+ for (const appId of appIds) {
12905
+ if (!isAppId(appId)) throw usage(`Invalid --app "${safeClipped(appId)}": ${APP_ID_RULE}.`);
12906
+ }
12907
+ if (appIds.length > APPS_AGENT_MAX_APPS) {
12908
+ throw usage(`--app can be repeated at most ${APPS_AGENT_MAX_APPS} times; for more apps, ask for read-only access to the whole space (leave --app off).`);
12909
+ }
12910
+ if (APPS_AGENT_VARIANTS[mode].perAppOnly && appIds.length === 0) {
12911
+ throw usage("--write needs --app <app_id>: write access is only given for named apps, never for the whole space.");
12912
+ }
12913
+ return { mode, appIds };
12914
+ }
12915
+ function lifetimeMs(value) {
12916
+ try {
12917
+ if (value === void 0) return parseExpiry3(DEFAULT_EXPIRY2);
12918
+ if (typeof value === "number") return Number.isFinite(value) && value > 0 ? value : void 0;
12919
+ return /^\d+$/.test(value.trim()) ? Number(value.trim()) : parseExpiry3(value);
12920
+ } catch {
12921
+ return void 0;
12922
+ }
12923
+ }
12924
+ function inApplicationsSpace(entry, applicationsSpace) {
12925
+ return normalizePkhIdentifier(entry.space ?? "") === normalizePkhIdentifier(applicationsSpace);
12926
+ }
12927
+ function sameActions(have, want) {
12928
+ return have.length === want.length && want.every((action) => have.includes(action));
12929
+ }
12930
+ function matchAppsAgentRequest(requested, requestedExpiry, applicationsSpace) {
12931
+ if (lifetimeMs(requestedExpiry) !== APPS_AGENT_EXPIRY_DAYS * DAY_MS2) return void 0;
12932
+ if (requested.some((entry) => entry.expiry !== void 0 || entry.caveats !== void 0)) return void 0;
12933
+ const kvPaths = requested.filter((entry) => entry.service === "tinycloud.kv").map((entry) => entry.path);
12934
+ const appIds = [];
12935
+ for (const path of new Set(kvPaths)) {
12936
+ if (path === "") continue;
12937
+ if (!path.endsWith("/") || !isAppId(path.slice(0, -1))) return void 0;
12938
+ appIds.push(path.slice(0, -1));
12939
+ }
12940
+ if (appIds.length > APPS_AGENT_MAX_APPS || appIds.length > 0 && kvPaths.includes("")) return void 0;
12941
+ for (const mode of ["read", "write"]) {
12942
+ if (APPS_AGENT_VARIANTS[mode].perAppOnly && appIds.length === 0) continue;
12943
+ const expected = appsAgentPermissions(mode, appIds);
12944
+ const exact = requested.length === expected.length && expected.every((want) => requested.some((have) => have.service === want.service && inApplicationsSpace(have, applicationsSpace) && have.path === want.path && sameActions(have.actions, want.actions)));
12945
+ if (exact) return { mode, appIds };
12946
+ }
12947
+ return void 0;
12948
+ }
12949
+ var RAW_ENCRYPTION_SPACE = "encryption";
12950
+ function badRequest(message) {
12951
+ return new CLIError("INVALID_AUTH_REQUEST", `${message} Nothing was signed.`, ExitCode.USAGE_ERROR);
12952
+ }
12953
+ var DID_SHAPE = /^did:[a-z0-9]+:[A-Za-z0-9._:%#-]+$/;
12954
+ function assertRequestFields(request) {
12955
+ if (typeof request.sessionDid !== "string" || request.sessionDid.length > 300 || !DID_SHAPE.test(request.sessionDid)) {
12956
+ throw badRequest("The request's sessionDid is not a valid DID.");
12957
+ }
12958
+ if (request.requestedExpiry !== void 0 && (typeof request.requestedExpiry !== "string" && typeof request.requestedExpiry !== "number" || lifetimeMs(request.requestedExpiry) === void 0)) {
12959
+ throw badRequest("The request's requestedExpiry is not a duration such as 7d or 12h.");
12960
+ }
12961
+ }
12962
+ function entriesForSigning(requested) {
12963
+ return requested.map((entry, index) => {
12964
+ const label = `Entry ${index + 1} of the request`;
12965
+ if (typeof entry !== "object" || entry === null || typeof entry.service !== "string" || entry.service === "" || typeof entry.path !== "string" || !Array.isArray(entry.actions) || entry.actions.length === 0 || entry.actions.some((action) => typeof action !== "string" || action === "")) {
12966
+ throw badRequest(`${label} is malformed: it needs a service, a path and at least one action.`);
12967
+ }
12968
+ if (entry.service === VAULT_PERMISSION_SERVICE2 || entry.service === "vault") {
12969
+ throw badRequest(`${label} asks for the vault (\`tinycloud.vault\`). This command does not sign vault access from a request file: the signer turns it into KV access on \`vault/\` for the vault actions you listed, so the printed list would not show what is signed.`);
12970
+ }
12971
+ if (entry.space !== void 0 && (typeof entry.space !== "string" || entry.space === "")) {
12972
+ throw badRequest(`${label} has an empty space: name the space, or leave it out to mean your \`${APPS_AGENT_SPACE}\` space.`);
12973
+ }
12974
+ return entry.space === void 0 && !isRawEncryptionPermission(entry) ? { ...entry, space: APPS_AGENT_SPACE } : entry;
12975
+ });
12976
+ }
12977
+ function finalizeSigningEntries(resolved) {
12978
+ const merged = /* @__PURE__ */ new Map();
12979
+ const spaces = /* @__PURE__ */ new Set();
12980
+ const spellings = /* @__PURE__ */ new Map();
12981
+ resolved.forEach((entry, index) => {
12982
+ const label = `Entry ${index + 1} of the request`;
12983
+ if (SERVICE_LONG_TO_SHORT3[entry.service] === void 0) {
12984
+ throw badRequest(`${label} names the service "${safeClipped(entry.service)}", which the signer cannot sign.`);
12985
+ }
12986
+ const space = entry.space;
12987
+ const raw = isRawEncryptionPermission(entry);
12988
+ const concrete = typeof space === "string" && (space.startsWith("tinycloud:") || space === RAW_ENCRYPTION_SPACE && raw);
12989
+ if (!concrete) {
12990
+ throw badRequest(`${label} does not resolve to a concrete space (got "${safeClipped(String(space))}").`);
12991
+ }
12992
+ const normalized = normalizePkhIdentifier(space);
12993
+ if (!raw) spaces.add(normalized);
12994
+ const spelled = spellings.get(normalized) ?? space;
12995
+ spellings.set(normalized, spelled);
12996
+ const unified = { ...entry, space: spelled };
12997
+ const key = JSON.stringify([entry.service, normalized, entry.path, entry.expiry ?? null, entry.caveats ?? null]);
12998
+ const existing = merged.get(key);
12999
+ merged.set(key, existing === void 0 ? unified : { ...existing, actions: [...existing.actions, ...entry.actions] });
13000
+ });
13001
+ if (spaces.size > 1) {
13002
+ throw badRequest(`The request names ${spaces.size} different spaces (${safeClippedList([...spaces])}); a grant covers one space, so make one request per space.`);
13003
+ }
13004
+ return [...merged.values()].map((entry) => ({ ...entry, actions: [...new Set(entry.actions)].sort() }));
13005
+ }
13006
+ function describeAction(action) {
13007
+ if (Object.prototype.hasOwnProperty.call(ACTION_WORDS, action)) return ACTION_WORDS[action];
13008
+ return action.endsWith("/*") ? `ALL abilities of ${safeInline(action.slice(0, -2))} (unrecognised: grants ${safeInline(action)})` : `unrecognised: grants ${safeInline(action)}`;
13009
+ }
13010
+ function describeEntry(entry, applicationsSpace) {
13011
+ const inApplications = inApplicationsSpace(entry, applicationsSpace);
13012
+ const space = inApplications ? "your `applications` space" : `space \`${safeInline(entry.space ?? "")}\``;
13013
+ const wholeSpace = entry.path === "" || entry.path === "/";
13014
+ const where = wholeSpace ? inApplications ? `ALL apps in ${space}` : `everything in ${space}` : `\`${safeInline(entry.path)}\` in ${space}`;
13015
+ const known = entry.service === "tinycloud.kv" || entry.service === "tinycloud.sql" || entry.service === "tinycloud.delegation" || entry.service === "tinycloud.capabilities";
13016
+ const service = known ? safeInline(entry.service) : `unrecognised service ${safeInline(entry.service)}`;
13017
+ return `${entry.actions.map(describeAction).join(", ")} on ${where} (${service})`;
13018
+ }
13019
+ function withinPresetSet(entry, applicationsSpace) {
13020
+ if (!inApplicationsSpace(entry, applicationsSpace) || entry.caveats !== void 0) return false;
13021
+ const reads = /* @__PURE__ */ new Set();
13022
+ const writes = /* @__PURE__ */ new Set();
13023
+ for (const { grants } of Object.values(APPS_AGENT_VARIANTS)) {
13024
+ for (const grant of grants) {
13025
+ if (grant.service !== entry.service) continue;
13026
+ for (const action of grant.actions) (action.endsWith("/put") || action.endsWith("/write") ? writes : reads).add(action);
13027
+ }
13028
+ }
13029
+ const firstSegment = entry.path.split("/")[0] ?? "";
13030
+ const named = entry.path.includes("/") && isAppId(firstSegment);
13031
+ return entry.actions.length > 0 && entry.actions.every((action) => reads.has(action) || writes.has(action) && named);
13032
+ }
13033
+ function describeGrantScope(requested, requestedExpiry, applicationsSpace) {
13034
+ const preset = matchAppsAgentRequest(requested, requestedExpiry, applicationsSpace);
13035
+ if (preset !== void 0) {
13036
+ return { text: appsAgentScopeNotice(preset.mode, preset.appIds), entries: requested, extras: [] };
13037
+ }
13038
+ const touchesApplications = requested.some((entry) => (entry.space ?? "").split(":").at(-1) === APPS_AGENT_SPACE);
13039
+ const extras = touchesApplications ? requested.filter((entry) => !withinPresetSet(entry, applicationsSpace)) : [];
13040
+ const others = requested.filter((entry) => !extras.includes(entry));
13041
+ const lines = [
13042
+ extras.length > 0 ? "This request asks for MORE than the apps-agent preset. Entries marked ! go beyond it:" : touchesApplications ? "This request is not the standard apps-agent grant (other paths or lifetime). It asks for:" : "This request asks for:",
13043
+ ...extras.map((entry) => `! ${describeEntry(entry, applicationsSpace)}`),
13044
+ ...others.map((entry) => `- ${describeEntry(entry, applicationsSpace)}`)
13045
+ ];
13046
+ if (requested.some((entry) => entry.service === "tinycloud.sql" && entry.actions.some((action) => STRUCTURE_ACTIONS.has(action)))) {
13047
+ lines.push("Because of how TinyCloud permissions work today (TC-1581), SQL write access can also change or delete tables (structure).");
13048
+ }
13049
+ const entryLifetimes = requested.filter((entry) => entry.expiry !== void 0).map((entry) => lifetimeMs(entry.expiry));
13050
+ const durations = [lifetimeMs(requestedExpiry), ...entryLifetimes].filter((ms2) => ms2 !== void 0);
13051
+ const longest = Math.max(...durations);
13052
+ lines.push(
13053
+ durations.length === 0 || !Number.isFinite(longest) ? "Its lifetime could not be read from the request." : `It lasts up to ${lifetimePhrase(longest)}${requestedExpiry === void 0 ? " (no lifetime was requested; this is the default)" : ""}; revoke it any time with \`${CLI_NAME15} delegation revoke <id>\`.`
13054
+ );
13055
+ return { text: lines.join("\n"), entries: requested, extras };
13056
+ }
13057
+
13058
+ // src/lib/consent-text.ts
13059
+ init_safe_text();
13060
+ init_permissions();
13061
+ function permissionConsentLine(permission) {
13062
+ return safeInline(compactPermission(permission));
13063
+ }
13064
+ function signExtrasQuestion(count) {
13065
+ return `Sign the ${count} ${count === 1 ? "entry" : "entries"} marked ! above, and the rest of the request? [y/N] `;
13066
+ }
13067
+
12618
13068
  // src/auth/replication-login.ts
12619
13069
  init_dist();
12620
13070
  init_errors();
@@ -12908,7 +13358,7 @@ async function revokeForLogout(ctx, discardLocalChanges) {
12908
13358
  "REVOCATION_FAILED",
12909
13359
  `Revocation failed for ${cid}: ${result.error.message}. Session, key, and replica files are retained for retry; local reads stay denied.`,
12910
13360
  ExitCode.ERROR,
12911
- { cid, hint: `Retry \`${CLI_NAME15} auth logout --revoke\` when the node is available.` }
13361
+ { cid, hint: `Retry \`${CLI_NAME16} auth logout --revoke\` when the node is available.` }
12912
13362
  );
12913
13363
  }
12914
13364
  if (result.data.cid !== cid || result.data.revoked !== true) {
@@ -12927,7 +13377,7 @@ async function revokeForLogout(ctx, discardLocalChanges) {
12927
13377
  "REVOCATION_FAILED",
12928
13378
  `Revocation failed before local logout: ${error instanceof Error ? error.message : String(error)}. Session, key, and replica files are retained for retry; local reads stay denied.`,
12929
13379
  ExitCode.ERROR,
12930
- { hint: `Retry \`${CLI_NAME15} auth logout --revoke\` when online authority is available.` }
13380
+ { hint: `Retry \`${CLI_NAME16} auth logout --revoke\` when online authority is available.` }
12931
13381
  );
12932
13382
  }
12933
13383
  }
@@ -13126,7 +13576,7 @@ async function finishProfileLogout(ctx, options = {}) {
13126
13576
  function registerAuthCommand(program, options) {
13127
13577
  const auth = program.command("auth").description("Authentication management");
13128
13578
  registerAuthGrantsCommand(auth, { openKeyAcquisition: options?.openKeyAcquisition });
13129
- auth.command("login").description("Authenticate with TinyCloud").option("--device", "Approve on another device (e.g. a phone) through OpenKey device authorization; requires a KV-scoped --manifest (SQL and secret decrypt need browser or --paste login)").option("--paste", "Wait up to 5 minutes for a signed code from OpenKey on stdin instead of a browser callback (Safari may require paste)").option("--no-popup", "Print the OpenKey URL without opening a browser").option("--method <method>", "Authentication method: local or openkey").option("--manifest <fileOrBase64>", `Request only this manifest's permissions (one space, plus raw encryption network entries such as a secrets decrypt grant); ${SHARE_PUBLISHING_MANIFEST_REF} covers \`${CLI_NAME15} share publish\``).option("--expiry <duration>", "OpenKey session lifetime, e.g. 1h or 7d (device login: at most 30d, default 30d)").option("--owner <did>", "Sign in as this owner (did:pkh:eip155:CHAIN:ADDRESS): OpenKey preselects that key, e.g. one that is not the account's primary key, and an approval by any other identity is refused. With --manifest, also names the secrets owner for a manifest's `secrets` on a profile with no recorded owner").option("--replace-session", "Scoped or device login: replace this profile's live session even though the new scope would narrow, change or shorten it (prefer a new profile)").option("--replication-prefix <prefix>", "Add replication get+sync authority for a folder prefix ending in /, or root (repeatable)", (prefix, values = []) => [...values, prefix]).option("--replication-allow-secrets", "Allow replication of secret material (network-wide decrypt warning)").action(async (options2, cmd) => {
13579
+ auth.command("login").description("Authenticate with TinyCloud").option("--device", "Approve on another device (e.g. a phone) through OpenKey device authorization; requires a KV-scoped --manifest (SQL and secret decrypt need browser or --paste login)").option("--paste", "Wait up to 5 minutes for a signed code from OpenKey on stdin instead of a browser callback (Safari may require paste)").option("--no-popup", "Print the OpenKey URL without opening a browser").option("--method <method>", "Authentication method: local or openkey").option("--manifest <fileOrBase64>", `Request only this manifest's permissions (one space, plus raw encryption network entries such as a secrets decrypt grant); ${SHARE_PUBLISHING_MANIFEST_REF} covers \`${CLI_NAME16} share publish\``).option("--expiry <duration>", "OpenKey session lifetime, e.g. 1h or 7d (device login: at most 30d, default 30d)").option("--owner <did>", "Sign in as this owner (did:pkh:eip155:CHAIN:ADDRESS): OpenKey preselects that key, e.g. one that is not the account's primary key, and an approval by any other identity is refused. With --manifest, also names the secrets owner for a manifest's `secrets` on a profile with no recorded owner").option("--replace-session", "Scoped or device login: replace this profile's live session even though the new scope would narrow, change or shorten it (prefer a new profile)").option("--replication-prefix <prefix>", "Add replication get+sync authority for a folder prefix ending in /, or root (repeatable)", (prefix, values = []) => [...values, prefix]).option("--replication-allow-secrets", "Allow replication of secret material (network-wide decrypt warning)").action(async (options2, cmd) => {
13130
13580
  try {
13131
13581
  if (options2.device && options2.paste) {
13132
13582
  throw new CLIError("INVALID_ARGUMENT", "--device and --paste are mutually exclusive.", ExitCode.USAGE_ERROR);
@@ -13176,6 +13626,7 @@ function registerAuthCommand(program, options) {
13176
13626
  await persistReplicationLogin(ctx.profile, replicationOptions);
13177
13627
  if (replicationOptions.allowSecrets) writeReplicationSecretsWarning();
13178
13628
  reportDeclined(result.declined);
13629
+ await ensureSpaceSetup({ profile: ctx.profile, host: ctx.host }, result.session, result.session.permissions);
13179
13630
  outputJson({
13180
13631
  authenticated: true,
13181
13632
  profile: ctx.profile,
@@ -13275,7 +13726,7 @@ function registerAuthCommand(program, options) {
13275
13726
  authenticated: false,
13276
13727
  replicasRemoved,
13277
13728
  replicasKept: options2.keepReplicas === true,
13278
- warning: online ? "The node confirmed revocation of the listed CIDs before local logout." : options2.keepReplicas ? "Legacy replica files remain on disk; this local logout clears the session but does not revoke node grants. Purge the files before a future sign-in if they must not be reused." : `Logout is local; delegations and grants on the node stay valid until they expire. Use \`${CLI_NAME15} delegation revoke <cid>\` to end access now.`,
13729
+ warning: online ? "The node confirmed revocation of the listed CIDs before local logout." : options2.keepReplicas ? "Legacy replica files remain on disk; this local logout clears the session but does not revoke node grants. Purge the files before a future sign-in if they must not be reused." : `Logout is local; delegations and grants on the node stay valid until they expire. Use \`${CLI_NAME16} delegation revoke <cid>\` to end access now.`,
13279
13730
  ...online === void 0 ? {} : {
13280
13731
  revokedCids: online.plan.cids,
13281
13732
  revocationExcluded: online.plan.excluded
@@ -13365,7 +13816,15 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
13365
13816
  ).option("--permission <file>", 'JSON permission request: { "permissions": PermissionEntry[] }').option("--manifest <fileOrBase64>", "Manifest file, base64:<json>, or raw base64 JSON").option(
13366
13817
  "--expiry <duration>",
13367
13818
  `Lifetime of the granted delegation. ms-format string (e.g. "7d", "30m") or raw milliseconds. Defaults to 7d, capped by the active session's expiry.`
13368
- ).option("--emit [file]", "Emit the request artifact to stdout, or write it to file when provided").option("--grant", "Grant the requested permissions immediately with this owner profile").option("--yes", "Skip local-key TTY confirmation", false).option("--no-popup", "Print the OpenKey URL without opening a browser when granting with OpenKey").option("--device", "With --grant: approve on another device (e.g. a phone) through OpenKey device authorization (KV-scoped; SQL needs browser login)").option("--paste", "With --grant on an OpenKey profile: wait up to 5 minutes for a signed code from OpenKey on stdin instead of a browser callback (use when the browser cannot reach this CLI)").action(async (options2, cmd) => {
13819
+ ).option("--emit [file]", "Emit the request artifact to stdout, or write it to file when provided").option("--grant", "Grant the requested permissions immediately with this owner profile").option("--yes", "Skip local-key TTY confirmation", false).option("--no-popup", "Print the OpenKey URL without opening a browser when granting with OpenKey").option("--device", "With --grant: approve on another device (e.g. a phone) through OpenKey device authorization (KV-scoped; SQL needs browser login)").option(
13820
+ "--preset <name>",
13821
+ `Request a named permission set instead of --cap: ${APPS_AGENT_PRESET} is READ-ONLY on the applications space (kv get/list + sql read, ${APPS_AGENT_EXPIRY_DAYS} days)`
13822
+ ).option(
13823
+ "--app <app_id>",
13824
+ `With --preset ${APPS_AGENT_PRESET}: limit the grant to this app's <app_id>/ prefix (recommended); repeat for up to ${APPS_AGENT_MAX_APPS} apps`,
13825
+ (value, previous) => [...previous, value],
13826
+ []
13827
+ ).option("--write", `Don't request \`--write\` during the tiny-apps beta. It currently also allows changes to the app's own code and manifest; the narrowed write ships in TC-1620. With --preset ${APPS_AGENT_PRESET} it also allows writes (kv put + sql write) in the named --app apps; SQL write can also change or drop those apps' tables`).option("--paste", "With --grant on an OpenKey profile: wait up to 5 minutes for a signed code from OpenKey on stdin instead of a browser callback (use when the browser cannot reach this CLI)").action(async (options2, cmd) => {
13369
13828
  try {
13370
13829
  const globalOpts = cmd.optsWithGlobals();
13371
13830
  const ctx = await ProfileManager.resolveContext(globalOpts, options2.grant ? {} : { storedSession: "unused" });
@@ -13376,8 +13835,13 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
13376
13835
  if (options2.device && (!options2.grant || resolveProfilePosture(profile) !== "owner-openkey")) {
13377
13836
  throw new CLIError("INVALID_ARGUMENT", "--device requires --grant on an OpenKey owner profile.", ExitCode.USAGE_ERROR);
13378
13837
  }
13379
- const requested = await collectRequestedPermissions(options2, ctx.profile);
13380
- const expiryOption = parseExpiryOption(options2.expiry);
13838
+ const preset = resolveAppsAgentRequest(options2);
13839
+ const requested = preset === void 0 ? await collectRequestedPermissions(options2, ctx.profile) : await resolvePermissionSpaces(appsAgentPermissions(preset.mode, preset.appIds), ctx.profile);
13840
+ const expiryOption = preset === void 0 ? parseExpiryOption(options2.expiry) : APPS_AGENT_EXPIRY;
13841
+ if (preset !== void 0) process.stderr.write(`
13842
+ ${appsAgentScopeNotice(preset.mode, preset.appIds)}
13843
+
13844
+ `);
13381
13845
  if (requested.length === 0) {
13382
13846
  throw new CLIError(
13383
13847
  "NO_CAPS_REQUESTED",
@@ -13405,7 +13869,7 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
13405
13869
  if (profile.authMethod === "openkey" || options2.device) {
13406
13870
  const key = await ProfileManager.getKey(ctx.profile);
13407
13871
  if (!key) {
13408
- throw new CLIError("NO_KEY", `No key found for profile "${ctx.profile}". Run \`${CLI_NAME15} --profile ${ctx.profile} auth rotate\` to create a new key and sign in.`, ExitCode.AUTH_REQUIRED);
13872
+ throw new CLIError("NO_KEY", `No key found for profile "${ctx.profile}". Run \`${CLI_NAME16} --profile ${ctx.profile} auth rotate\` to create a new key and sign in.`, ExitCode.AUTH_REQUIRED);
13409
13873
  }
13410
13874
  const openkeyHost = resolveOpenKeyHost(profile);
13411
13875
  const grants = [];
@@ -13425,7 +13889,7 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
13425
13889
  if (notices) process.stderr.write(`${notices.prompts[index]}
13426
13890
  `);
13427
13891
  const reason = permissionGrantReason(
13428
- `Grant requested TinyCloud permissions from \`${CLI_NAME15} auth request --grant\`.`,
13892
+ `Grant requested TinyCloud permissions from \`${CLI_NAME16} auth request --grant\`.`,
13429
13893
  group
13430
13894
  );
13431
13895
  const request = grantRequestPermissions(group, profile.spaceId ?? profile.spaceName);
@@ -13535,7 +13999,7 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
13535
13999
  kind: parsed.kind,
13536
14000
  requestId: parsed.requestId,
13537
14001
  requested: parsed.requested,
13538
- next: `${CLI_NAME15} auth retry ${parsed.requestId}`
14002
+ next: `${CLI_NAME16} auth retry ${parsed.requestId}`
13539
14003
  });
13540
14004
  return;
13541
14005
  }
@@ -13669,7 +14133,12 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
13669
14133
  const raw = await readAuthArtifactSource(source, {
13670
14134
  stdin: options2.stdin === true || options2.paste === true
13671
14135
  });
13672
- const parsed = JSON.parse(raw);
14136
+ let parsed;
14137
+ try {
14138
+ parsed = JSON.parse(raw);
14139
+ } catch {
14140
+ throw new CLIError("INVALID_AUTH_REQUEST", "The request file is not valid JSON.", ExitCode.USAGE_ERROR);
14141
+ }
13673
14142
  if (!isCompatiblePermissionRequestArtifact(parsed)) {
13674
14143
  throw new CLIError(
13675
14144
  "INVALID_AUTH_REQUEST",
@@ -13677,7 +14146,31 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
13677
14146
  ExitCode.USAGE_ERROR
13678
14147
  );
13679
14148
  }
13680
- const requested = await resolvePermissionSpaces(parsed.requested, ctx.profile);
14149
+ assertRequestFields(parsed);
14150
+ const requested = finalizeSigningEntries(
14151
+ await resolvePermissionSpaces(entriesForSigning(parsed.requested), ctx.profile)
14152
+ );
14153
+ const [applications] = await resolvePermissionSpaces(
14154
+ [{ service: "tinycloud.kv", space: APPS_AGENT_SPACE, path: "", actions: ["tinycloud.kv/get"] }],
14155
+ ctx.profile
14156
+ );
14157
+ const scope = describeGrantScope(requested, parsed.requestedExpiry, applications.space ?? APPS_AGENT_SPACE);
14158
+ process.stderr.write(`
14159
+ ${scope.text}
14160
+
14161
+ `);
14162
+ let confirmedExtras = false;
14163
+ if (scope.extras.length > 0 && options2.yes !== true) {
14164
+ if (!isInteractive()) {
14165
+ throw new CLIError(
14166
+ "CONFIRMATION_REQUIRED",
14167
+ `This request asks for more than the apps-agent preset (${scope.extras.length} entr${scope.extras.length === 1 ? "y" : "ies"} marked ! in the list above). Nothing was signed. Read the list, then re-run with --yes to sign it.`,
14168
+ ExitCode.USAGE_ERROR
14169
+ );
14170
+ }
14171
+ await askYesNo(signExtrasQuestion(scope.extras.length), "Grant cancelled. Nothing was signed.");
14172
+ confirmedExtras = true;
14173
+ }
13681
14174
  const resolvedRequest = { ...parsed, requested };
13682
14175
  const node = await ensureAuthenticated(ctx);
13683
14176
  await ensureDelegationAuthority({
@@ -13687,7 +14180,7 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
13687
14180
  requested,
13688
14181
  expiryOption: parsed.requestedExpiry,
13689
14182
  reason: "Grant permissions requested by a TinyCloud auth request artifact.",
13690
- yes: options2.yes === true
14183
+ yes: options2.yes === true || confirmedExtras
13691
14184
  });
13692
14185
  const grant = await grantAuthRequest(node, resolvedRequest);
13693
14186
  await appendLocalGrantArtifact(ctx.profile, grant.delegation);
@@ -14086,7 +14579,7 @@ async function ensureDelegationAuthority(params) {
14086
14579
  if (!key) {
14087
14580
  throw new CLIError(
14088
14581
  "NO_KEY",
14089
- `No key found for profile "${params.ctx.profile}". Run \`${CLI_NAME15} --profile ${params.ctx.profile} auth rotate\` to create a new key and sign in.`,
14582
+ `No key found for profile "${params.ctx.profile}". Run \`${CLI_NAME16} --profile ${params.ctx.profile} auth rotate\` to create a new key and sign in.`,
14090
14583
  ExitCode.AUTH_REQUIRED
14091
14584
  );
14092
14585
  }
@@ -14198,26 +14691,29 @@ async function collectRequestedPermissions(options, profile) {
14198
14691
  }
14199
14692
  return permissions;
14200
14693
  }
14201
- async function confirmPermissionRequest(permissions) {
14202
- process.stderr.write("\n" + theme.heading("Additional Permissions") + "\n");
14203
- for (const permission of permissions) {
14204
- const dangerous = isDangerousPermission(permission);
14205
- const line = ` ${compactPermission(permission)}`;
14206
- process.stderr.write((dangerous ? theme.warn(line) : theme.value(line)) + "\n");
14207
- }
14208
- process.stderr.write("\n");
14694
+ async function askYesNo(question, cancelMessage) {
14209
14695
  const rl = createInterface3({
14210
14696
  input: process.stdin,
14211
14697
  output: process.stderr
14212
14698
  });
14213
14699
  const answer = await new Promise((resolve8) => {
14214
- rl.question("Approve local-key delegation? [y/N] ", resolve8);
14700
+ rl.question(question, resolve8);
14215
14701
  });
14216
14702
  rl.close();
14217
14703
  if (!/^y(es)?$/i.test(answer.trim())) {
14218
- throw new CLIError("REQUEST_CANCELLED", "Permission request cancelled.", ExitCode.ERROR);
14704
+ throw new CLIError("REQUEST_CANCELLED", cancelMessage, ExitCode.ERROR);
14219
14705
  }
14220
14706
  }
14707
+ async function confirmPermissionRequest(permissions) {
14708
+ process.stderr.write("\n" + theme.heading("Additional Permissions") + "\n");
14709
+ for (const permission of permissions) {
14710
+ const dangerous = isDangerousPermission(permission);
14711
+ const line = ` ${permissionConsentLine(permission)}`;
14712
+ process.stderr.write((dangerous ? theme.warn(line) : theme.value(line)) + "\n");
14713
+ }
14714
+ process.stderr.write("\n");
14715
+ await askYesNo("Approve local-key delegation? [y/N] ", "Permission request cancelled.");
14716
+ }
14221
14717
  async function requirePermissionConsent(permissions, yes) {
14222
14718
  if (isInteractive()) {
14223
14719
  if (!yes) await confirmPermissionRequest(permissions);
@@ -14332,7 +14828,7 @@ async function rotateAuthKey(profileName2, host, options = {}) {
14332
14828
  if (!profile.privateKey) {
14333
14829
  throw new CLIError(
14334
14830
  "LOCAL_OWNER_KEY_REQUIRED",
14335
- `Profile "${profileName2}" does not have a local owner private key. Run \`${CLI_NAME15} auth login --method local\` first.`,
14831
+ `Profile "${profileName2}" does not have a local owner private key. Run \`${CLI_NAME16} auth login --method local\` first.`,
14336
14832
  ExitCode.AUTH_REQUIRED
14337
14833
  );
14338
14834
  }
@@ -14518,6 +15014,7 @@ async function handleLocalAuth(profileName2, host, options = {}) {
14518
15014
  async function handleOpenKeyAuth(profileName2, host, options = {}) {
14519
15015
  const { profile, delegationData, declined, legacyNested } = await refreshOpenKeySession(profileName2, host, options);
14520
15016
  reportDeclined(declined, legacyNested);
15017
+ await ensureSpaceSetup({ profile: profileName2, host }, delegationData, delegationData.permissions);
14521
15018
  outputJson({
14522
15019
  authenticated: true,
14523
15020
  profile: profileName2,
@@ -14772,7 +15269,7 @@ async function refreshOpenKeySession(profileName2, host, options = {}) {
14772
15269
  }
14773
15270
 
14774
15271
  // src/commands/completion.ts
14775
- import { CLI_NAME as CLI_NAME16 } from "@tinycloud/operations/cli-name";
15272
+ import { CLI_NAME as CLI_NAME17 } from "@tinycloud/operations/cli-name";
14776
15273
  function registerCompletionCommand(program) {
14777
15274
  const completion = program.command("completion").description("Generate shell completions");
14778
15275
  completion.command("bash").description("Output bash completions").action(() => {
@@ -14789,7 +15286,7 @@ function registerCompletionCommand(program) {
14789
15286
  });
14790
15287
  }
14791
15288
  function generateBashCompletion() {
14792
- return `# ${CLI_NAME16} bash completion
15289
+ return `# ${CLI_NAME17} bash completion
14793
15290
  _tc_completions() {
14794
15291
  local cur prev commands subcommands
14795
15292
  COMPREPLY=()
@@ -14814,11 +15311,11 @@ _tc_completions() {
14814
15311
  COMPREPLY=( $(compgen -W "\${subcommands}" -- "\${cur}") )
14815
15312
  fi
14816
15313
  }
14817
- complete -F _tc_completions ${CLI_NAME16}
15314
+ complete -F _tc_completions ${CLI_NAME17}
14818
15315
  `;
14819
15316
  }
14820
15317
  function generateZshCompletion() {
14821
- return `#compdef ${CLI_NAME16}
15318
+ return `#compdef ${CLI_NAME17}
14822
15319
 
14823
15320
  _tc() {
14824
15321
  local -a commands
@@ -14869,50 +15366,50 @@ _tc
14869
15366
  `;
14870
15367
  }
14871
15368
  function generateFishCompletion() {
14872
- return `# ${CLI_NAME16} fish completion
15369
+ return `# ${CLI_NAME17} fish completion
14873
15370
  set -l commands init auth kv space delegation share node profile completion
14874
15371
 
14875
15372
  # Disable file completion by default
14876
- complete -c ${CLI_NAME16} -f
15373
+ complete -c ${CLI_NAME17} -f
14877
15374
 
14878
15375
  # Top-level commands
14879
- complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a init -d "Initialize a new TinyCloud profile"
14880
- complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a auth -d "Authentication management"
14881
- complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a kv -d "Key-value store operations"
14882
- complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a space -d "Space management"
14883
- complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a delegation -d "Manage delegations"
14884
- complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a share -d "Share data with others"
14885
- complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a node -d "Node health and info"
14886
- complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a profile -d "Profile management"
14887
- complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a completion -d "Generate shell completions"
15376
+ complete -c ${CLI_NAME17} -n "not __fish_seen_subcommand_from $commands" -a init -d "Initialize a new TinyCloud profile"
15377
+ complete -c ${CLI_NAME17} -n "not __fish_seen_subcommand_from $commands" -a auth -d "Authentication management"
15378
+ complete -c ${CLI_NAME17} -n "not __fish_seen_subcommand_from $commands" -a kv -d "Key-value store operations"
15379
+ complete -c ${CLI_NAME17} -n "not __fish_seen_subcommand_from $commands" -a space -d "Space management"
15380
+ complete -c ${CLI_NAME17} -n "not __fish_seen_subcommand_from $commands" -a delegation -d "Manage delegations"
15381
+ complete -c ${CLI_NAME17} -n "not __fish_seen_subcommand_from $commands" -a share -d "Share data with others"
15382
+ complete -c ${CLI_NAME17} -n "not __fish_seen_subcommand_from $commands" -a node -d "Node health and info"
15383
+ complete -c ${CLI_NAME17} -n "not __fish_seen_subcommand_from $commands" -a profile -d "Profile management"
15384
+ complete -c ${CLI_NAME17} -n "not __fish_seen_subcommand_from $commands" -a completion -d "Generate shell completions"
14888
15385
 
14889
15386
  # Subcommands
14890
- complete -c ${CLI_NAME16} -n "__fish_seen_subcommand_from auth" -a "login logout rotate status whoami"
14891
- complete -c ${CLI_NAME16} -n "__fish_seen_subcommand_from kv" -a "get put delete list head"
14892
- complete -c ${CLI_NAME16} -n "__fish_seen_subcommand_from space" -a "list create info switch"
14893
- complete -c ${CLI_NAME16} -n "__fish_seen_subcommand_from delegation" -a "create list info revoke"
14894
- complete -c ${CLI_NAME16} -n "__fish_seen_subcommand_from share" -a "create receive list revoke"
14895
- complete -c ${CLI_NAME16} -n "__fish_seen_subcommand_from node" -a "health version status"
14896
- complete -c ${CLI_NAME16} -n "__fish_seen_subcommand_from profile" -a "list create show switch delete"
14897
- complete -c ${CLI_NAME16} -n "__fish_seen_subcommand_from completion" -a "bash zsh fish"
15387
+ complete -c ${CLI_NAME17} -n "__fish_seen_subcommand_from auth" -a "login logout rotate status whoami"
15388
+ complete -c ${CLI_NAME17} -n "__fish_seen_subcommand_from kv" -a "get put delete list head"
15389
+ complete -c ${CLI_NAME17} -n "__fish_seen_subcommand_from space" -a "list create info switch"
15390
+ complete -c ${CLI_NAME17} -n "__fish_seen_subcommand_from delegation" -a "create list info revoke"
15391
+ complete -c ${CLI_NAME17} -n "__fish_seen_subcommand_from share" -a "create receive list revoke"
15392
+ complete -c ${CLI_NAME17} -n "__fish_seen_subcommand_from node" -a "health version status"
15393
+ complete -c ${CLI_NAME17} -n "__fish_seen_subcommand_from profile" -a "list create show switch delete"
15394
+ complete -c ${CLI_NAME17} -n "__fish_seen_subcommand_from completion" -a "bash zsh fish"
14898
15395
 
14899
15396
  # Global options
14900
- complete -c ${CLI_NAME16} -l profile -s p -d "Profile to use"
14901
- complete -c ${CLI_NAME16} -l host -s H -d "TinyCloud node URL"
14902
- complete -c ${CLI_NAME16} -l verbose -s v -d "Enable verbose output"
14903
- complete -c ${CLI_NAME16} -l no-cache -d "Disable caching"
14904
- complete -c ${CLI_NAME16} -l quiet -s q -d "Suppress non-essential output"
14905
- complete -c ${CLI_NAME16} -l replication -d "Enable read-through replication"
14906
- complete -c ${CLI_NAME16} -l no-replication -d "Disable replication"
14907
- complete -c ${CLI_NAME16} -l replication-debug -d "Write replication diagnostics to stderr"
15397
+ complete -c ${CLI_NAME17} -l profile -s p -d "Profile to use"
15398
+ complete -c ${CLI_NAME17} -l host -s H -d "TinyCloud node URL"
15399
+ complete -c ${CLI_NAME17} -l verbose -s v -d "Enable verbose output"
15400
+ complete -c ${CLI_NAME17} -l no-cache -d "Disable caching"
15401
+ complete -c ${CLI_NAME17} -l quiet -s q -d "Suppress non-essential output"
15402
+ complete -c ${CLI_NAME17} -l replication -d "Enable read-through replication"
15403
+ complete -c ${CLI_NAME17} -l no-replication -d "Disable replication"
15404
+ complete -c ${CLI_NAME17} -l replication-debug -d "Write replication diagnostics to stderr"
14908
15405
  `;
14909
15406
  }
14910
15407
 
14911
15408
  // src/commands/courier.ts
14912
- import { CLI_NAME as CLI_NAME17 } from "@tinycloud/operations/cli-name";
14913
- var TC_OPTIONS_BEFORE_COURIER = `${CLI_NAME17} courier takes no ${CLI_NAME17} options and does not use ${CLI_NAME17} profiles; Courier keeps its own profile in $COURIER_HOME (default ~/.courier). Put flags after "courier", e.g. ${CLI_NAME17} courier init --host <url>.`;
15409
+ import { CLI_NAME as CLI_NAME18 } from "@tinycloud/operations/cli-name";
15410
+ var TC_OPTIONS_BEFORE_COURIER = `${CLI_NAME18} courier takes no ${CLI_NAME18} options and does not use ${CLI_NAME18} profiles; Courier keeps its own profile in $COURIER_HOME (default ~/.courier). Put flags after "courier", e.g. ${CLI_NAME18} courier init --host <url>.`;
14914
15411
  function registerCourierCommand(program) {
14915
- const courier = program.command("courier").description(`Agent-to-agent threads over TinyCloud; run \`${CLI_NAME17} courier --help\` (beta)`).argument("[args...]").allowUnknownOption().helpOption(false);
15412
+ const courier = program.command("courier").description(`Agent-to-agent threads over TinyCloud; run \`${CLI_NAME18} courier --help\` (beta)`).argument("[args...]").allowUnknownOption().helpOption(false);
14916
15413
  courier.action(() => courier.error(TC_OPTIONS_BEFORE_COURIER, { exitCode: 2, code: "tc.courier.options" }));
14917
15414
  }
14918
15415
 
@@ -14957,7 +15454,7 @@ init_formatter();
14957
15454
  init_errors();
14958
15455
  init_constants();
14959
15456
  init_sdk();
14960
- import { CLI_NAME as CLI_NAME18 } from "@tinycloud/operations/cli-name";
15457
+ import { CLI_NAME as CLI_NAME19 } from "@tinycloud/operations/cli-name";
14961
15458
  import { PermissionNotInManifestError } from "@tinycloud/node-sdk";
14962
15459
  async function queryDelegations(node, options, until) {
14963
15460
  const found = [];
@@ -14968,7 +15465,7 @@ async function queryDelegations(node, options, until) {
14968
15465
  if (page.error.code === "PERMISSION_DENIED") {
14969
15466
  throw new CLIError(
14970
15467
  "PERMISSION_DENIED",
14971
- `This session cannot read delegations: it lacks tinycloud.delegation/list. Run \`${CLI_NAME18} auth login\` again to grant it.`,
15468
+ `This session cannot read delegations: it lacks tinycloud.delegation/list. Run \`${CLI_NAME19} auth login\` again to grant it.`,
14972
15469
  ExitCode.PERMISSION_DENIED
14973
15470
  );
14974
15471
  }
@@ -15143,7 +15640,7 @@ init_constants();
15143
15640
  init_formatter();
15144
15641
  init_theme();
15145
15642
  init_errors();
15146
- import { CLI_NAME as CLI_NAME19 } from "@tinycloud/operations/cli-name";
15643
+ import { CLI_NAME as CLI_NAME20 } from "@tinycloud/operations/cli-name";
15147
15644
  init_session_expired();
15148
15645
  init_types();
15149
15646
  function registerDoctorCommand(program) {
@@ -15177,7 +15674,7 @@ function registerDoctorCommand(program) {
15177
15674
  const profile = await ProfileManager.getProfile(profileName2);
15178
15675
  keyDetail = profile.did ? `${profile.did.slice(0, 20)}...` : "key found";
15179
15676
  } else {
15180
- keyDetail = `no key \u2014 run ${CLI_NAME19} init`;
15677
+ keyDetail = `no key \u2014 run ${CLI_NAME20} init`;
15181
15678
  }
15182
15679
  } catch {
15183
15680
  keyDetail = "error reading key";
@@ -15199,7 +15696,7 @@ function registerDoctorCommand(program) {
15199
15696
  } else if (state2 === "unverified") {
15200
15697
  sessionDetail = `not usable (no signed OpenKey proof) \u2014 ${signInAgainHint(profileName2, resolveProfilePosture(profile))}`;
15201
15698
  } else {
15202
- sessionDetail = sessionOk ? "active" : `no session \u2014 run ${CLI_NAME19} auth login`;
15699
+ sessionDetail = sessionOk ? "active" : `no session \u2014 run ${CLI_NAME20} auth login`;
15203
15700
  }
15204
15701
  } catch {
15205
15702
  sessionDetail = "error reading session";
@@ -15227,7 +15724,7 @@ function registerDoctorCommand(program) {
15227
15724
  try {
15228
15725
  const profile = await ProfileManager.getProfile(profileName2);
15229
15726
  spaceOk = Boolean(profile.spaceId);
15230
- spaceDetail = spaceOk ? `${profile.spaceId.slice(0, 16)}...` : `no space \u2014 run ${CLI_NAME19} space create`;
15727
+ spaceDetail = spaceOk ? `${profile.spaceId.slice(0, 16)}...` : `no space \u2014 run ${CLI_NAME20} space create`;
15231
15728
  } catch {
15232
15729
  spaceDetail = "error checking space";
15233
15730
  }
@@ -15417,7 +15914,7 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
15417
15914
  }
15418
15915
 
15419
15916
  // src/commands/enable.ts
15420
- import { CLI_NAME as CLI_NAME20 } from "@tinycloud/operations/cli-name";
15917
+ import { CLI_NAME as CLI_NAME21 } from "@tinycloud/operations/cli-name";
15421
15918
  init_constants();
15422
15919
  init_profiles();
15423
15920
  init_errors();
@@ -15425,7 +15922,7 @@ init_formatter();
15425
15922
  init_publishing_manifest();
15426
15923
  function registerEnableCommand(program) {
15427
15924
  const enable = program.command("enable").description("Enable a narrowly scoped TinyCloud service");
15428
- enable.command("share").description(`Approve Share publishing scope through OpenKey device authorization (same as \`${CLI_NAME20} auth login --device --manifest builtin:share-publishing\`). Use a dedicated profile, e.g. \`${CLI_NAME20} init --name publisher --key-only && ${CLI_NAME20} --profile publisher enable share\`; keep existing profiles for their apps.`).option("--replace-session", "Replace this profile's live session even though the new scope would narrow, change or shorten it (prefer a new profile)").action(async (options, command) => {
15925
+ enable.command("share").description(`Approve Share publishing scope through OpenKey device authorization (same as \`${CLI_NAME21} auth login --device --manifest builtin:share-publishing\`). Use a dedicated profile, e.g. \`${CLI_NAME21} init --name publisher --key-only && ${CLI_NAME21} --profile publisher enable share\`; keep existing profiles for their apps.`).option("--replace-session", "Replace this profile's live session even though the new scope would narrow, change or shorten it (prefer a new profile)").action(async (options, command) => {
15429
15926
  try {
15430
15927
  const globalOptions = command.optsWithGlobals();
15431
15928
  const context = await ProfileManager.resolveContext(globalOptions, { storedSession: "unused" });
@@ -15461,7 +15958,7 @@ init_profiles();
15461
15958
  init_formatter();
15462
15959
  init_errors();
15463
15960
  init_constants();
15464
- import { CLI_NAME as CLI_NAME21 } from "@tinycloud/operations/cli-name";
15961
+ import { CLI_NAME as CLI_NAME22 } from "@tinycloud/operations/cli-name";
15465
15962
  function registerInitCommand(program) {
15466
15963
  program.command("init").description("Initialize a new TinyCloud profile").option("--name <profile>", "Profile name", "default").option("--key-only", "Only generate key, skip authentication").option("--host <url>", "TinyCloud node URL").option("--paste", "Use manual paste mode for authentication").option("--no-popup", "Print the OpenKey URL without opening a browser").option("--default-space <name>", "Default space used when --space is omitted (e.g. applications)").action(async (options, cmd) => {
15467
15964
  try {
@@ -15479,7 +15976,7 @@ function registerInitCommand(program) {
15479
15976
  if (await ProfileManager.profileExists(profileName2)) {
15480
15977
  throw new CLIError(
15481
15978
  "PROFILE_EXISTS",
15482
- `Profile "${profileName2}" already exists. Use \`${CLI_NAME21} profile delete ${profileName2}\` first or choose a different name.`,
15979
+ `Profile "${profileName2}" already exists. Use \`${CLI_NAME22} profile delete ${profileName2}\` first or choose a different name.`,
15483
15980
  ExitCode.ERROR
15484
15981
  );
15485
15982
  }
@@ -15516,6 +16013,7 @@ function registerInitCommand(program) {
15516
16013
  noPopup: options.popup === false,
15517
16014
  persistHost: true
15518
16015
  });
16016
+ await ensureSpaceSetup({ profile: profileName2, host }, delegationData, delegationData.permissions);
15519
16017
  outputJson({
15520
16018
  profile: profileName2,
15521
16019
  did,
@@ -15529,6 +16027,183 @@ function registerInitCommand(program) {
15529
16027
  });
15530
16028
  }
15531
16029
 
16030
+ // src/commands/knowledge.ts
16031
+ init_session_expired();
16032
+ init_constants();
16033
+ init_profiles();
16034
+ import { invokeOperation as invokeOperation2 } from "@tinycloud/operations";
16035
+ import { CLI_NAME as CLI_NAME23 } from "@tinycloud/operations/cli-name";
16036
+
16037
+ // src/lib/knowledge-input.ts
16038
+ init_constants();
16039
+ init_errors();
16040
+ init_safe_text();
16041
+ function usage2(message) {
16042
+ return new CLIError("INVALID_ARGUMENT", message, ExitCode.USAGE_ERROR);
16043
+ }
16044
+ function assertKnowledgeAppId(value) {
16045
+ if (!isAppId(value)) throw usage2(`Invalid app id "${safeClipped(value)}": ${APP_ID_RULE}.`);
16046
+ return value;
16047
+ }
16048
+ function assertKnowledgeConcept(value) {
16049
+ const reason = value.length > 300 ? "it is longer than 300 characters" : value.startsWith("/") ? "it starts with a slash" : /[*\\]/.test(value) ? "it contains a wildcard or backslash" : /%(?:2e|2f|5c)/i.test(value) ? "it contains an encoded dot, slash or backslash" : value.split("/").some((segment) => segment === "" || segment === "." || segment === "..") ? "it has an empty, `.` or `..` segment" : void 0;
16050
+ if (reason !== void 0) {
16051
+ throw usage2(
16052
+ `Invalid concept "${safeClipped(value)}": ${reason}. Use a path inside the app's knowledge folder, such as app or tables/meal.md.`
16053
+ );
16054
+ }
16055
+ return value;
16056
+ }
16057
+
16058
+ // src/commands/knowledge.ts
16059
+ init_permissions();
16060
+ init_errors();
16061
+ init_formatter();
16062
+
16063
+ // src/lib/knowledge-render.ts
16064
+ init_safe_text();
16065
+ function knowledgeListRows(apps) {
16066
+ return apps.map((app) => [
16067
+ safeInline(app.app_id),
16068
+ app.name === void 0 ? "\u2014" : safeInline(app.name),
16069
+ safeInline(app.has_index ? app.root : `${app.root} (no index)`),
16070
+ String(app.concepts.length)
16071
+ ]);
16072
+ }
16073
+ function knowledgeReadParts(output2) {
16074
+ const body = safeBlock(output2.body);
16075
+ return {
16076
+ heading: safeInline(output2.key),
16077
+ ...output2.frontmatter === null ? {} : { meta: safeInline(JSON.stringify(output2.frontmatter)) },
16078
+ body: body.endsWith("\n") ? body : `${body}
16079
+ `
16080
+ };
16081
+ }
16082
+
16083
+ // src/commands/knowledge.ts
16084
+ init_theme();
16085
+ function registerKnowledgeCommand(program) {
16086
+ const knowledge = program.command("knowledge").description("Read the knowledge bundles of apps installed in a user's TinyCloud");
16087
+ knowledge.command("list").description("List installed apps that carry a knowledge bundle (`<app_id>/knowledge/index.md` in the applications space)").option("--app <app_id>", "Only this app; works with a grant scoped to that app").action(async (options, cmd) => {
16088
+ try {
16089
+ const appId = options.app === void 0 ? void 0 : assertKnowledgeAppId(options.app);
16090
+ const ctx = await ProfileManager.resolveContext(cmd.optsWithGlobals());
16091
+ const result = await withSpinner("Listing installed apps...", () => invokeOperation2(
16092
+ "tinycloud.knowledge.list",
16093
+ 1,
16094
+ { profile: ctx.profile, host: ctx.host, allowOwnerProfile: true },
16095
+ appId === void 0 ? {} : { app_id: appId }
16096
+ ));
16097
+ if (result.status !== "ok") throw knowledgeFailure(result, ctx.profile, appId);
16098
+ const output2 = result.output;
16099
+ if (shouldOutputJson()) {
16100
+ outputJson(result.output);
16101
+ return;
16102
+ }
16103
+ if (output2.apps.length === 0) {
16104
+ process.stdout.write(theme.muted("No installed apps with a knowledge bundle found.") + "\n");
16105
+ return;
16106
+ }
16107
+ process.stdout.write(
16108
+ formatTable(["App ID", "Name", "Root", "Concepts"], knowledgeListRows(output2.apps)) + "\n"
16109
+ );
16110
+ if (output2.truncated) {
16111
+ process.stderr.write(theme.warn("The applications space has more keys than one listing covers; some apps may be missing.") + "\n");
16112
+ }
16113
+ } catch (error) {
16114
+ return handleError(error);
16115
+ }
16116
+ });
16117
+ knowledge.command("read <app_id> [concept]").description("Read an app's knowledge index, or one concept (for example tables/meal.md), with frontmatter parsed").action(async (appIdArgument, conceptArgument, _options, cmd) => {
16118
+ try {
16119
+ const appId = assertKnowledgeAppId(appIdArgument);
16120
+ const concept = conceptArgument === void 0 ? void 0 : assertKnowledgeConcept(conceptArgument);
16121
+ const ctx = await ProfileManager.resolveContext(cmd.optsWithGlobals());
16122
+ const result = await withSpinner("Reading knowledge...", () => invokeOperation2(
16123
+ "tinycloud.knowledge.read",
16124
+ 1,
16125
+ { profile: ctx.profile, host: ctx.host, allowOwnerProfile: true },
16126
+ { app_id: appId, ...concept === void 0 ? {} : { concept } }
16127
+ ));
16128
+ if (result.status !== "ok") throw knowledgeFailure(result, ctx.profile, appId);
16129
+ const output2 = result.output;
16130
+ if (shouldOutputJson()) {
16131
+ outputJson(result.output);
16132
+ return;
16133
+ }
16134
+ const parts = knowledgeReadParts(output2);
16135
+ process.stdout.write(`${theme.heading(parts.heading)}
16136
+ `);
16137
+ if (parts.meta !== void 0) process.stdout.write(`${parts.meta}
16138
+ `);
16139
+ process.stdout.write(`
16140
+ ${parts.body}`);
16141
+ } catch (error) {
16142
+ return handleError(error);
16143
+ }
16144
+ });
16145
+ }
16146
+ function knowledgeFailure(result, profile, appId) {
16147
+ switch (result.status) {
16148
+ case "authority_required": {
16149
+ return new CLIError(
16150
+ "PERMISSION_DENIED",
16151
+ `This profile has no grant for that. Missing: ${result.missing.map((entry) => compactPermission(entry)).join("; ")}`,
16152
+ ExitCode.PERMISSION_DENIED,
16153
+ { hint: missingGrantHint(profile, appId) }
16154
+ );
16155
+ }
16156
+ case "setup_required":
16157
+ return new CLIError("SETUP_REQUIRED", "The operation needs setup before it can run.", ExitCode.ERROR);
16158
+ case "error": {
16159
+ if (result.error.code === "SESSION_NOT_FOUND" || result.error.code === "PROFILE_POSTURE_NOT_ALLOWED" && result.context.posture === "unauthenticated") {
16160
+ return new CLIError("AUTH_REQUIRED", "Not signed in to TinyCloud.", ExitCode.AUTH_REQUIRED, {
16161
+ hint: `Sign in with: ${CLI_NAME23} --profile ${result.context.profile} auth login`
16162
+ });
16163
+ }
16164
+ if (result.error.code === "NODE_UNREACHABLE") {
16165
+ return new CLIError("NETWORK_ERROR", result.error.message, ExitCode.NETWORK_ERROR);
16166
+ }
16167
+ if (result.error.code === "KV_NOT_FOUND") {
16168
+ return new CLIError(
16169
+ "NOT_FOUND",
16170
+ "No such knowledge file. Run `knowledge list` for the installed apps, then `knowledge read <app_id>` for the index.",
16171
+ ExitCode.NOT_FOUND
16172
+ );
16173
+ }
16174
+ if (result.error.code === "PERMISSION_DENIED") {
16175
+ return new CLIError("PERMISSION_DENIED", result.error.message, ExitCode.PERMISSION_DENIED, {
16176
+ hint: missingGrantHint(profile, appId)
16177
+ });
16178
+ }
16179
+ if (result.error.code === "INPUT_INVALID") {
16180
+ return new CLIError(
16181
+ "INVALID_ARGUMENT",
16182
+ "The app id or concept is not valid. An app id looks like xyz.example.app; a concept is a path inside the app's knowledge folder, such as app or tables/meal.md.",
16183
+ ExitCode.USAGE_ERROR
16184
+ );
16185
+ }
16186
+ return withSignInHint(cliErrorFromService(result.error), result.context.profile, result.context.posture);
16187
+ }
16188
+ }
16189
+ }
16190
+ function missingGrantHint(profile, appId) {
16191
+ const request = `${CLI_NAME23} --profile ${profile} auth request`;
16192
+ const lines = appId === void 0 ? [
16193
+ ` ${request} --preset ${APPS_AGENT_PRESET} --emit req.json (read-only, all installed apps)`,
16194
+ ` ${request} --cap tinycloud.kv:${APPS_AGENT_SPACE}::get,list --emit req.json (all files, no table access)`,
16195
+ "To limit the grant to one app, add `--app` and that app's id to the first command (repeat `--app` for up to 5)."
16196
+ ] : [
16197
+ ` ${request} --preset ${APPS_AGENT_PRESET} --app ${appId} --emit req.json (read-only: files and table rows of ${appId})`,
16198
+ ` ${request} --cap tinycloud.kv:${APPS_AGENT_SPACE}:${appId}/knowledge/:get,list --emit req.json (only ${appId}'s knowledge)`
16199
+ ];
16200
+ return [
16201
+ "Ask the owner for a grant, narrowest first. Create the request:",
16202
+ ...lines,
16203
+ `The owner runs \`${CLI_NAME23} auth grant req.json > grant.json\`; then import the result with \`${CLI_NAME23} --profile ${profile} auth import grant.json\`.`
16204
+ ].join("\n");
16205
+ }
16206
+
15532
16207
  // src/commands/kv.ts
15533
16208
  import { readFile as readFile10 } from "fs/promises";
15534
16209
 
@@ -15833,7 +16508,7 @@ init_errors();
15833
16508
  init_constants();
15834
16509
  init_theme();
15835
16510
  import { resolveManifestKnowledgeRoot as resolveManifestKnowledgeRoot2 } from "@tinycloud/sdk-core";
15836
- import { CLI_NAME as CLI_NAME22 } from "@tinycloud/operations/cli-name";
16511
+ import { CLI_NAME as CLI_NAME24 } from "@tinycloud/operations/cli-name";
15837
16512
  import { readFile as readFile11 } from "fs/promises";
15838
16513
  var DEFAULT_APP_SPACE = "applications";
15839
16514
  function registerManifestCommand(program) {
@@ -15841,8 +16516,8 @@ function registerManifestCommand(program) {
15841
16516
  manifest.command("resolve <source>").description("Resolve a manifest file or URL to its effective space, paths, and DB basenames").addHelpText("after", `
15842
16517
 
15843
16518
  Examples:
15844
- $ ${CLI_NAME22} manifest resolve ./manifest.json
15845
- $ ${CLI_NAME22} manifest resolve https://app.example.com/manifest.json --json
16519
+ $ ${CLI_NAME24} manifest resolve ./manifest.json
16520
+ $ ${CLI_NAME24} manifest resolve https://app.example.com/manifest.json --json
15846
16521
 
15847
16522
  What it shows:
15848
16523
  - app_id, name, manifest_version
@@ -15852,7 +16527,7 @@ What it shows:
15852
16527
 
15853
16528
  This command is read-only and does NOT contact the node \u2014 it just resolves
15854
16529
  the manifest against the active profile's address/chain so you know which
15855
- \`--space\` and \`--db\` values to pass to other ${CLI_NAME22} commands.
16530
+ \`--space\` and \`--db\` values to pass to other ${CLI_NAME24} commands.
15856
16531
  `).action(async (source, _options, cmd) => {
15857
16532
  try {
15858
16533
  const globalOpts = cmd.optsWithGlobals();
@@ -15921,7 +16596,7 @@ ${theme.heading("SQL databases")}
15921
16596
  `);
15922
16597
  }
15923
16598
  process.stdout.write(theme.muted(`
15924
- Use with: ${CLI_NAME22} sql query --space ${spaceName} --db <db> "..."
16599
+ Use with: ${CLI_NAME24} sql query --space ${spaceName} --db <db> "..."
15925
16600
  `));
15926
16601
  }
15927
16602
  if (permissions.length > 0) {
@@ -16041,7 +16716,7 @@ init_profiles();
16041
16716
  init_formatter();
16042
16717
  init_errors();
16043
16718
  init_constants();
16044
- import { CLI_NAME as CLI_NAME23 } from "@tinycloud/operations/cli-name";
16719
+ import { CLI_NAME as CLI_NAME25 } from "@tinycloud/operations/cli-name";
16045
16720
  import { createInterface as createInterface5 } from "readline";
16046
16721
  init_theme();
16047
16722
  init_types();
@@ -16187,9 +16862,9 @@ profile at command time. Precedence for every kv/sql command:
16187
16862
  explicit --space flag > profile defaultSpace > primary space.
16188
16863
 
16189
16864
  Examples:
16190
- $ ${CLI_NAME23} profile set-default-space applications
16191
- $ ${CLI_NAME23} profile set-default-space applications --profile cli-test
16192
- $ ${CLI_NAME23} profile set-default-space --unset
16865
+ $ ${CLI_NAME25} profile set-default-space applications
16866
+ $ ${CLI_NAME25} profile set-default-space applications --profile cli-test
16867
+ $ ${CLI_NAME25} profile set-default-space --unset
16193
16868
  `).action(async (name, options, cmd) => {
16194
16869
  try {
16195
16870
  const globalOpts = cmd.optsWithGlobals();
@@ -16201,7 +16876,7 @@ Examples:
16201
16876
  if (!options.unset && (name === void 0 || name === "")) {
16202
16877
  throw new CLIError(
16203
16878
  "USAGE_ERROR",
16204
- `Provide a space name (e.g. \`${CLI_NAME23} profile set-default-space applications\`) or pass --unset.`,
16879
+ `Provide a space name (e.g. \`${CLI_NAME25} profile set-default-space applications\`) or pass --unset.`,
16205
16880
  ExitCode.USAGE_ERROR
16206
16881
  );
16207
16882
  }
@@ -16260,7 +16935,7 @@ function parseOperatorType(raw) {
16260
16935
 
16261
16936
  // src/commands/replica.ts
16262
16937
  init_profile_replicas();
16263
- import { CLI_NAME as CLI_NAME24 } from "@tinycloud/operations/cli-name";
16938
+ import { CLI_NAME as CLI_NAME26 } from "@tinycloud/operations/cli-name";
16264
16939
  import { randomBytes as randomBytes7 } from "crypto";
16265
16940
  import { constants as fsConstants5 } from "fs";
16266
16941
  import { mkdir as mkdir5, open as open9, readdir as readdir6, rename as rename4, unlink as unlink4, lstat as lstat9 } from "fs/promises";
@@ -16401,11 +17076,11 @@ var EXIT_BY_CODE = {
16401
17076
  var READ_FAILURE = {
16402
17077
  absent: { code: "KEY_ABSENT", exit: ExitCode.NOT_FOUND, what: "is not in the replica (the replica covers its whole prefix)" },
16403
17078
  deleted: { code: "KEY_DELETED", exit: ExitCode.NOT_FOUND, what: "was deleted at the source" },
16404
- content_missing: { code: "CONTENT_MISSING", exit: ExitCode.NOT_FOUND, what: `is known but its content has not been fetched yet; run ${CLI_NAME24} replica sync` },
17079
+ content_missing: { code: "CONTENT_MISSING", exit: ExitCode.NOT_FOUND, what: `is known but its content has not been fetched yet; run ${CLI_NAME26} replica sync` },
16405
17080
  coverage_incomplete: {
16406
17081
  code: "COVERAGE_INCOMPLETE",
16407
17082
  exit: ExitCode.NOT_FOUND,
16408
- what: `is not in the replica, which has not finished its first sync; run ${CLI_NAME24} replica sync`
17083
+ what: `is not in the replica, which has not finished its first sync; run ${CLI_NAME26} replica sync`
16409
17084
  },
16410
17085
  not_covered: { code: "NOT_COVERED", exit: ExitCode.USAGE_ERROR, what: "is outside the replica's prefix" }
16411
17086
  };
@@ -16523,7 +17198,7 @@ async function existingReplicaName(profile, option) {
16523
17198
  const names = await replicaNames(profile);
16524
17199
  if (names.length === 1) return names[0];
16525
17200
  if (names.length === 0) {
16526
- throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, `Profile "${profile}" has no replicas. Create one with: ${CLI_NAME24} replica sync --prefix <prefix>`);
17201
+ throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, `Profile "${profile}" has no replicas. Create one with: ${CLI_NAME26} replica sync --prefix <prefix>`);
16527
17202
  }
16528
17203
  throw new CLIError("USAGE_ERROR", `Profile "${profile}" has several replicas (${names.join(", ")}); pass --replica <name>.`, ExitCode.USAGE_ERROR);
16529
17204
  }
@@ -16631,9 +17306,9 @@ async function storedGrants(profile) {
16631
17306
  function grantHint(space, prefix) {
16632
17307
  return [
16633
17308
  "A replica needs a device grant with get and sync on its prefix:",
16634
- ` device: ${CLI_NAME24} auth request --cap tinycloud.kv:${space}:${prefix}:get,list,metadata,sync --expiry 30d --emit req.json`,
16635
- ` owner: ${CLI_NAME24} auth grant req.json > grant.json`,
16636
- ` device: ${CLI_NAME24} auth import grant.json`
17309
+ ` device: ${CLI_NAME26} auth request --cap tinycloud.kv:${space}:${prefix}:get,list,metadata,sync --expiry 30d --emit req.json`,
17310
+ ` owner: ${CLI_NAME26} auth grant req.json > grant.json`,
17311
+ ` device: ${CLI_NAME26} auth import grant.json`
16637
17312
  ].join("\n");
16638
17313
  }
16639
17314
  function resolveSpace(input, prefix, grants) {
@@ -16724,7 +17399,7 @@ function describeStatus(status) {
16724
17399
  formatField("Size", formatBytes(status.bytes)),
16725
17400
  formatField("Grant", status.device.delegationCid),
16726
17401
  formatField("Last error", status.lastError ? `${status.lastError.code}: ${status.lastError.message}` : null),
16727
- formatField("Purge", status.purgePending ? `incomplete: content files remain; the next ${CLI_NAME24} replica command retries` : null)
17402
+ formatField("Purge", status.purgePending ? `incomplete: content files remain; the next ${CLI_NAME26} replica command retries` : null)
16728
17403
  ].join("\n");
16729
17404
  }
16730
17405
  async function writeFileAtomic(path, bytes) {
@@ -16888,7 +17563,7 @@ or coverage incomplete; 5 grant expired, revoked or not yet valid; 6 network;
16888
17563
  (options, cmd) => run(async () => {
16889
17564
  const profile = await profileName(cmd);
16890
17565
  if (options.purge) {
16891
- throw new CLIError("REPORT_READ_ONLY", `Replica report is read-only. To remove a named local replica, run ${CLI_NAME24} replica reset --replica <name> --purge; flag-owned purge has no equivalent yet (TC-1309).`, ExitCode.USAGE_ERROR);
17566
+ throw new CLIError("REPORT_READ_ONLY", `Replica report is read-only. To remove a named local replica, run ${CLI_NAME26} replica reset --replica <name> --purge; flag-owned purge has no equivalent yet (TC-1309).`, ExitCode.USAGE_ERROR);
16892
17567
  }
16893
17568
  if (options.clearPending) {
16894
17569
  throw new CLIError("REPORT_READ_ONLY", "Clearing pending replica writes has no equivalent yet; see TC-1309 for a safe replacement command.", ExitCode.USAGE_ERROR);
@@ -16990,7 +17665,7 @@ or coverage incomplete; 5 grant expired, revoked or not yet valid; 6 network;
16990
17665
  if (differs !== void 0) {
16991
17666
  throw new ReplicaError(
16992
17667
  ReplicaErrorCode.CONFIG_MISMATCH,
16993
- `Replica ${name} already follows ${differs}. Use another --replica name, or ${CLI_NAME24} replica reset --replica ${name} --purge first.`
17668
+ `Replica ${name} already follows ${differs}. Use another --replica name, or ${CLI_NAME26} replica reset --replica ${name} --purge first.`
16994
17669
  );
16995
17670
  }
16996
17671
  }
@@ -17155,12 +17830,12 @@ init_theme();
17155
17830
  init_errors();
17156
17831
  init_warnings();
17157
17832
  init_constants();
17158
- import { CLI_NAME as CLI_NAME26 } from "@tinycloud/operations/cli-name";
17833
+ import { CLI_NAME as CLI_NAME28 } from "@tinycloud/operations/cli-name";
17159
17834
  import { readFile as readFile12 } from "fs/promises";
17160
17835
  import { join as join16 } from "path";
17161
17836
  import { homedir, hostname, userInfo } from "os";
17162
17837
  import { appSecretScope } from "@tinycloud/sdk-core";
17163
- import { invokeOperation as invokeOperation2 } from "@tinycloud/operations";
17838
+ import { invokeOperation as invokeOperation3 } from "@tinycloud/operations";
17164
17839
  import { isLocalOwnerProfile as isLocalOwnerProfile4, ownerSessionLacksProof as ownerSessionLacksProof5 } from "@tinycloud/operations/profile";
17165
17840
  import {
17166
17841
  buildSecretManagerUrl,
@@ -17366,7 +18041,7 @@ function parseStatusEnvelope(line) {
17366
18041
  // src/lib/secrets-wait.ts
17367
18042
  init_constants();
17368
18043
  init_errors();
17369
- import { CLI_NAME as CLI_NAME25 } from "@tinycloud/operations/cli-name";
18044
+ import { CLI_NAME as CLI_NAME27 } from "@tinycloud/operations/cli-name";
17370
18045
  var WAIT_BACKOFF_MS = [3e3, 4500, 6750, 10125];
17371
18046
  var WAIT_STEADY_INTERVAL_MS = 15e3;
17372
18047
  var DEFAULT_WAIT_TIMEOUT = "5m";
@@ -17374,7 +18049,7 @@ var MAX_WAIT_TIMEOUT_MS = 9 * 6e4;
17374
18049
  var MAX_CONSECUTIVE_FAILURES = 5;
17375
18050
  var TIMEOUT_RE = /^(0|\d+s|\d+m)$/;
17376
18051
  var RETRYABLE_STATUS_ERRORS = { NODE_UNREACHABLE: true, NODE_ERROR: true };
17377
- var EXPLICIT_KEY_HINT = `${CLI_NAME25} secrets wait never signs in, and an explicit private key has no stored session. Use a local profile signed in once with ${CLI_NAME25} --profile <profile> auth login --method local, without TC_PRIVATE_KEY.`;
18052
+ var EXPLICIT_KEY_HINT = `${CLI_NAME27} secrets wait never signs in, and an explicit private key has no stored session. Use a local profile signed in once with ${CLI_NAME27} --profile <profile> auth login --method local, without TC_PRIVATE_KEY.`;
17378
18053
  function abortableSleep(ms2, signal) {
17379
18054
  return new Promise((resolve8) => {
17380
18055
  if (signal.aborted) {
@@ -17410,7 +18085,7 @@ async function runSecretsWait(request, deps) {
17410
18085
  let step = 0;
17411
18086
  const cancelled = () => new CLIError(
17412
18087
  "CANCELLED",
17413
- `Interrupted: ${CLI_NAME25} secrets wait stopped before the secrets were ready.`,
18088
+ `Interrupted: ${CLI_NAME27} secrets wait stopped before the secrets were ready.`,
17414
18089
  ExitCode.CANCELLED,
17415
18090
  progress === void 0 ? {} : { progress }
17416
18091
  );
@@ -17554,13 +18229,13 @@ function statusError(error, profile, warnings) {
17554
18229
  if (error.code === "AUTH_REQUIRED" || error.code === "SESSION_NOT_FOUND") {
17555
18230
  const recovery = recoveryOf(error, "sign_in");
17556
18231
  if (recovery !== void 0) {
17557
- const hint = recovery.command !== void 0 ? `Sign in once with: ${recovery.command}, then retry.` : `Sign in to profile "${profile}" once with ${CLI_NAME25} auth login, then retry.`;
18232
+ const hint = recovery.command !== void 0 ? `Sign in once with: ${recovery.command}, then retry.` : `Sign in to profile "${profile}" once with ${CLI_NAME27} auth login, then retry.`;
17558
18233
  return new CLIError("AUTH_REQUIRED", error.message, ExitCode.AUTH_REQUIRED, { hint, recovery, ...extra });
17559
18234
  }
17560
18235
  }
17561
18236
  if (error.code === "PROFILE_MIGRATION_REQUIRED") {
17562
18237
  const recovery = recoveryOf(error, "complete_migration");
17563
- const hint = recovery?.command !== void 0 ? `Run ${recovery.command} once to finish this profile's one-time upgrade, then retry wait.` : `Run ${CLI_NAME25} auth caps once with profile "${profile}" to finish its one-time upgrade, then retry wait.`;
18238
+ const hint = recovery?.command !== void 0 ? `Run ${recovery.command} once to finish this profile's one-time upgrade, then retry wait.` : `Run ${CLI_NAME27} auth caps once with profile "${profile}" to finish its one-time upgrade, then retry wait.`;
17564
18239
  return new CLIError("PROFILE_MIGRATION_REQUIRED", error.message, ExitCode.ERROR, {
17565
18240
  hint,
17566
18241
  ...recovery === void 0 ? {} : { recovery },
@@ -17752,11 +18427,11 @@ async function runSecretOperation(params) {
17752
18427
  }
17753
18428
  function secretPermissionReason(action, name) {
17754
18429
  const target = name ? ` secret "${name}"` : " secrets";
17755
- return `Allow \`${CLI_NAME26} secrets ${action}${name ? ` ${name}` : ""}\` to access${target} with the required TinyCloud permissions.`;
18430
+ return `Allow \`${CLI_NAME28} secrets ${action}${name ? ` ${name}` : ""}\` to access${target} with the required TinyCloud permissions.`;
17756
18431
  }
17757
18432
  function assertOwnerApprovalPossible(profileName2, profile, action, name, missing = []) {
17758
18433
  if (profile.authMethod !== "openkey" || process.stdin.isTTY || process.stderr.isTTY) return;
17759
- const command = `${CLI_NAME26} secrets ${action === "del" ? "delete" : action}${name ? ` ${name}` : ""}`;
18434
+ const command = `${CLI_NAME28} secrets ${action === "del" ? "delete" : action}${name ? ` ${name}` : ""}`;
17760
18435
  throw new CLIError(
17761
18436
  "PERMISSION_DENIED",
17762
18437
  action === "get" ? missing.length > 0 && missing.every((permission) => permission.service === "tinycloud.encryption" && Array.isArray(permission.actions) && permission.actions.length > 0 && permission.actions.every((capability) => capability === SECRET_DECRYPT_CAPABILITY)) ? `Profile "${profileName2}" lacks the scoped decrypt authority (${SECRET_DECRYPT_CAPABILITY}) needed to read secret "${name}", and requesting it needs an interactive browser approval.` : `Profile "${profileName2}" lacks a scoped read or decrypt grant needed to read secret "${name}", and requesting it needs an interactive browser approval.` : `Profile "${profileName2}" holds no grant for \`${command}\`, and requesting one needs an interactive browser approval.`,
@@ -17767,7 +18442,7 @@ function assertOwnerApprovalPossible(profileName2, profile, action, name, missin
17767
18442
  );
17768
18443
  }
17769
18444
  function scopedSecretLoginHint(profileName2) {
17770
- return `Have the owner approve a scoped login whose manifest names the secret: ${CLI_NAME26} --profile ${profileName2} auth login --method openkey --paste --manifest <manifest.json>. Pass the owner's code on stdin, newline-terminated.`;
18445
+ return `Have the owner approve a scoped login whose manifest names the secret: ${CLI_NAME28} --profile ${profileName2} auth login --method openkey --paste --manifest <manifest.json>. Pass the owner's code on stdin, newline-terminated.`;
17771
18446
  }
17772
18447
  async function runSecretOperationAttempt(label, operation) {
17773
18448
  try {
@@ -17828,7 +18503,7 @@ async function canonicalSecretGet(params, collect) {
17828
18503
  };
17829
18504
  const invoke = async () => collect(await withSpinner(
17830
18505
  params.label,
17831
- () => auth?.privateKey ? invokeSecretsGetWithLocalAuthorityRetry(target, input) : invokeOperation2("tinycloud.secrets.get", 1, target, input)
18506
+ () => auth?.privateKey ? invokeSecretsGetWithLocalAuthorityRetry(target, input) : invokeOperation3("tinycloud.secrets.get", 1, target, input)
17832
18507
  ));
17833
18508
  let first = await invoke();
17834
18509
  if (first.status === "error" && first.error.code === "SESSION_NOT_FOUND" && auth?.privateKey === void 0) {
@@ -17901,7 +18576,7 @@ ${message}`,
17901
18576
  "AUTH_REQUIRED",
17902
18577
  "Not signed in to TinyCloud.",
17903
18578
  ExitCode.AUTH_REQUIRED,
17904
- { hint: `Sign in with: ${CLI_NAME26} --profile ${result.context.profile} auth login` }
18579
+ { hint: `Sign in with: ${CLI_NAME28} --profile ${result.context.profile} auth login` }
17905
18580
  );
17906
18581
  }
17907
18582
  if (result.error.code === "NODE_UNREACHABLE") {
@@ -18378,7 +19053,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
18378
19053
  name: "Encryption network",
18379
19054
  ok: false,
18380
19055
  detail: `${networkName} not found`,
18381
- hint: `${CLI_NAME26} secrets network init ${networkName}`
19056
+ hint: `${CLI_NAME28} secrets network init ${networkName}`
18382
19057
  }
18383
19058
  ];
18384
19059
  let secret;
@@ -18424,7 +19099,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
18424
19099
  name: "Secret access",
18425
19100
  ok: false,
18426
19101
  detail: notFound ? `${resolved.permissionPaths.vault} not found` : result.error.message,
18427
- hint: notFound ? local ? `${CLI_NAME26} secrets put ${resolved.name}${formatSecretScopeFlag(scopeOptions)} --stdin` : `${CLI_NAME26} secrets request ${resolved.name}${formatSecretScopeFlag(scopeOptions)}` : `Ask the owner profile to grant ${secretCapabilityAction("get")} and ${SECRET_DECRYPT_CAPABILITY}.`
19102
+ hint: notFound ? local ? `${CLI_NAME28} secrets put ${resolved.name}${formatSecretScopeFlag(scopeOptions)} --stdin` : `${CLI_NAME28} secrets request ${resolved.name}${formatSecretScopeFlag(scopeOptions)}` : `Ask the owner profile to grant ${secretCapabilityAction("get")} and ${SECRET_DECRYPT_CAPABILITY}.`
18428
19103
  });
18429
19104
  }
18430
19105
  } else {
@@ -18668,7 +19343,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
18668
19343
  if (effectiveLocal(profile, options)) {
18669
19344
  throw new CLIError(
18670
19345
  "LOCAL_OWNER_PROFILE",
18671
- `Profile "${ctx.profile}" uses a local key, which owns its secrets: store them with ${CLI_NAME26} secrets put NAME --stdin instead of requesting them.`,
19346
+ `Profile "${ctx.profile}" uses a local key, which owns its secrets: store them with ${CLI_NAME28} secrets put NAME --stdin instead of requesting them.`,
18672
19347
  ExitCode.USAGE_ERROR
18673
19348
  );
18674
19349
  }
@@ -18771,7 +19446,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
18771
19446
  try {
18772
19447
  const ctx = await ProfileManager.resolveContext(cmd.optsWithGlobals());
18773
19448
  const privateKey = process.env.TC_PRIVATE_KEY;
18774
- const result = await invokeOperation2("tinycloud.secrets.status", 1, {
19449
+ const result = await invokeOperation3("tinycloud.secrets.status", 1, {
18775
19450
  profile: ctx.profile,
18776
19451
  host: ctx.host,
18777
19452
  allowOwnerProfile: true,
@@ -18791,7 +19466,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
18791
19466
 
18792
19467
  // src/commands/share.ts
18793
19468
  import { Option } from "commander";
18794
- import { CLI_NAME as CLI_NAME27 } from "@tinycloud/operations/cli-name";
19469
+ import { CLI_NAME as CLI_NAME29 } from "@tinycloud/operations/cli-name";
18795
19470
 
18796
19471
  // ../share-sdk/dist/artifact.js
18797
19472
  init_sha256();
@@ -29641,14 +30316,14 @@ function assertKey(key32) {
29641
30316
  throw new TypeError(`key must be ${KEY_LENGTH} bytes, got ${key32.length}`);
29642
30317
  }
29643
30318
  }
29644
- async function importAesKey(key32, usage) {
30319
+ async function importAesKey(key32, usage3) {
29645
30320
  assertKey(key32);
29646
30321
  return globalThis.crypto.subtle.importKey(
29647
30322
  "raw",
29648
30323
  key32,
29649
30324
  "AES-GCM",
29650
30325
  false,
29651
- [usage]
30326
+ [usage3]
29652
30327
  );
29653
30328
  }
29654
30329
  async function open11(blob, key32) {
@@ -36976,12 +37651,12 @@ function shareCliError(error, operation = "publish") {
36976
37651
  const profileName2 = "profileName" in failure ? failure.profileName : void 0;
36977
37652
  const localKey = "localKey" in failure && failure.localKey === true;
36978
37653
  const profileHint = profileName2 === void 0 ? "" : `--profile ${profileName2} `;
36979
- const loginHint = localKey ? `\`${CLI_NAME27} ${profileHint}auth login --method local\`` : `\`${CLI_NAME27} ${profileHint}auth login --device --manifest builtin:share-publishing\` (or \`${CLI_NAME27} ${profileHint}enable share\`)`;
37654
+ const loginHint = localKey ? `\`${CLI_NAME29} ${profileHint}auth login --method local\`` : `\`${CLI_NAME29} ${profileHint}auth login --device --manifest builtin:share-publishing\` (or \`${CLI_NAME29} ${profileHint}enable share\`)`;
36980
37655
  if (failure.kind === "caveated-session") {
36981
37656
  const holder = profileName2 === void 0 ? "this session" : `profile ${profileName2}'s session`;
36982
37657
  return new CLIError(
36983
37658
  "PERMISSION_DENIED",
36984
- `${holder} carries signed restrictions (caveats) on the authority an anyone-with-link share needs, so it cannot create the share link; nothing was shared. Approve Share publishing without restrictions on a new, dedicated profile (any unused name): \`${CLI_NAME27} init --name publisher --key-only && ${CLI_NAME27} --profile publisher enable share\``,
37659
+ `${holder} carries signed restrictions (caveats) on the authority an anyone-with-link share needs, so it cannot create the share link; nothing was shared. Approve Share publishing without restrictions on a new, dedicated profile (any unused name): \`${CLI_NAME29} init --name publisher --key-only && ${CLI_NAME29} --profile publisher enable share\``,
36985
37660
  5
36986
37661
  );
36987
37662
  }
@@ -37067,7 +37742,7 @@ function shareCliError(error, operation = "publish") {
37067
37742
  if (nodeCode === "ENOENT") return new CLIError("INVALID_ARGUMENT", "share input was not found", 2);
37068
37743
  if (nodeCode === "EISDIR") return new CLIError("INVALID_ARGUMENT", "share input must be a Markdown file", 2);
37069
37744
  const mapped = typeof nodeCode === "string" && known[nodeCode] !== void 0 ? known[nodeCode] : known[message];
37070
- const classified = mapped === void 0 ? classifyShareFailure(error, `\`${CLI_NAME27} auth login\``) : void 0;
37745
+ const classified = mapped === void 0 ? classifyShareFailure(error, `\`${CLI_NAME29} auth login\``) : void 0;
37071
37746
  if (classified !== void 0) return Object.assign(new CLIError(classified.code, classified.reason, classified.exitCode), { cause: error });
37072
37747
  if (error instanceof TypeError) return new CLIError("INVALID_ARGUMENT", "share input is invalid", 2);
37073
37748
  return new CLIError(mapped?.code ?? "INVALID_ARGUMENT", mapped ? mapped.code : "share operation failed", mapped?.exit ?? 2);
@@ -37400,7 +38075,7 @@ init_constants();
37400
38075
  init_sdk();
37401
38076
  init_host();
37402
38077
  init_theme();
37403
- import { CLI_NAME as CLI_NAME28 } from "@tinycloud/operations/cli-name";
38078
+ import { CLI_NAME as CLI_NAME30 } from "@tinycloud/operations/cli-name";
37404
38079
  import { ACCOUNT_REGISTRY_SPACE } from "@tinycloud/node-sdk";
37405
38080
  import { randomBytes as randomBytes9 } from "crypto";
37406
38081
  import { mkdir as mkdir7, writeFile as writeFile4 } from "fs/promises";
@@ -37447,7 +38122,7 @@ function registerSpaceCommand(program) {
37447
38122
  } else {
37448
38123
  const profile = await ProfileManager.getProfile(ctx.profile);
37449
38124
  const cap = "tinycloud.kv:account:spaces/:get,list";
37450
- const hint = resolveProfilePosture(profile) === "delegate-session" ? `Ask the space owner to grant it. Create the request with: ${CLI_NAME28} --profile ${ctx.profile} auth request --cap ${cap} --emit` : `Grant it to this session with: ${CLI_NAME28} --profile ${ctx.profile} auth request --grant --cap ${cap}. The default OpenKey owner sign-in covers only the default space, so it includes neither.`;
38125
+ const hint = resolveProfilePosture(profile) === "delegate-session" ? `Ask the space owner to grant it. Create the request with: ${CLI_NAME30} --profile ${ctx.profile} auth request --cap ${cap} --emit` : `Grant it to this session with: ${CLI_NAME30} --profile ${ctx.profile} auth request --grant --cap ${cap}. The default OpenKey owner sign-in covers only the default space, so it includes neither.`;
37451
38126
  throw new CLIError(
37452
38127
  "PERMISSION_DENIED",
37453
38128
  `Listing spaces needs tinycloud.kv/get and tinycloud.kv/list on the account space's spaces/ registry (${accountSpaceId ?? ACCOUNT_REGISTRY_SPACE}), or tinycloud.space/list. This session holds neither.`,
@@ -37484,10 +38159,10 @@ function registerSpaceCommand(program) {
37484
38159
 
37485
38160
  A delegate cannot host a space \u2014 only its owner (root authority) can. This
37486
38161
  emits a tinycloud.host.request artifact naming the space and its owner so you
37487
- can hand it to the owner; they run \`${CLI_NAME28} space host <name>\` and confirm.
38162
+ can hand it to the owner; they run \`${CLI_NAME30} space host <name>\` and confirm.
37488
38163
 
37489
38164
  If you ARE the owner of the resolved space, this refuses and tells you to host
37490
- it directly with \`${CLI_NAME28} space host <name>\` (no request needed).
38165
+ it directly with \`${CLI_NAME30} space host <name>\` (no request needed).
37491
38166
  `).action(async (name, options, cmd) => {
37492
38167
  try {
37493
38168
  const globalOpts = cmd.optsWithGlobals();
@@ -37498,7 +38173,7 @@ it directly with \`${CLI_NAME28} space host <name>\` (no request needed).
37498
38173
  if (await isRootAuthority(spaceId, ctx.profile)) {
37499
38174
  throw new CLIError(
37500
38175
  "ALREADY_ROOT_AUTHORITY",
37501
- `You are the owner of ${spaceId}. Host it directly: ${CLI_NAME28} space host ${spaceName}`,
38176
+ `You are the owner of ${spaceId}. Host it directly: ${CLI_NAME30} space host ${spaceName}`,
37502
38177
  ExitCode.USAGE_ERROR
37503
38178
  );
37504
38179
  }
@@ -37584,7 +38259,7 @@ init_sdk();
37584
38259
  init_space();
37585
38260
  init_host();
37586
38261
  init_theme();
37587
- import { CLI_NAME as CLI_NAME29 } from "@tinycloud/operations/cli-name";
38262
+ import { CLI_NAME as CLI_NAME31 } from "@tinycloud/operations/cli-name";
37588
38263
  import { writeFile as writeFile5 } from "fs/promises";
37589
38264
  import { resolve as resolve7 } from "path";
37590
38265
  async function dbHandle(node, dbName, spaceInput, profileName2) {
@@ -37608,12 +38283,12 @@ database for simple apps, or pass --db to target a named database. Pass
37608
38283
  --space to target a non-primary space (e.g. the manifest "applications" space).
37609
38284
 
37610
38285
  Common workflows:
37611
- $ ${CLI_NAME29} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
37612
- $ ${CLI_NAME29} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["ship docs"]'
37613
- $ ${CLI_NAME29} sql query "SELECT id, body FROM notes ORDER BY id"
37614
- $ ${CLI_NAME29} sql query "SELECT * FROM events WHERE type = ?" --db analytics --params '["signup"]'
37615
- $ ${CLI_NAME29} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
37616
- $ ${CLI_NAME29} sql export --db analytics --output analytics.db
38286
+ $ ${CLI_NAME31} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
38287
+ $ ${CLI_NAME31} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["ship docs"]'
38288
+ $ ${CLI_NAME31} sql query "SELECT id, body FROM notes ORDER BY id"
38289
+ $ ${CLI_NAME31} sql query "SELECT * FROM events WHERE type = ?" --db analytics --params '["signup"]'
38290
+ $ ${CLI_NAME31} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
38291
+ $ ${CLI_NAME31} sql export --db analytics --output analytics.db
37617
38292
 
37618
38293
  Commands:
37619
38294
  query Read rows with SELECT statements
@@ -37630,10 +38305,10 @@ Tips:
37630
38305
  sql.command("query <sql>").description("Run a read-only SELECT query").option("--db <name>", "SQLite database name within the current space", "default").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").option("--params <json>", "Bind parameters as a JSON array for ? placeholders").addHelpText("after", `
37631
38306
 
37632
38307
  Examples:
37633
- $ ${CLI_NAME29} sql query "SELECT * FROM notes ORDER BY id"
37634
- $ ${CLI_NAME29} sql query "SELECT * FROM notes WHERE id = ?" --params '[42]'
37635
- $ ${CLI_NAME29} sql query "SELECT count(*) AS total FROM events" --db analytics --json
37636
- $ ${CLI_NAME29} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
38308
+ $ ${CLI_NAME31} sql query "SELECT * FROM notes ORDER BY id"
38309
+ $ ${CLI_NAME31} sql query "SELECT * FROM notes WHERE id = ?" --params '[42]'
38310
+ $ ${CLI_NAME31} sql query "SELECT count(*) AS total FROM events" --db analytics --json
38311
+ $ ${CLI_NAME31} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
37637
38312
 
37638
38313
  Output:
37639
38314
  Human output is formatted as a table. Piped output or --json returns
@@ -37674,11 +38349,11 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
37674
38349
  sql.command("execute <sql>").description("Run a write or schema statement").option("--db <name>", "SQLite database name within the current space", "default").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").option("--params <json>", "Bind parameters as a JSON array for ? placeholders").addHelpText("after", `
37675
38350
 
37676
38351
  Examples:
37677
- $ ${CLI_NAME29} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
37678
- $ ${CLI_NAME29} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["first note"]'
37679
- $ ${CLI_NAME29} sql execute "UPDATE notes SET body = ? WHERE id = ?" --params '["edited", 1]'
37680
- $ ${CLI_NAME29} sql execute "DROP TABLE old_notes" --db archive
37681
- $ ${CLI_NAME29} sql execute "DELETE FROM conversation WHERE id = ?" --space applications --db xyz.tinycloud.listen/conversations --params '["abc"]'
38352
+ $ ${CLI_NAME31} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
38353
+ $ ${CLI_NAME31} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["first note"]'
38354
+ $ ${CLI_NAME31} sql execute "UPDATE notes SET body = ? WHERE id = ?" --params '["edited", 1]'
38355
+ $ ${CLI_NAME31} sql execute "DROP TABLE old_notes" --db archive
38356
+ $ ${CLI_NAME31} sql execute "DELETE FROM conversation WHERE id = ?" --space applications --db xyz.tinycloud.listen/conversations --params '["abc"]'
37682
38357
 
37683
38358
  Output:
37684
38359
  Returns JSON with the changed row count and last inserted row id when available.
@@ -37707,9 +38382,9 @@ Output:
37707
38382
  sql.command("export").description("Export a SQLite database as a binary .db file").option("--db <name>", "SQLite database name within the current space", "default").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").option("-o, --output <file>", "Output file path", "export.db").addHelpText("after", `
37708
38383
 
37709
38384
  Examples:
37710
- $ ${CLI_NAME29} sql export
37711
- $ ${CLI_NAME29} sql export --db analytics --output analytics.db
37712
- $ ${CLI_NAME29} sql export --space applications --db xyz.tinycloud.listen/conversations --output listen.db
38385
+ $ ${CLI_NAME31} sql export
38386
+ $ ${CLI_NAME31} sql export --db analytics --output analytics.db
38387
+ $ ${CLI_NAME31} sql export --space applications --db xyz.tinycloud.listen/conversations --output listen.db
37713
38388
 
37714
38389
  Output:
37715
38390
  Writes the database file locally and returns JSON with the path and size.
@@ -37742,19 +38417,19 @@ Output:
37742
38417
  sql.command("copy").description("Copy rows between SQL databases (optionally across spaces)").requiredOption("--from-db <name>", "Source database name").requiredOption("--to-db <name>", "Destination database name").option("--from-space <name|uri>", "Source space (defaults to primary)").option("--to-space <name|uri>", "Destination space (defaults to primary)").option("--table <name...>", "Restrict copy to specific tables (repeat or comma-separated)").option("--dry-run", "Print the plan without writing", false).addHelpText("after", `
37743
38418
 
37744
38419
  Examples:
37745
- $ ${CLI_NAME29} sql copy --from-db com.tinycloud.conversation-sync/conversations \\
38420
+ $ ${CLI_NAME31} sql copy --from-db com.tinycloud.conversation-sync/conversations \\
37746
38421
  --to-db xyz.tinycloud.listen/conversations \\
37747
38422
  --space applications --dry-run
37748
- $ ${CLI_NAME29} sql copy --from-space applications --from-db com.foo/data \\
38423
+ $ ${CLI_NAME31} sql copy --from-space applications --from-db com.foo/data \\
37749
38424
  --to-space applications --to-db com.bar/data \\
37750
38425
  --table conversation --table participant
37751
38426
 
37752
38427
  Notes:
37753
38428
  - Refuses to run when (resolved space, db) is identical for source and destination.
37754
- - Does NOT create destination tables. Run the target app once (or use \`${CLI_NAME29} sql execute\`)
38429
+ - Does NOT create destination tables. Run the target app once (or use \`${CLI_NAME31} sql execute\`)
37755
38430
  to materialize the schema before copying.
37756
38431
  - One row at a time; suitable for small/medium datasets. Large copies should
37757
- use \`${CLI_NAME29} sql export\` + bulk import.
38432
+ use \`${CLI_NAME31} sql export\` + bulk import.
37758
38433
  - Authorization: the active session/delegation must cover sql/read on source
37759
38434
  AND sql/write on destination. Otherwise the relevant operation will fail.
37760
38435
  `).action(async (options, cmd) => {
@@ -37854,7 +38529,7 @@ init_errors();
37854
38529
  init_formatter();
37855
38530
  init_theme();
37856
38531
  init_permissions();
37857
- import { CLI_NAME as CLI_NAME30 } from "@tinycloud/operations/cli-name";
38532
+ import { CLI_NAME as CLI_NAME32 } from "@tinycloud/operations/cli-name";
37858
38533
  import {
37859
38534
  NodeWasmBindings as NodeWasmBindings2
37860
38535
  } from "@tinycloud/node-sdk";
@@ -38128,7 +38803,7 @@ function formatStatus(summary) {
38128
38803
  lines.push(`Default profile: ${theme.value(summary.defaultProfile)}`);
38129
38804
  lines.push("");
38130
38805
  if (summary.profiles.length === 0) {
38131
- lines.push(theme.muted(`No profiles configured. Run: ${CLI_NAME30} init`));
38806
+ lines.push(theme.muted(`No profiles configured. Run: ${CLI_NAME32} init`));
38132
38807
  return `${lines.join("\n")}
38133
38808
  `;
38134
38809
  }
@@ -38626,6 +39301,7 @@ function registerTinyCloudCommands(program) {
38626
39301
  registerSqlCommand(program);
38627
39302
  registerDuckdbCommand(program);
38628
39303
  registerManifestCommand(program);
39304
+ registerKnowledgeCommand(program);
38629
39305
  registerUpgradeCommand(program);
38630
39306
  registerStatusCommand(program);
38631
39307
  registerAccountCommand(program);