@tinycloud/cli 1.3.0-beta.40 → 1.3.0-beta.42

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.
@@ -12795,19 +12795,21 @@ var APPS_AGENT_EXPIRY_DAYS = 7;
12795
12795
  var APPS_AGENT_EXPIRY = `${APPS_AGENT_EXPIRY_DAYS}d`;
12796
12796
  var APPS_AGENT_MAX_APPS = 5;
12797
12797
  var DAY_MS2 = 864e5;
12798
+ var APPS_AGENT_WRITE_FOLDERS = ["knowledge/", "prefs/"];
12798
12799
  var APPS_AGENT_VARIANTS = {
12799
12800
  read: {
12800
12801
  grants: [
12801
- { service: "tinycloud.kv", actions: ["tinycloud.kv/get", "tinycloud.kv/list"] },
12802
- { service: "tinycloud.sql", actions: ["tinycloud.sql/read"] }
12802
+ { service: "tinycloud.kv", actions: ["tinycloud.kv/get", "tinycloud.kv/list"], within: [""] },
12803
+ { service: "tinycloud.sql", actions: ["tinycloud.sql/read"], within: [""] }
12803
12804
  ],
12804
12805
  perAppOnly: false,
12805
12806
  changesStructure: false
12806
12807
  },
12807
12808
  write: {
12808
12809
  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"] }
12810
+ { service: "tinycloud.kv", actions: ["tinycloud.kv/get", "tinycloud.kv/list"], within: [""] },
12811
+ { service: "tinycloud.kv", actions: ["tinycloud.kv/put"], within: APPS_AGENT_WRITE_FOLDERS },
12812
+ { service: "tinycloud.sql", actions: ["tinycloud.sql/read", "tinycloud.sql/write"], within: [""] }
12811
12813
  ],
12812
12814
  perAppOnly: true,
12813
12815
  changesStructure: true
@@ -12842,8 +12844,8 @@ function isAppId(value) {
12842
12844
  return value.length <= 200 && APP_ID.test(value);
12843
12845
  }
12844
12846
  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
+ const bases = appIds.length === 0 ? [""] : appIds.map((appId) => `${appId}/`);
12848
+ return APPS_AGENT_VARIANTS[mode].grants.flatMap(({ service, actions, within }) => within.flatMap((folder) => bases.map((base4) => ({ service, space: APPS_AGENT_SPACE, path: `${base4}${folder}`, actions: [...actions] }))));
12847
12849
  }
12848
12850
  function quoteApps(appIds) {
12849
12851
  return appIds.map((appId) => `\`${appId}\``).join(", ");
@@ -12861,14 +12863,17 @@ function revokeSentence(lifetimeMs2) {
12861
12863
  }
12862
12864
  function appsAgentScopeNotice(mode, appIds = []) {
12863
12865
  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";
12866
+ const writes = variant.grants.some(({ actions }) => actions.includes("tinycloud.kv/put"));
12867
+ const writableFolders = appIds.flatMap((appId) => APPS_AGENT_WRITE_FOLDERS.map((folder) => `\`${appId}/${folder}\``)).join(", ");
12868
+ const reads = writes ? `read everything there, write knowledge and data files (${writableFolders}), and add, change and delete table rows` : "list and read its files and read its table rows";
12865
12869
  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
12870
  const lines = [
12867
12871
  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
12872
  ];
12869
12873
  if (variant.changesStructure) {
12870
12874
  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.`,
12875
+ `It cannot change ${appIds.length === 1 ? "the app's" : "the apps'"} code, manifest or install record: files outside those folders are read-only for it.`,
12876
+ `SQL write is separate from that: it can change ${appIds.length === 1 ? "this app's" : "these apps'"} table rows and, because of how TinyCloud permissions work today (TC-1581), also change or delete ${appIds.length === 1 ? "its" : "their"} tables (structure).`,
12872
12877
  "It cannot touch other apps, other spaces or secrets."
12873
12878
  );
12874
12879
  } else {
@@ -12932,10 +12937,12 @@ function matchAppsAgentRequest(requested, requestedExpiry, applicationsSpace) {
12932
12937
  if (requested.some((entry) => entry.expiry !== void 0 || entry.caveats !== void 0)) return void 0;
12933
12938
  const kvPaths = requested.filter((entry) => entry.service === "tinycloud.kv").map((entry) => entry.path);
12934
12939
  const appIds = [];
12935
- for (const path of new Set(kvPaths)) {
12940
+ for (const path of kvPaths) {
12936
12941
  if (path === "") continue;
12937
- if (!path.endsWith("/") || !isAppId(path.slice(0, -1))) return void 0;
12938
- appIds.push(path.slice(0, -1));
12942
+ const slash = path.indexOf("/");
12943
+ const appId = path.slice(0, slash);
12944
+ if (slash < 1 || !isAppId(appId)) return void 0;
12945
+ if (!appIds.includes(appId)) appIds.push(appId);
12939
12946
  }
12940
12947
  if (appIds.length > APPS_AGENT_MAX_APPS || appIds.length > 0 && kvPaths.includes("")) return void 0;
12941
12948
  for (const mode of ["read", "write"]) {
@@ -13018,17 +13025,11 @@ function describeEntry(entry, applicationsSpace) {
13018
13025
  }
13019
13026
  function withinPresetSet(entry, applicationsSpace) {
13020
13027
  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);
13028
+ if (entry.path.split("/").some((segment) => segment === "." || segment === "..")) return false;
13029
+ const slash = entry.path.indexOf("/");
13030
+ const folder = slash > 0 && isAppId(entry.path.slice(0, slash)) ? entry.path.slice(slash + 1) : void 0;
13031
+ const grants = Object.values(APPS_AGENT_VARIANTS).flatMap(({ grants: variantGrants }) => variantGrants.filter((grant) => grant.service === entry.service));
13032
+ return entry.actions.length > 0 && entry.actions.every((action) => grants.some((grant) => grant.actions.includes(action) && (!(action.endsWith("/put") || action.endsWith("/write")) || folder !== void 0 && grant.within.some((within) => folder.startsWith(within)))));
13032
13033
  }
13033
13034
  function describeGrantScope(requested, requestedExpiry, applicationsSpace) {
13034
13035
  const preset = matchAppsAgentRequest(requested, requestedExpiry, applicationsSpace);
@@ -13818,13 +13819,13 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
13818
13819
  `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.`
13819
13820
  ).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
13821
  "--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
+ `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); add --write --app <app_id> to also write those apps' knowledge and data files`
13822
13823
  ).option(
13823
13824
  "--app <app_id>",
13824
13825
  `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
13826
  (value, previous) => [...previous, value],
13826
13827
  []
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) => {
13828
+ ).option("--write", `With --preset ${APPS_AGENT_PRESET}: also allow writing the named --app apps' knowledge/ and prefs/ files (kv put) and table rows (sql write); their code, manifest and install record stay read-only. 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) => {
13828
13829
  try {
13829
13830
  const globalOpts = cmd.optsWithGlobals();
13830
13831
  const ctx = await ProfileManager.resolveContext(globalOpts, options2.grant ? {} : { storedSession: "unused" });