@tinycloud/cli 1.3.0-beta.53 → 1.3.0-beta.55
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.
- package/README.md +9 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +477 -128
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +7 -7
- package/skills/tc-cli/AUTH.md +1 -1
- package/skills/tc-cli/REFERENCE.md +1 -1
- package/skills/tc-cli/SKILL.md +17 -4
package/dist/legacy-entry.js
CHANGED
|
@@ -599,7 +599,7 @@ var MANAGE_STORAGE_URL, MAX_CAUSE_DEPTH;
|
|
|
599
599
|
var init_storage2 = __esm({
|
|
600
600
|
"src/output/storage.ts"() {
|
|
601
601
|
"use strict";
|
|
602
|
-
MANAGE_STORAGE_URL = "https://account.tinycloud.xyz/
|
|
602
|
+
MANAGE_STORAGE_URL = "https://account.tinycloud.xyz/storage";
|
|
603
603
|
MAX_CAUSE_DEPTH = 4;
|
|
604
604
|
}
|
|
605
605
|
});
|
|
@@ -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:
|
|
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:
|
|
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
|
|
11086
|
+
let errorMessage3 = "";
|
|
11087
11087
|
const maps = errorMaps.filter((m) => !!m).slice().reverse();
|
|
11088
11088
|
for (const map of maps) {
|
|
11089
|
-
|
|
11089
|
+
errorMessage3 = map(fullIssue, { data, defaultError: errorMessage3 }).message;
|
|
11090
11090
|
}
|
|
11091
11091
|
return {
|
|
11092
11092
|
...issueData,
|
|
11093
11093
|
path: fullPath,
|
|
11094
|
-
message:
|
|
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
|
|
16615
|
+
let errorMessage3 = "";
|
|
16616
16616
|
const maps = errorMaps.filter((m) => !!m).slice().reverse();
|
|
16617
16617
|
for (const map of maps) {
|
|
16618
|
-
|
|
16618
|
+
errorMessage3 = map(fullIssue, { data, defaultError: errorMessage3 }).message;
|
|
16619
16619
|
}
|
|
16620
16620
|
return {
|
|
16621
16621
|
...issueData,
|
|
16622
16622
|
path: fullPath,
|
|
16623
|
-
message:
|
|
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
|
|
23661
|
+
let errorMessage3 = "";
|
|
23662
23662
|
const maps = errorMaps.filter((m) => !!m).slice().reverse();
|
|
23663
23663
|
for (const map of maps) {
|
|
23664
|
-
|
|
23664
|
+
errorMessage3 = map(fullIssue, { data, defaultError: errorMessage3 }).message;
|
|
23665
23665
|
}
|
|
23666
23666
|
return {
|
|
23667
23667
|
...issueData,
|
|
23668
23668
|
path: fullPath,
|
|
23669
|
-
message:
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
31192
|
-
"tinycloud.sql/
|
|
31193
|
-
"tinycloud.sql/
|
|
31194
|
-
"tinycloud.sql
|
|
31195
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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) =>
|
|
31404
|
-
lines.push("
|
|
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
|
|
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
|
|
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 \`${
|
|
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
|
|
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: ${
|
|
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 = `${
|
|
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 \`${
|
|
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
|
|
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
|
-
$ ${
|
|
34980
|
-
$ ${
|
|
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 ${
|
|
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: ${
|
|
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
|
|
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
|
-
$ ${
|
|
35326
|
-
$ ${
|
|
35327
|
-
$ ${
|
|
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. \`${
|
|
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
|
|
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 ${
|
|
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 ${
|
|
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: ${
|
|
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: ${
|
|
35770
|
-
` owner: ${
|
|
35771
|
-
` device: ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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
|
|
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
|
|
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 = `${
|
|
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: ${
|
|
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 ${
|
|
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 ${
|
|
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 \`${
|
|
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 = `${
|
|
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: ${
|
|
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: ${
|
|
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: `${
|
|
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 ? `${
|
|
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 ${
|
|
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
|
|
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
|
|
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 \`${
|
|
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 ? `\`${
|
|
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): \`${
|
|
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, `\`${
|
|
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",
|
|
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",
|
|
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",
|
|
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
|
|
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: ${
|
|
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 \`${
|
|
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 \`${
|
|
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: ${
|
|
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
|
|
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
|
-
$ ${
|
|
40060
|
-
$ ${
|
|
40061
|
-
$ ${
|
|
40062
|
-
$ ${
|
|
40063
|
-
$ ${
|
|
40064
|
-
$ ${
|
|
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
|
-
$ ${
|
|
40082
|
-
$ ${
|
|
40083
|
-
$ ${
|
|
40084
|
-
$ ${
|
|
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
|
-
$ ${
|
|
40126
|
-
$ ${
|
|
40127
|
-
$ ${
|
|
40128
|
-
$ ${
|
|
40129
|
-
$ ${
|
|
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
|
-
$ ${
|
|
40159
|
-
$ ${
|
|
40160
|
-
$ ${
|
|
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
|
-
$ ${
|
|
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
|
-
$ ${
|
|
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 \`${
|
|
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 \`${
|
|
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
|
|
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: ${
|
|
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);
|