@tinycloud/cli 1.3.0-beta.37 → 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.
@@ -282,6 +282,40 @@ var init_replication_registry = __esm({
282
282
  }
283
283
  });
284
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
+
285
319
  // src/output/theme.ts
286
320
  import chalk from "chalk";
287
321
  var TC_PALETTE, theme;
@@ -364,7 +398,7 @@ var init_warnings = __esm({
364
398
  import ora from "ora";
365
399
  import { CLI_NAME } from "@tinycloud/operations/cli-name";
366
400
  function outputJson(data) {
367
- process.stdout.write(JSON.stringify(data, null, 2) + "\n");
401
+ process.stdout.write(safeJsonStringify(data, 2) + "\n");
368
402
  }
369
403
  function warningLine(warnings) {
370
404
  const which = warnings.map((warning) => `${warning.grantCid ?? "unidentified"}: ${warning.reason}`).join(", ");
@@ -375,12 +409,12 @@ function outputError(code4, message, hint, details = {}) {
375
409
  const { meta, warnings = [], setup, recovery, progress } = details;
376
410
  if (!shouldOutputJson()) {
377
411
  process.stderr.write(
378
- `${theme.error("\u2717")} ${theme.label(code4)}: ${message}
412
+ `${theme.error("\u2717")} ${theme.label(safeInline(code4))}: ${safeBlock(message)}
379
413
  `
380
414
  );
381
415
  if (hint) {
382
416
  for (const line of hint.split("\n")) {
383
- process.stderr.write(` ${theme.hint(line)}
417
+ process.stderr.write(` ${theme.hint(safeBlock(line))}
384
418
  `);
385
419
  }
386
420
  }
@@ -396,13 +430,13 @@ function outputError(code4, message, hint, details = {}) {
396
430
  if (progress) payload.error.progress = progress;
397
431
  if (meta) payload.error.meta = meta;
398
432
  if (warnings.length > 0) payload.error.warnings = warnings;
399
- process.stderr.write(JSON.stringify(payload, null, 2) + "\n");
433
+ process.stderr.write(safeJsonStringify(payload, 2) + "\n");
400
434
  }
401
435
  }
402
436
  function outputWarnings(warnings) {
403
437
  const combined = mergeWarnings(drainWarnings(), operationWarnings(warnings));
404
438
  if (combined.length === 0) return;
405
- process.stderr.write(shouldOutputJson() ? `${JSON.stringify({ warnings: combined })}
439
+ process.stderr.write(shouldOutputJson() ? `${safeJsonStringify({ warnings: combined })}
406
440
  ` : `${theme.warn("!")} ${warningLine(combined)}
407
441
  `);
408
442
  }
@@ -480,6 +514,7 @@ var WARNING_CODE, WARNING_REASON, DELEGATION_CID, JSON_REQUESTED;
480
514
  var init_formatter = __esm({
481
515
  "src/output/formatter.ts"() {
482
516
  "use strict";
517
+ init_safe_text();
483
518
  init_theme();
484
519
  init_warnings();
485
520
  WARNING_CODE = /^[A-Z][A-Z_]{0,63}$/;
@@ -882,6 +917,13 @@ var init_types = __esm({
882
917
 
883
918
  // src/lib/space.ts
884
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
+ }
885
927
  function canonicalizeAddress(address) {
886
928
  const trimmed = address.trim();
887
929
  return trimmed.startsWith("0x") ? `0x${trimmed.slice(2).toLowerCase()}` : trimmed.toLowerCase();
@@ -945,22 +987,10 @@ async function resolveSpaceUri(input, profileName2, options = {}) {
945
987
  if (!effective) return void 0;
946
988
  if (effective.startsWith("tinycloud:")) {
947
989
  const parsed = parseSpaceUri(effective);
948
- if (!parsed) {
949
- throw new CLIError(
950
- "INVALID_SPACE",
951
- `Invalid space "${effective}". Use a short name ([A-Za-z0-9_-]) or a full tinycloud:... URI.`,
952
- ExitCode.USAGE_ERROR
953
- );
954
- }
990
+ if (!parsed) throw invalidSpaceError(effective);
955
991
  return buildSpaceUri(parsed.owner, parsed.name);
956
992
  }
957
- if (!/^[A-Za-z0-9_-]+$/.test(effective)) {
958
- throw new CLIError(
959
- "INVALID_SPACE",
960
- `Invalid space "${effective}". Use a short name ([A-Za-z0-9_-]) or a full tinycloud:... URI.`,
961
- ExitCode.USAGE_ERROR
962
- );
963
- }
993
+ if (!/^[A-Za-z0-9_-]+$/.test(effective)) throw invalidSpaceError(effective);
964
994
  const session = await ProfileManager.getSession(profileName2);
965
995
  const address = resolveAddress(profile, session);
966
996
  const chainId = resolveChainId(profile, session);
@@ -971,6 +1001,7 @@ var init_space = __esm({
971
1001
  "use strict";
972
1002
  init_errors();
973
1003
  init_constants();
1004
+ init_safe_text();
974
1005
  init_profiles();
975
1006
  }
976
1007
  });
@@ -10362,7 +10393,7 @@ function formatSpace(space) {
10362
10393
  init_profile_gate();
10363
10394
  init_dist();
10364
10395
  init_profiles();
10365
- import { CLI_NAME as CLI_NAME15 } from "@tinycloud/operations/cli-name";
10396
+ import { CLI_NAME as CLI_NAME16 } from "@tinycloud/operations/cli-name";
10366
10397
  import { get as httpGet } from "http";
10367
10398
  import { get as httpsGet } from "https";
10368
10399
  import { spawn } from "child_process";
@@ -12347,7 +12378,7 @@ async function cidBoundOwnerSpace(record3, ownerDid, node, host) {
12347
12378
  try {
12348
12379
  const signed = parseSignedCompactUcanAttenuation2(withoutBearer2(authorization), delegation.cid);
12349
12380
  for (const resource of Object.keys(signed.payload.att)) {
12350
- const space = /^(tinycloud:[^/]+)\/[^/]+\/.*$/.exec(resource)?.[1];
12381
+ const space = /^(tinycloud:[^/]+)\/[^/]+(?:\/.*)?$/.exec(resource)?.[1];
12351
12382
  if (space && ownerDidFromSpace(space) && normalizeDid(ownerDidFromSpace(space)) === normalizeDid(ownerDid)) {
12352
12383
  return space;
12353
12384
  }
@@ -12750,6 +12781,290 @@ function approvalNotices(groups) {
12750
12781
  };
12751
12782
  }
12752
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
+
12753
13068
  // src/auth/replication-login.ts
12754
13069
  init_dist();
12755
13070
  init_errors();
@@ -13043,7 +13358,7 @@ async function revokeForLogout(ctx, discardLocalChanges) {
13043
13358
  "REVOCATION_FAILED",
13044
13359
  `Revocation failed for ${cid}: ${result.error.message}. Session, key, and replica files are retained for retry; local reads stay denied.`,
13045
13360
  ExitCode.ERROR,
13046
- { 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.` }
13047
13362
  );
13048
13363
  }
13049
13364
  if (result.data.cid !== cid || result.data.revoked !== true) {
@@ -13062,7 +13377,7 @@ async function revokeForLogout(ctx, discardLocalChanges) {
13062
13377
  "REVOCATION_FAILED",
13063
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.`,
13064
13379
  ExitCode.ERROR,
13065
- { 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.` }
13066
13381
  );
13067
13382
  }
13068
13383
  }
