@tinycloud/cli 1.3.0-beta.53 → 1.3.0-beta.54

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.
@@ -2734,11 +2734,11 @@ async function getLastPermissionRequestArtifact(profile) {
2734
2734
  return existing.at(-1) ?? null;
2735
2735
  }
2736
2736
  async function replayAdditionalDelegations(node, profile, options) {
2737
- const { operationSpaceResolver: operationSpaceResolver2, replayProfileDelegations } = await import("@tinycloud/operations/delegation-binding");
2737
+ const { operationSpaceResolver: operationSpaceResolver3, replayProfileDelegations } = await import("@tinycloud/operations/delegation-binding");
2738
2738
  const { warnings } = await replayProfileDelegations(profile, node, {
2739
2739
  host: options.host,
2740
2740
  migrate: options.migrate,
2741
- resolveSpace: operationSpaceResolver2(node, options.ownerSpace)
2741
+ resolveSpace: operationSpaceResolver3(node, options.ownerSpace)
2742
2742
  });
2743
2743
  return warnings;
2744
2744
  }
@@ -11083,15 +11083,15 @@ var makeIssue = (params) => {
11083
11083
  message: issueData.message
11084
11084
  };
11085
11085
  }
11086
- let errorMessage2 = "";
11086
+ let errorMessage3 = "";
11087
11087
  const maps = errorMaps.filter((m) => !!m).slice().reverse();
11088
11088
  for (const map of maps) {
11089
- errorMessage2 = map(fullIssue, { data, defaultError: errorMessage2 }).message;
11089
+ errorMessage3 = map(fullIssue, { data, defaultError: errorMessage3 }).message;
11090
11090
  }
11091
11091
  return {
11092
11092
  ...issueData,
11093
11093
  path: fullPath,
11094
- message: errorMessage2
11094
+ message: errorMessage3
11095
11095
  };
11096
11096
  };
11097
11097
  var EMPTY_PATH = [];
@@ -16612,15 +16612,15 @@ var makeIssue2 = (params) => {
16612
16612
  message: issueData.message
16613
16613
  };
16614
16614
  }
16615
- let errorMessage2 = "";
16615
+ let errorMessage3 = "";
16616
16616
  const maps = errorMaps.filter((m) => !!m).slice().reverse();
16617
16617
  for (const map of maps) {
16618
- errorMessage2 = map(fullIssue, { data, defaultError: errorMessage2 }).message;
16618
+ errorMessage3 = map(fullIssue, { data, defaultError: errorMessage3 }).message;
16619
16619
  }
16620
16620
  return {
16621
16621
  ...issueData,
16622
16622
  path: fullPath,
16623
- message: errorMessage2
16623
+ message: errorMessage3
16624
16624
  };
16625
16625
  };
16626
16626
  var EMPTY_PATH2 = [];
@@ -23658,15 +23658,15 @@ var makeIssue3 = (params) => {
23658
23658
  message: issueData.message
23659
23659
  };
23660
23660
  }
23661
- let errorMessage2 = "";
23661
+ let errorMessage3 = "";
23662
23662
  const maps = errorMaps.filter((m) => !!m).slice().reverse();
23663
23663
  for (const map of maps) {
23664
- errorMessage2 = map(fullIssue, { data, defaultError: errorMessage2 }).message;
23664
+ errorMessage3 = map(fullIssue, { data, defaultError: errorMessage3 }).message;
23665
23665
  }
23666
23666
  return {
23667
23667
  ...issueData,
23668
23668
  path: fullPath,
23669
- message: errorMessage2
23669
+ message: errorMessage3
23670
23670
  };
23671
23671
  };
23672
23672
  var EMPTY_PATH3 = [];
@@ -31159,7 +31159,7 @@ var APPS_AGENT_VARIANTS = {
31159
31159
  { service: "tinycloud.sql", actions: ["tinycloud.sql/read"], within: [""] }
31160
31160
  ],
31161
31161
  perAppOnly: false,
31162
- changesStructure: false
31162
+ schemaForCatalogApps: false
31163
31163
  },
31164
31164
  write: {
31165
31165
  grants: [
@@ -31168,9 +31168,13 @@ var APPS_AGENT_VARIANTS = {
31168
31168
  { service: "tinycloud.sql", actions: ["tinycloud.sql/read", "tinycloud.sql/write"], within: [""] }
31169
31169
  ],
31170
31170
  perAppOnly: true,
31171
- changesStructure: true
31171
+ schemaForCatalogApps: true
31172
31172
  }
31173
31173
  };
31174
+ var APPS_AGENT_SCHEMA_DATABASES = {
31175
+ "xyz.tinycloud.sample.healthlog": "healthlog"
31176
+ };
31177
+ var SCHEMA_ACTION = "tinycloud.sql/schema";
31174
31178
  var ACTION_WORDS = {
31175
31179
  "tinycloud.kv/get": "read files",
31176
31180
  "tinycloud.kv/list": "list files",
@@ -31188,12 +31192,11 @@ var ACTION_WORDS = {
31188
31192
  "tinycloud.sql/*": "do anything in the database",
31189
31193
  "tinycloud.capabilities/read": "see which permissions this key holds"
31190
31194
  };
31191
- var STRUCTURE_ACTIONS = /* @__PURE__ */ new Set([
31192
- "tinycloud.sql/write",
31193
- "tinycloud.sql/schema",
31194
- "tinycloud.sql/admin",
31195
- "tinycloud.sql/*"
31196
- ]);
31195
+ var STRUCTURE_ACTIONS = {
31196
+ "tinycloud.sql/schema": true,
31197
+ "tinycloud.sql/admin": true,
31198
+ "tinycloud.sql/*": true
31199
+ };
31197
31200
  var APP_ID = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
31198
31201
  var APP_ID_RULE = "an app id looks like xyz.example.app: letters, digits, dots, hyphens and underscores, no slashes or spaces";
31199
31202
  function isAppId(value) {
@@ -31201,7 +31204,15 @@ function isAppId(value) {
31201
31204
  }
31202
31205
  function appsAgentPermissions(mode, appIds = []) {
31203
31206
  const bases = appIds.length === 0 ? [""] : appIds.map((appId) => `${appId}/`);
31204
- 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] }))));
31207
+ const grants = 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] }))));
31208
+ const schema = APPS_AGENT_VARIANTS[mode].schemaForCatalogApps ? schemaPathsFor(appIds).map((path) => ({ service: "tinycloud.sql", space: APPS_AGENT_SPACE, path, actions: [SCHEMA_ACTION] })) : [];
31209
+ return [...grants, ...schema];
31210
+ }
31211
+ function schemaPathsFor(appIds) {
31212
+ return appIds.flatMap((appId) => {
31213
+ if (!Object.hasOwn(APPS_AGENT_SCHEMA_DATABASES, appId)) return [];
31214
+ return [`${appId}/${APPS_AGENT_SCHEMA_DATABASES[appId]}`];
31215
+ });
31205
31216
  }
31206
31217
  function quoteApps(appIds) {
31207
31218
  return appIds.map((appId) => `\`${appId}\``).join(", ");
@@ -31224,12 +31235,14 @@ function appsAgentScopeNotice(mode, appIds = []) {
31224
31235
  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";
31225
31236
  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`;
31226
31237
  const lines = [
31227
- variant.changesStructure ? `This gives the agent access to ${scope}: it can ${reads}.` : `This gives the agent READ-ONLY access to ${scope}: it can ${reads}.`
31238
+ writes ? `This gives the agent access to ${scope}: it can ${reads}.` : `This gives the agent READ-ONLY access to ${scope}: it can ${reads}.`
31228
31239
  ];
31229
- if (variant.changesStructure) {
31240
+ if (writes) {
31241
+ const schemaPaths = schemaPathsFor(appIds).map((path) => `\`${path}\``);
31230
31242
  lines.push(
31231
31243
  `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.`,
31232
- `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).`,
31244
+ `SQL write is separate from that: it can add, change and delete ${appIds.length === 1 ? "this app's" : "these apps'"} table rows.`,
31245
+ variant.schemaForCatalogApps && schemaPaths.length > 0 ? `It can also create, change and drop the tables in ${schemaPaths.join(", ")}, which the app needs to set itself up. That grant names no other database for table changes. Nodes before 1.21.6 do not enforce that database limit, so the write grant may still let it change tables elsewhere there.` : "This grant does not include permission to create, change or drop tables (tinycloud.sql/schema). Nodes before 1.21.6 do not enforce that split, so the write grant may still let it do so there.",
31233
31246
  "It cannot touch other apps, other spaces or secrets."
31234
31247
  );
31235
31248
  } else {
@@ -31382,6 +31395,7 @@ function describeEntry(entry, applicationsSpace) {
31382
31395
  function withinPresetSet(entry, applicationsSpace) {
31383
31396
  if (!inApplicationsSpace(entry, applicationsSpace) || entry.caveats !== void 0) return false;
31384
31397
  if (entry.path.split("/").some((segment) => segment === "." || segment === "..")) return false;
31398
+ if (entry.actions.includes(SCHEMA_ACTION)) return false;
31385
31399
  const slash = entry.path.indexOf("/");
31386
31400
  const folder = slash > 0 && isAppId(entry.path.slice(0, slash)) ? entry.path.slice(slash + 1) : void 0;
31387
31401
  const grants = Object.values(APPS_AGENT_VARIANTS).flatMap(({ grants: variantGrants }) => variantGrants.filter((grant) => grant.service === entry.service));
@@ -31400,8 +31414,8 @@ function describeGrantScope(requested, requestedExpiry, applicationsSpace, maxLi
31400
31414
  ...extras.map((entry) => `! ${describeEntry(entry, applicationsSpace)}`),
31401
31415
  ...others.map((entry) => `- ${describeEntry(entry, applicationsSpace)}`)
31402
31416
  ];
31403
- if (requested.some((entry) => entry.service === "tinycloud.sql" && entry.actions.some((action) => STRUCTURE_ACTIONS.has(action)))) {
31404
- lines.push("Because of how TinyCloud permissions work today (TC-1581), SQL write access can also change or delete tables (structure).");
31417
+ if (requested.some((entry) => entry.service === "tinycloud.sql" && entry.actions.some((action) => Object.hasOwn(STRUCTURE_ACTIONS, action)))) {
31418
+ lines.push("This request can create, change and drop tables (an SQL schema, admin or wildcard entry).");
31405
31419
  }
31406
31420
  const entryLifetimes = requested.filter((entry) => entry.expiry !== void 0).map((entry) => lifetimeMs(entry.expiry));
31407
31421
  const durations = [lifetimeMs(requestedExpiry), ...entryLifetimes].filter((ms2) => ms2 !== void 0).map((ms2) => Math.min(ms2, maxLifetimeMs ?? Number.POSITIVE_INFINITY));
@@ -32184,7 +32198,7 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
32184
32198
  `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`,
32185
32199
  (value, previous) => [...previous, value],
32186
32200
  []
32187
- ).option("--write", `With --preset ${APPS_AGENT_PRESET}: also let the agent add and change the named --app apps' data: write their knowledge/ and prefs/ files (kv put) and change their tables (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) => {
32201
+ ).option("--write", `With --preset ${APPS_AGENT_PRESET}: also let the agent add and change the named --app apps' data: write their knowledge/ and prefs/ files (kv put) and add, change or delete their table rows (sql write). Their code, manifest and install record stay read-only. Creating, changing or dropping tables is granted only for an app's catalog database (today Health Log's healthlog)`).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) => {
32188
32202
  try {
32189
32203
  const globalOpts = cmd.optsWithGlobals();
32190
32204
  const ctx = await ProfileManager.resolveContext(globalOpts, options2.grant ? {} : { storedSession: "unused" });
@@ -34412,12 +34426,369 @@ function registerEnableCommand(program) {
34412
34426
  });
34413
34427
  }
34414
34428
 
34429
+ // src/commands/apps.ts
34430
+ init_profiles();
34431
+ init_constants();
34432
+ init_sdk();
34433
+ import { CLI_NAME as CLI_NAME23 } from "@tinycloud/operations/cli-name";
34434
+ import { operationSpaceResolver as operationSpaceResolver2 } from "@tinycloud/operations/delegation-binding";
34435
+
34436
+ // src/lib/knowledge-input.ts
34437
+ init_constants();
34438
+ init_errors();
34439
+ init_safe_text();
34440
+ function usage2(message) {
34441
+ return new CLIError("INVALID_ARGUMENT", message, ExitCode.USAGE_ERROR);
34442
+ }
34443
+ function assertKnowledgeAppId(value) {
34444
+ if (!isAppId(value)) throw usage2(`Invalid app id "${safeClipped(value)}": ${APP_ID_RULE}.`);
34445
+ return value;
34446
+ }
34447
+ function assertKnowledgeConcept(value) {
34448
+ 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;
34449
+ if (reason !== void 0) {
34450
+ throw usage2(
34451
+ `Invalid concept "${safeClipped(value)}": ${reason}. Use a path inside the app's knowledge folder, such as app or tables/meal.md.`
34452
+ );
34453
+ }
34454
+ return value;
34455
+ }
34456
+
34457
+ // src/lib/apps-install-pin.ts
34458
+ var SHARE_ORIGIN = "https://share.tinycloud.xyz";
34459
+ var HEALTHLOG_ID = "xyz.tinycloud.sample.healthlog";
34460
+ var PINNED = {
34461
+ [HEALTHLOG_ID]: {
34462
+ appId: HEALTHLOG_ID,
34463
+ database: `${HEALTHLOG_ID}/healthlog`,
34464
+ bundleDigest: "dc3ec19fffce8fee350944469948531659a258347538a98aacdd5544deea5014",
34465
+ files: [
34466
+ { path: "schema.sql", sha256: "bb46ab35ac7887f672beba2f50b9f982c31d41607b3fa77ac9c2e72003edf199", size: 4105 },
34467
+ { path: "knowledge/app.md", sha256: "74a65d0332ba06fe5f7fb47c67243ebc6ca80435f3388cb5201712287240f4c7", size: 3994 },
34468
+ { path: "knowledge/kv/app.md", sha256: "1583fdefdf6261a8ce110d8de22843855f8393f760de9fa79c3491868924bd42", size: 1283 },
34469
+ { path: "knowledge/kv/knowledge.md", sha256: "1f3fb9d50e0ccea10dbe4a223efdbbde2b277bbed8f4790251331c5a7dcebc0f", size: 1170 },
34470
+ { path: "knowledge/kv/prefs.md", sha256: "0de88eb2cd8c2ad7302b84c2f6b972364d9e9354f15b70657349339167dd52e4", size: 831 },
34471
+ { path: "knowledge/log.md", sha256: "7753435d03afe51c36023c596330c7f04c597319586b5b39d4cf8a675512b1d0", size: 263 },
34472
+ { path: "knowledge/tables/meal.md", sha256: "1bc09271c8c2cf987ef81dfb99c4427234fdfbc795f5229a1d631aef0fbff795", size: 1921 },
34473
+ { path: "knowledge/tables/weight.md", sha256: "fbae8b103931f976b838024d3ec7ea2596f1b1af3e9834e8aaba1d216d227bc0", size: 1401 },
34474
+ { path: "knowledge/views/day_card.md", sha256: "f1ea0738b6905a4952ab1f9c269310f2f888c6606b16304c12bdf69dfb3fafbe", size: 1707 },
34475
+ { path: "knowledge/views/week_rollup.md", sha256: "b9773f2ae10fadb9b0ce5f5a78885c87f90bd202a00f7f157428911239c2077e", size: 1579 },
34476
+ { path: "knowledge/index.md", sha256: "06e54fa6a4f14ae13bdbcf2ad5ab22b6d561da90e04c27fc616787762e45811e", size: 945 }
34477
+ ]
34478
+ }
34479
+ };
34480
+ function pinnedApp(appId) {
34481
+ return Object.hasOwn(PINNED, appId) ? PINNED[appId] : void 0;
34482
+ }
34483
+
34484
+ // src/lib/apps-install.ts
34485
+ import { createHash as createHash3 } from "crypto";
34486
+ function errorMessage2(error) {
34487
+ return error instanceof Error ? error.message : String(error);
34488
+ }
34489
+ var MAX_FILE_BYTES = 1e6;
34490
+ var AppsInstallError = class extends Error {
34491
+ constructor(code4, message) {
34492
+ super(message);
34493
+ this.code = code4;
34494
+ this.name = "AppsInstallError";
34495
+ }
34496
+ };
34497
+ function sha256Hex2(bytes) {
34498
+ return createHash3("sha256").update(bytes).digest("hex");
34499
+ }
34500
+ function bytesEqual(a, b) {
34501
+ return a.length === b.length && Buffer.from(a).equals(Buffer.from(b));
34502
+ }
34503
+ async function fetchVerified(app, io) {
34504
+ const verified = /* @__PURE__ */ new Map();
34505
+ for (const file of app.files) {
34506
+ const url = `${SHARE_ORIGIN}/apps/b/${app.bundleDigest}/${file.path}`;
34507
+ let bytes;
34508
+ try {
34509
+ bytes = await io.fetchBytes(url);
34510
+ } catch (error) {
34511
+ throw new AppsInstallError("FETCH_FAILED", `Could not fetch ${file.path}: ${errorMessage2(error)}`);
34512
+ }
34513
+ verifyBytes(file, bytes);
34514
+ verified.set(file.path, bytes);
34515
+ }
34516
+ return verified;
34517
+ }
34518
+ function verifyBytes(file, bytes) {
34519
+ if (bytes.length > MAX_FILE_BYTES) {
34520
+ throw new AppsInstallError("INTEGRITY_FAILED", `${file.path} is ${bytes.length} bytes, above the ${MAX_FILE_BYTES} byte limit`);
34521
+ }
34522
+ if (bytes.length !== file.size || sha256Hex2(bytes) !== file.sha256) {
34523
+ throw new AppsInstallError("INTEGRITY_FAILED", `${file.path} does not match the pinned sha256 and size`);
34524
+ }
34525
+ }
34526
+ function splitStatements(sql) {
34527
+ let stripped = "";
34528
+ let inQuote = false;
34529
+ for (let i = 0; i < sql.length; i++) {
34530
+ const ch = sql[i];
34531
+ if (ch === "'") inQuote = !inQuote;
34532
+ if (!inQuote && ch === "-" && sql[i + 1] === "-") {
34533
+ while (i < sql.length && sql[i] !== "\n") i++;
34534
+ stripped += "\n";
34535
+ continue;
34536
+ }
34537
+ stripped += ch;
34538
+ }
34539
+ return stripped.split(";").map((statement) => statement.replace(/\s+/g, " ").trim()).filter((statement) => statement.length > 0);
34540
+ }
34541
+ var TABLE_FORM = /^CREATE TABLE IF NOT EXISTS ([A-Za-z_][A-Za-z0-9_]*) \(.*\)$/i;
34542
+ var INDEX_FORM = /^CREATE INDEX IF NOT EXISTS ([A-Za-z_][A-Za-z0-9_]*) ON [A-Za-z_][A-Za-z0-9_]*\([A-Za-z_][A-Za-z0-9_, ]*\)$/i;
34543
+ var VIEW_FORM = /^CREATE VIEW IF NOT EXISTS ([A-Za-z_][A-Za-z0-9_]*) AS SELECT .+$/i;
34544
+ function planDdl(schemaSql) {
34545
+ const tables = [];
34546
+ const views = [];
34547
+ const statements = [];
34548
+ for (const statement of splitStatements(schemaSql)) {
34549
+ const table = TABLE_FORM.exec(statement);
34550
+ const index = INDEX_FORM.exec(statement);
34551
+ const view = VIEW_FORM.exec(statement);
34552
+ if (table) {
34553
+ tables.push(table[1]);
34554
+ statements.push(statement);
34555
+ } else if (index) {
34556
+ statements.push(statement);
34557
+ } else if (view) {
34558
+ views.push(view[1]);
34559
+ statements.push(`DROP VIEW IF EXISTS ${view[1]}`, statement);
34560
+ } else {
34561
+ throw new AppsInstallError("DDL_REFUSED", `Pinned schema statement is not an allowed form: ${statement.slice(0, 80)}`);
34562
+ }
34563
+ }
34564
+ return { tables, views, statements };
34565
+ }
34566
+ async function applySchema(plan, io) {
34567
+ let existing;
34568
+ try {
34569
+ existing = new Set(await io.sqlTableNames());
34570
+ } catch (error) {
34571
+ throw new AppsInstallError("SQL_FAILED", `Could not list tables: ${errorMessage2(error)}`);
34572
+ }
34573
+ for (const statement of plan.statements) {
34574
+ try {
34575
+ await io.sqlExecute(statement);
34576
+ } catch (error) {
34577
+ throw new AppsInstallError("SQL_FAILED", `Schema statement failed: ${errorMessage2(error)}`);
34578
+ }
34579
+ }
34580
+ return {
34581
+ tablesCreated: plan.tables.filter((name) => !existing.has(name)),
34582
+ viewsRecreated: [...plan.views]
34583
+ };
34584
+ }
34585
+ function knowledgePaths(app) {
34586
+ const paths = app.files.map((file) => file.path).filter((path) => path.startsWith("knowledge/"));
34587
+ return [...paths.filter((path) => path !== "knowledge/index.md"), ...paths.filter((path) => path === "knowledge/index.md")];
34588
+ }
34589
+ async function inspectKnowledge(appId, app, verified, io) {
34590
+ const missing = [];
34591
+ const unchanged = [];
34592
+ const conflicts = [];
34593
+ for (const path of knowledgePaths(app)) {
34594
+ const current = await readKv(`${appId}/${path}`, io);
34595
+ if (current === void 0) missing.push(path);
34596
+ else if (bytesEqual(current, verified.get(path))) unchanged.push(path);
34597
+ else conflicts.push(path);
34598
+ }
34599
+ return { missing, unchanged, conflicts };
34600
+ }
34601
+ async function createMissing(appId, missing, verified, io) {
34602
+ const written = [];
34603
+ const unchanged = [];
34604
+ const conflicts = [];
34605
+ for (const path of missing) {
34606
+ const key = `${appId}/${path}`;
34607
+ const bytes = verified.get(path);
34608
+ const outcome = await createKv(key, bytes, io);
34609
+ if (outcome === "created") {
34610
+ written.push(path);
34611
+ continue;
34612
+ }
34613
+ const raced = await readKv(key, io);
34614
+ if (raced !== void 0 && bytesEqual(raced, bytes)) unchanged.push(path);
34615
+ else conflicts.push(path);
34616
+ }
34617
+ return { written, unchanged, conflicts };
34618
+ }
34619
+ async function readKv(key, io) {
34620
+ try {
34621
+ return await io.kvGet(key);
34622
+ } catch (error) {
34623
+ throw new AppsInstallError("KV_FAILED", `Could not read ${key}: ${errorMessage2(error)}`);
34624
+ }
34625
+ }
34626
+ async function checkInstallRecord(appId, app, io) {
34627
+ const key = `${appId}/app/install.json`;
34628
+ const bytes = await readKv(key, io);
34629
+ if (bytes === void 0) return;
34630
+ const digest2 = installRecordDigest(bytes);
34631
+ if (digest2 === void 0) {
34632
+ throw new AppsInstallError("INSTALL_RECORD_INVALID", `${key} is not an install record with a digest`);
34633
+ }
34634
+ if (digest2 !== app.bundleDigest) {
34635
+ throw new AppsInstallError(
34636
+ "INSTALL_RECORD_MISMATCH",
34637
+ `${key} names bundle ${digest2}, but this CLI pins ${app.bundleDigest}; update the CLI before installing`
34638
+ );
34639
+ }
34640
+ }
34641
+ function installRecordDigest(bytes) {
34642
+ let parsed;
34643
+ try {
34644
+ parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
34645
+ } catch {
34646
+ return void 0;
34647
+ }
34648
+ if (typeof parsed !== "object" || parsed === null || !("digest" in parsed)) return void 0;
34649
+ return typeof parsed.digest === "string" ? parsed.digest : void 0;
34650
+ }
34651
+ async function createKv(key, bytes, io) {
34652
+ try {
34653
+ return await io.kvCreate(key, bytes);
34654
+ } catch (error) {
34655
+ throw new AppsInstallError("KV_FAILED", `Could not write ${key}: ${errorMessage2(error)}`);
34656
+ }
34657
+ }
34658
+ async function installApp(appId, io, lookup = pinnedApp) {
34659
+ const app = lookup(appId);
34660
+ if (!app) throw new AppsInstallError("UNKNOWN_APP", `No pinned catalog entry for ${appId}`);
34661
+ const verified = await fetchVerified(app, io);
34662
+ await checkInstallRecord(appId, app, io);
34663
+ const schemaSql = new TextDecoder("utf-8", { fatal: true }).decode(verified.get("schema.sql"));
34664
+ const plan = planDdl(schemaSql);
34665
+ const inspected = await inspectKnowledge(appId, app, verified, io);
34666
+ const created = await createMissing(appId, inspected.missing, verified, io);
34667
+ let schema;
34668
+ try {
34669
+ schema = await applySchema(plan, io);
34670
+ } catch (error) {
34671
+ if (error instanceof AppsInstallError) {
34672
+ throw new AppsInstallError(error.code, `notes saved; tables not set up: run again. ${error.message}`);
34673
+ }
34674
+ throw error;
34675
+ }
34676
+ return {
34677
+ appId,
34678
+ status: created.written.length > 0 ? "installed" : "already",
34679
+ database: app.database,
34680
+ filesWritten: created.written,
34681
+ filesUnchanged: [...inspected.unchanged, ...created.unchanged],
34682
+ conflicts: [...inspected.conflicts, ...created.conflicts],
34683
+ tablesCreated: schema.tablesCreated,
34684
+ viewsRecreated: schema.viewsRecreated
34685
+ };
34686
+ }
34687
+
34688
+ // src/commands/apps.ts
34689
+ init_errors();
34690
+ init_formatter();
34691
+ init_theme();
34692
+ var APPLICATIONS_SPACE = "applications";
34693
+ function registerAppsCommand(program) {
34694
+ const apps = program.command("apps").description("Install catalog apps into your applications space");
34695
+ apps.command("install <app_id>").description("Install a pinned catalog app: verify Share bytes, apply its schema, write its knowledge bundle").addHelpText(
34696
+ "after",
34697
+ `
34698
+ Examples:
34699
+ $ ${CLI_NAME23} apps install xyz.tinycloud.sample.healthlog
34700
+ `
34701
+ ).action(async (appIdInput, _options, cmd) => {
34702
+ try {
34703
+ const appId = assertKnowledgeAppId(appIdInput);
34704
+ const entry = pinnedApp(appId);
34705
+ if (!entry) throw new CLIError("UNKNOWN_APP", `No pinned catalog entry for ${appId}`, ExitCode.USAGE_ERROR);
34706
+ const ctx = await ProfileManager.resolveContext(cmd.optsWithGlobals());
34707
+ const node = await ensureAuthenticated(ctx);
34708
+ const profile = await ProfileManager.getProfile(ctx.profile);
34709
+ const spaceUri = operationSpaceResolver2(node, profile.spaceId)(APPLICATIONS_SPACE);
34710
+ const io = appsInstallIo(node.kvForSpace(spaceUri), node.sqlForSpace(spaceUri).db(entry.database));
34711
+ const result = await withSpinner(`Installing ${appId}...`, () => installApp(appId, io)).catch((error) => {
34712
+ throw installFailure(error);
34713
+ });
34714
+ if (shouldOutputJson()) outputJson(result);
34715
+ else printResult(result);
34716
+ } catch (error) {
34717
+ return handleError(error);
34718
+ }
34719
+ });
34720
+ }
34721
+ function appsInstallIo(kv, db) {
34722
+ return {
34723
+ async fetchBytes(url) {
34724
+ const response = await fetch(url, { redirect: "error" });
34725
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
34726
+ return new Uint8Array(await response.arrayBuffer());
34727
+ },
34728
+ async kvGet(key) {
34729
+ const result = await kv.get(key, { binary: true });
34730
+ if (result.ok) {
34731
+ const bytes = result.data.data;
34732
+ if (bytes instanceof Uint8Array) return bytes;
34733
+ throw new Error("KV_GET_NOT_BYTES");
34734
+ }
34735
+ if (result.error.code === "KV_NOT_FOUND") return void 0;
34736
+ throw new Error(result.error.code ?? "KV_GET_FAILED");
34737
+ },
34738
+ async kvCreate(key, bytes) {
34739
+ const result = await kv.put(key, bytes, { ifNoneMatch: "*" });
34740
+ if (result.ok) return "created";
34741
+ if (result.error.code === "KV_PRECONDITION_FAILED") return "exists";
34742
+ throw new Error(result.error.code ?? "KV_PUT_FAILED");
34743
+ },
34744
+ async sqlExecute(statement) {
34745
+ const result = await db.execute(statement);
34746
+ if (!result.ok) throw new Error(result.error.code ?? "SQL_EXECUTE_FAILED");
34747
+ },
34748
+ async sqlTableNames() {
34749
+ const result = await db.query("SELECT name FROM sqlite_master WHERE type = 'table'");
34750
+ if (!result.ok) throw new Error(result.error.code ?? "SQL_QUERY_FAILED");
34751
+ return result.data.rows.map(tableName);
34752
+ }
34753
+ };
34754
+ }
34755
+ function tableName(row) {
34756
+ if (Array.isArray(row)) return String(row[0]);
34757
+ if (row !== null && typeof row === "object" && "name" in row) return String(row.name);
34758
+ throw new Error("SQL_ROW_UNEXPECTED");
34759
+ }
34760
+ function installFailure(error) {
34761
+ if (!(error instanceof AppsInstallError)) return error;
34762
+ return new CLIError(error.code, error.message, ExitCode.ERROR);
34763
+ }
34764
+ function printResult(result) {
34765
+ const rows = [
34766
+ ["App", result.appId],
34767
+ ["Status", result.status],
34768
+ ["Database", result.database],
34769
+ ["Tables created", result.tablesCreated.join(", ") || "none"],
34770
+ ["Views recreated", result.viewsRecreated.join(", ") || "none"],
34771
+ ["Knowledge written", String(result.filesWritten.length)],
34772
+ ["Knowledge unchanged", String(result.filesUnchanged.length)],
34773
+ ["Knowledge left as edited", String(result.conflicts.length)]
34774
+ ];
34775
+ process.stdout.write(formatTable(["Field", "Value"], rows) + "\n");
34776
+ if (result.conflicts.length > 0) {
34777
+ const count = result.conflicts.length;
34778
+ const sentence = count === 1 ? "1 note was left as you edited it" : `${count} notes were left as you edited them`;
34779
+ process.stdout.write(theme.muted(`${sentence}: ${result.conflicts.join(", ")}`) + "\n");
34780
+ }
34781
+ if (result.status === "already") {
34782
+ process.stdout.write(theme.muted("Already installed; views refreshed.") + "\n");
34783
+ }
34784
+ }
34785
+
34415
34786
  // src/commands/init.ts
34416
34787
  init_profiles();
34417
34788
  init_formatter();
34418
34789
  init_errors();
34419
34790
  init_constants();
34420
- import { CLI_NAME as CLI_NAME23 } from "@tinycloud/operations/cli-name";
34791
+ import { CLI_NAME as CLI_NAME24 } from "@tinycloud/operations/cli-name";
34421
34792
  function registerInitCommand(program) {
34422
34793
  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) => {
34423
34794
  try {
@@ -34435,7 +34806,7 @@ function registerInitCommand(program) {
34435
34806
  if (await ProfileManager.profileExists(profileName2)) {
34436
34807
  throw new CLIError(
34437
34808
  "PROFILE_EXISTS",
34438
- `Profile "${profileName2}" already exists. Use \`${CLI_NAME23} profile delete ${profileName2}\` first or choose a different name.`,
34809
+ `Profile "${profileName2}" already exists. Use \`${CLI_NAME24} profile delete ${profileName2}\` first or choose a different name.`,
34439
34810
  ExitCode.ERROR
34440
34811
  );
34441
34812
  }
@@ -34491,30 +34862,7 @@ init_session_expired();
34491
34862
  init_constants();
34492
34863
  init_profiles();
34493
34864
  import { invokeOperation as invokeOperation2 } from "@tinycloud/operations";
34494
- import { CLI_NAME as CLI_NAME24 } from "@tinycloud/operations/cli-name";
34495
-
34496
- // src/lib/knowledge-input.ts
34497
- init_constants();
34498
- init_errors();
34499
- init_safe_text();
34500
- function usage2(message) {
34501
- return new CLIError("INVALID_ARGUMENT", message, ExitCode.USAGE_ERROR);
34502
- }
34503
- function assertKnowledgeAppId(value) {
34504
- if (!isAppId(value)) throw usage2(`Invalid app id "${safeClipped(value)}": ${APP_ID_RULE}.`);
34505
- return value;
34506
- }
34507
- function assertKnowledgeConcept(value) {
34508
- 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;
34509
- if (reason !== void 0) {
34510
- throw usage2(
34511
- `Invalid concept "${safeClipped(value)}": ${reason}. Use a path inside the app's knowledge folder, such as app or tables/meal.md.`
34512
- );
34513
- }
34514
- return value;
34515
- }
34516
-
34517
- // src/commands/knowledge.ts
34865
+ import { CLI_NAME as CLI_NAME25 } from "@tinycloud/operations/cli-name";
34518
34866
  init_permissions();
34519
34867
  init_errors();
34520
34868
  init_formatter();
@@ -34617,7 +34965,7 @@ function knowledgeFailure(result, profile, appId) {
34617
34965
  case "error": {
34618
34966
  if (result.error.code === "SESSION_NOT_FOUND" || result.error.code === "PROFILE_POSTURE_NOT_ALLOWED" && result.context.posture === "unauthenticated") {
34619
34967
  return new CLIError("AUTH_REQUIRED", "Not signed in to TinyCloud.", ExitCode.AUTH_REQUIRED, {
34620
- hint: `Sign in with: ${CLI_NAME24} --profile ${result.context.profile} auth login`
34968
+ hint: `Sign in with: ${CLI_NAME25} --profile ${result.context.profile} auth login`
34621
34969
  });
34622
34970
  }
34623
34971
  if (result.error.code === "NODE_UNREACHABLE") {
@@ -34647,7 +34995,7 @@ function knowledgeFailure(result, profile, appId) {
34647
34995
  }
34648
34996
  }
34649
34997
  function missingGrantHint(profile, appId) {
34650
- const request = `${CLI_NAME24} --profile ${profile} auth request`;
34998
+ const request = `${CLI_NAME25} --profile ${profile} auth request`;
34651
34999
  const lines = appId === void 0 ? [
34652
35000
  ` ${request} --preset ${APPS_AGENT_PRESET} --emit req.json (read-only, all installed apps)`,
34653
35001
  ` ${request} --cap tinycloud.kv:${APPS_AGENT_SPACE}::get,list --emit req.json (all files, no table access)`,
@@ -34660,7 +35008,7 @@ function missingGrantHint(profile, appId) {
34660
35008
  return [
34661
35009
  "Ask the owner for a grant, narrowest first. Create the request:",
34662
35010
  ...lines,
34663
- `The owner runs \`${CLI_NAME24} auth grant req.json > grant.json\`; then import the result with \`${CLI_NAME24} --profile ${profile} auth import grant.json\`.`
35011
+ `The owner runs \`${CLI_NAME25} auth grant req.json > grant.json\`; then import the result with \`${CLI_NAME25} --profile ${profile} auth import grant.json\`.`
34664
35012
  ].join("\n");
34665
35013
  }
34666
35014
 
@@ -34968,7 +35316,7 @@ init_errors();
34968
35316
  init_constants();
34969
35317
  init_theme();
34970
35318
  import { resolveManifestKnowledgeRoot as resolveManifestKnowledgeRoot2 } from "@tinycloud/sdk-core";
34971
- import { CLI_NAME as CLI_NAME25 } from "@tinycloud/operations/cli-name";
35319
+ import { CLI_NAME as CLI_NAME26 } from "@tinycloud/operations/cli-name";
34972
35320
  import { readFile as readFile12 } from "fs/promises";
34973
35321
  var DEFAULT_APP_SPACE = "applications";
34974
35322
  function registerManifestCommand(program) {
@@ -34976,8 +35324,8 @@ function registerManifestCommand(program) {
34976
35324
  manifest.command("resolve <source>").description("Resolve a manifest file or URL to its effective space, paths, and DB basenames").addHelpText("after", `
34977
35325
 
34978
35326
  Examples:
34979
- $ ${CLI_NAME25} manifest resolve ./manifest.json
34980
- $ ${CLI_NAME25} manifest resolve https://app.example.com/manifest.json --json
35327
+ $ ${CLI_NAME26} manifest resolve ./manifest.json
35328
+ $ ${CLI_NAME26} manifest resolve https://app.example.com/manifest.json --json
34981
35329
 
34982
35330
  What it shows:
34983
35331
  - app_id, name, manifest_version
@@ -34987,7 +35335,7 @@ What it shows:
34987
35335
 
34988
35336
  This command is read-only and does NOT contact the node \u2014 it just resolves
34989
35337
  the manifest against the active profile's address/chain so you know which
34990
- \`--space\` and \`--db\` values to pass to other ${CLI_NAME25} commands.
35338
+ \`--space\` and \`--db\` values to pass to other ${CLI_NAME26} commands.
34991
35339
  `).action(async (source, _options, cmd) => {
34992
35340
  try {
34993
35341
  const globalOpts = cmd.optsWithGlobals();
@@ -35056,7 +35404,7 @@ ${theme.heading("SQL databases")}
35056
35404
  `);
35057
35405
  }
35058
35406
  process.stdout.write(theme.muted(`
35059
- Use with: ${CLI_NAME25} sql query --space ${spaceName} --db <db> "..."
35407
+ Use with: ${CLI_NAME26} sql query --space ${spaceName} --db <db> "..."
35060
35408
  `));
35061
35409
  }
35062
35410
  if (permissions.length > 0) {
@@ -35176,7 +35524,7 @@ init_profiles();
35176
35524
  init_formatter();
35177
35525
  init_errors();
35178
35526
  init_constants();
35179
- import { CLI_NAME as CLI_NAME26 } from "@tinycloud/operations/cli-name";
35527
+ import { CLI_NAME as CLI_NAME27 } from "@tinycloud/operations/cli-name";
35180
35528
  import { createInterface as createInterface5 } from "readline";
35181
35529
  init_theme();
35182
35530
  init_types();
@@ -35322,9 +35670,9 @@ profile at command time. Precedence for every kv/sql command:
35322
35670
  explicit --space flag > profile defaultSpace > primary space.
35323
35671
 
35324
35672
  Examples:
35325
- $ ${CLI_NAME26} profile set-default-space applications
35326
- $ ${CLI_NAME26} profile set-default-space applications --profile cli-test
35327
- $ ${CLI_NAME26} profile set-default-space --unset
35673
+ $ ${CLI_NAME27} profile set-default-space applications
35674
+ $ ${CLI_NAME27} profile set-default-space applications --profile cli-test
35675
+ $ ${CLI_NAME27} profile set-default-space --unset
35328
35676
  `).action(async (name, options, cmd) => {
35329
35677
  try {
35330
35678
  const globalOpts = cmd.optsWithGlobals();
@@ -35336,7 +35684,7 @@ Examples:
35336
35684
  if (!options.unset && (name === void 0 || name === "")) {
35337
35685
  throw new CLIError(
35338
35686
  "USAGE_ERROR",
35339
- `Provide a space name (e.g. \`${CLI_NAME26} profile set-default-space applications\`) or pass --unset.`,
35687
+ `Provide a space name (e.g. \`${CLI_NAME27} profile set-default-space applications\`) or pass --unset.`,
35340
35688
  ExitCode.USAGE_ERROR
35341
35689
  );
35342
35690
  }
@@ -35395,7 +35743,7 @@ function parseOperatorType(raw) {
35395
35743
 
35396
35744
  // src/commands/replica.ts
35397
35745
  init_profile_replicas();
35398
- import { CLI_NAME as CLI_NAME27 } from "@tinycloud/operations/cli-name";
35746
+ import { CLI_NAME as CLI_NAME28 } from "@tinycloud/operations/cli-name";
35399
35747
  import { randomBytes as randomBytes7 } from "crypto";
35400
35748
  import { constants as fsConstants5 } from "fs";
35401
35749
  import { mkdir as mkdir5, open as open11, readdir as readdir6, rename as rename4, unlink as unlink5, lstat as lstat9 } from "fs/promises";
@@ -35536,11 +35884,11 @@ var EXIT_BY_CODE = {
35536
35884
  var READ_FAILURE = {
35537
35885
  absent: { code: "KEY_ABSENT", exit: ExitCode.NOT_FOUND, what: "is not in the replica (the replica covers its whole prefix)" },
35538
35886
  deleted: { code: "KEY_DELETED", exit: ExitCode.NOT_FOUND, what: "was deleted at the source" },
35539
- content_missing: { code: "CONTENT_MISSING", exit: ExitCode.NOT_FOUND, what: `is known but its content has not been fetched yet; run ${CLI_NAME27} replica sync` },
35887
+ content_missing: { code: "CONTENT_MISSING", exit: ExitCode.NOT_FOUND, what: `is known but its content has not been fetched yet; run ${CLI_NAME28} replica sync` },
35540
35888
  coverage_incomplete: {
35541
35889
  code: "COVERAGE_INCOMPLETE",
35542
35890
  exit: ExitCode.NOT_FOUND,
35543
- what: `is not in the replica, which has not finished its first sync; run ${CLI_NAME27} replica sync`
35891
+ what: `is not in the replica, which has not finished its first sync; run ${CLI_NAME28} replica sync`
35544
35892
  },
35545
35893
  not_covered: { code: "NOT_COVERED", exit: ExitCode.USAGE_ERROR, what: "is outside the replica's prefix" }
35546
35894
  };
@@ -35658,7 +36006,7 @@ async function existingReplicaName(profile, option) {
35658
36006
  const names = await replicaNames(profile);
35659
36007
  if (names.length === 1) return names[0];
35660
36008
  if (names.length === 0) {
35661
- throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, `Profile "${profile}" has no replicas. Create one with: ${CLI_NAME27} replica sync --prefix <prefix>`);
36009
+ throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, `Profile "${profile}" has no replicas. Create one with: ${CLI_NAME28} replica sync --prefix <prefix>`);
35662
36010
  }
35663
36011
  throw new CLIError("USAGE_ERROR", `Profile "${profile}" has several replicas (${names.join(", ")}); pass --replica <name>.`, ExitCode.USAGE_ERROR);
35664
36012
  }
@@ -35766,9 +36114,9 @@ async function storedGrants(profile) {
35766
36114
  function grantHint(space, prefix) {
35767
36115
  return [
35768
36116
  "A replica needs a device grant with get and sync on its prefix:",
35769
- ` device: ${CLI_NAME27} auth request --cap tinycloud.kv:${space}:${prefix}:get,list,metadata,sync --expiry 30d --emit req.json`,
35770
- ` owner: ${CLI_NAME27} auth grant req.json > grant.json`,
35771
- ` device: ${CLI_NAME27} auth import grant.json`
36117
+ ` device: ${CLI_NAME28} auth request --cap tinycloud.kv:${space}:${prefix}:get,list,metadata,sync --expiry 30d --emit req.json`,
36118
+ ` owner: ${CLI_NAME28} auth grant req.json > grant.json`,
36119
+ ` device: ${CLI_NAME28} auth import grant.json`
35772
36120
  ].join("\n");
35773
36121
  }
35774
36122
  function resolveSpace(input, prefix, grants) {
@@ -35859,7 +36207,7 @@ function describeStatus(status) {
35859
36207
  formatField("Size", formatBytes(status.bytes)),
35860
36208
  formatField("Grant", status.device.delegationCid),
35861
36209
  formatField("Last error", status.lastError ? `${status.lastError.code}: ${status.lastError.message}` : null),
35862
- formatField("Purge", status.purgePending ? `incomplete: content files remain; the next ${CLI_NAME27} replica command retries` : null)
36210
+ formatField("Purge", status.purgePending ? `incomplete: content files remain; the next ${CLI_NAME28} replica command retries` : null)
35863
36211
  ].join("\n");
35864
36212
  }
35865
36213
  async function writeFileAtomic(path, bytes) {
@@ -36023,7 +36371,7 @@ or coverage incomplete; 5 grant expired, revoked or not yet valid; 6 network;
36023
36371
  (options, cmd) => run(async () => {
36024
36372
  const profile = await profileName(cmd);
36025
36373
  if (options.purge) {
36026
- throw new CLIError("REPORT_READ_ONLY", `Replica report is read-only. To remove a named local replica, run ${CLI_NAME27} replica reset --replica <name> --purge; flag-owned purge has no equivalent yet (TC-1309).`, ExitCode.USAGE_ERROR);
36374
+ throw new CLIError("REPORT_READ_ONLY", `Replica report is read-only. To remove a named local replica, run ${CLI_NAME28} replica reset --replica <name> --purge; flag-owned purge has no equivalent yet (TC-1309).`, ExitCode.USAGE_ERROR);
36027
36375
  }
36028
36376
  if (options.clearPending) {
36029
36377
  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);
@@ -36125,7 +36473,7 @@ or coverage incomplete; 5 grant expired, revoked or not yet valid; 6 network;
36125
36473
  if (differs !== void 0) {
36126
36474
  throw new ReplicaError(
36127
36475
  ReplicaErrorCode.CONFIG_MISMATCH,
36128
- `Replica ${name} already follows ${differs}. Use another --replica name, or ${CLI_NAME27} replica reset --replica ${name} --purge first.`
36476
+ `Replica ${name} already follows ${differs}. Use another --replica name, or ${CLI_NAME28} replica reset --replica ${name} --purge first.`
36129
36477
  );
36130
36478
  }
36131
36479
  }
@@ -36290,7 +36638,7 @@ init_theme();
36290
36638
  init_errors();
36291
36639
  init_warnings();
36292
36640
  init_constants();
36293
- import { CLI_NAME as CLI_NAME29 } from "@tinycloud/operations/cli-name";
36641
+ import { CLI_NAME as CLI_NAME30 } from "@tinycloud/operations/cli-name";
36294
36642
  import { readFile as readFile13 } from "fs/promises";
36295
36643
  import { join as join17 } from "path";
36296
36644
  import { homedir, hostname, userInfo } from "os";
@@ -36501,7 +36849,7 @@ function parseStatusEnvelope(line) {
36501
36849
  // src/lib/secrets-wait.ts
36502
36850
  init_constants();
36503
36851
  init_errors();
36504
- import { CLI_NAME as CLI_NAME28 } from "@tinycloud/operations/cli-name";
36852
+ import { CLI_NAME as CLI_NAME29 } from "@tinycloud/operations/cli-name";
36505
36853
  var WAIT_BACKOFF_MS = [3e3, 4500, 6750, 10125];
36506
36854
  var WAIT_STEADY_INTERVAL_MS = 15e3;
36507
36855
  var DEFAULT_WAIT_TIMEOUT = "5m";
@@ -36509,7 +36857,7 @@ var MAX_WAIT_TIMEOUT_MS = 9 * 6e4;
36509
36857
  var MAX_CONSECUTIVE_FAILURES = 5;
36510
36858
  var TIMEOUT_RE = /^(0|\d+s|\d+m)$/;
36511
36859
  var RETRYABLE_STATUS_ERRORS = { NODE_UNREACHABLE: true, NODE_ERROR: true };
36512
- var EXPLICIT_KEY_HINT = `${CLI_NAME28} secrets wait never signs in, and an explicit private key has no stored session. Use a local profile signed in once with ${CLI_NAME28} --profile <profile> auth login --method local, without TC_PRIVATE_KEY.`;
36860
+ var EXPLICIT_KEY_HINT = `${CLI_NAME29} secrets wait never signs in, and an explicit private key has no stored session. Use a local profile signed in once with ${CLI_NAME29} --profile <profile> auth login --method local, without TC_PRIVATE_KEY.`;
36513
36861
  function abortableSleep(ms2, signal) {
36514
36862
  return new Promise((resolve9) => {
36515
36863
  if (signal.aborted) {
@@ -36545,7 +36893,7 @@ async function runSecretsWait(request, deps) {
36545
36893
  let step = 0;
36546
36894
  const cancelled = () => new CLIError(
36547
36895
  "CANCELLED",
36548
- `Interrupted: ${CLI_NAME28} secrets wait stopped before the secrets were ready.`,
36896
+ `Interrupted: ${CLI_NAME29} secrets wait stopped before the secrets were ready.`,
36549
36897
  ExitCode.CANCELLED,
36550
36898
  progress === void 0 ? {} : { progress }
36551
36899
  );
@@ -36689,13 +37037,13 @@ function statusError(error, profile, warnings) {
36689
37037
  if (error.code === "AUTH_REQUIRED" || error.code === "SESSION_NOT_FOUND") {
36690
37038
  const recovery = recoveryOf(error, "sign_in");
36691
37039
  if (recovery !== void 0) {
36692
- const hint = recovery.command !== void 0 ? `Sign in once with: ${recovery.command}, then retry.` : `Sign in to profile "${profile}" once with ${CLI_NAME28} auth login, then retry.`;
37040
+ const hint = recovery.command !== void 0 ? `Sign in once with: ${recovery.command}, then retry.` : `Sign in to profile "${profile}" once with ${CLI_NAME29} auth login, then retry.`;
36693
37041
  return new CLIError("AUTH_REQUIRED", error.message, ExitCode.AUTH_REQUIRED, { hint, recovery, ...extra });
36694
37042
  }
36695
37043
  }
36696
37044
  if (error.code === "PROFILE_MIGRATION_REQUIRED") {
36697
37045
  const recovery = recoveryOf(error, "complete_migration");
36698
- const hint = recovery?.command !== void 0 ? `Run ${recovery.command} once to finish this profile's one-time upgrade, then retry wait.` : `Run ${CLI_NAME28} auth caps once with profile "${profile}" to finish its one-time upgrade, then retry wait.`;
37046
+ const hint = recovery?.command !== void 0 ? `Run ${recovery.command} once to finish this profile's one-time upgrade, then retry wait.` : `Run ${CLI_NAME29} auth caps once with profile "${profile}" to finish its one-time upgrade, then retry wait.`;
36699
37047
  return new CLIError("PROFILE_MIGRATION_REQUIRED", error.message, ExitCode.ERROR, {
36700
37048
  hint,
36701
37049
  ...recovery === void 0 ? {} : { recovery },
@@ -36887,11 +37235,11 @@ async function runSecretOperation(params) {
36887
37235
  }
36888
37236
  function secretPermissionReason(action, name) {
36889
37237
  const target = name ? ` secret "${name}"` : " secrets";
36890
- return `Allow \`${CLI_NAME29} secrets ${action}${name ? ` ${name}` : ""}\` to access${target} with the required TinyCloud permissions.`;
37238
+ return `Allow \`${CLI_NAME30} secrets ${action}${name ? ` ${name}` : ""}\` to access${target} with the required TinyCloud permissions.`;
36891
37239
  }
36892
37240
  function assertOwnerApprovalPossible(profileName2, profile, action, name, missing = []) {
36893
37241
  if (profile.authMethod !== "openkey" || process.stdin.isTTY || process.stderr.isTTY) return;
36894
- const command = `${CLI_NAME29} secrets ${action === "del" ? "delete" : action}${name ? ` ${name}` : ""}`;
37242
+ const command = `${CLI_NAME30} secrets ${action === "del" ? "delete" : action}${name ? ` ${name}` : ""}`;
36895
37243
  throw new CLIError(
36896
37244
  "PERMISSION_DENIED",
36897
37245
  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.`,
@@ -36902,7 +37250,7 @@ function assertOwnerApprovalPossible(profileName2, profile, action, name, missin
36902
37250
  );
36903
37251
  }
36904
37252
  function scopedSecretLoginHint(profileName2) {
36905
- return `Have the owner approve a scoped login whose manifest names the secret: ${CLI_NAME29} --profile ${profileName2} auth login --method openkey --paste --manifest <manifest.json>. Pass the owner's code on stdin, newline-terminated.`;
37253
+ return `Have the owner approve a scoped login whose manifest names the secret: ${CLI_NAME30} --profile ${profileName2} auth login --method openkey --paste --manifest <manifest.json>. Pass the owner's code on stdin, newline-terminated.`;
36906
37254
  }
36907
37255
  async function runSecretOperationAttempt(label, operation) {
36908
37256
  try {
@@ -37036,7 +37384,7 @@ ${message}`,
37036
37384
  "AUTH_REQUIRED",
37037
37385
  "Not signed in to TinyCloud.",
37038
37386
  ExitCode.AUTH_REQUIRED,
37039
- { hint: `Sign in with: ${CLI_NAME29} --profile ${result.context.profile} auth login` }
37387
+ { hint: `Sign in with: ${CLI_NAME30} --profile ${result.context.profile} auth login` }
37040
37388
  );
37041
37389
  }
37042
37390
  if (result.error.code === "NODE_UNREACHABLE") {
@@ -37513,7 +37861,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
37513
37861
  name: "Encryption network",
37514
37862
  ok: false,
37515
37863
  detail: `${networkName} not found`,
37516
- hint: `${CLI_NAME29} secrets network init ${networkName}`
37864
+ hint: `${CLI_NAME30} secrets network init ${networkName}`
37517
37865
  }
37518
37866
  ];
37519
37867
  let secret;
@@ -37559,7 +37907,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
37559
37907
  name: "Secret access",
37560
37908
  ok: false,
37561
37909
  detail: notFound ? `${resolved.permissionPaths.vault} not found` : result.error.message,
37562
- hint: notFound ? local ? `${CLI_NAME29} secrets put ${resolved.name}${formatSecretScopeFlag(scopeOptions)} --stdin` : `${CLI_NAME29} secrets request ${resolved.name}${formatSecretScopeFlag(scopeOptions)}` : `Ask the owner profile to grant ${secretCapabilityAction("get")} and ${SECRET_DECRYPT_CAPABILITY}.`
37910
+ hint: notFound ? local ? `${CLI_NAME30} secrets put ${resolved.name}${formatSecretScopeFlag(scopeOptions)} --stdin` : `${CLI_NAME30} secrets request ${resolved.name}${formatSecretScopeFlag(scopeOptions)}` : `Ask the owner profile to grant ${secretCapabilityAction("get")} and ${SECRET_DECRYPT_CAPABILITY}.`
37563
37911
  });
37564
37912
  }
37565
37913
  } else {
@@ -37803,7 +38151,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
37803
38151
  if (effectiveLocal(profile, options)) {
37804
38152
  throw new CLIError(
37805
38153
  "LOCAL_OWNER_PROFILE",
37806
- `Profile "${ctx.profile}" uses a local key, which owns its secrets: store them with ${CLI_NAME29} secrets put NAME --stdin instead of requesting them.`,
38154
+ `Profile "${ctx.profile}" uses a local key, which owns its secrets: store them with ${CLI_NAME30} secrets put NAME --stdin instead of requesting them.`,
37807
38155
  ExitCode.USAGE_ERROR
37808
38156
  );
37809
38157
  }
@@ -37926,7 +38274,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
37926
38274
 
37927
38275
  // src/commands/share.ts
37928
38276
  import { Option } from "commander";
37929
- import { CLI_NAME as CLI_NAME30 } from "@tinycloud/operations/cli-name";
38277
+ import { CLI_NAME as CLI_NAME31 } from "@tinycloud/operations/cli-name";
37930
38278
  import { ProfileLockTimeoutError as ProfileLockTimeoutError3 } from "@tinycloud/operations/state";
37931
38279
  init_errors();
37932
38280
 
@@ -39320,7 +39668,7 @@ function htmlFolderNotice(bundle) {
39320
39668
  }
39321
39669
 
39322
39670
  // src/commands/share.ts
39323
- var SHARE_ORIGIN = "https://share.tinycloud.xyz";
39671
+ var SHARE_ORIGIN2 = "https://share.tinycloud.xyz";
39324
39672
  var NOTIFY_WINDOW_MESSAGE = "Share notification authorization expired (at share expiry or 5 minutes after publication); publish a new share and invite the recipient then.\n";
39325
39673
  var ONGOING_UNAVAILABLE = "Ongoing shares are not available yet; v1 shares pin a version";
39326
39674
  function artifactRequest(options, actions, inputCount) {
@@ -39385,7 +39733,7 @@ function shareCliError(error, operation = "publish") {
39385
39733
  "SHARE_HISTORY_UNREADABLE",
39386
39734
  "sender history cannot be read with the current session key",
39387
39735
  1,
39388
- { hint: `Run \`${CLI_NAME30} share history recover\` to move the unreadable file aside. The old file is kept, not deleted.` }
39736
+ { hint: `Run \`${CLI_NAME31} share history recover\` to move the unreadable file aside. The old file is kept, not deleted.` }
39389
39737
  );
39390
39738
  }
39391
39739
  if (error instanceof SharePublishAuthorityError) {
@@ -39393,12 +39741,12 @@ function shareCliError(error, operation = "publish") {
39393
39741
  const profileName2 = "profileName" in failure ? failure.profileName : void 0;
39394
39742
  const localKey = "localKey" in failure && failure.localKey === true;
39395
39743
  const profileHint = profileName2 === void 0 ? "" : `--profile ${profileName2} `;
39396
- const loginHint = localKey ? `\`${CLI_NAME30} ${profileHint}auth login --method local\`` : `\`${CLI_NAME30} ${profileHint}auth login --device --manifest builtin:share-publishing\` (or \`${CLI_NAME30} ${profileHint}enable share\`)`;
39744
+ const loginHint = localKey ? `\`${CLI_NAME31} ${profileHint}auth login --method local\`` : `\`${CLI_NAME31} ${profileHint}auth login --device --manifest builtin:share-publishing\` (or \`${CLI_NAME31} ${profileHint}enable share\`)`;
39397
39745
  if (failure.kind === "caveated-session") {
39398
39746
  const holder = profileName2 === void 0 ? "this session" : `profile ${profileName2}'s session`;
39399
39747
  return new CLIError(
39400
39748
  "PERMISSION_DENIED",
39401
- `${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_NAME30} init --name publisher --key-only && ${CLI_NAME30} --profile publisher enable share\``,
39749
+ `${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_NAME31} init --name publisher --key-only && ${CLI_NAME31} --profile publisher enable share\``,
39402
39750
  5
39403
39751
  );
39404
39752
  }
@@ -39484,7 +39832,7 @@ function shareCliError(error, operation = "publish") {
39484
39832
  if (nodeCode === "ENOENT") return new CLIError("INVALID_ARGUMENT", "share input was not found", 2);
39485
39833
  if (nodeCode === "EISDIR") return new CLIError("INVALID_ARGUMENT", "share input must be a Markdown file", 2);
39486
39834
  const mapped = typeof nodeCode === "string" && known[nodeCode] !== void 0 ? known[nodeCode] : known[message];
39487
- const classified = mapped === void 0 ? classifyShareFailure(error, `\`${CLI_NAME30} auth login\``) : void 0;
39835
+ const classified = mapped === void 0 ? classifyShareFailure(error, `\`${CLI_NAME31} auth login\``) : void 0;
39488
39836
  if (classified !== void 0) return Object.assign(new CLIError(classified.code, classified.reason, classified.exitCode), { cause: error });
39489
39837
  if (error instanceof TypeError) return new CLIError("INVALID_ARGUMENT", "share input is invalid", 2);
39490
39838
  return new CLIError(mapped?.code ?? "INVALID_ARGUMENT", mapped ? mapped.code : "share operation failed", mapped?.exit ?? 2);
@@ -39573,7 +39921,7 @@ function assertAggregateInputLimit(inputs, maxBytes) {
39573
39921
  }
39574
39922
  function registerShareCommand(program) {
39575
39923
  const share = program.command("share").description("Publish and consume TinyCloud Share links");
39576
- share.command("publish <files...>").description("Publish one or more bounded files as a Share, or a folder with an index.html as one self-contained page (anyone links only)").option("--name <filename>", "Filename for stdin input, or the published .html name of a folder").option("--to <target>", "Share target", "anyone").option("--notify", "Email an exact-email recipient, or a domain mailbox on node 1.17.3+").option("--notify-to <address>", "Mailbox to invite for --to domain:<name> --notify (node 1.17.3+)").option("--expires <duration>", "Share lifetime").option("--max-bytes <bytes>", "Bound input bytes").option("--media-type <type>", "Media type for a single input").option("--action <actions...>", "Addressed permission: read, list, or edit").option("--prefix", "Publish multiple inputs beneath one addressed prefix").option("--binary", "Allow non-UTF-8 bearer content").option("--artifact <id>", 'Publish as the next write-once version of an artifact ("new" starts one); the share is pinned to that version').addOption(new Option("--ongoing").hideHelp()).option("--note <text>", "Note stored with the artifact version").option("--json", "Print versioned redacted JSON").option("--viewer-origin <origin>", "Canonical HTTPS viewer origin", SHARE_ORIGIN).action(async (files, options, command) => {
39924
+ share.command("publish <files...>").description("Publish one or more bounded files as a Share, or a folder with an index.html as one self-contained page (anyone links only)").option("--name <filename>", "Filename for stdin input, or the published .html name of a folder").option("--to <target>", "Share target", "anyone").option("--notify", "Email an exact-email recipient, or a domain mailbox on node 1.17.3+").option("--notify-to <address>", "Mailbox to invite for --to domain:<name> --notify (node 1.17.3+)").option("--expires <duration>", "Share lifetime").option("--max-bytes <bytes>", "Bound input bytes").option("--media-type <type>", "Media type for a single input").option("--action <actions...>", "Addressed permission: read, list, or edit").option("--prefix", "Publish multiple inputs beneath one addressed prefix").option("--binary", "Allow non-UTF-8 bearer content").option("--artifact <id>", 'Publish as the next write-once version of an artifact ("new" starts one); the share is pinned to that version').addOption(new Option("--ongoing").hideHelp()).option("--note <text>", "Note stored with the artifact version").option("--json", "Print versioned redacted JSON").option("--viewer-origin <origin>", "Canonical HTTPS viewer origin", SHARE_ORIGIN2).action(async (files, options, command) => {
39577
39925
  try {
39578
39926
  const json = jsonOutput(options, command);
39579
39927
  if (options.ongoing === true) throw new CLIError("UNSUPPORTED_TARGET", ONGOING_UNAVAILABLE, 2);
@@ -39664,7 +40012,7 @@ function registerShareCommand(program) {
39664
40012
  return handleError(shareCliError(error));
39665
40013
  }
39666
40014
  });
39667
- share.command("inspect [url]").description("Verify a share link and print safe metadata").option("--stdin", "Read the complete URL from stdin").option("--json", "Print versioned redacted JSON").option("--viewer-origin <origin>", "Require this canonical Share origin", SHARE_ORIGIN).action(async (url, options, command) => {
40015
+ share.command("inspect [url]").description("Verify a share link and print safe metadata").option("--stdin", "Read the complete URL from stdin").option("--json", "Print versioned redacted JSON").option("--viewer-origin <origin>", "Require this canonical Share origin", SHARE_ORIGIN2).action(async (url, options, command) => {
39668
40016
  try {
39669
40017
  const json = jsonOutput(options, command);
39670
40018
  const result = await inspectShareInputOnce(url, options.stdin === true, options.viewerOrigin);
@@ -39709,7 +40057,7 @@ function registerShareCommand(program) {
39709
40057
  return handleError(shareCliError(error));
39710
40058
  }
39711
40059
  });
39712
- share.command("receive [url]").description("Verify and receive a share link").option("--stdin", "Read the complete URL from stdin").option("--output <directory>", "Create the file in this directory").option("--stdout", "Write verified plaintext bytes to stdout").option("--force", "Allow replacing an existing non-symlink output").option("--max-bytes <bytes>", "Bound received content bytes").option("--json", "Print versioned redacted JSON").option("--viewer-origin <origin>", "Require this canonical Share origin", SHARE_ORIGIN).action(async (url, options, command) => {
40060
+ share.command("receive [url]").description("Verify and receive a share link").option("--stdin", "Read the complete URL from stdin").option("--output <directory>", "Create the file in this directory").option("--stdout", "Write verified plaintext bytes to stdout").option("--force", "Allow replacing an existing non-symlink output").option("--max-bytes <bytes>", "Bound received content bytes").option("--json", "Print versioned redacted JSON").option("--viewer-origin <origin>", "Require this canonical Share origin", SHARE_ORIGIN2).action(async (url, options, command) => {
39713
40061
  try {
39714
40062
  const json = jsonOutput(options, command);
39715
40063
  if (options.stdout && json) throw new CLIError("INVALID_ARGUMENT", "--stdout and --json are mutually exclusive", 2);
@@ -39848,7 +40196,7 @@ init_constants();
39848
40196
  init_sdk();
39849
40197
  init_host();
39850
40198
  init_theme();
39851
- import { CLI_NAME as CLI_NAME31 } from "@tinycloud/operations/cli-name";
40199
+ import { CLI_NAME as CLI_NAME32 } from "@tinycloud/operations/cli-name";
39852
40200
  import { ACCOUNT_REGISTRY_SPACE } from "@tinycloud/node-sdk";
39853
40201
  import { randomBytes as randomBytes9 } from "crypto";
39854
40202
  import { mkdir as mkdir7, writeFile as writeFile4 } from "fs/promises";
@@ -39895,7 +40243,7 @@ function registerSpaceCommand(program) {
39895
40243
  } else {
39896
40244
  const profile = await ProfileManager.getProfile(ctx.profile);
39897
40245
  const cap = "tinycloud.kv:account:spaces/:get,list";
39898
- const hint = resolveProfilePosture(profile) === "delegate-session" ? `Ask the space owner to grant it. Create the request with: ${CLI_NAME31} --profile ${ctx.profile} auth request --cap ${cap} --emit` : `Grant it to this session with: ${CLI_NAME31} --profile ${ctx.profile} auth request --grant --cap ${cap}. The default OpenKey owner sign-in covers only the default space, so it includes neither.`;
40246
+ const hint = resolveProfilePosture(profile) === "delegate-session" ? `Ask the space owner to grant it. Create the request with: ${CLI_NAME32} --profile ${ctx.profile} auth request --cap ${cap} --emit` : `Grant it to this session with: ${CLI_NAME32} --profile ${ctx.profile} auth request --grant --cap ${cap}. The default OpenKey owner sign-in covers only the default space, so it includes neither.`;
39899
40247
  throw new CLIError(
39900
40248
  "PERMISSION_DENIED",
39901
40249
  `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.`,
@@ -39932,10 +40280,10 @@ function registerSpaceCommand(program) {
39932
40280
 
39933
40281
  A delegate cannot host a space \u2014 only its owner (root authority) can. This
39934
40282
  emits a tinycloud.host.request artifact naming the space and its owner so you
39935
- can hand it to the owner; they run \`${CLI_NAME31} space host <name>\` and confirm.
40283
+ can hand it to the owner; they run \`${CLI_NAME32} space host <name>\` and confirm.
39936
40284
 
39937
40285
  If you ARE the owner of the resolved space, this refuses and tells you to host
39938
- it directly with \`${CLI_NAME31} space host <name>\` (no request needed).
40286
+ it directly with \`${CLI_NAME32} space host <name>\` (no request needed).
39939
40287
  `).action(async (name, options, cmd) => {
39940
40288
  try {
39941
40289
  const globalOpts = cmd.optsWithGlobals();
@@ -39946,7 +40294,7 @@ it directly with \`${CLI_NAME31} space host <name>\` (no request needed).
39946
40294
  if (await isRootAuthority(spaceId, ctx.profile)) {
39947
40295
  throw new CLIError(
39948
40296
  "ALREADY_ROOT_AUTHORITY",
39949
- `You are the owner of ${spaceId}. Host it directly: ${CLI_NAME31} space host ${spaceName}`,
40297
+ `You are the owner of ${spaceId}. Host it directly: ${CLI_NAME32} space host ${spaceName}`,
39950
40298
  ExitCode.USAGE_ERROR
39951
40299
  );
39952
40300
  }
@@ -40032,7 +40380,7 @@ init_sdk();
40032
40380
  init_space();
40033
40381
  init_host();
40034
40382
  init_theme();
40035
- import { CLI_NAME as CLI_NAME32 } from "@tinycloud/operations/cli-name";
40383
+ import { CLI_NAME as CLI_NAME33 } from "@tinycloud/operations/cli-name";
40036
40384
  import { writeFile as writeFile5 } from "fs/promises";
40037
40385
  import { resolve as resolve8 } from "path";
40038
40386
  async function dbHandle(node, dbName, spaceInput, profileName2) {
@@ -40056,12 +40404,12 @@ database for simple apps, or pass --db to target a named database. Pass
40056
40404
  --space to target a non-primary space (e.g. the manifest "applications" space).
40057
40405
 
40058
40406
  Common workflows:
40059
- $ ${CLI_NAME32} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
40060
- $ ${CLI_NAME32} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["ship docs"]'
40061
- $ ${CLI_NAME32} sql query "SELECT id, body FROM notes ORDER BY id"
40062
- $ ${CLI_NAME32} sql query "SELECT * FROM events WHERE type = ?" --db analytics --params '["signup"]'
40063
- $ ${CLI_NAME32} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
40064
- $ ${CLI_NAME32} sql export --db analytics --output analytics.db
40407
+ $ ${CLI_NAME33} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
40408
+ $ ${CLI_NAME33} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["ship docs"]'
40409
+ $ ${CLI_NAME33} sql query "SELECT id, body FROM notes ORDER BY id"
40410
+ $ ${CLI_NAME33} sql query "SELECT * FROM events WHERE type = ?" --db analytics --params '["signup"]'
40411
+ $ ${CLI_NAME33} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
40412
+ $ ${CLI_NAME33} sql export --db analytics --output analytics.db
40065
40413
 
40066
40414
  Commands:
40067
40415
  query Read rows with SELECT statements
@@ -40078,10 +40426,10 @@ Tips:
40078
40426
  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", `
40079
40427
 
40080
40428
  Examples:
40081
- $ ${CLI_NAME32} sql query "SELECT * FROM notes ORDER BY id"
40082
- $ ${CLI_NAME32} sql query "SELECT * FROM notes WHERE id = ?" --params '[42]'
40083
- $ ${CLI_NAME32} sql query "SELECT count(*) AS total FROM events" --db analytics --json
40084
- $ ${CLI_NAME32} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
40429
+ $ ${CLI_NAME33} sql query "SELECT * FROM notes ORDER BY id"
40430
+ $ ${CLI_NAME33} sql query "SELECT * FROM notes WHERE id = ?" --params '[42]'
40431
+ $ ${CLI_NAME33} sql query "SELECT count(*) AS total FROM events" --db analytics --json
40432
+ $ ${CLI_NAME33} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
40085
40433
 
40086
40434
  Output:
40087
40435
  Human output is formatted as a table. Piped output or --json returns
@@ -40122,11 +40470,11 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
40122
40470
  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", `
40123
40471
 
40124
40472
  Examples:
40125
- $ ${CLI_NAME32} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
40126
- $ ${CLI_NAME32} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["first note"]'
40127
- $ ${CLI_NAME32} sql execute "UPDATE notes SET body = ? WHERE id = ?" --params '["edited", 1]'
40128
- $ ${CLI_NAME32} sql execute "DROP TABLE old_notes" --db archive
40129
- $ ${CLI_NAME32} sql execute "DELETE FROM conversation WHERE id = ?" --space applications --db xyz.tinycloud.listen/conversations --params '["abc"]'
40473
+ $ ${CLI_NAME33} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
40474
+ $ ${CLI_NAME33} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["first note"]'
40475
+ $ ${CLI_NAME33} sql execute "UPDATE notes SET body = ? WHERE id = ?" --params '["edited", 1]'
40476
+ $ ${CLI_NAME33} sql execute "DROP TABLE old_notes" --db archive
40477
+ $ ${CLI_NAME33} sql execute "DELETE FROM conversation WHERE id = ?" --space applications --db xyz.tinycloud.listen/conversations --params '["abc"]'
40130
40478
 
40131
40479
  Output:
40132
40480
  Returns JSON with the changed row count and last inserted row id when available.
@@ -40155,9 +40503,9 @@ Output:
40155
40503
  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", `
40156
40504
 
40157
40505
  Examples:
40158
- $ ${CLI_NAME32} sql export
40159
- $ ${CLI_NAME32} sql export --db analytics --output analytics.db
40160
- $ ${CLI_NAME32} sql export --space applications --db xyz.tinycloud.listen/conversations --output listen.db
40506
+ $ ${CLI_NAME33} sql export
40507
+ $ ${CLI_NAME33} sql export --db analytics --output analytics.db
40508
+ $ ${CLI_NAME33} sql export --space applications --db xyz.tinycloud.listen/conversations --output listen.db
40161
40509
 
40162
40510
  Output:
40163
40511
  Writes the database file locally and returns JSON with the path and size.
@@ -40190,19 +40538,19 @@ Output:
40190
40538
  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", `
40191
40539
 
40192
40540
  Examples:
40193
- $ ${CLI_NAME32} sql copy --from-db com.tinycloud.conversation-sync/conversations \\
40541
+ $ ${CLI_NAME33} sql copy --from-db com.tinycloud.conversation-sync/conversations \\
40194
40542
  --to-db xyz.tinycloud.listen/conversations \\
40195
40543
  --space applications --dry-run
40196
- $ ${CLI_NAME32} sql copy --from-space applications --from-db com.foo/data \\
40544
+ $ ${CLI_NAME33} sql copy --from-space applications --from-db com.foo/data \\
40197
40545
  --to-space applications --to-db com.bar/data \\
40198
40546
  --table conversation --table participant
40199
40547
 
40200
40548
  Notes:
40201
40549
  - Refuses to run when (resolved space, db) is identical for source and destination.
40202
- - Does NOT create destination tables. Run the target app once (or use \`${CLI_NAME32} sql execute\`)
40550
+ - Does NOT create destination tables. Run the target app once (or use \`${CLI_NAME33} sql execute\`)
40203
40551
  to materialize the schema before copying.
40204
40552
  - One row at a time; suitable for small/medium datasets. Large copies should
40205
- use \`${CLI_NAME32} sql export\` + bulk import.
40553
+ use \`${CLI_NAME33} sql export\` + bulk import.
40206
40554
  - Authorization: the active session/delegation must cover sql/read on source
40207
40555
  AND sql/write on destination. Otherwise the relevant operation will fail.
40208
40556
  `).action(async (options, cmd) => {
@@ -40302,7 +40650,7 @@ init_errors();
40302
40650
  init_formatter();
40303
40651
  init_theme();
40304
40652
  init_permissions();
40305
- import { CLI_NAME as CLI_NAME33 } from "@tinycloud/operations/cli-name";
40653
+ import { CLI_NAME as CLI_NAME34 } from "@tinycloud/operations/cli-name";
40306
40654
  import {
40307
40655
  NodeWasmBindings as NodeWasmBindings2
40308
40656
  } from "@tinycloud/node-sdk";
@@ -40576,7 +40924,7 @@ function formatStatus(summary) {
40576
40924
  lines.push(`Default profile: ${theme.value(summary.defaultProfile)}`);
40577
40925
  lines.push("");
40578
40926
  if (summary.profiles.length === 0) {
40579
- lines.push(theme.muted(`No profiles configured. Run: ${CLI_NAME33} init`));
40927
+ lines.push(theme.muted(`No profiles configured. Run: ${CLI_NAME34} init`));
40580
40928
  return `${lines.join("\n")}
40581
40929
  `;
40582
40930
  }
@@ -41075,6 +41423,7 @@ function registerTinyCloudCommands(program) {
41075
41423
  registerDuckdbCommand(program);
41076
41424
  registerManifestCommand(program);
41077
41425
  registerKnowledgeCommand(program);
41426
+ registerAppsCommand(program);
41078
41427
  registerUpgradeCommand(program);
41079
41428
  registerStatusCommand(program);
41080
41429
  registerAccountCommand(program);