@@ -13261,7 +13576,7 @@ async function finishProfileLogout(ctx, options = {}) {
13261
13576
  function registerAuthCommand(program, options) {
13262
13577
  const auth = program.command("auth").description("Authentication management");
13263
13578
  registerAuthGrantsCommand(auth, { openKeyAcquisition: options?.openKeyAcquisition });
13264
- 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) => {
13265
13580
  try {
13266
13581
  if (options2.device && options2.paste) {
13267
13582
  throw new CLIError("INVALID_ARGUMENT", "--device and --paste are mutually exclusive.", ExitCode.USAGE_ERROR);
@@ -13411,7 +13726,7 @@ function registerAuthCommand(program, options) {
13411
13726
  authenticated: false,
13412
13727
  replicasRemoved,
13413
13728
  replicasKept: options2.keepReplicas === true,
13414
- 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.`,
13415
13730
  ...online === void 0 ? {} : {
13416
13731
  revokedCids: online.plan.cids,
13417
13732
  revocationExcluded: online.plan.excluded
@@ -13501,7 +13816,15 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
13501
13816
  ).option("--permission <file>", 'JSON permission request: { "permissions": PermissionEntry[] }').option("--manifest <fileOrBase64>", "Manifest file, base64:<json>, or raw base64 JSON").option(
13502
13817
  "--expiry <duration>",
13503
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.`
13504
- ).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) => {
13505
13828
  try {
13506
13829
  const globalOpts = cmd.optsWithGlobals();
13507
13830
  const ctx = await ProfileManager.resolveContext(globalOpts, options2.grant ? {} : { storedSession: "unused" });
@@ -13512,8 +13835,13 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
13512
13835
  if (options2.device && (!options2.grant || resolveProfilePosture(profile) !== "owner-openkey")) {
13513
13836
  throw new CLIError("INVALID_ARGUMENT", "--device requires --grant on an OpenKey owner profile.", ExitCode.USAGE_ERROR);
13514
13837
  }
13515
- const requested = await collectRequestedPermissions(options2, ctx.profile);
13516
- 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
+ `);
13517
13845
  if (requested.length === 0) {
13518
13846
  throw new CLIError(
13519
13847
  "NO_CAPS_REQUESTED",
@@ -13541,7 +13869,7 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
13541
13869
  if (profile.authMethod === "openkey" || options2.device) {
13542
13870
  const key = await ProfileManager.getKey(ctx.profile);
13543
13871
  if (!key) {
13544
- 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);
13545
13873
  }
13546
13874
  const openkeyHost = resolveOpenKeyHost(profile);
13547
13875
  const grants = [];
@@ -13561,7 +13889,7 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
13561
13889
  if (notices) process.stderr.write(`${notices.prompts[index]}
13562
13890
  `);
13563
13891
  const reason = permissionGrantReason(
13564
- `Grant requested TinyCloud permissions from \`${CLI_NAME15} auth request --grant\`.`,
13892
+ `Grant requested TinyCloud permissions from \`${CLI_NAME16} auth request --grant\`.`,
13565
13893
  group
13566
13894
  );
13567
13895
  const request = grantRequestPermissions(group, profile.spaceId ?? profile.spaceName);
@@ -13671,7 +13999,7 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
13671
13999
  kind: parsed.kind,
13672
14000
  requestId: parsed.requestId,
13673
14001
  requested: parsed.requested,
13674
- next: `${CLI_NAME15} auth retry ${parsed.requestId}`
14002
+ next: `${CLI_NAME16} auth retry ${parsed.requestId}`
13675
14003
  });
13676
14004
  return;
13677
14005
  }
@@ -13805,7 +14133,12 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
13805
14133
  const raw = await readAuthArtifactSource(source, {
13806
14134
  stdin: options2.stdin === true || options2.paste === true
13807
14135
  });
13808
- 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
+ }
13809
14142
  if (!isCompatiblePermissionRequestArtifact(parsed)) {
13810
14143
  throw new CLIError(
13811
14144
  "INVALID_AUTH_REQUEST",
@@ -13813,7 +14146,31 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
13813
14146
  ExitCode.USAGE_ERROR
13814
14147
  );
13815
14148
  }
13816
- 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
+ }
13817
14174
  const resolvedRequest = { ...parsed, requested };
13818
14175
  const node = await ensureAuthenticated(ctx);
13819
14176
  await ensureDelegationAuthority({
@@ -13823,7 +14180,7 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
13823
14180
  requested,
13824
14181
  expiryOption: parsed.requestedExpiry,
13825
14182
  reason: "Grant permissions requested by a TinyCloud auth request artifact.",
13826
- yes: options2.yes === true
14183
+ yes: options2.yes === true || confirmedExtras
13827
14184
  });
13828
14185
  const grant = await grantAuthRequest(node, resolvedRequest);
13829
14186
  await appendLocalGrantArtifact(ctx.profile, grant.delegation);
@@ -14222,7 +14579,7 @@ async function ensureDelegationAuthority(params) {
14222
14579
  if (!key) {
14223
14580
  throw new CLIError(
14224
14581
  "NO_KEY",
14225
- `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.`,
14226
14583
  ExitCode.AUTH_REQUIRED
14227
14584
  );
14228
14585
  }
@@ -14334,25 +14691,28 @@ async function collectRequestedPermissions(options, profile) {
14334
14691
  }
14335
14692
  return permissions;
14336
14693
  }
14337
- async function confirmPermissionRequest(permissions) {
14338
- process.stderr.write("\n" + theme.heading("Additional Permissions") + "\n");
14339
- for (const permission of permissions) {
14340
- const dangerous = isDangerousPermission(permission);
14341
- const line = ` ${compactPermission(permission)}`;
14342
- process.stderr.write((dangerous ? theme.warn(line) : theme.value(line)) + "\n");
14343
- }
14344
- process.stderr.write("\n");
14694
+ async function askYesNo(question, cancelMessage) {
14345
14695
  const rl = createInterface3({
14346
14696
  input: process.stdin,
14347
14697
  output: process.stderr
14348
14698
  });
14349
14699
  const answer = await new Promise((resolve8) => {
14350
- rl.question("Approve local-key delegation? [y/N] ", resolve8);
14700
+ rl.question(question, resolve8);
14351
14701
  });
14352
14702
  rl.close();
14353
14703
  if (!/^y(es)?$/i.test(answer.trim())) {
14354
- throw new CLIError("REQUEST_CANCELLED", "Permission request cancelled.", ExitCode.ERROR);
14704
+ throw new CLIError("REQUEST_CANCELLED", cancelMessage, ExitCode.ERROR);
14705
+ }
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");
14355
14713
  }
14714
+ process.stderr.write("\n");
14715
+ await askYesNo("Approve local-key delegation? [y/N] ", "Permission request cancelled.");
14356
14716
  }
14357
14717
  async function requirePermissionConsent(permissions, yes) {
14358
14718
  if (isInteractive()) {
@@ -14468,7 +14828,7 @@ async function rotateAuthKey(profileName2, host, options = {}) {
14468
14828
  if (!profile.privateKey) {
14469
14829
  throw new CLIError(
14470
14830
  "LOCAL_OWNER_KEY_REQUIRED",
14471
- `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.`,
14472
14832
  ExitCode.AUTH_REQUIRED
14473
14833
  );
14474
14834
  }
@@ -14909,7 +15269,7 @@ async function refreshOpenKeySession(profileName2, host, options = {}) {
14909
15269
  }
14910
15270
 
14911
15271
  // src/commands/completion.ts
14912
- import { CLI_NAME as CLI_NAME16 } from "@tinycloud/operations/cli-name";
15272
+ import { CLI_NAME as CLI_NAME17 } from "@tinycloud/operations/cli-name";
14913
15273
  function registerCompletionCommand(program) {
14914
15274
  const completion = program.command("completion").description("Generate shell completions");
14915
15275
  completion.command("bash").description("Output bash completions").action(() => {
@@ -14926,7 +15286,7 @@ function registerCompletionCommand(program) {
14926
15286
  });
14927
15287
  }
14928
15288
  function generateBashCompletion() {
14929
- return `# ${CLI_NAME16} bash completion
15289
+ return `# ${CLI_NAME17} bash completion
14930
15290
  _tc_completions() {
14931
15291
  local cur prev commands subcommands
14932
15292
  COMPREPLY=()
@@ -14951,11 +15311,11 @@ _tc_completions() {
14951
15311
  COMPREPLY=( $(compgen -W "\${subcommands}" -- "\${cur}") )
14952
15312
  fi
14953
15313
  }
14954
- complete -F _tc_completions ${CLI_NAME16}
15314
+ complete -F _tc_completions ${CLI_NAME17}
14955
15315
  `;
14956
15316
  }
14957
15317
  function generateZshCompletion() {
14958
- return `#compdef ${CLI_NAME16}
15318
+ return `#compdef ${CLI_NAME17}
14959
15319
 
14960
15320
  _tc() {
14961
15321
  local -a commands
@@ -15006,50 +15366,50 @@ _tc
15006
15366
  `;
15007
15367
  }
15008
15368
  function generateFishCompletion() {
15009
- return `# ${CLI_NAME16} fish completion
15369
+ return `# ${CLI_NAME17} fish completion
15010
15370
  set -l commands init auth kv space delegation share node profile completion
15011
15371
 
15012
15372
  # Disable file completion by default
15013
- complete -c ${CLI_NAME16} -f
15373
+ complete -c ${CLI_NAME17} -f
15014
15374
 
15015
15375
  # Top-level commands
15016
- complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a init -d "Initialize a new TinyCloud profile"
15017
- complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a auth -d "Authentication management"
15018
- complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a kv -d "Key-value store operations"
15019
- complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a space -d "Space management"
15020
- complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a delegation -d "Manage delegations"
15021
- complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a share -d "Share data with others"
15022
- complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a node -d "Node health and info"
15023
- complete -c ${CLI_NAME16} -n "not __fish_seen_subcommand_from $commands" -a profile -d "Profile management"
15024
- 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"
15025
15385
 
15026
15386
  # Subcommands
15027
- complete -c ${CLI_NAME16} -n "__fish_seen_subcommand_from auth" -a "login logout rotate status whoami"
15028
- complete -c ${CLI_NAME16} -n "__fish_seen_subcommand_from kv" -a "get put delete list head"
15029
- complete -c ${CLI_NAME16} -n "__fish_seen_subcommand_from space" -a "list create info switch"
15030
- complete -c ${CLI_NAME16} -n "__fish_seen_subcommand_from delegation" -a "create list info revoke"
15031
- complete -c ${CLI_NAME16} -n "__fish_seen_subcommand_from share" -a "create receive list revoke"
15032
- complete -c ${CLI_NAME16} -n "__fish_seen_subcommand_from node" -a "health version status"
15033
- complete -c ${CLI_NAME16} -n "__fish_seen_subcommand_from profile" -a "list create show switch delete"
15034
- 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"
15035
15395
 
15036
15396
  # Global options
15037
- complete -c ${CLI_NAME16} -l profile -s p -d "Profile to use"
15038
- complete -c ${CLI_NAME16} -l host -s H -d "TinyCloud node URL"
15039
- complete -c ${CLI_NAME16} -l verbose -s v -d "Enable verbose output"
15040
- complete -c ${CLI_NAME16} -l no-cache -d "Disable caching"
15041
- complete -c ${CLI_NAME16} -l quiet -s q -d "Suppress non-essential output"
15042
- complete -c ${CLI_NAME16} -l replication -d "Enable read-through replication"
15043
- complete -c ${CLI_NAME16} -l no-replication -d "Disable replication"
15044
- 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"
15045
15405
  `;
15046
15406
  }
15047
15407
 
15048
15408
  // src/commands/courier.ts
15049
- import { CLI_NAME as CLI_NAME17 } from "@tinycloud/operations/cli-name";
15050
- 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>.`;
15051
15411
  function registerCourierCommand(program) {
15052
- 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);
15053
15413
  courier.action(() => courier.error(TC_OPTIONS_BEFORE_COURIER, { exitCode: 2, code: "tc.courier.options" }));
15054
15414
  }
15055
15415
 
@@ -15094,7 +15454,7 @@ init_formatter();
15094
15454
  init_errors();
15095
15455
  init_constants();
15096
15456
  init_sdk();
15097
- import { CLI_NAME as CLI_NAME18 } from "@tinycloud/operations/cli-name";
15457
+ import { CLI_NAME as CLI_NAME19 } from "@tinycloud/operations/cli-name";
15098
15458
  import { PermissionNotInManifestError } from "@tinycloud/node-sdk";
15099
15459
  async function queryDelegations(node, options, until) {
15100
15460
  const found = [];
@@ -15105,7 +15465,7 @@ async function queryDelegations(node, options, until) {
15105
15465
  if (page.error.code === "PERMISSION_DENIED") {
15106
15466
  throw new CLIError(
15107
15467
  "PERMISSION_DENIED",
15108
- `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.`,
15109
15469
  ExitCode.PERMISSION_DENIED
15110
15470
  );
15111
15471
  }
@@ -15280,7 +15640,7 @@ init_constants();
15280
15640
  init_formatter();
15281
15641
  init_theme();
15282
15642
  init_errors();
15283
- import { CLI_NAME as CLI_NAME19 } from "@tinycloud/operations/cli-name";
15643
+ import { CLI_NAME as CLI_NAME20 } from "@tinycloud/operations/cli-name";
15284
15644
  init_session_expired();
15285
15645
  init_types();
15286
15646
  function registerDoctorCommand(program) {
@@ -15314,7 +15674,7 @@ function registerDoctorCommand(program) {
15314
15674
  const profile = await ProfileManager.getProfile(profileName2);
15315
15675
  keyDetail = profile.did ? `${profile.did.slice(0, 20)}...` : "key found";
15316
15676
  } else {
15317
- keyDetail = `no key \u2014 run ${CLI_NAME19} init`;
15677
+ keyDetail = `no key \u2014 run ${CLI_NAME20} init`;
15318
15678
  }
15319
15679
  } catch {
15320
15680
  keyDetail = "error reading key";
@@ -15336,7 +15696,7 @@ function registerDoctorCommand(program) {
15336
15696
  } else if (state2 === "unverified") {
15337
15697
  sessionDetail = `not usable (no signed OpenKey proof) \u2014 ${signInAgainHint(profileName2, resolveProfilePosture(profile))}`;
15338
15698
  } else {
15339
- sessionDetail = sessionOk ? "active" : `no session \u2014 run ${CLI_NAME19} auth login`;
15699
+ sessionDetail = sessionOk ? "active" : `no session \u2014 run ${CLI_NAME20} auth login`;
15340
15700
  }
15341
15701
  } catch {
15342
15702
  sessionDetail = "error reading session";
@@ -15364,7 +15724,7 @@ function registerDoctorCommand(program) {
15364
15724
  try {
15365
15725
  const profile = await ProfileManager.getProfile(profileName2);
15366
15726
  spaceOk = Boolean(profile.spaceId);
15367
- 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`;
15368
15728
  } catch {
15369
15729
  spaceDetail = "error checking space";
15370
15730
  }
@@ -15554,7 +15914,7 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
15554
15914
  }
15555
15915
 
15556
15916
  // src/commands/enable.ts
15557
- import { CLI_NAME as CLI_NAME20 } from "@tinycloud/operations/cli-name";
15917
+ import { CLI_NAME as CLI_NAME21 } from "@tinycloud/operations/cli-name";
15558
15918
  init_constants();
15559
15919
  init_profiles();
15560
15920
  init_errors();
@@ -15562,7 +15922,7 @@ init_formatter();
15562
15922
  init_publishing_manifest();
15563
15923
  function registerEnableCommand(program) {
15564
15924
  const enable = program.command("enable").description("Enable a narrowly scoped TinyCloud service");
15565
- 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) => {
15566
15926
  try {
15567
15927
  const globalOptions = command.optsWithGlobals();
15568
15928
  const context = await ProfileManager.resolveContext(globalOptions, { storedSession: "unused" });
@@ -15598,7 +15958,7 @@ init_profiles();
15598
15958
  init_formatter();
15599
15959
  init_errors();
15600
15960
  init_constants();
15601
- import { CLI_NAME as CLI_NAME21 } from "@tinycloud/operations/cli-name";
15961
+ import { CLI_NAME as CLI_NAME22 } from "@tinycloud/operations/cli-name";
15602
15962
  function registerInitCommand(program) {
15603
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) => {
15604
15964
  try {
@@ -15616,7 +15976,7 @@ function registerInitCommand(program) {
15616
15976
  if (await ProfileManager.profileExists(profileName2)) {
15617
15977
  throw new CLIError(
15618
15978
  "PROFILE_EXISTS",
15619
- `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.`,
15620
15980
  ExitCode.ERROR
15621
15981
  );
15622
15982
  }
@@ -15667,6 +16027,183 @@ function registerInitCommand(program) {
15667
16027
  });
15668
16028
  }
15669
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
+
15670
16207
  // src/commands/kv.ts
15671
16208
  import { readFile as readFile10 } from "fs/promises";
15672
16209
 
@@ -15971,7 +16508,7 @@ init_errors();
15971
16508
  init_constants();
15972
16509
  init_theme();
15973
16510
  import { resolveManifestKnowledgeRoot as resolveManifestKnowledgeRoot2 } from "@tinycloud/sdk-core";
15974
- import { CLI_NAME as CLI_NAME22 } from "@tinycloud/operations/cli-name";
16511
+ import { CLI_NAME as CLI_NAME24 } from "@tinycloud/operations/cli-name";
15975
16512
  import { readFile as readFile11 } from "fs/promises";
15976
16513
  var DEFAULT_APP_SPACE = "applications";
15977
16514
  function registerManifestCommand(program) {
@@ -15979,8 +16516,8 @@ function registerManifestCommand(program) {
15979
16516
  manifest.command("resolve <source>").description("Resolve a manifest file or URL to its effective space, paths, and DB basenames").addHelpText("after", `
15980
16517
 
15981
16518
  Examples:
15982
- $ ${CLI_NAME22} manifest resolve ./manifest.json
15983
- $ ${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
15984
16521
 
15985
16522
  What it shows:
15986
16523
  - app_id, name, manifest_version
@@ -15990,7 +16527,7 @@ What it shows:
15990
16527
 
15991
16528
  This command is read-only and does NOT contact the node \u2014 it just resolves
15992
16529
  the manifest against the active profile's address/chain so you know which
15993
- \`--space\` and \`--db\` values to pass to other ${CLI_NAME22} commands.
16530
+ \`--space\` and \`--db\` values to pass to other ${CLI_NAME24} commands.
15994
16531
  `).action(async (source, _options, cmd) => {
15995
16532
  try {
15996
16533
  const globalOpts = cmd.optsWithGlobals();
@@ -16059,7 +16596,7 @@ ${theme.heading("SQL databases")}
16059
16596
  `);
16060
16597
  }
16061
16598
  process.stdout.write(theme.muted(`
16062
- Use with: ${CLI_NAME22} sql query --space ${spaceName} --db <db> "..."
16599
+ Use with: ${CLI_NAME24} sql query --space ${spaceName} --db <db> "..."
16063
16600
  `));
16064
16601
  }
16065
16602
  if (permissions.length > 0) {
@@ -16179,7 +16716,7 @@ init_profiles();
16179
16716
  init_formatter();
16180
16717
  init_errors();
16181
16718
  init_constants();
16182
- import { CLI_NAME as CLI_NAME23 } from "@tinycloud/operations/cli-name";
16719
+ import { CLI_NAME as CLI_NAME25 } from "@tinycloud/operations/cli-name";
16183
16720
  import { createInterface as createInterface5 } from "readline";
16184
16721
  init_theme();
16185
16722
  init_types();
@@ -16325,9 +16862,9 @@ profile at command time. Precedence for every kv/sql command:
16325
16862
  explicit --space flag > profile defaultSpace > primary space.
16326
16863
 
16327
16864
  Examples:
16328
- $ ${CLI_NAME23} profile set-default-space applications
16329
- $ ${CLI_NAME23} profile set-default-space applications --profile cli-test
16330
- $ ${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
16331
16868
  `).action(async (name, options, cmd) => {
16332
16869
  try {
16333
16870
  const globalOpts = cmd.optsWithGlobals();
@@ -16339,7 +16876,7 @@ Examples:
16339
16876
  if (!options.unset && (name === void 0 || name === "")) {
16340
16877
  throw new CLIError(
16341
16878
  "USAGE_ERROR",
16342
- `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.`,
16343
16880
  ExitCode.USAGE_ERROR
16344
16881
  );
16345
16882
  }
@@ -16398,7 +16935,7 @@ function parseOperatorType(raw) {
16398
16935
 
16399
16936
  // src/commands/replica.ts
16400
16937
  init_profile_replicas();
16401
- import { CLI_NAME as CLI_NAME24 } from "@tinycloud/operations/cli-name";
16938
+ import { CLI_NAME as CLI_NAME26 } from "@tinycloud/operations/cli-name";
16402
16939
  import { randomBytes as randomBytes7 } from "crypto";
16403
16940
  import { constants as fsConstants5 } from "fs";
16404
16941
  import { mkdir as mkdir5, open as open9, readdir as readdir6, rename as rename4, unlink as unlink4, lstat as lstat9 } from "fs/promises";
@@ -16539,11 +17076,11 @@ var EXIT_BY_CODE = {
16539
17076
  var READ_FAILURE = {
16540
17077
  absent: { code: "KEY_ABSENT", exit: ExitCode.NOT_FOUND, what: "is not in the replica (the replica covers its whole prefix)" },
16541
17078
  deleted: { code: "KEY_DELETED", exit: ExitCode.NOT_FOUND, what: "was deleted at the source" },
16542
- 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` },
16543
17080
  coverage_incomplete: {
16544
17081
  code: "COVERAGE_INCOMPLETE",
16545
17082
  exit: ExitCode.NOT_FOUND,
16546
- 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`
16547
17084
  },
16548
17085
  not_covered: { code: "NOT_COVERED", exit: ExitCode.USAGE_ERROR, what: "is outside the replica's prefix" }
16549
17086
  };
@@ -16661,7 +17198,7 @@ async function existingReplicaName(profile, option) {
16661
17198
  const names = await replicaNames(profile);
16662
17199
  if (names.length === 1) return names[0];
16663
17200
  if (names.length === 0) {
16664
- 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>`);
16665
17202
  }
16666
17203
  throw new CLIError("USAGE_ERROR", `Profile "${profile}" has several replicas (${names.join(", ")}); pass --replica <name>.`, ExitCode.USAGE_ERROR);
16667
17204
  }
@@ -16769,9 +17306,9 @@ async function storedGrants(profile) {
16769
17306
  function grantHint(space, prefix) {
16770
17307
  return [
16771
17308
  "A replica needs a device grant with get and sync on its prefix:",
16772
- ` device: ${CLI_NAME24} auth request --cap tinycloud.kv:${space}:${prefix}:get,list,metadata,sync --expiry 30d --emit req.json`,
16773
- ` owner: ${CLI_NAME24} auth grant req.json > grant.json`,
16774
- ` 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`
16775
17312
  ].join("\n");
16776
17313
  }
16777
17314
  function resolveSpace(input, prefix, grants) {
@@ -16862,7 +17399,7 @@ function describeStatus(status) {
16862
17399
  formatField("Size", formatBytes(status.bytes)),
16863
17400
  formatField("Grant", status.device.delegationCid),
16864
17401
  formatField("Last error", status.lastError ? `${status.lastError.code}: ${status.lastError.message}` : null),
16865
- 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)
16866
17403
  ].join("\n");
16867
17404
  }
16868
17405
  async function writeFileAtomic(path, bytes) {
@@ -17026,7 +17563,7 @@ or coverage incomplete; 5 grant expired, revoked or not yet valid; 6 network;
17026
17563
  (options, cmd) => run(async () => {
17027
17564
  const profile = await profileName(cmd);
17028
17565
  if (options.purge) {
17029
- 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);
17030
17567
  }
17031
17568
  if (options.clearPending) {
17032
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);
@@ -17128,7 +17665,7 @@ or coverage incomplete; 5 grant expired, revoked or not yet valid; 6 network;
17128
17665
  if (differs !== void 0) {
17129
17666
  throw new ReplicaError(
17130
17667
  ReplicaErrorCode.CONFIG_MISMATCH,
17131
- `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.`
17132
17669
  );
17133
17670
  }
17134
17671
  }
@@ -17293,12 +17830,12 @@ init_theme();
17293
17830
  init_errors();
17294
17831
  init_warnings();
17295
17832
  init_constants();
17296
- import { CLI_NAME as CLI_NAME26 } from "@tinycloud/operations/cli-name";
17833
+ import { CLI_NAME as CLI_NAME28 } from "@tinycloud/operations/cli-name";
17297
17834
  import { readFile as readFile12 } from "fs/promises";
17298
17835
  import { join as join16 } from "path";
17299
17836
  import { homedir, hostname, userInfo } from "os";
17300
17837
  import { appSecretScope } from "@tinycloud/sdk-core";
17301
- import { invokeOperation as invokeOperation2 } from "@tinycloud/operations";
17838
+ import { invokeOperation as invokeOperation3 } from "@tinycloud/operations";
17302
17839
  import { isLocalOwnerProfile as isLocalOwnerProfile4, ownerSessionLacksProof as ownerSessionLacksProof5 } from "@tinycloud/operations/profile";
17303
17840
  import {
17304
17841
  buildSecretManagerUrl,
@@ -17504,7 +18041,7 @@ function parseStatusEnvelope(line) {
17504
18041
  // src/lib/secrets-wait.ts
17505
18042
  init_constants();
17506
18043
  init_errors();
17507
- import { CLI_NAME as CLI_NAME25 } from "@tinycloud/operations/cli-name";
18044
+ import { CLI_NAME as CLI_NAME27 } from "@tinycloud/operations/cli-name";
17508
18045
  var WAIT_BACKOFF_MS = [3e3, 4500, 6750, 10125];
17509
18046
  var WAIT_STEADY_INTERVAL_MS = 15e3;
17510
18047
  var DEFAULT_WAIT_TIMEOUT = "5m";
@@ -17512,7 +18049,7 @@ var MAX_WAIT_TIMEOUT_MS = 9 * 6e4;
17512
18049
  var MAX_CONSECUTIVE_FAILURES = 5;
17513
18050
  var TIMEOUT_RE = /^(0|\d+s|\d+m)$/;
17514
18051
  var RETRYABLE_STATUS_ERRORS = { NODE_UNREACHABLE: true, NODE_ERROR: true };
17515
- 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.`;
17516
18053
  function abortableSleep(ms2, signal) {
17517
18054
  return new Promise((resolve8) => {
17518
18055
  if (signal.aborted) {
@@ -17548,7 +18085,7 @@ async function runSecretsWait(request, deps) {
17548
18085
  let step = 0;
17549
18086
  const cancelled = () => new CLIError(
17550
18087
  "CANCELLED",
17551
- `Interrupted: ${CLI_NAME25} secrets wait stopped before the secrets were ready.`,
18088
+ `Interrupted: ${CLI_NAME27} secrets wait stopped before the secrets were ready.`,
17552
18089
  ExitCode.CANCELLED,
17553
18090
  progress === void 0 ? {} : { progress }
17554
18091
  );
@@ -17692,13 +18229,13 @@ function statusError(error, profile, warnings) {
17692
18229
  if (error.code === "AUTH_REQUIRED" || error.code === "SESSION_NOT_FOUND") {
17693
18230
  const recovery = recoveryOf(error, "sign_in");
17694
18231
  if (recovery !== void 0) {
17695
- 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.`;
17696
18233
  return new CLIError("AUTH_REQUIRED", error.message, ExitCode.AUTH_REQUIRED, { hint, recovery, ...extra });
17697
18234
  }
17698
18235
  }
17699
18236
  if (error.code === "PROFILE_MIGRATION_REQUIRED") {
17700
18237
  const recovery = recoveryOf(error, "complete_migration");
17701
- 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.`;
17702
18239
  return new CLIError("PROFILE_MIGRATION_REQUIRED", error.message, ExitCode.ERROR, {
17703
18240
  hint,
17704
18241
  ...recovery === void 0 ? {} : { recovery },
@@ -17890,11 +18427,11 @@ async function runSecretOperation(params) {
17890
18427
  }
17891
18428
  function secretPermissionReason(action, name) {
17892
18429
  const target = name ? ` secret "${name}"` : " secrets";
17893
- 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.`;
17894
18431
  }
17895
18432
  function assertOwnerApprovalPossible(profileName2, profile, action, name, missing = []) {
17896
18433
  if (profile.authMethod !== "openkey" || process.stdin.isTTY || process.stderr.isTTY) return;
17897
- const command = `${CLI_NAME26} secrets ${action === "del" ? "delete" : action}${name ? ` ${name}` : ""}`;
18434
+ const command = `${CLI_NAME28} secrets ${action === "del" ? "delete" : action}${name ? ` ${name}` : ""}`;
17898
18435
  throw new CLIError(
17899
18436
  "PERMISSION_DENIED",
17900
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.`,
@@ -17905,7 +18442,7 @@ function assertOwnerApprovalPossible(profileName2, profile, action, name, missin
17905
18442
  );
17906
18443
  }
17907
18444
  function scopedSecretLoginHint(profileName2) {
17908
- 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.`;
17909
18446
  }
17910
18447
  async function runSecretOperationAttempt(label, operation) {
17911
18448
  try {
@@ -17966,7 +18503,7 @@ async function canonicalSecretGet(params, collect) {
17966
18503
  };
17967
18504
  const invoke = async () => collect(await withSpinner(
17968
18505
  params.label,
17969
- () => 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)
17970
18507
  ));
17971
18508
  let first = await invoke();
17972
18509
  if (first.status === "error" && first.error.code === "SESSION_NOT_FOUND" && auth?.privateKey === void 0) {
@@ -18039,7 +18576,7 @@ ${message}`,
18039
18576
  "AUTH_REQUIRED",
18040
18577
  "Not signed in to TinyCloud.",
18041
18578
  ExitCode.AUTH_REQUIRED,
18042
- { 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` }
18043
18580
  );
18044
18581
  }
18045
18582
  if (result.error.code === "NODE_UNREACHABLE") {
@@ -18516,7 +19053,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
18516
19053
  name: "Encryption network",
18517
19054
  ok: false,
18518
19055
  detail: `${networkName} not found`,
18519
- hint: `${CLI_NAME26} secrets network init ${networkName}`
19056
+ hint: `${CLI_NAME28} secrets network init ${networkName}`
18520
19057
  }
18521
19058
  ];
18522
19059
  let secret;
@@ -18562,7 +19099,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
18562
19099
  name: "Secret access",
18563
19100
  ok: false,
18564
19101
  detail: notFound ? `${resolved.permissionPaths.vault} not found` : result.error.message,
18565
- 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}.`
18566
19103
  });
18567
19104
  }
18568
19105
  } else {
@@ -18806,7 +19343,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
18806
19343
  if (effectiveLocal(profile, options)) {
18807
19344
  throw new CLIError(
18808
19345
  "LOCAL_OWNER_PROFILE",
18809
- `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.`,
18810
19347
  ExitCode.USAGE_ERROR
18811
19348
  );
18812
19349
  }
@@ -18909,7 +19446,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
18909
19446
  try {
18910
19447
  const ctx = await ProfileManager.resolveContext(cmd.optsWithGlobals());
18911
19448
  const privateKey = process.env.TC_PRIVATE_KEY;
18912
- const result = await invokeOperation2("tinycloud.secrets.status", 1, {
19449
+ const result = await invokeOperation3("tinycloud.secrets.status", 1, {
18913
19450
  profile: ctx.profile,
18914
19451
  host: ctx.host,
18915
19452
  allowOwnerProfile: true,
@@ -18929,7 +19466,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
18929
19466
 
18930
19467
  // src/commands/share.ts
18931
19468
  import { Option } from "commander";
18932
- import { CLI_NAME as CLI_NAME27 } from "@tinycloud/operations/cli-name";
19469
+ import { CLI_NAME as CLI_NAME29 } from "@tinycloud/operations/cli-name";
18933
19470
 
18934
19471
  // ../share-sdk/dist/artifact.js
18935
19472
  init_sha256();
@@ -29779,14 +30316,14 @@ function assertKey(key32) {
29779
30316
  throw new TypeError(`key must be ${KEY_LENGTH} bytes, got ${key32.length}`);
29780
30317
  }
29781
30318
  }
29782
- async function importAesKey(key32, usage) {
30319
+ async function importAesKey(key32, usage3) {
29783
30320
  assertKey(key32);
29784
30321
  return globalThis.crypto.subtle.importKey(
29785
30322
  "raw",
29786
30323
  key32,
29787
30324
  "AES-GCM",
29788
30325
  false,
29789
- [usage]
30326
+ [usage3]
29790
30327
  );
29791
30328
  }
29792
30329
  async function open11(blob, key32) {
@@ -37114,12 +37651,12 @@ function shareCliError(error, operation = "publish") {
37114
37651
  const profileName2 = "profileName" in failure ? failure.profileName : void 0;
37115
37652
  const localKey = "localKey" in failure && failure.localKey === true;
37116
37653
  const profileHint = profileName2 === void 0 ? "" : `--profile ${profileName2} `;
37117
- 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\`)`;
37118
37655
  if (failure.kind === "caveated-session") {
37119
37656
  const holder = profileName2 === void 0 ? "this session" : `profile ${profileName2}'s session`;
37120
37657
  return new CLIError(
37121
37658
  "PERMISSION_DENIED",
37122
- `${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\``,
37123
37660
  5
37124
37661
  );
37125
37662
  }
@@ -37205,7 +37742,7 @@ function shareCliError(error, operation = "publish") {
37205
37742
  if (nodeCode === "ENOENT") return new CLIError("INVALID_ARGUMENT", "share input was not found", 2);
37206
37743
  if (nodeCode === "EISDIR") return new CLIError("INVALID_ARGUMENT", "share input must be a Markdown file", 2);
37207
37744
  const mapped = typeof nodeCode === "string" && known[nodeCode] !== void 0 ? known[nodeCode] : known[message];
37208
- 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;
37209
37746
  if (classified !== void 0) return Object.assign(new CLIError(classified.code, classified.reason, classified.exitCode), { cause: error });
37210
37747
  if (error instanceof TypeError) return new CLIError("INVALID_ARGUMENT", "share input is invalid", 2);
37211
37748
  return new CLIError(mapped?.code ?? "INVALID_ARGUMENT", mapped ? mapped.code : "share operation failed", mapped?.exit ?? 2);
@@ -37538,7 +38075,7 @@ init_constants();
37538
38075
  init_sdk();
37539
38076
  init_host();
37540
38077
  init_theme();
37541
- import { CLI_NAME as CLI_NAME28 } from "@tinycloud/operations/cli-name";
38078
+ import { CLI_NAME as CLI_NAME30 } from "@tinycloud/operations/cli-name";
37542
38079
  import { ACCOUNT_REGISTRY_SPACE } from "@tinycloud/node-sdk";
37543
38080
  import { randomBytes as randomBytes9 } from "crypto";
37544
38081
  import { mkdir as mkdir7, writeFile as writeFile4 } from "fs/promises";
@@ -37585,7 +38122,7 @@ function registerSpaceCommand(program) {
37585
38122
  } else {
37586
38123
  const profile = await ProfileManager.getProfile(ctx.profile);
37587
38124
  const cap = "tinycloud.kv:account:spaces/:get,list";
37588
- 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.`;
37589
38126
  throw new CLIError(
37590
38127
  "PERMISSION_DENIED",
37591
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.`,
@@ -37622,10 +38159,10 @@ function registerSpaceCommand(program) {
37622
38159
 
37623
38160
  A delegate cannot host a space \u2014 only its owner (root authority) can. This
37624
38161
  emits a tinycloud.host.request artifact naming the space and its owner so you
37625
- 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.
37626
38163
 
37627
38164
  If you ARE the owner of the resolved space, this refuses and tells you to host
37628
- it directly with \`${CLI_NAME28} space host <name>\` (no request needed).
38165
+ it directly with \`${CLI_NAME30} space host <name>\` (no request needed).
37629
38166
  `).action(async (name, options, cmd) => {
37630
38167
  try {
37631
38168
  const globalOpts = cmd.optsWithGlobals();
@@ -37636,7 +38173,7 @@ it directly with \`${CLI_NAME28} space host <name>\` (no request needed).
37636
38173
  if (await isRootAuthority(spaceId, ctx.profile)) {
37637
38174
  throw new CLIError(
37638
38175
  "ALREADY_ROOT_AUTHORITY",
37639
- `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}`,
37640
38177
  ExitCode.USAGE_ERROR
37641
38178
  );
37642
38179
  }
@@ -37722,7 +38259,7 @@ init_sdk();
37722
38259
  init_space();
37723
38260
  init_host();
37724
38261
  init_theme();
37725
- import { CLI_NAME as CLI_NAME29 } from "@tinycloud/operations/cli-name";
38262
+ import { CLI_NAME as CLI_NAME31 } from "@tinycloud/operations/cli-name";
37726
38263
  import { writeFile as writeFile5 } from "fs/promises";
37727
38264
  import { resolve as resolve7 } from "path";
37728
38265
  async function dbHandle(node, dbName, spaceInput, profileName2) {
@@ -37746,12 +38283,12 @@ database for simple apps, or pass --db to target a named database. Pass
37746
38283
  --space to target a non-primary space (e.g. the manifest "applications" space).
37747
38284
 
37748
38285
  Common workflows:
37749
- $ ${CLI_NAME29} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
37750
- $ ${CLI_NAME29} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["ship docs"]'
37751
- $ ${CLI_NAME29} sql query "SELECT id, body FROM notes ORDER BY id"
37752
- $ ${CLI_NAME29} sql query "SELECT * FROM events WHERE type = ?" --db analytics --params '["signup"]'
37753
- $ ${CLI_NAME29} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
37754
- $ ${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
37755
38292
 
37756
38293
  Commands:
37757
38294
  query Read rows with SELECT statements
@@ -37768,10 +38305,10 @@ Tips:
37768
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", `
37769
38306
 
37770
38307
  Examples:
37771
- $ ${CLI_NAME29} sql query "SELECT * FROM notes ORDER BY id"
37772
- $ ${CLI_NAME29} sql query "SELECT * FROM notes WHERE id = ?" --params '[42]'
37773
- $ ${CLI_NAME29} sql query "SELECT count(*) AS total FROM events" --db analytics --json
37774
- $ ${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
37775
38312
 
37776
38313
  Output:
37777
38314
  Human output is formatted as a table. Piped output or --json returns
@@ -37812,11 +38349,11 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
37812
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", `
37813
38350
 
37814
38351
  Examples:
37815
- $ ${CLI_NAME29} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
37816
- $ ${CLI_NAME29} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["first note"]'
37817
- $ ${CLI_NAME29} sql execute "UPDATE notes SET body = ? WHERE id = ?" --params '["edited", 1]'
37818
- $ ${CLI_NAME29} sql execute "DROP TABLE old_notes" --db archive
37819
- $ ${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"]'
37820
38357
 
37821
38358
  Output:
37822
38359
  Returns JSON with the changed row count and last inserted row id when available.
@@ -37845,9 +38382,9 @@ Output:
37845
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", `
37846
38383
 
37847
38384
  Examples:
37848
- $ ${CLI_NAME29} sql export
37849
- $ ${CLI_NAME29} sql export --db analytics --output analytics.db
37850
- $ ${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
37851
38388
 
37852
38389
  Output:
37853
38390
  Writes the database file locally and returns JSON with the path and size.
@@ -37880,19 +38417,19 @@ Output:
37880
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", `
37881
38418
 
37882
38419
  Examples:
37883
- $ ${CLI_NAME29} sql copy --from-db com.tinycloud.conversation-sync/conversations \\
38420
+ $ ${CLI_NAME31} sql copy --from-db com.tinycloud.conversation-sync/conversations \\
37884
38421
  --to-db xyz.tinycloud.listen/conversations \\
37885
38422
  --space applications --dry-run
37886
- $ ${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 \\
37887
38424
  --to-space applications --to-db com.bar/data \\
37888
38425
  --table conversation --table participant
37889
38426
 
37890
38427
  Notes:
37891
38428
  - Refuses to run when (resolved space, db) is identical for source and destination.
37892
- - 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\`)
37893
38430
  to materialize the schema before copying.
37894
38431
  - One row at a time; suitable for small/medium datasets. Large copies should
37895
- use \`${CLI_NAME29} sql export\` + bulk import.
38432
+ use \`${CLI_NAME31} sql export\` + bulk import.
37896
38433
  - Authorization: the active session/delegation must cover sql/read on source
37897
38434
  AND sql/write on destination. Otherwise the relevant operation will fail.
37898
38435
  `).action(async (options, cmd) => {
@@ -37992,7 +38529,7 @@ init_errors();
37992
38529
  init_formatter();
37993
38530
  init_theme();
37994
38531
  init_permissions();
37995
- import { CLI_NAME as CLI_NAME30 } from "@tinycloud/operations/cli-name";
38532
+ import { CLI_NAME as CLI_NAME32 } from "@tinycloud/operations/cli-name";
37996
38533
  import {
37997
38534
  NodeWasmBindings as NodeWasmBindings2
37998
38535
  } from "@tinycloud/node-sdk";
@@ -38266,7 +38803,7 @@ function formatStatus(summary) {
38266
38803
  lines.push(`Default profile: ${theme.value(summary.defaultProfile)}`);
38267
38804
  lines.push("");
38268
38805
  if (summary.profiles.length === 0) {
38269
- lines.push(theme.muted(`No profiles configured. Run: ${CLI_NAME30} init`));
38806
+ lines.push(theme.muted(`No profiles configured. Run: ${CLI_NAME32} init`));
38270
38807
  return `${lines.join("\n")}
38271
38808
  `;
38272
38809
  }
@@ -38764,6 +39301,7 @@ function registerTinyCloudCommands(program) {
38764
39301
  registerSqlCommand(program);
38765
39302
  registerDuckdbCommand(program);
38766
39303
  registerManifestCommand(program);
39304
+ registerKnowledgeCommand(program);
38767
39305
  registerUpgradeCommand(program);
38768
39306
  registerStatusCommand(program);
38769
39307
  registerAccountCommand(program);