@tinycloud/cli 1.3.0-beta.37 → 1.3.0-beta.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/courier-entry.js +1261 -79
- package/dist/courier-entry.js.map +1 -1
- package/dist/index.js +44 -18
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +700 -162
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +4 -4
- package/skills/tc-cli/AUTH.md +2 -0
- package/skills/tc-cli/REFERENCE.md +3 -1
- package/skills/tc-cli/SKILL.md +30 -0
package/dist/legacy-entry.js
CHANGED
|
@@ -282,6 +282,40 @@ var init_replication_registry = __esm({
|
|
|
282
282
|
}
|
|
283
283
|
});
|
|
284
284
|
|
|
285
|
+
// src/output/safe-text.ts
|
|
286
|
+
function escape(match) {
|
|
287
|
+
return `\\u{${match.codePointAt(0).toString(16)}}`;
|
|
288
|
+
}
|
|
289
|
+
function safeBlock(text) {
|
|
290
|
+
return text.replace(UNSAFE_BLOCK, escape);
|
|
291
|
+
}
|
|
292
|
+
function safeInline(text) {
|
|
293
|
+
return text.replace(UNSAFE_INLINE, escape);
|
|
294
|
+
}
|
|
295
|
+
function safeClipped(text, max = ECHO_FIELD_MAX) {
|
|
296
|
+
const escaped = Array.from(safeInline(text));
|
|
297
|
+
return escaped.length <= max ? escaped.join("") : `${escaped.slice(0, max).join("")}\u2026`;
|
|
298
|
+
}
|
|
299
|
+
function safeClippedList(items) {
|
|
300
|
+
const shown = items.slice(0, ECHO_LIST_MAX).map((item) => `"${safeClipped(item)}"`).join(", ");
|
|
301
|
+
return items.length > ECHO_LIST_MAX ? `${shown} \u2026 and ${items.length - ECHO_LIST_MAX} more` : shown;
|
|
302
|
+
}
|
|
303
|
+
function safeJsonStringify(value, space) {
|
|
304
|
+
const text = JSON.stringify(value, null, space);
|
|
305
|
+
return text === void 0 ? text : text.replace(JSON_UNSAFE, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
306
|
+
}
|
|
307
|
+
var UNSAFE_BLOCK, UNSAFE_INLINE, ECHO_FIELD_MAX, ECHO_LIST_MAX, JSON_UNSAFE;
|
|
308
|
+
var init_safe_text = __esm({
|
|
309
|
+
"src/output/safe-text.ts"() {
|
|
310
|
+
"use strict";
|
|
311
|
+
UNSAFE_BLOCK = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f\u061c\u200b-\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069\ufeff]/g;
|
|
312
|
+
UNSAFE_INLINE = /[\u0000-\u001f\u007f-\u009f\u061c\u200b-\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069\ufeff]/g;
|
|
313
|
+
ECHO_FIELD_MAX = 80;
|
|
314
|
+
ECHO_LIST_MAX = 5;
|
|
315
|
+
JSON_UNSAFE = /[\u007f-\u009f\u061c\u200b-\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069\ufeff]/g;
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
285
319
|
// src/output/theme.ts
|
|
286
320
|
import chalk from "chalk";
|
|
287
321
|
var TC_PALETTE, theme;
|
|
@@ -364,7 +398,7 @@ var init_warnings = __esm({
|
|
|
364
398
|
import ora from "ora";
|
|
365
399
|
import { CLI_NAME } from "@tinycloud/operations/cli-name";
|
|
366
400
|
function outputJson(data) {
|
|
367
|
-
process.stdout.write(
|
|
401
|
+
process.stdout.write(safeJsonStringify(data, 2) + "\n");
|
|
368
402
|
}
|
|
369
403
|
function warningLine(warnings) {
|
|
370
404
|
const which = warnings.map((warning) => `${warning.grantCid ?? "unidentified"}: ${warning.reason}`).join(", ");
|
|
@@ -375,12 +409,12 @@ function outputError(code4, message, hint, details = {}) {
|
|
|
375
409
|
const { meta, warnings = [], setup, recovery, progress } = details;
|
|
376
410
|
if (!shouldOutputJson()) {
|
|
377
411
|
process.stderr.write(
|
|
378
|
-
`${theme.error("\u2717")} ${theme.label(code4)}: ${message}
|
|
412
|
+
`${theme.error("\u2717")} ${theme.label(safeInline(code4))}: ${safeBlock(message)}
|
|
379
413
|
`
|
|
380
414
|
);
|
|
381
415
|
if (hint) {
|
|
382
416
|
for (const line of hint.split("\n")) {
|
|
383
|
-
process.stderr.write(` ${theme.hint(line)}
|
|
417
|
+
process.stderr.write(` ${theme.hint(safeBlock(line))}
|
|
384
418
|
`);
|
|
385
419
|
}
|
|
386
420
|
}
|
|
@@ -396,13 +430,13 @@ function outputError(code4, message, hint, details = {}) {
|
|
|
396
430
|
if (progress) payload.error.progress = progress;
|
|
397
431
|
if (meta) payload.error.meta = meta;
|
|
398
432
|
if (warnings.length > 0) payload.error.warnings = warnings;
|
|
399
|
-
process.stderr.write(
|
|
433
|
+
process.stderr.write(safeJsonStringify(payload, 2) + "\n");
|
|
400
434
|
}
|
|
401
435
|
}
|
|
402
436
|
function outputWarnings(warnings) {
|
|
403
437
|
const combined = mergeWarnings(drainWarnings(), operationWarnings(warnings));
|
|
404
438
|
if (combined.length === 0) return;
|
|
405
|
-
process.stderr.write(shouldOutputJson() ? `${
|
|
439
|
+
process.stderr.write(shouldOutputJson() ? `${safeJsonStringify({ warnings: combined })}
|
|
406
440
|
` : `${theme.warn("!")} ${warningLine(combined)}
|
|
407
441
|
`);
|
|
408
442
|
}
|
|
@@ -480,6 +514,7 @@ var WARNING_CODE, WARNING_REASON, DELEGATION_CID, JSON_REQUESTED;
|
|
|
480
514
|
var init_formatter = __esm({
|
|
481
515
|
"src/output/formatter.ts"() {
|
|
482
516
|
"use strict";
|
|
517
|
+
init_safe_text();
|
|
483
518
|
init_theme();
|
|
484
519
|
init_warnings();
|
|
485
520
|
WARNING_CODE = /^[A-Z][A-Z_]{0,63}$/;
|
|
@@ -882,6 +917,13 @@ var init_types = __esm({
|
|
|
882
917
|
|
|
883
918
|
// src/lib/space.ts
|
|
884
919
|
import { CLI_NAME as CLI_NAME3 } from "@tinycloud/operations/cli-name";
|
|
920
|
+
function invalidSpaceError(input) {
|
|
921
|
+
return new CLIError(
|
|
922
|
+
"INVALID_SPACE",
|
|
923
|
+
`Invalid space "${safeClipped(input)}". Use a short name ([A-Za-z0-9_-]) or a full tinycloud:... URI.`,
|
|
924
|
+
ExitCode.USAGE_ERROR
|
|
925
|
+
);
|
|
926
|
+
}
|
|
885
927
|
function canonicalizeAddress(address) {
|
|
886
928
|
const trimmed = address.trim();
|
|
887
929
|
return trimmed.startsWith("0x") ? `0x${trimmed.slice(2).toLowerCase()}` : trimmed.toLowerCase();
|
|
@@ -945,22 +987,10 @@ async function resolveSpaceUri(input, profileName2, options = {}) {
|
|
|
945
987
|
if (!effective) return void 0;
|
|
946
988
|
if (effective.startsWith("tinycloud:")) {
|
|
947
989
|
const parsed = parseSpaceUri(effective);
|
|
948
|
-
if (!parsed)
|
|
949
|
-
throw new CLIError(
|
|
950
|
-
"INVALID_SPACE",
|
|
951
|
-
`Invalid space "${effective}". Use a short name ([A-Za-z0-9_-]) or a full tinycloud:... URI.`,
|
|
952
|
-
ExitCode.USAGE_ERROR
|
|
953
|
-
);
|
|
954
|
-
}
|
|
990
|
+
if (!parsed) throw invalidSpaceError(effective);
|
|
955
991
|
return buildSpaceUri(parsed.owner, parsed.name);
|
|
956
992
|
}
|
|
957
|
-
if (!/^[A-Za-z0-9_-]+$/.test(effective))
|
|
958
|
-
throw new CLIError(
|
|
959
|
-
"INVALID_SPACE",
|
|
960
|
-
`Invalid space "${effective}". Use a short name ([A-Za-z0-9_-]) or a full tinycloud:... URI.`,
|
|
961
|
-
ExitCode.USAGE_ERROR
|
|
962
|
-
);
|
|
963
|
-
}
|
|
993
|
+
if (!/^[A-Za-z0-9_-]+$/.test(effective)) throw invalidSpaceError(effective);
|
|
964
994
|
const session = await ProfileManager.getSession(profileName2);
|
|
965
995
|
const address = resolveAddress(profile, session);
|
|
966
996
|
const chainId = resolveChainId(profile, session);
|
|
@@ -971,6 +1001,7 @@ var init_space = __esm({
|
|
|
971
1001
|
"use strict";
|
|
972
1002
|
init_errors();
|
|
973
1003
|
init_constants();
|
|
1004
|
+
init_safe_text();
|
|
974
1005
|
init_profiles();
|
|
975
1006
|
}
|
|
976
1007
|
});
|
|
@@ -10362,7 +10393,7 @@ function formatSpace(space) {
|
|
|
10362
10393
|
init_profile_gate();
|
|
10363
10394
|
init_dist();
|
|
10364
10395
|
init_profiles();
|
|
10365
|
-
import { CLI_NAME as
|
|
10396
|
+
import { CLI_NAME as CLI_NAME16 } from "@tinycloud/operations/cli-name";
|
|
10366
10397
|
import { get as httpGet } from "http";
|
|
10367
10398
|
import { get as httpsGet } from "https";
|
|
10368
10399
|
import { spawn } from "child_process";
|
|
@@ -12347,7 +12378,7 @@ async function cidBoundOwnerSpace(record3, ownerDid, node, host) {
|
|
|
12347
12378
|
try {
|
|
12348
12379
|
const signed = parseSignedCompactUcanAttenuation2(withoutBearer2(authorization), delegation.cid);
|
|
12349
12380
|
for (const resource of Object.keys(signed.payload.att)) {
|
|
12350
|
-
const space = /^(tinycloud:[^/]+)\/[^/]
|
|
12381
|
+
const space = /^(tinycloud:[^/]+)\/[^/]+(?:\/.*)?$/.exec(resource)?.[1];
|
|
12351
12382
|
if (space && ownerDidFromSpace(space) && normalizeDid(ownerDidFromSpace(space)) === normalizeDid(ownerDid)) {
|
|
12352
12383
|
return space;
|
|
12353
12384
|
}
|
|
@@ -12750,6 +12781,290 @@ function approvalNotices(groups) {
|
|
|
12750
12781
|
};
|
|
12751
12782
|
}
|
|
12752
12783
|
|
|
12784
|
+
// src/lib/apps-agent-preset.ts
|
|
12785
|
+
init_constants();
|
|
12786
|
+
init_errors();
|
|
12787
|
+
init_safe_text();
|
|
12788
|
+
init_raw_encryption();
|
|
12789
|
+
init_space();
|
|
12790
|
+
import { DEFAULT_EXPIRY as DEFAULT_EXPIRY2, parseExpiry as parseExpiry3, SERVICE_LONG_TO_SHORT as SERVICE_LONG_TO_SHORT3, VAULT_PERMISSION_SERVICE as VAULT_PERMISSION_SERVICE2 } from "@tinycloud/sdk-core";
|
|
12791
|
+
import { CLI_NAME as CLI_NAME15 } from "@tinycloud/operations/cli-name";
|
|
12792
|
+
var APPS_AGENT_PRESET = "apps-agent";
|
|
12793
|
+
var APPS_AGENT_SPACE = "applications";
|
|
12794
|
+
var APPS_AGENT_EXPIRY_DAYS = 7;
|
|
12795
|
+
var APPS_AGENT_EXPIRY = `${APPS_AGENT_EXPIRY_DAYS}d`;
|
|
12796
|
+
var APPS_AGENT_MAX_APPS = 5;
|
|
12797
|
+
var DAY_MS2 = 864e5;
|
|
12798
|
+
var APPS_AGENT_VARIANTS = {
|
|
12799
|
+
read: {
|
|
12800
|
+
grants: [
|
|
12801
|
+
{ service: "tinycloud.kv", actions: ["tinycloud.kv/get", "tinycloud.kv/list"] },
|
|
12802
|
+
{ service: "tinycloud.sql", actions: ["tinycloud.sql/read"] }
|
|
12803
|
+
],
|
|
12804
|
+
perAppOnly: false,
|
|
12805
|
+
changesStructure: false
|
|
12806
|
+
},
|
|
12807
|
+
write: {
|
|
12808
|
+
grants: [
|
|
12809
|
+
{ service: "tinycloud.kv", actions: ["tinycloud.kv/get", "tinycloud.kv/list", "tinycloud.kv/put"] },
|
|
12810
|
+
{ service: "tinycloud.sql", actions: ["tinycloud.sql/read", "tinycloud.sql/write"] }
|
|
12811
|
+
],
|
|
12812
|
+
perAppOnly: true,
|
|
12813
|
+
changesStructure: true
|
|
12814
|
+
}
|
|
12815
|
+
};
|
|
12816
|
+
var ACTION_WORDS = {
|
|
12817
|
+
"tinycloud.kv/get": "read files",
|
|
12818
|
+
"tinycloud.kv/list": "list files",
|
|
12819
|
+
"tinycloud.kv/put": "create and overwrite files",
|
|
12820
|
+
"tinycloud.kv/del": "delete files",
|
|
12821
|
+
"tinycloud.kv/metadata": "read file metadata",
|
|
12822
|
+
"tinycloud.sql/read": "read table rows",
|
|
12823
|
+
"tinycloud.sql/write": "add, change and delete table rows",
|
|
12824
|
+
"tinycloud.sql/schema": "create, change and drop tables",
|
|
12825
|
+
"tinycloud.sql/admin": "do anything in the database",
|
|
12826
|
+
"tinycloud.delegation/create": "can give other people or agents access on your behalf",
|
|
12827
|
+
"tinycloud.delegation/revoke": "can cancel access you gave",
|
|
12828
|
+
"tinycloud.delegation/*": "can do everything the delegation permissions allow, including giving access on to others",
|
|
12829
|
+
"tinycloud.kv/*": "do anything with files (read, write, delete)",
|
|
12830
|
+
"tinycloud.sql/*": "do anything in the database",
|
|
12831
|
+
"tinycloud.capabilities/read": "see which permissions this key holds"
|
|
12832
|
+
};
|
|
12833
|
+
var STRUCTURE_ACTIONS = /* @__PURE__ */ new Set([
|
|
12834
|
+
"tinycloud.sql/write",
|
|
12835
|
+
"tinycloud.sql/schema",
|
|
12836
|
+
"tinycloud.sql/admin",
|
|
12837
|
+
"tinycloud.sql/*"
|
|
12838
|
+
]);
|
|
12839
|
+
var APP_ID = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
12840
|
+
var APP_ID_RULE = "an app id looks like xyz.example.app: letters, digits, dots, hyphens and underscores, no slashes or spaces";
|
|
12841
|
+
function isAppId(value) {
|
|
12842
|
+
return value.length <= 200 && APP_ID.test(value);
|
|
12843
|
+
}
|
|
12844
|
+
function appsAgentPermissions(mode, appIds = []) {
|
|
12845
|
+
const paths = appIds.length === 0 ? [""] : appIds.map((appId) => `${appId}/`);
|
|
12846
|
+
return APPS_AGENT_VARIANTS[mode].grants.flatMap(({ service, actions }) => paths.map((path) => ({ service, space: APPS_AGENT_SPACE, path, actions: [...actions] })));
|
|
12847
|
+
}
|
|
12848
|
+
function quoteApps(appIds) {
|
|
12849
|
+
return appIds.map((appId) => `\`${appId}\``).join(", ");
|
|
12850
|
+
}
|
|
12851
|
+
function lifetimePhrase(ms2) {
|
|
12852
|
+
if (ms2 >= DAY_MS2) {
|
|
12853
|
+
const days = Math.ceil(ms2 / DAY_MS2);
|
|
12854
|
+
return `${days} day${days === 1 ? "" : "s"}`;
|
|
12855
|
+
}
|
|
12856
|
+
const hours = Math.max(1, Math.ceil(ms2 / 36e5));
|
|
12857
|
+
return `${hours} hour${hours === 1 ? "" : "s"}`;
|
|
12858
|
+
}
|
|
12859
|
+
function revokeSentence(lifetimeMs2) {
|
|
12860
|
+
return `It lasts up to ${lifetimePhrase(lifetimeMs2)}; revoke it any time with \`${CLI_NAME15} delegation revoke <id>\`.`;
|
|
12861
|
+
}
|
|
12862
|
+
function appsAgentScopeNotice(mode, appIds = []) {
|
|
12863
|
+
const variant = APPS_AGENT_VARIANTS[mode];
|
|
12864
|
+
const reads = variant.grants.some(({ actions }) => actions.includes("tinycloud.kv/put")) ? "read it and add and change its files and table rows" : "list and read its files and read its table rows";
|
|
12865
|
+
const scope = appIds.length === 0 ? `ALL your installed apps' data in the \`${APPS_AGENT_SPACE}\` space` : `the data of ${quoteApps(appIds)} (everything under ${appIds.map((appId) => `\`${appId}/\``).join(", ")}) in the \`${APPS_AGENT_SPACE}\` space`;
|
|
12866
|
+
const lines = [
|
|
12867
|
+
variant.changesStructure ? `This gives the agent access to ${scope}: it can ${reads}.` : `This gives the agent READ-ONLY access to ${scope}: it can ${reads}.`
|
|
12868
|
+
];
|
|
12869
|
+
if (variant.changesStructure) {
|
|
12870
|
+
lines.push(
|
|
12871
|
+
`Because of how TinyCloud permissions work today, it can also change or delete ${appIds.length === 1 ? "this app's" : "these apps'"} tables (structure) and data.`,
|
|
12872
|
+
"It cannot touch other apps, other spaces or secrets."
|
|
12873
|
+
);
|
|
12874
|
+
} else {
|
|
12875
|
+
lines.push("It cannot create, change or delete anything, and it cannot touch other spaces or secrets.");
|
|
12876
|
+
}
|
|
12877
|
+
lines.push(revokeSentence(APPS_AGENT_EXPIRY_DAYS * DAY_MS2));
|
|
12878
|
+
return lines.join("\n");
|
|
12879
|
+
}
|
|
12880
|
+
function usage(message) {
|
|
12881
|
+
return new CLIError("INVALID_ARGUMENT", message, ExitCode.USAGE_ERROR);
|
|
12882
|
+
}
|
|
12883
|
+
function resolveAppsAgentRequest(options) {
|
|
12884
|
+
const apps = options.app ?? [];
|
|
12885
|
+
if (options.preset === void 0) {
|
|
12886
|
+
if (apps.length > 0 || options.write === true) {
|
|
12887
|
+
throw usage(`--app and --write require --preset ${APPS_AGENT_PRESET}.`);
|
|
12888
|
+
}
|
|
12889
|
+
return void 0;
|
|
12890
|
+
}
|
|
12891
|
+
if (options.preset !== APPS_AGENT_PRESET) {
|
|
12892
|
+
throw usage(`Unknown --preset "${safeClipped(options.preset)}". Available: ${APPS_AGENT_PRESET}.`);
|
|
12893
|
+
}
|
|
12894
|
+
if ((options.cap?.length ?? 0) > 0 || options.permission !== void 0 || options.manifest !== void 0 || options.expiry !== void 0) {
|
|
12895
|
+
throw usage(
|
|
12896
|
+
`--preset ${APPS_AGENT_PRESET} is the whole request (${APPS_AGENT_EXPIRY}); it cannot be combined with --cap, --permission, --manifest or --expiry.`
|
|
12897
|
+
);
|
|
12898
|
+
}
|
|
12899
|
+
if (options.device === true) {
|
|
12900
|
+
throw usage(`--preset ${APPS_AGENT_PRESET} includes SQL, which device approval cannot grant. Use browser or --paste approval.`);
|
|
12901
|
+
}
|
|
12902
|
+
const mode = options.write === true ? "write" : "read";
|
|
12903
|
+
const appIds = [...new Set(apps)];
|
|
12904
|
+
for (const appId of appIds) {
|
|
12905
|
+
if (!isAppId(appId)) throw usage(`Invalid --app "${safeClipped(appId)}": ${APP_ID_RULE}.`);
|
|
12906
|
+
}
|
|
12907
|
+
if (appIds.length > APPS_AGENT_MAX_APPS) {
|
|
12908
|
+
throw usage(`--app can be repeated at most ${APPS_AGENT_MAX_APPS} times; for more apps, ask for read-only access to the whole space (leave --app off).`);
|
|
12909
|
+
}
|
|
12910
|
+
if (APPS_AGENT_VARIANTS[mode].perAppOnly && appIds.length === 0) {
|
|
12911
|
+
throw usage("--write needs --app <app_id>: write access is only given for named apps, never for the whole space.");
|
|
12912
|
+
}
|
|
12913
|
+
return { mode, appIds };
|
|
12914
|
+
}
|
|
12915
|
+
function lifetimeMs(value) {
|
|
12916
|
+
try {
|
|
12917
|
+
if (value === void 0) return parseExpiry3(DEFAULT_EXPIRY2);
|
|
12918
|
+
if (typeof value === "number") return Number.isFinite(value) && value > 0 ? value : void 0;
|
|
12919
|
+
return /^\d+$/.test(value.trim()) ? Number(value.trim()) : parseExpiry3(value);
|
|
12920
|
+
} catch {
|
|
12921
|
+
return void 0;
|
|
12922
|
+
}
|
|
12923
|
+
}
|
|
12924
|
+
function inApplicationsSpace(entry, applicationsSpace) {
|
|
12925
|
+
return normalizePkhIdentifier(entry.space ?? "") === normalizePkhIdentifier(applicationsSpace);
|
|
12926
|
+
}
|
|
12927
|
+
function sameActions(have, want) {
|
|
12928
|
+
return have.length === want.length && want.every((action) => have.includes(action));
|
|
12929
|
+
}
|
|
12930
|
+
function matchAppsAgentRequest(requested, requestedExpiry, applicationsSpace) {
|
|
12931
|
+
if (lifetimeMs(requestedExpiry) !== APPS_AGENT_EXPIRY_DAYS * DAY_MS2) return void 0;
|
|
12932
|
+
if (requested.some((entry) => entry.expiry !== void 0 || entry.caveats !== void 0)) return void 0;
|
|
12933
|
+
const kvPaths = requested.filter((entry) => entry.service === "tinycloud.kv").map((entry) => entry.path);
|
|
12934
|
+
const appIds = [];
|
|
12935
|
+
for (const path of new Set(kvPaths)) {
|
|
12936
|
+
if (path === "") continue;
|
|
12937
|
+
if (!path.endsWith("/") || !isAppId(path.slice(0, -1))) return void 0;
|
|
12938
|
+
appIds.push(path.slice(0, -1));
|
|
12939
|
+
}
|
|
12940
|
+
if (appIds.length > APPS_AGENT_MAX_APPS || appIds.length > 0 && kvPaths.includes("")) return void 0;
|
|
12941
|
+
for (const mode of ["read", "write"]) {
|
|
12942
|
+
if (APPS_AGENT_VARIANTS[mode].perAppOnly && appIds.length === 0) continue;
|
|
12943
|
+
const expected = appsAgentPermissions(mode, appIds);
|
|
12944
|
+
const exact = requested.length === expected.length && expected.every((want) => requested.some((have) => have.service === want.service && inApplicationsSpace(have, applicationsSpace) && have.path === want.path && sameActions(have.actions, want.actions)));
|
|
12945
|
+
if (exact) return { mode, appIds };
|
|
12946
|
+
}
|
|
12947
|
+
return void 0;
|
|
12948
|
+
}
|
|
12949
|
+
var RAW_ENCRYPTION_SPACE = "encryption";
|
|
12950
|
+
function badRequest(message) {
|
|
12951
|
+
return new CLIError("INVALID_AUTH_REQUEST", `${message} Nothing was signed.`, ExitCode.USAGE_ERROR);
|
|
12952
|
+
}
|
|
12953
|
+
var DID_SHAPE = /^did:[a-z0-9]+:[A-Za-z0-9._:%#-]+$/;
|
|
12954
|
+
function assertRequestFields(request) {
|
|
12955
|
+
if (typeof request.sessionDid !== "string" || request.sessionDid.length > 300 || !DID_SHAPE.test(request.sessionDid)) {
|
|
12956
|
+
throw badRequest("The request's sessionDid is not a valid DID.");
|
|
12957
|
+
}
|
|
12958
|
+
if (request.requestedExpiry !== void 0 && (typeof request.requestedExpiry !== "string" && typeof request.requestedExpiry !== "number" || lifetimeMs(request.requestedExpiry) === void 0)) {
|
|
12959
|
+
throw badRequest("The request's requestedExpiry is not a duration such as 7d or 12h.");
|
|
12960
|
+
}
|
|
12961
|
+
}
|
|
12962
|
+
function entriesForSigning(requested) {
|
|
12963
|
+
return requested.map((entry, index) => {
|
|
12964
|
+
const label = `Entry ${index + 1} of the request`;
|
|
12965
|
+
if (typeof entry !== "object" || entry === null || typeof entry.service !== "string" || entry.service === "" || typeof entry.path !== "string" || !Array.isArray(entry.actions) || entry.actions.length === 0 || entry.actions.some((action) => typeof action !== "string" || action === "")) {
|
|
12966
|
+
throw badRequest(`${label} is malformed: it needs a service, a path and at least one action.`);
|
|
12967
|
+
}
|
|
12968
|
+
if (entry.service === VAULT_PERMISSION_SERVICE2 || entry.service === "vault") {
|
|
12969
|
+
throw badRequest(`${label} asks for the vault (\`tinycloud.vault\`). This command does not sign vault access from a request file: the signer turns it into KV access on \`vault/\` for the vault actions you listed, so the printed list would not show what is signed.`);
|
|
12970
|
+
}
|
|
12971
|
+
if (entry.space !== void 0 && (typeof entry.space !== "string" || entry.space === "")) {
|
|
12972
|
+
throw badRequest(`${label} has an empty space: name the space, or leave it out to mean your \`${APPS_AGENT_SPACE}\` space.`);
|
|
12973
|
+
}
|
|
12974
|
+
return entry.space === void 0 && !isRawEncryptionPermission(entry) ? { ...entry, space: APPS_AGENT_SPACE } : entry;
|
|
12975
|
+
});
|
|
12976
|
+
}
|
|
12977
|
+
function finalizeSigningEntries(resolved) {
|
|
12978
|
+
const merged = /* @__PURE__ */ new Map();
|
|
12979
|
+
const spaces = /* @__PURE__ */ new Set();
|
|
12980
|
+
const spellings = /* @__PURE__ */ new Map();
|
|
12981
|
+
resolved.forEach((entry, index) => {
|
|
12982
|
+
const label = `Entry ${index + 1} of the request`;
|
|
12983
|
+
if (SERVICE_LONG_TO_SHORT3[entry.service] === void 0) {
|
|
12984
|
+
throw badRequest(`${label} names the service "${safeClipped(entry.service)}", which the signer cannot sign.`);
|
|
12985
|
+
}
|
|
12986
|
+
const space = entry.space;
|
|
12987
|
+
const raw = isRawEncryptionPermission(entry);
|
|
12988
|
+
const concrete = typeof space === "string" && (space.startsWith("tinycloud:") || space === RAW_ENCRYPTION_SPACE && raw);
|
|
12989
|
+
if (!concrete) {
|
|
12990
|
+
throw badRequest(`${label} does not resolve to a concrete space (got "${safeClipped(String(space))}").`);
|
|
12991
|
+
}
|
|
12992
|
+
const normalized = normalizePkhIdentifier(space);
|
|
12993
|
+
if (!raw) spaces.add(normalized);
|
|
12994
|
+
const spelled = spellings.get(normalized) ?? space;
|
|
12995
|
+
spellings.set(normalized, spelled);
|
|
12996
|
+
const unified = { ...entry, space: spelled };
|
|
12997
|
+
const key = JSON.stringify([entry.service, normalized, entry.path, entry.expiry ?? null, entry.caveats ?? null]);
|
|
12998
|
+
const existing = merged.get(key);
|
|
12999
|
+
merged.set(key, existing === void 0 ? unified : { ...existing, actions: [...existing.actions, ...entry.actions] });
|
|
13000
|
+
});
|
|
13001
|
+
if (spaces.size > 1) {
|
|
13002
|
+
throw badRequest(`The request names ${spaces.size} different spaces (${safeClippedList([...spaces])}); a grant covers one space, so make one request per space.`);
|
|
13003
|
+
}
|
|
13004
|
+
return [...merged.values()].map((entry) => ({ ...entry, actions: [...new Set(entry.actions)].sort() }));
|
|
13005
|
+
}
|
|
13006
|
+
function describeAction(action) {
|
|
13007
|
+
if (Object.prototype.hasOwnProperty.call(ACTION_WORDS, action)) return ACTION_WORDS[action];
|
|
13008
|
+
return action.endsWith("/*") ? `ALL abilities of ${safeInline(action.slice(0, -2))} (unrecognised: grants ${safeInline(action)})` : `unrecognised: grants ${safeInline(action)}`;
|
|
13009
|
+
}
|
|
13010
|
+
function describeEntry(entry, applicationsSpace) {
|
|
13011
|
+
const inApplications = inApplicationsSpace(entry, applicationsSpace);
|
|
13012
|
+
const space = inApplications ? "your `applications` space" : `space \`${safeInline(entry.space ?? "")}\``;
|
|
13013
|
+
const wholeSpace = entry.path === "" || entry.path === "/";
|
|
13014
|
+
const where = wholeSpace ? inApplications ? `ALL apps in ${space}` : `everything in ${space}` : `\`${safeInline(entry.path)}\` in ${space}`;
|
|
13015
|
+
const known = entry.service === "tinycloud.kv" || entry.service === "tinycloud.sql" || entry.service === "tinycloud.delegation" || entry.service === "tinycloud.capabilities";
|
|
13016
|
+
const service = known ? safeInline(entry.service) : `unrecognised service ${safeInline(entry.service)}`;
|
|
13017
|
+
return `${entry.actions.map(describeAction).join(", ")} on ${where} (${service})`;
|
|
13018
|
+
}
|
|
13019
|
+
function withinPresetSet(entry, applicationsSpace) {
|
|
13020
|
+
if (!inApplicationsSpace(entry, applicationsSpace) || entry.caveats !== void 0) return false;
|
|
13021
|
+
const reads = /* @__PURE__ */ new Set();
|
|
13022
|
+
const writes = /* @__PURE__ */ new Set();
|
|
13023
|
+
for (const { grants } of Object.values(APPS_AGENT_VARIANTS)) {
|
|
13024
|
+
for (const grant of grants) {
|
|
13025
|
+
if (grant.service !== entry.service) continue;
|
|
13026
|
+
for (const action of grant.actions) (action.endsWith("/put") || action.endsWith("/write") ? writes : reads).add(action);
|
|
13027
|
+
}
|
|
13028
|
+
}
|
|
13029
|
+
const firstSegment = entry.path.split("/")[0] ?? "";
|
|
13030
|
+
const named = entry.path.includes("/") && isAppId(firstSegment);
|
|
13031
|
+
return entry.actions.length > 0 && entry.actions.every((action) => reads.has(action) || writes.has(action) && named);
|
|
13032
|
+
}
|
|
13033
|
+
function describeGrantScope(requested, requestedExpiry, applicationsSpace) {
|
|
13034
|
+
const preset = matchAppsAgentRequest(requested, requestedExpiry, applicationsSpace);
|
|
13035
|
+
if (preset !== void 0) {
|
|
13036
|
+
return { text: appsAgentScopeNotice(preset.mode, preset.appIds), entries: requested, extras: [] };
|
|
13037
|
+
}
|
|
13038
|
+
const touchesApplications = requested.some((entry) => (entry.space ?? "").split(":").at(-1) === APPS_AGENT_SPACE);
|
|
13039
|
+
const extras = touchesApplications ? requested.filter((entry) => !withinPresetSet(entry, applicationsSpace)) : [];
|
|
13040
|
+
const others = requested.filter((entry) => !extras.includes(entry));
|
|
13041
|
+
const lines = [
|
|
13042
|
+
extras.length > 0 ? "This request asks for MORE than the apps-agent preset. Entries marked ! go beyond it:" : touchesApplications ? "This request is not the standard apps-agent grant (other paths or lifetime). It asks for:" : "This request asks for:",
|
|
13043
|
+
...extras.map((entry) => `! ${describeEntry(entry, applicationsSpace)}`),
|
|
13044
|
+
...others.map((entry) => `- ${describeEntry(entry, applicationsSpace)}`)
|
|
13045
|
+
];
|
|
13046
|
+
if (requested.some((entry) => entry.service === "tinycloud.sql" && entry.actions.some((action) => STRUCTURE_ACTIONS.has(action)))) {
|
|
13047
|
+
lines.push("Because of how TinyCloud permissions work today (TC-1581), SQL write access can also change or delete tables (structure).");
|
|
13048
|
+
}
|
|
13049
|
+
const entryLifetimes = requested.filter((entry) => entry.expiry !== void 0).map((entry) => lifetimeMs(entry.expiry));
|
|
13050
|
+
const durations = [lifetimeMs(requestedExpiry), ...entryLifetimes].filter((ms2) => ms2 !== void 0);
|
|
13051
|
+
const longest = Math.max(...durations);
|
|
13052
|
+
lines.push(
|
|
13053
|
+
durations.length === 0 || !Number.isFinite(longest) ? "Its lifetime could not be read from the request." : `It lasts up to ${lifetimePhrase(longest)}${requestedExpiry === void 0 ? " (no lifetime was requested; this is the default)" : ""}; revoke it any time with \`${CLI_NAME15} delegation revoke <id>\`.`
|
|
13054
|
+
);
|
|
13055
|
+
return { text: lines.join("\n"), entries: requested, extras };
|
|
13056
|
+
}
|
|
13057
|
+
|
|
13058
|
+
// src/lib/consent-text.ts
|
|
13059
|
+
init_safe_text();
|
|
13060
|
+
init_permissions();
|
|
13061
|
+
function permissionConsentLine(permission) {
|
|
13062
|
+
return safeInline(compactPermission(permission));
|
|
13063
|
+
}
|
|
13064
|
+
function signExtrasQuestion(count) {
|
|
13065
|
+
return `Sign the ${count} ${count === 1 ? "entry" : "entries"} marked ! above, and the rest of the request? [y/N] `;
|
|
13066
|
+
}
|
|
13067
|
+
|
|
12753
13068
|
// src/auth/replication-login.ts
|
|
12754
13069
|
init_dist();
|
|
12755
13070
|
init_errors();
|
|
@@ -13043,7 +13358,7 @@ async function revokeForLogout(ctx, discardLocalChanges) {
|
|
|
13043
13358
|
"REVOCATION_FAILED",
|
|
13044
13359
|
`Revocation failed for ${cid}: ${result.error.message}. Session, key, and replica files are retained for retry; local reads stay denied.`,
|
|
13045
13360
|
ExitCode.ERROR,
|
|
13046
|
-
{ cid, hint: `Retry \`${
|
|
13361
|
+
{ cid, hint: `Retry \`${CLI_NAME16} auth logout --revoke\` when the node is available.` }
|
|
13047
13362
|
);
|
|
13048
13363
|
}
|
|
13049
13364
|
if (result.data.cid !== cid || result.data.revoked !== true) {
|
|
@@ -13062,7 +13377,7 @@ async function revokeForLogout(ctx, discardLocalChanges) {
|
|
|
13062
13377
|
"REVOCATION_FAILED",
|
|
13063
13378
|
`Revocation failed before local logout: ${error instanceof Error ? error.message : String(error)}. Session, key, and replica files are retained for retry; local reads stay denied.`,
|
|
13064
13379
|
ExitCode.ERROR,
|
|
13065
|
-
{ hint: `Retry \`${
|
|
13380
|
+
{ hint: `Retry \`${CLI_NAME16} auth logout --revoke\` when online authority is available.` }
|
|
13066
13381
|
);
|
|
13067
13382
|
}
|
|
13068
13383
|
}
|
|
@@ -13261,7 +13576,7 @@ async function finishProfileLogout(ctx, options = {}) {
|
|
|
13261
13576
|
function registerAuthCommand(program, options) {
|
|
13262
13577
|
const auth = program.command("auth").description("Authentication management");
|
|
13263
13578
|
registerAuthGrantsCommand(auth, { openKeyAcquisition: options?.openKeyAcquisition });
|
|
13264
|
-
auth.command("login").description("Authenticate with TinyCloud").option("--device", "Approve on another device (e.g. a phone) through OpenKey device authorization; requires a KV-scoped --manifest (SQL and secret decrypt need browser or --paste login)").option("--paste", "Wait up to 5 minutes for a signed code from OpenKey on stdin instead of a browser callback (Safari may require paste)").option("--no-popup", "Print the OpenKey URL without opening a browser").option("--method <method>", "Authentication method: local or openkey").option("--manifest <fileOrBase64>", `Request only this manifest's permissions (one space, plus raw encryption network entries such as a secrets decrypt grant); ${SHARE_PUBLISHING_MANIFEST_REF} covers \`${
|
|
13579
|
+
auth.command("login").description("Authenticate with TinyCloud").option("--device", "Approve on another device (e.g. a phone) through OpenKey device authorization; requires a KV-scoped --manifest (SQL and secret decrypt need browser or --paste login)").option("--paste", "Wait up to 5 minutes for a signed code from OpenKey on stdin instead of a browser callback (Safari may require paste)").option("--no-popup", "Print the OpenKey URL without opening a browser").option("--method <method>", "Authentication method: local or openkey").option("--manifest <fileOrBase64>", `Request only this manifest's permissions (one space, plus raw encryption network entries such as a secrets decrypt grant); ${SHARE_PUBLISHING_MANIFEST_REF} covers \`${CLI_NAME16} share publish\``).option("--expiry <duration>", "OpenKey session lifetime, e.g. 1h or 7d (device login: at most 30d, default 30d)").option("--owner <did>", "Sign in as this owner (did:pkh:eip155:CHAIN:ADDRESS): OpenKey preselects that key, e.g. one that is not the account's primary key, and an approval by any other identity is refused. With --manifest, also names the secrets owner for a manifest's `secrets` on a profile with no recorded owner").option("--replace-session", "Scoped or device login: replace this profile's live session even though the new scope would narrow, change or shorten it (prefer a new profile)").option("--replication-prefix <prefix>", "Add replication get+sync authority for a folder prefix ending in /, or root (repeatable)", (prefix, values = []) => [...values, prefix]).option("--replication-allow-secrets", "Allow replication of secret material (network-wide decrypt warning)").action(async (options2, cmd) => {
|
|
13265
13580
|
try {
|
|
13266
13581
|
if (options2.device && options2.paste) {
|
|
13267
13582
|
throw new CLIError("INVALID_ARGUMENT", "--device and --paste are mutually exclusive.", ExitCode.USAGE_ERROR);
|
|
@@ -13411,7 +13726,7 @@ function registerAuthCommand(program, options) {
|
|
|
13411
13726
|
authenticated: false,
|
|
13412
13727
|
replicasRemoved,
|
|
13413
13728
|
replicasKept: options2.keepReplicas === true,
|
|
13414
|
-
warning: online ? "The node confirmed revocation of the listed CIDs before local logout." : options2.keepReplicas ? "Legacy replica files remain on disk; this local logout clears the session but does not revoke node grants. Purge the files before a future sign-in if they must not be reused." : `Logout is local; delegations and grants on the node stay valid until they expire. Use \`${
|
|
13729
|
+
warning: online ? "The node confirmed revocation of the listed CIDs before local logout." : options2.keepReplicas ? "Legacy replica files remain on disk; this local logout clears the session but does not revoke node grants. Purge the files before a future sign-in if they must not be reused." : `Logout is local; delegations and grants on the node stay valid until they expire. Use \`${CLI_NAME16} delegation revoke <cid>\` to end access now.`,
|
|
13415
13730
|
...online === void 0 ? {} : {
|
|
13416
13731
|
revokedCids: online.plan.cids,
|
|
13417
13732
|
revocationExcluded: online.plan.excluded
|
|
@@ -13501,7 +13816,15 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
|
|
|
13501
13816
|
).option("--permission <file>", 'JSON permission request: { "permissions": PermissionEntry[] }').option("--manifest <fileOrBase64>", "Manifest file, base64:<json>, or raw base64 JSON").option(
|
|
13502
13817
|
"--expiry <duration>",
|
|
13503
13818
|
`Lifetime of the granted delegation. ms-format string (e.g. "7d", "30m") or raw milliseconds. Defaults to 7d, capped by the active session's expiry.`
|
|
13504
|
-
).option("--emit [file]", "Emit the request artifact to stdout, or write it to file when provided").option("--grant", "Grant the requested permissions immediately with this owner profile").option("--yes", "Skip local-key TTY confirmation", false).option("--no-popup", "Print the OpenKey URL without opening a browser when granting with OpenKey").option("--device", "With --grant: approve on another device (e.g. a phone) through OpenKey device authorization (KV-scoped; SQL needs browser login)").option(
|
|
13819
|
+
).option("--emit [file]", "Emit the request artifact to stdout, or write it to file when provided").option("--grant", "Grant the requested permissions immediately with this owner profile").option("--yes", "Skip local-key TTY confirmation", false).option("--no-popup", "Print the OpenKey URL without opening a browser when granting with OpenKey").option("--device", "With --grant: approve on another device (e.g. a phone) through OpenKey device authorization (KV-scoped; SQL needs browser login)").option(
|
|
13820
|
+
"--preset <name>",
|
|
13821
|
+
`Request a named permission set instead of --cap: ${APPS_AGENT_PRESET} is READ-ONLY on the applications space (kv get/list + sql read, ${APPS_AGENT_EXPIRY_DAYS} days)`
|
|
13822
|
+
).option(
|
|
13823
|
+
"--app <app_id>",
|
|
13824
|
+
`With --preset ${APPS_AGENT_PRESET}: limit the grant to this app's <app_id>/ prefix (recommended); repeat for up to ${APPS_AGENT_MAX_APPS} apps`,
|
|
13825
|
+
(value, previous) => [...previous, value],
|
|
13826
|
+
[]
|
|
13827
|
+
).option("--write", `Don't request \`--write\` during the tiny-apps beta. It currently also allows changes to the app's own code and manifest; the narrowed write ships in TC-1620. With --preset ${APPS_AGENT_PRESET} it also allows writes (kv put + sql write) in the named --app apps; SQL write can also change or drop those apps' tables`).option("--paste", "With --grant on an OpenKey profile: wait up to 5 minutes for a signed code from OpenKey on stdin instead of a browser callback (use when the browser cannot reach this CLI)").action(async (options2, cmd) => {
|
|
13505
13828
|
try {
|
|
13506
13829
|
const globalOpts = cmd.optsWithGlobals();
|
|
13507
13830
|
const ctx = await ProfileManager.resolveContext(globalOpts, options2.grant ? {} : { storedSession: "unused" });
|
|
@@ -13512,8 +13835,13 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
|
|
|
13512
13835
|
if (options2.device && (!options2.grant || resolveProfilePosture(profile) !== "owner-openkey")) {
|
|
13513
13836
|
throw new CLIError("INVALID_ARGUMENT", "--device requires --grant on an OpenKey owner profile.", ExitCode.USAGE_ERROR);
|
|
13514
13837
|
}
|
|
13515
|
-
const
|
|
13516
|
-
const
|
|
13838
|
+
const preset = resolveAppsAgentRequest(options2);
|
|
13839
|
+
const requested = preset === void 0 ? await collectRequestedPermissions(options2, ctx.profile) : await resolvePermissionSpaces(appsAgentPermissions(preset.mode, preset.appIds), ctx.profile);
|
|
13840
|
+
const expiryOption = preset === void 0 ? parseExpiryOption(options2.expiry) : APPS_AGENT_EXPIRY;
|
|
13841
|
+
if (preset !== void 0) process.stderr.write(`
|
|
13842
|
+
${appsAgentScopeNotice(preset.mode, preset.appIds)}
|
|
13843
|
+
|
|
13844
|
+
`);
|
|
13517
13845
|
if (requested.length === 0) {
|
|
13518
13846
|
throw new CLIError(
|
|
13519
13847
|
"NO_CAPS_REQUESTED",
|
|
@@ -13541,7 +13869,7 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
|
|
|
13541
13869
|
if (profile.authMethod === "openkey" || options2.device) {
|
|
13542
13870
|
const key = await ProfileManager.getKey(ctx.profile);
|
|
13543
13871
|
if (!key) {
|
|
13544
|
-
throw new CLIError("NO_KEY", `No key found for profile "${ctx.profile}". Run \`${
|
|
13872
|
+
throw new CLIError("NO_KEY", `No key found for profile "${ctx.profile}". Run \`${CLI_NAME16} --profile ${ctx.profile} auth rotate\` to create a new key and sign in.`, ExitCode.AUTH_REQUIRED);
|
|
13545
13873
|
}
|
|
13546
13874
|
const openkeyHost = resolveOpenKeyHost(profile);
|
|
13547
13875
|
const grants = [];
|
|
@@ -13561,7 +13889,7 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
|
|
|
13561
13889
|
if (notices) process.stderr.write(`${notices.prompts[index]}
|
|
13562
13890
|
`);
|
|
13563
13891
|
const reason = permissionGrantReason(
|
|
13564
|
-
`Grant requested TinyCloud permissions from \`${
|
|
13892
|
+
`Grant requested TinyCloud permissions from \`${CLI_NAME16} auth request --grant\`.`,
|
|
13565
13893
|
group
|
|
13566
13894
|
);
|
|
13567
13895
|
const request = grantRequestPermissions(group, profile.spaceId ?? profile.spaceName);
|
|
@@ -13671,7 +13999,7 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
|
|
|
13671
13999
|
kind: parsed.kind,
|
|
13672
14000
|
requestId: parsed.requestId,
|
|
13673
14001
|
requested: parsed.requested,
|
|
13674
|
-
next: `${
|
|
14002
|
+
next: `${CLI_NAME16} auth retry ${parsed.requestId}`
|
|
13675
14003
|
});
|
|
13676
14004
|
return;
|
|
13677
14005
|
}
|
|
@@ -13805,7 +14133,12 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
|
|
|
13805
14133
|
const raw = await readAuthArtifactSource(source, {
|
|
13806
14134
|
stdin: options2.stdin === true || options2.paste === true
|
|
13807
14135
|
});
|
|
13808
|
-
|
|
14136
|
+
let parsed;
|
|
14137
|
+
try {
|
|
14138
|
+
parsed = JSON.parse(raw);
|
|
14139
|
+
} catch {
|
|
14140
|
+
throw new CLIError("INVALID_AUTH_REQUEST", "The request file is not valid JSON.", ExitCode.USAGE_ERROR);
|
|
14141
|
+
}
|
|
13809
14142
|
if (!isCompatiblePermissionRequestArtifact(parsed)) {
|
|
13810
14143
|
throw new CLIError(
|
|
13811
14144
|
"INVALID_AUTH_REQUEST",
|
|
@@ -13813,7 +14146,31 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
|
|
|
13813
14146
|
ExitCode.USAGE_ERROR
|
|
13814
14147
|
);
|
|
13815
14148
|
}
|
|
13816
|
-
|
|
14149
|
+
assertRequestFields(parsed);
|
|
14150
|
+
const requested = finalizeSigningEntries(
|
|
14151
|
+
await resolvePermissionSpaces(entriesForSigning(parsed.requested), ctx.profile)
|
|
14152
|
+
);
|
|
14153
|
+
const [applications] = await resolvePermissionSpaces(
|
|
14154
|
+
[{ service: "tinycloud.kv", space: APPS_AGENT_SPACE, path: "", actions: ["tinycloud.kv/get"] }],
|
|
14155
|
+
ctx.profile
|
|
14156
|
+
);
|
|
14157
|
+
const scope = describeGrantScope(requested, parsed.requestedExpiry, applications.space ?? APPS_AGENT_SPACE);
|
|
14158
|
+
process.stderr.write(`
|
|
14159
|
+
${scope.text}
|
|
14160
|
+
|
|
14161
|
+
`);
|
|
14162
|
+
let confirmedExtras = false;
|
|
14163
|
+
if (scope.extras.length > 0 && options2.yes !== true) {
|
|
14164
|
+
if (!isInteractive()) {
|
|
14165
|
+
throw new CLIError(
|
|
14166
|
+
"CONFIRMATION_REQUIRED",
|
|
14167
|
+
`This request asks for more than the apps-agent preset (${scope.extras.length} entr${scope.extras.length === 1 ? "y" : "ies"} marked ! in the list above). Nothing was signed. Read the list, then re-run with --yes to sign it.`,
|
|
14168
|
+
ExitCode.USAGE_ERROR
|
|
14169
|
+
);
|
|
14170
|
+
}
|
|
14171
|
+
await askYesNo(signExtrasQuestion(scope.extras.length), "Grant cancelled. Nothing was signed.");
|
|
14172
|
+
confirmedExtras = true;
|
|
14173
|
+
}
|
|
13817
14174
|
const resolvedRequest = { ...parsed, requested };
|
|
13818
14175
|
const node = await ensureAuthenticated(ctx);
|
|
13819
14176
|
await ensureDelegationAuthority({
|
|
@@ -13823,7 +14180,7 @@ ${signInAgainHint(ctx.profile, posture ?? void 0)}
|
|
|
13823
14180
|
requested,
|
|
13824
14181
|
expiryOption: parsed.requestedExpiry,
|
|
13825
14182
|
reason: "Grant permissions requested by a TinyCloud auth request artifact.",
|
|
13826
|
-
yes: options2.yes === true
|
|
14183
|
+
yes: options2.yes === true || confirmedExtras
|
|
13827
14184
|
});
|
|
13828
14185
|
const grant = await grantAuthRequest(node, resolvedRequest);
|
|
13829
14186
|
await appendLocalGrantArtifact(ctx.profile, grant.delegation);
|
|
@@ -14222,7 +14579,7 @@ async function ensureDelegationAuthority(params) {
|
|
|
14222
14579
|
if (!key) {
|
|
14223
14580
|
throw new CLIError(
|
|
14224
14581
|
"NO_KEY",
|
|
14225
|
-
`No key found for profile "${params.ctx.profile}". Run \`${
|
|
14582
|
+
`No key found for profile "${params.ctx.profile}". Run \`${CLI_NAME16} --profile ${params.ctx.profile} auth rotate\` to create a new key and sign in.`,
|
|
14226
14583
|
ExitCode.AUTH_REQUIRED
|
|
14227
14584
|
);
|
|
14228
14585
|
}
|
|
@@ -14334,25 +14691,28 @@ async function collectRequestedPermissions(options, profile) {
|
|
|
14334
14691
|
}
|
|
14335
14692
|
return permissions;
|
|
14336
14693
|
}
|
|
14337
|
-
async function
|
|
14338
|
-
process.stderr.write("\n" + theme.heading("Additional Permissions") + "\n");
|
|
14339
|
-
for (const permission of permissions) {
|
|
14340
|
-
const dangerous = isDangerousPermission(permission);
|
|
14341
|
-
const line = ` ${compactPermission(permission)}`;
|
|
14342
|
-
process.stderr.write((dangerous ? theme.warn(line) : theme.value(line)) + "\n");
|
|
14343
|
-
}
|
|
14344
|
-
process.stderr.write("\n");
|
|
14694
|
+
async function askYesNo(question, cancelMessage) {
|
|
14345
14695
|
const rl = createInterface3({
|
|
14346
14696
|
input: process.stdin,
|
|
14347
14697
|
output: process.stderr
|
|
14348
14698
|
});
|
|
14349
14699
|
const answer = await new Promise((resolve8) => {
|
|
14350
|
-
rl.question(
|
|
14700
|
+
rl.question(question, resolve8);
|
|
14351
14701
|
});
|
|
14352
14702
|
rl.close();
|
|
14353
14703
|
if (!/^y(es)?$/i.test(answer.trim())) {
|
|
14354
|
-
throw new CLIError("REQUEST_CANCELLED",
|
|
14704
|
+
throw new CLIError("REQUEST_CANCELLED", cancelMessage, ExitCode.ERROR);
|
|
14705
|
+
}
|
|
14706
|
+
}
|
|
14707
|
+
async function confirmPermissionRequest(permissions) {
|
|
14708
|
+
process.stderr.write("\n" + theme.heading("Additional Permissions") + "\n");
|
|
14709
|
+
for (const permission of permissions) {
|
|
14710
|
+
const dangerous = isDangerousPermission(permission);
|
|
14711
|
+
const line = ` ${permissionConsentLine(permission)}`;
|
|
14712
|
+
process.stderr.write((dangerous ? theme.warn(line) : theme.value(line)) + "\n");
|
|
14355
14713
|
}
|
|
14714
|
+
process.stderr.write("\n");
|
|
14715
|
+
await askYesNo("Approve local-key delegation? [y/N] ", "Permission request cancelled.");
|
|
14356
14716
|
}
|
|
14357
14717
|
async function requirePermissionConsent(permissions, yes) {
|
|
14358
14718
|
if (isInteractive()) {
|
|
@@ -14468,7 +14828,7 @@ async function rotateAuthKey(profileName2, host, options = {}) {
|
|
|
14468
14828
|
if (!profile.privateKey) {
|
|
14469
14829
|
throw new CLIError(
|
|
14470
14830
|
"LOCAL_OWNER_KEY_REQUIRED",
|
|
14471
|
-
`Profile "${profileName2}" does not have a local owner private key. Run \`${
|
|
14831
|
+
`Profile "${profileName2}" does not have a local owner private key. Run \`${CLI_NAME16} auth login --method local\` first.`,
|
|
14472
14832
|
ExitCode.AUTH_REQUIRED
|
|
14473
14833
|
);
|
|
14474
14834
|
}
|
|
@@ -14909,7 +15269,7 @@ async function refreshOpenKeySession(profileName2, host, options = {}) {
|
|
|
14909
15269
|
}
|
|
14910
15270
|
|
|
14911
15271
|
// src/commands/completion.ts
|
|
14912
|
-
import { CLI_NAME as
|
|
15272
|
+
import { CLI_NAME as CLI_NAME17 } from "@tinycloud/operations/cli-name";
|
|
14913
15273
|
function registerCompletionCommand(program) {
|
|
14914
15274
|
const completion = program.command("completion").description("Generate shell completions");
|
|
14915
15275
|
completion.command("bash").description("Output bash completions").action(() => {
|
|
@@ -14926,7 +15286,7 @@ function registerCompletionCommand(program) {
|
|
|
14926
15286
|
});
|
|
14927
15287
|
}
|
|
14928
15288
|
function generateBashCompletion() {
|
|
14929
|
-
return `# ${
|
|
15289
|
+
return `# ${CLI_NAME17} bash completion
|
|
14930
15290
|
_tc_completions() {
|
|
14931
15291
|
local cur prev commands subcommands
|
|
14932
15292
|
COMPREPLY=()
|
|
@@ -14951,11 +15311,11 @@ _tc_completions() {
|
|
|
14951
15311
|
COMPREPLY=( $(compgen -W "\${subcommands}" -- "\${cur}") )
|
|
14952
15312
|
fi
|
|
14953
15313
|
}
|
|
14954
|
-
complete -F _tc_completions ${
|
|
15314
|
+
complete -F _tc_completions ${CLI_NAME17}
|
|
14955
15315
|
`;
|
|
14956
15316
|
}
|
|
14957
15317
|
function generateZshCompletion() {
|
|
14958
|
-
return `#compdef ${
|
|
15318
|
+
return `#compdef ${CLI_NAME17}
|
|
14959
15319
|
|
|
14960
15320
|
_tc() {
|
|
14961
15321
|
local -a commands
|
|
@@ -15006,50 +15366,50 @@ _tc
|
|
|
15006
15366
|
`;
|
|
15007
15367
|
}
|
|
15008
15368
|
function generateFishCompletion() {
|
|
15009
|
-
return `# ${
|
|
15369
|
+
return `# ${CLI_NAME17} fish completion
|
|
15010
15370
|
set -l commands init auth kv space delegation share node profile completion
|
|
15011
15371
|
|
|
15012
15372
|
# Disable file completion by default
|
|
15013
|
-
complete -c ${
|
|
15373
|
+
complete -c ${CLI_NAME17} -f
|
|
15014
15374
|
|
|
15015
15375
|
# Top-level commands
|
|
15016
|
-
complete -c ${
|
|
15017
|
-
complete -c ${
|
|
15018
|
-
complete -c ${
|
|
15019
|
-
complete -c ${
|
|
15020
|
-
complete -c ${
|
|
15021
|
-
complete -c ${
|
|
15022
|
-
complete -c ${
|
|
15023
|
-
complete -c ${
|
|
15024
|
-
complete -c ${
|
|
15376
|
+
complete -c ${CLI_NAME17} -n "not __fish_seen_subcommand_from $commands" -a init -d "Initialize a new TinyCloud profile"
|
|
15377
|
+
complete -c ${CLI_NAME17} -n "not __fish_seen_subcommand_from $commands" -a auth -d "Authentication management"
|
|
15378
|
+
complete -c ${CLI_NAME17} -n "not __fish_seen_subcommand_from $commands" -a kv -d "Key-value store operations"
|
|
15379
|
+
complete -c ${CLI_NAME17} -n "not __fish_seen_subcommand_from $commands" -a space -d "Space management"
|
|
15380
|
+
complete -c ${CLI_NAME17} -n "not __fish_seen_subcommand_from $commands" -a delegation -d "Manage delegations"
|
|
15381
|
+
complete -c ${CLI_NAME17} -n "not __fish_seen_subcommand_from $commands" -a share -d "Share data with others"
|
|
15382
|
+
complete -c ${CLI_NAME17} -n "not __fish_seen_subcommand_from $commands" -a node -d "Node health and info"
|
|
15383
|
+
complete -c ${CLI_NAME17} -n "not __fish_seen_subcommand_from $commands" -a profile -d "Profile management"
|
|
15384
|
+
complete -c ${CLI_NAME17} -n "not __fish_seen_subcommand_from $commands" -a completion -d "Generate shell completions"
|
|
15025
15385
|
|
|
15026
15386
|
# Subcommands
|
|
15027
|
-
complete -c ${
|
|
15028
|
-
complete -c ${
|
|
15029
|
-
complete -c ${
|
|
15030
|
-
complete -c ${
|
|
15031
|
-
complete -c ${
|
|
15032
|
-
complete -c ${
|
|
15033
|
-
complete -c ${
|
|
15034
|
-
complete -c ${
|
|
15387
|
+
complete -c ${CLI_NAME17} -n "__fish_seen_subcommand_from auth" -a "login logout rotate status whoami"
|
|
15388
|
+
complete -c ${CLI_NAME17} -n "__fish_seen_subcommand_from kv" -a "get put delete list head"
|
|
15389
|
+
complete -c ${CLI_NAME17} -n "__fish_seen_subcommand_from space" -a "list create info switch"
|
|
15390
|
+
complete -c ${CLI_NAME17} -n "__fish_seen_subcommand_from delegation" -a "create list info revoke"
|
|
15391
|
+
complete -c ${CLI_NAME17} -n "__fish_seen_subcommand_from share" -a "create receive list revoke"
|
|
15392
|
+
complete -c ${CLI_NAME17} -n "__fish_seen_subcommand_from node" -a "health version status"
|
|
15393
|
+
complete -c ${CLI_NAME17} -n "__fish_seen_subcommand_from profile" -a "list create show switch delete"
|
|
15394
|
+
complete -c ${CLI_NAME17} -n "__fish_seen_subcommand_from completion" -a "bash zsh fish"
|
|
15035
15395
|
|
|
15036
15396
|
# Global options
|
|
15037
|
-
complete -c ${
|
|
15038
|
-
complete -c ${
|
|
15039
|
-
complete -c ${
|
|
15040
|
-
complete -c ${
|
|
15041
|
-
complete -c ${
|
|
15042
|
-
complete -c ${
|
|
15043
|
-
complete -c ${
|
|
15044
|
-
complete -c ${
|
|
15397
|
+
complete -c ${CLI_NAME17} -l profile -s p -d "Profile to use"
|
|
15398
|
+
complete -c ${CLI_NAME17} -l host -s H -d "TinyCloud node URL"
|
|
15399
|
+
complete -c ${CLI_NAME17} -l verbose -s v -d "Enable verbose output"
|
|
15400
|
+
complete -c ${CLI_NAME17} -l no-cache -d "Disable caching"
|
|
15401
|
+
complete -c ${CLI_NAME17} -l quiet -s q -d "Suppress non-essential output"
|
|
15402
|
+
complete -c ${CLI_NAME17} -l replication -d "Enable read-through replication"
|
|
15403
|
+
complete -c ${CLI_NAME17} -l no-replication -d "Disable replication"
|
|
15404
|
+
complete -c ${CLI_NAME17} -l replication-debug -d "Write replication diagnostics to stderr"
|
|
15045
15405
|
`;
|
|
15046
15406
|
}
|
|
15047
15407
|
|
|
15048
15408
|
// src/commands/courier.ts
|
|
15049
|
-
import { CLI_NAME as
|
|
15050
|
-
var TC_OPTIONS_BEFORE_COURIER = `${
|
|
15409
|
+
import { CLI_NAME as CLI_NAME18 } from "@tinycloud/operations/cli-name";
|
|
15410
|
+
var TC_OPTIONS_BEFORE_COURIER = `${CLI_NAME18} courier takes no ${CLI_NAME18} options and does not use ${CLI_NAME18} profiles; Courier keeps its own profile in $COURIER_HOME (default ~/.courier). Put flags after "courier", e.g. ${CLI_NAME18} courier init --host <url>.`;
|
|
15051
15411
|
function registerCourierCommand(program) {
|
|
15052
|
-
const courier = program.command("courier").description(`Agent-to-agent threads over TinyCloud; run \`${
|
|
15412
|
+
const courier = program.command("courier").description(`Agent-to-agent threads over TinyCloud; run \`${CLI_NAME18} courier --help\` (beta)`).argument("[args...]").allowUnknownOption().helpOption(false);
|
|
15053
15413
|
courier.action(() => courier.error(TC_OPTIONS_BEFORE_COURIER, { exitCode: 2, code: "tc.courier.options" }));
|
|
15054
15414
|
}
|
|
15055
15415
|
|
|
@@ -15094,7 +15454,7 @@ init_formatter();
|
|
|
15094
15454
|
init_errors();
|
|
15095
15455
|
init_constants();
|
|
15096
15456
|
init_sdk();
|
|
15097
|
-
import { CLI_NAME as
|
|
15457
|
+
import { CLI_NAME as CLI_NAME19 } from "@tinycloud/operations/cli-name";
|
|
15098
15458
|
import { PermissionNotInManifestError } from "@tinycloud/node-sdk";
|
|
15099
15459
|
async function queryDelegations(node, options, until) {
|
|
15100
15460
|
const found = [];
|
|
@@ -15105,7 +15465,7 @@ async function queryDelegations(node, options, until) {
|
|
|
15105
15465
|
if (page.error.code === "PERMISSION_DENIED") {
|
|
15106
15466
|
throw new CLIError(
|
|
15107
15467
|
"PERMISSION_DENIED",
|
|
15108
|
-
`This session cannot read delegations: it lacks tinycloud.delegation/list. Run \`${
|
|
15468
|
+
`This session cannot read delegations: it lacks tinycloud.delegation/list. Run \`${CLI_NAME19} auth login\` again to grant it.`,
|
|
15109
15469
|
ExitCode.PERMISSION_DENIED
|
|
15110
15470
|
);
|
|
15111
15471
|
}
|
|
@@ -15280,7 +15640,7 @@ init_constants();
|
|
|
15280
15640
|
init_formatter();
|
|
15281
15641
|
init_theme();
|
|
15282
15642
|
init_errors();
|
|
15283
|
-
import { CLI_NAME as
|
|
15643
|
+
import { CLI_NAME as CLI_NAME20 } from "@tinycloud/operations/cli-name";
|
|
15284
15644
|
init_session_expired();
|
|
15285
15645
|
init_types();
|
|
15286
15646
|
function registerDoctorCommand(program) {
|
|
@@ -15314,7 +15674,7 @@ function registerDoctorCommand(program) {
|
|
|
15314
15674
|
const profile = await ProfileManager.getProfile(profileName2);
|
|
15315
15675
|
keyDetail = profile.did ? `${profile.did.slice(0, 20)}...` : "key found";
|
|
15316
15676
|
} else {
|
|
15317
|
-
keyDetail = `no key \u2014 run ${
|
|
15677
|
+
keyDetail = `no key \u2014 run ${CLI_NAME20} init`;
|
|
15318
15678
|
}
|
|
15319
15679
|
} catch {
|
|
15320
15680
|
keyDetail = "error reading key";
|
|
@@ -15336,7 +15696,7 @@ function registerDoctorCommand(program) {
|
|
|
15336
15696
|
} else if (state2 === "unverified") {
|
|
15337
15697
|
sessionDetail = `not usable (no signed OpenKey proof) \u2014 ${signInAgainHint(profileName2, resolveProfilePosture(profile))}`;
|
|
15338
15698
|
} else {
|
|
15339
|
-
sessionDetail = sessionOk ? "active" : `no session \u2014 run ${
|
|
15699
|
+
sessionDetail = sessionOk ? "active" : `no session \u2014 run ${CLI_NAME20} auth login`;
|
|
15340
15700
|
}
|
|
15341
15701
|
} catch {
|
|
15342
15702
|
sessionDetail = "error reading session";
|
|
@@ -15364,7 +15724,7 @@ function registerDoctorCommand(program) {
|
|
|
15364
15724
|
try {
|
|
15365
15725
|
const profile = await ProfileManager.getProfile(profileName2);
|
|
15366
15726
|
spaceOk = Boolean(profile.spaceId);
|
|
15367
|
-
spaceDetail = spaceOk ? `${profile.spaceId.slice(0, 16)}...` : `no space \u2014 run ${
|
|
15727
|
+
spaceDetail = spaceOk ? `${profile.spaceId.slice(0, 16)}...` : `no space \u2014 run ${CLI_NAME20} space create`;
|
|
15368
15728
|
} catch {
|
|
15369
15729
|
spaceDetail = "error checking space";
|
|
15370
15730
|
}
|
|
@@ -15554,7 +15914,7 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
|
|
|
15554
15914
|
}
|
|
15555
15915
|
|
|
15556
15916
|
// src/commands/enable.ts
|
|
15557
|
-
import { CLI_NAME as
|
|
15917
|
+
import { CLI_NAME as CLI_NAME21 } from "@tinycloud/operations/cli-name";
|
|
15558
15918
|
init_constants();
|
|
15559
15919
|
init_profiles();
|
|
15560
15920
|
init_errors();
|
|
@@ -15562,7 +15922,7 @@ init_formatter();
|
|
|
15562
15922
|
init_publishing_manifest();
|
|
15563
15923
|
function registerEnableCommand(program) {
|
|
15564
15924
|
const enable = program.command("enable").description("Enable a narrowly scoped TinyCloud service");
|
|
15565
|
-
enable.command("share").description(`Approve Share publishing scope through OpenKey device authorization (same as \`${
|
|
15925
|
+
enable.command("share").description(`Approve Share publishing scope through OpenKey device authorization (same as \`${CLI_NAME21} auth login --device --manifest builtin:share-publishing\`). Use a dedicated profile, e.g. \`${CLI_NAME21} init --name publisher --key-only && ${CLI_NAME21} --profile publisher enable share\`; keep existing profiles for their apps.`).option("--replace-session", "Replace this profile's live session even though the new scope would narrow, change or shorten it (prefer a new profile)").action(async (options, command) => {
|
|
15566
15926
|
try {
|
|
15567
15927
|
const globalOptions = command.optsWithGlobals();
|
|
15568
15928
|
const context = await ProfileManager.resolveContext(globalOptions, { storedSession: "unused" });
|
|
@@ -15598,7 +15958,7 @@ init_profiles();
|
|
|
15598
15958
|
init_formatter();
|
|
15599
15959
|
init_errors();
|
|
15600
15960
|
init_constants();
|
|
15601
|
-
import { CLI_NAME as
|
|
15961
|
+
import { CLI_NAME as CLI_NAME22 } from "@tinycloud/operations/cli-name";
|
|
15602
15962
|
function registerInitCommand(program) {
|
|
15603
15963
|
program.command("init").description("Initialize a new TinyCloud profile").option("--name <profile>", "Profile name", "default").option("--key-only", "Only generate key, skip authentication").option("--host <url>", "TinyCloud node URL").option("--paste", "Use manual paste mode for authentication").option("--no-popup", "Print the OpenKey URL without opening a browser").option("--default-space <name>", "Default space used when --space is omitted (e.g. applications)").action(async (options, cmd) => {
|
|
15604
15964
|
try {
|
|
@@ -15616,7 +15976,7 @@ function registerInitCommand(program) {
|
|
|
15616
15976
|
if (await ProfileManager.profileExists(profileName2)) {
|
|
15617
15977
|
throw new CLIError(
|
|
15618
15978
|
"PROFILE_EXISTS",
|
|
15619
|
-
`Profile "${profileName2}" already exists. Use \`${
|
|
15979
|
+
`Profile "${profileName2}" already exists. Use \`${CLI_NAME22} profile delete ${profileName2}\` first or choose a different name.`,
|
|
15620
15980
|
ExitCode.ERROR
|
|
15621
15981
|
);
|
|
15622
15982
|
}
|
|
@@ -15667,6 +16027,183 @@ function registerInitCommand(program) {
|
|
|
15667
16027
|
});
|
|
15668
16028
|
}
|
|
15669
16029
|
|
|
16030
|
+
// src/commands/knowledge.ts
|
|
16031
|
+
init_session_expired();
|
|
16032
|
+
init_constants();
|
|
16033
|
+
init_profiles();
|
|
16034
|
+
import { invokeOperation as invokeOperation2 } from "@tinycloud/operations";
|
|
16035
|
+
import { CLI_NAME as CLI_NAME23 } from "@tinycloud/operations/cli-name";
|
|
16036
|
+
|
|
16037
|
+
// src/lib/knowledge-input.ts
|
|
16038
|
+
init_constants();
|
|
16039
|
+
init_errors();
|
|
16040
|
+
init_safe_text();
|
|
16041
|
+
function usage2(message) {
|
|
16042
|
+
return new CLIError("INVALID_ARGUMENT", message, ExitCode.USAGE_ERROR);
|
|
16043
|
+
}
|
|
16044
|
+
function assertKnowledgeAppId(value) {
|
|
16045
|
+
if (!isAppId(value)) throw usage2(`Invalid app id "${safeClipped(value)}": ${APP_ID_RULE}.`);
|
|
16046
|
+
return value;
|
|
16047
|
+
}
|
|
16048
|
+
function assertKnowledgeConcept(value) {
|
|
16049
|
+
const reason = value.length > 300 ? "it is longer than 300 characters" : value.startsWith("/") ? "it starts with a slash" : /[*\\]/.test(value) ? "it contains a wildcard or backslash" : /%(?:2e|2f|5c)/i.test(value) ? "it contains an encoded dot, slash or backslash" : value.split("/").some((segment) => segment === "" || segment === "." || segment === "..") ? "it has an empty, `.` or `..` segment" : void 0;
|
|
16050
|
+
if (reason !== void 0) {
|
|
16051
|
+
throw usage2(
|
|
16052
|
+
`Invalid concept "${safeClipped(value)}": ${reason}. Use a path inside the app's knowledge folder, such as app or tables/meal.md.`
|
|
16053
|
+
);
|
|
16054
|
+
}
|
|
16055
|
+
return value;
|
|
16056
|
+
}
|
|
16057
|
+
|
|
16058
|
+
// src/commands/knowledge.ts
|
|
16059
|
+
init_permissions();
|
|
16060
|
+
init_errors();
|
|
16061
|
+
init_formatter();
|
|
16062
|
+
|
|
16063
|
+
// src/lib/knowledge-render.ts
|
|
16064
|
+
init_safe_text();
|
|
16065
|
+
function knowledgeListRows(apps) {
|
|
16066
|
+
return apps.map((app) => [
|
|
16067
|
+
safeInline(app.app_id),
|
|
16068
|
+
app.name === void 0 ? "\u2014" : safeInline(app.name),
|
|
16069
|
+
safeInline(app.has_index ? app.root : `${app.root} (no index)`),
|
|
16070
|
+
String(app.concepts.length)
|
|
16071
|
+
]);
|
|
16072
|
+
}
|
|
16073
|
+
function knowledgeReadParts(output2) {
|
|
16074
|
+
const body = safeBlock(output2.body);
|
|
16075
|
+
return {
|
|
16076
|
+
heading: safeInline(output2.key),
|
|
16077
|
+
...output2.frontmatter === null ? {} : { meta: safeInline(JSON.stringify(output2.frontmatter)) },
|
|
16078
|
+
body: body.endsWith("\n") ? body : `${body}
|
|
16079
|
+
`
|
|
16080
|
+
};
|
|
16081
|
+
}
|
|
16082
|
+
|
|
16083
|
+
// src/commands/knowledge.ts
|
|
16084
|
+
init_theme();
|
|
16085
|
+
function registerKnowledgeCommand(program) {
|
|
16086
|
+
const knowledge = program.command("knowledge").description("Read the knowledge bundles of apps installed in a user's TinyCloud");
|
|
16087
|
+
knowledge.command("list").description("List installed apps that carry a knowledge bundle (`<app_id>/knowledge/index.md` in the applications space)").option("--app <app_id>", "Only this app; works with a grant scoped to that app").action(async (options, cmd) => {
|
|
16088
|
+
try {
|
|
16089
|
+
const appId = options.app === void 0 ? void 0 : assertKnowledgeAppId(options.app);
|
|
16090
|
+
const ctx = await ProfileManager.resolveContext(cmd.optsWithGlobals());
|
|
16091
|
+
const result = await withSpinner("Listing installed apps...", () => invokeOperation2(
|
|
16092
|
+
"tinycloud.knowledge.list",
|
|
16093
|
+
1,
|
|
16094
|
+
{ profile: ctx.profile, host: ctx.host, allowOwnerProfile: true },
|
|
16095
|
+
appId === void 0 ? {} : { app_id: appId }
|
|
16096
|
+
));
|
|
16097
|
+
if (result.status !== "ok") throw knowledgeFailure(result, ctx.profile, appId);
|
|
16098
|
+
const output2 = result.output;
|
|
16099
|
+
if (shouldOutputJson()) {
|
|
16100
|
+
outputJson(result.output);
|
|
16101
|
+
return;
|
|
16102
|
+
}
|
|
16103
|
+
if (output2.apps.length === 0) {
|
|
16104
|
+
process.stdout.write(theme.muted("No installed apps with a knowledge bundle found.") + "\n");
|
|
16105
|
+
return;
|
|
16106
|
+
}
|
|
16107
|
+
process.stdout.write(
|
|
16108
|
+
formatTable(["App ID", "Name", "Root", "Concepts"], knowledgeListRows(output2.apps)) + "\n"
|
|
16109
|
+
);
|
|
16110
|
+
if (output2.truncated) {
|
|
16111
|
+
process.stderr.write(theme.warn("The applications space has more keys than one listing covers; some apps may be missing.") + "\n");
|
|
16112
|
+
}
|
|
16113
|
+
} catch (error) {
|
|
16114
|
+
return handleError(error);
|
|
16115
|
+
}
|
|
16116
|
+
});
|
|
16117
|
+
knowledge.command("read <app_id> [concept]").description("Read an app's knowledge index, or one concept (for example tables/meal.md), with frontmatter parsed").action(async (appIdArgument, conceptArgument, _options, cmd) => {
|
|
16118
|
+
try {
|
|
16119
|
+
const appId = assertKnowledgeAppId(appIdArgument);
|
|
16120
|
+
const concept = conceptArgument === void 0 ? void 0 : assertKnowledgeConcept(conceptArgument);
|
|
16121
|
+
const ctx = await ProfileManager.resolveContext(cmd.optsWithGlobals());
|
|
16122
|
+
const result = await withSpinner("Reading knowledge...", () => invokeOperation2(
|
|
16123
|
+
"tinycloud.knowledge.read",
|
|
16124
|
+
1,
|
|
16125
|
+
{ profile: ctx.profile, host: ctx.host, allowOwnerProfile: true },
|
|
16126
|
+
{ app_id: appId, ...concept === void 0 ? {} : { concept } }
|
|
16127
|
+
));
|
|
16128
|
+
if (result.status !== "ok") throw knowledgeFailure(result, ctx.profile, appId);
|
|
16129
|
+
const output2 = result.output;
|
|
16130
|
+
if (shouldOutputJson()) {
|
|
16131
|
+
outputJson(result.output);
|
|
16132
|
+
return;
|
|
16133
|
+
}
|
|
16134
|
+
const parts = knowledgeReadParts(output2);
|
|
16135
|
+
process.stdout.write(`${theme.heading(parts.heading)}
|
|
16136
|
+
`);
|
|
16137
|
+
if (parts.meta !== void 0) process.stdout.write(`${parts.meta}
|
|
16138
|
+
`);
|
|
16139
|
+
process.stdout.write(`
|
|
16140
|
+
${parts.body}`);
|
|
16141
|
+
} catch (error) {
|
|
16142
|
+
return handleError(error);
|
|
16143
|
+
}
|
|
16144
|
+
});
|
|
16145
|
+
}
|
|
16146
|
+
function knowledgeFailure(result, profile, appId) {
|
|
16147
|
+
switch (result.status) {
|
|
16148
|
+
case "authority_required": {
|
|
16149
|
+
return new CLIError(
|
|
16150
|
+
"PERMISSION_DENIED",
|
|
16151
|
+
`This profile has no grant for that. Missing: ${result.missing.map((entry) => compactPermission(entry)).join("; ")}`,
|
|
16152
|
+
ExitCode.PERMISSION_DENIED,
|
|
16153
|
+
{ hint: missingGrantHint(profile, appId) }
|
|
16154
|
+
);
|
|
16155
|
+
}
|
|
16156
|
+
case "setup_required":
|
|
16157
|
+
return new CLIError("SETUP_REQUIRED", "The operation needs setup before it can run.", ExitCode.ERROR);
|
|
16158
|
+
case "error": {
|
|
16159
|
+
if (result.error.code === "SESSION_NOT_FOUND" || result.error.code === "PROFILE_POSTURE_NOT_ALLOWED" && result.context.posture === "unauthenticated") {
|
|
16160
|
+
return new CLIError("AUTH_REQUIRED", "Not signed in to TinyCloud.", ExitCode.AUTH_REQUIRED, {
|
|
16161
|
+
hint: `Sign in with: ${CLI_NAME23} --profile ${result.context.profile} auth login`
|
|
16162
|
+
});
|
|
16163
|
+
}
|
|
16164
|
+
if (result.error.code === "NODE_UNREACHABLE") {
|
|
16165
|
+
return new CLIError("NETWORK_ERROR", result.error.message, ExitCode.NETWORK_ERROR);
|
|
16166
|
+
}
|
|
16167
|
+
if (result.error.code === "KV_NOT_FOUND") {
|
|
16168
|
+
return new CLIError(
|
|
16169
|
+
"NOT_FOUND",
|
|
16170
|
+
"No such knowledge file. Run `knowledge list` for the installed apps, then `knowledge read <app_id>` for the index.",
|
|
16171
|
+
ExitCode.NOT_FOUND
|
|
16172
|
+
);
|
|
16173
|
+
}
|
|
16174
|
+
if (result.error.code === "PERMISSION_DENIED") {
|
|
16175
|
+
return new CLIError("PERMISSION_DENIED", result.error.message, ExitCode.PERMISSION_DENIED, {
|
|
16176
|
+
hint: missingGrantHint(profile, appId)
|
|
16177
|
+
});
|
|
16178
|
+
}
|
|
16179
|
+
if (result.error.code === "INPUT_INVALID") {
|
|
16180
|
+
return new CLIError(
|
|
16181
|
+
"INVALID_ARGUMENT",
|
|
16182
|
+
"The app id or concept is not valid. An app id looks like xyz.example.app; a concept is a path inside the app's knowledge folder, such as app or tables/meal.md.",
|
|
16183
|
+
ExitCode.USAGE_ERROR
|
|
16184
|
+
);
|
|
16185
|
+
}
|
|
16186
|
+
return withSignInHint(cliErrorFromService(result.error), result.context.profile, result.context.posture);
|
|
16187
|
+
}
|
|
16188
|
+
}
|
|
16189
|
+
}
|
|
16190
|
+
function missingGrantHint(profile, appId) {
|
|
16191
|
+
const request = `${CLI_NAME23} --profile ${profile} auth request`;
|
|
16192
|
+
const lines = appId === void 0 ? [
|
|
16193
|
+
` ${request} --preset ${APPS_AGENT_PRESET} --emit req.json (read-only, all installed apps)`,
|
|
16194
|
+
` ${request} --cap tinycloud.kv:${APPS_AGENT_SPACE}::get,list --emit req.json (all files, no table access)`,
|
|
16195
|
+
"To limit the grant to one app, add `--app` and that app's id to the first command (repeat `--app` for up to 5)."
|
|
16196
|
+
] : [
|
|
16197
|
+
` ${request} --preset ${APPS_AGENT_PRESET} --app ${appId} --emit req.json (read-only: files and table rows of ${appId})`,
|
|
16198
|
+
` ${request} --cap tinycloud.kv:${APPS_AGENT_SPACE}:${appId}/knowledge/:get,list --emit req.json (only ${appId}'s knowledge)`
|
|
16199
|
+
];
|
|
16200
|
+
return [
|
|
16201
|
+
"Ask the owner for a grant, narrowest first. Create the request:",
|
|
16202
|
+
...lines,
|
|
16203
|
+
`The owner runs \`${CLI_NAME23} auth grant req.json > grant.json\`; then import the result with \`${CLI_NAME23} --profile ${profile} auth import grant.json\`.`
|
|
16204
|
+
].join("\n");
|
|
16205
|
+
}
|
|
16206
|
+
|
|
15670
16207
|
// src/commands/kv.ts
|
|
15671
16208
|
import { readFile as readFile10 } from "fs/promises";
|
|
15672
16209
|
|
|
@@ -15971,7 +16508,7 @@ init_errors();
|
|
|
15971
16508
|
init_constants();
|
|
15972
16509
|
init_theme();
|
|
15973
16510
|
import { resolveManifestKnowledgeRoot as resolveManifestKnowledgeRoot2 } from "@tinycloud/sdk-core";
|
|
15974
|
-
import { CLI_NAME as
|
|
16511
|
+
import { CLI_NAME as CLI_NAME24 } from "@tinycloud/operations/cli-name";
|
|
15975
16512
|
import { readFile as readFile11 } from "fs/promises";
|
|
15976
16513
|
var DEFAULT_APP_SPACE = "applications";
|
|
15977
16514
|
function registerManifestCommand(program) {
|
|
@@ -15979,8 +16516,8 @@ function registerManifestCommand(program) {
|
|
|
15979
16516
|
manifest.command("resolve <source>").description("Resolve a manifest file or URL to its effective space, paths, and DB basenames").addHelpText("after", `
|
|
15980
16517
|
|
|
15981
16518
|
Examples:
|
|
15982
|
-
$ ${
|
|
15983
|
-
$ ${
|
|
16519
|
+
$ ${CLI_NAME24} manifest resolve ./manifest.json
|
|
16520
|
+
$ ${CLI_NAME24} manifest resolve https://app.example.com/manifest.json --json
|
|
15984
16521
|
|
|
15985
16522
|
What it shows:
|
|
15986
16523
|
- app_id, name, manifest_version
|
|
@@ -15990,7 +16527,7 @@ What it shows:
|
|
|
15990
16527
|
|
|
15991
16528
|
This command is read-only and does NOT contact the node \u2014 it just resolves
|
|
15992
16529
|
the manifest against the active profile's address/chain so you know which
|
|
15993
|
-
\`--space\` and \`--db\` values to pass to other ${
|
|
16530
|
+
\`--space\` and \`--db\` values to pass to other ${CLI_NAME24} commands.
|
|
15994
16531
|
`).action(async (source, _options, cmd) => {
|
|
15995
16532
|
try {
|
|
15996
16533
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -16059,7 +16596,7 @@ ${theme.heading("SQL databases")}
|
|
|
16059
16596
|
`);
|
|
16060
16597
|
}
|
|
16061
16598
|
process.stdout.write(theme.muted(`
|
|
16062
|
-
Use with: ${
|
|
16599
|
+
Use with: ${CLI_NAME24} sql query --space ${spaceName} --db <db> "..."
|
|
16063
16600
|
`));
|
|
16064
16601
|
}
|
|
16065
16602
|
if (permissions.length > 0) {
|
|
@@ -16179,7 +16716,7 @@ init_profiles();
|
|
|
16179
16716
|
init_formatter();
|
|
16180
16717
|
init_errors();
|
|
16181
16718
|
init_constants();
|
|
16182
|
-
import { CLI_NAME as
|
|
16719
|
+
import { CLI_NAME as CLI_NAME25 } from "@tinycloud/operations/cli-name";
|
|
16183
16720
|
import { createInterface as createInterface5 } from "readline";
|
|
16184
16721
|
init_theme();
|
|
16185
16722
|
init_types();
|
|
@@ -16325,9 +16862,9 @@ profile at command time. Precedence for every kv/sql command:
|
|
|
16325
16862
|
explicit --space flag > profile defaultSpace > primary space.
|
|
16326
16863
|
|
|
16327
16864
|
Examples:
|
|
16328
|
-
$ ${
|
|
16329
|
-
$ ${
|
|
16330
|
-
$ ${
|
|
16865
|
+
$ ${CLI_NAME25} profile set-default-space applications
|
|
16866
|
+
$ ${CLI_NAME25} profile set-default-space applications --profile cli-test
|
|
16867
|
+
$ ${CLI_NAME25} profile set-default-space --unset
|
|
16331
16868
|
`).action(async (name, options, cmd) => {
|
|
16332
16869
|
try {
|
|
16333
16870
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -16339,7 +16876,7 @@ Examples:
|
|
|
16339
16876
|
if (!options.unset && (name === void 0 || name === "")) {
|
|
16340
16877
|
throw new CLIError(
|
|
16341
16878
|
"USAGE_ERROR",
|
|
16342
|
-
`Provide a space name (e.g. \`${
|
|
16879
|
+
`Provide a space name (e.g. \`${CLI_NAME25} profile set-default-space applications\`) or pass --unset.`,
|
|
16343
16880
|
ExitCode.USAGE_ERROR
|
|
16344
16881
|
);
|
|
16345
16882
|
}
|
|
@@ -16398,7 +16935,7 @@ function parseOperatorType(raw) {
|
|
|
16398
16935
|
|
|
16399
16936
|
// src/commands/replica.ts
|
|
16400
16937
|
init_profile_replicas();
|
|
16401
|
-
import { CLI_NAME as
|
|
16938
|
+
import { CLI_NAME as CLI_NAME26 } from "@tinycloud/operations/cli-name";
|
|
16402
16939
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
16403
16940
|
import { constants as fsConstants5 } from "fs";
|
|
16404
16941
|
import { mkdir as mkdir5, open as open9, readdir as readdir6, rename as rename4, unlink as unlink4, lstat as lstat9 } from "fs/promises";
|
|
@@ -16539,11 +17076,11 @@ var EXIT_BY_CODE = {
|
|
|
16539
17076
|
var READ_FAILURE = {
|
|
16540
17077
|
absent: { code: "KEY_ABSENT", exit: ExitCode.NOT_FOUND, what: "is not in the replica (the replica covers its whole prefix)" },
|
|
16541
17078
|
deleted: { code: "KEY_DELETED", exit: ExitCode.NOT_FOUND, what: "was deleted at the source" },
|
|
16542
|
-
content_missing: { code: "CONTENT_MISSING", exit: ExitCode.NOT_FOUND, what: `is known but its content has not been fetched yet; run ${
|
|
17079
|
+
content_missing: { code: "CONTENT_MISSING", exit: ExitCode.NOT_FOUND, what: `is known but its content has not been fetched yet; run ${CLI_NAME26} replica sync` },
|
|
16543
17080
|
coverage_incomplete: {
|
|
16544
17081
|
code: "COVERAGE_INCOMPLETE",
|
|
16545
17082
|
exit: ExitCode.NOT_FOUND,
|
|
16546
|
-
what: `is not in the replica, which has not finished its first sync; run ${
|
|
17083
|
+
what: `is not in the replica, which has not finished its first sync; run ${CLI_NAME26} replica sync`
|
|
16547
17084
|
},
|
|
16548
17085
|
not_covered: { code: "NOT_COVERED", exit: ExitCode.USAGE_ERROR, what: "is outside the replica's prefix" }
|
|
16549
17086
|
};
|
|
@@ -16661,7 +17198,7 @@ async function existingReplicaName(profile, option) {
|
|
|
16661
17198
|
const names = await replicaNames(profile);
|
|
16662
17199
|
if (names.length === 1) return names[0];
|
|
16663
17200
|
if (names.length === 0) {
|
|
16664
|
-
throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, `Profile "${profile}" has no replicas. Create one with: ${
|
|
17201
|
+
throw new ReplicaError(ReplicaErrorCode.NOT_FOUND, `Profile "${profile}" has no replicas. Create one with: ${CLI_NAME26} replica sync --prefix <prefix>`);
|
|
16665
17202
|
}
|
|
16666
17203
|
throw new CLIError("USAGE_ERROR", `Profile "${profile}" has several replicas (${names.join(", ")}); pass --replica <name>.`, ExitCode.USAGE_ERROR);
|
|
16667
17204
|
}
|
|
@@ -16769,9 +17306,9 @@ async function storedGrants(profile) {
|
|
|
16769
17306
|
function grantHint(space, prefix) {
|
|
16770
17307
|
return [
|
|
16771
17308
|
"A replica needs a device grant with get and sync on its prefix:",
|
|
16772
|
-
` device: ${
|
|
16773
|
-
` owner: ${
|
|
16774
|
-
` device: ${
|
|
17309
|
+
` device: ${CLI_NAME26} auth request --cap tinycloud.kv:${space}:${prefix}:get,list,metadata,sync --expiry 30d --emit req.json`,
|
|
17310
|
+
` owner: ${CLI_NAME26} auth grant req.json > grant.json`,
|
|
17311
|
+
` device: ${CLI_NAME26} auth import grant.json`
|
|
16775
17312
|
].join("\n");
|
|
16776
17313
|
}
|
|
16777
17314
|
function resolveSpace(input, prefix, grants) {
|
|
@@ -16862,7 +17399,7 @@ function describeStatus(status) {
|
|
|
16862
17399
|
formatField("Size", formatBytes(status.bytes)),
|
|
16863
17400
|
formatField("Grant", status.device.delegationCid),
|
|
16864
17401
|
formatField("Last error", status.lastError ? `${status.lastError.code}: ${status.lastError.message}` : null),
|
|
16865
|
-
formatField("Purge", status.purgePending ? `incomplete: content files remain; the next ${
|
|
17402
|
+
formatField("Purge", status.purgePending ? `incomplete: content files remain; the next ${CLI_NAME26} replica command retries` : null)
|
|
16866
17403
|
].join("\n");
|
|
16867
17404
|
}
|
|
16868
17405
|
async function writeFileAtomic(path, bytes) {
|
|
@@ -17026,7 +17563,7 @@ or coverage incomplete; 5 grant expired, revoked or not yet valid; 6 network;
|
|
|
17026
17563
|
(options, cmd) => run(async () => {
|
|
17027
17564
|
const profile = await profileName(cmd);
|
|
17028
17565
|
if (options.purge) {
|
|
17029
|
-
throw new CLIError("REPORT_READ_ONLY", `Replica report is read-only. To remove a named local replica, run ${
|
|
17566
|
+
throw new CLIError("REPORT_READ_ONLY", `Replica report is read-only. To remove a named local replica, run ${CLI_NAME26} replica reset --replica <name> --purge; flag-owned purge has no equivalent yet (TC-1309).`, ExitCode.USAGE_ERROR);
|
|
17030
17567
|
}
|
|
17031
17568
|
if (options.clearPending) {
|
|
17032
17569
|
throw new CLIError("REPORT_READ_ONLY", "Clearing pending replica writes has no equivalent yet; see TC-1309 for a safe replacement command.", ExitCode.USAGE_ERROR);
|
|
@@ -17128,7 +17665,7 @@ or coverage incomplete; 5 grant expired, revoked or not yet valid; 6 network;
|
|
|
17128
17665
|
if (differs !== void 0) {
|
|
17129
17666
|
throw new ReplicaError(
|
|
17130
17667
|
ReplicaErrorCode.CONFIG_MISMATCH,
|
|
17131
|
-
`Replica ${name} already follows ${differs}. Use another --replica name, or ${
|
|
17668
|
+
`Replica ${name} already follows ${differs}. Use another --replica name, or ${CLI_NAME26} replica reset --replica ${name} --purge first.`
|
|
17132
17669
|
);
|
|
17133
17670
|
}
|
|
17134
17671
|
}
|
|
@@ -17293,12 +17830,12 @@ init_theme();
|
|
|
17293
17830
|
init_errors();
|
|
17294
17831
|
init_warnings();
|
|
17295
17832
|
init_constants();
|
|
17296
|
-
import { CLI_NAME as
|
|
17833
|
+
import { CLI_NAME as CLI_NAME28 } from "@tinycloud/operations/cli-name";
|
|
17297
17834
|
import { readFile as readFile12 } from "fs/promises";
|
|
17298
17835
|
import { join as join16 } from "path";
|
|
17299
17836
|
import { homedir, hostname, userInfo } from "os";
|
|
17300
17837
|
import { appSecretScope } from "@tinycloud/sdk-core";
|
|
17301
|
-
import { invokeOperation as
|
|
17838
|
+
import { invokeOperation as invokeOperation3 } from "@tinycloud/operations";
|
|
17302
17839
|
import { isLocalOwnerProfile as isLocalOwnerProfile4, ownerSessionLacksProof as ownerSessionLacksProof5 } from "@tinycloud/operations/profile";
|
|
17303
17840
|
import {
|
|
17304
17841
|
buildSecretManagerUrl,
|
|
@@ -17504,7 +18041,7 @@ function parseStatusEnvelope(line) {
|
|
|
17504
18041
|
// src/lib/secrets-wait.ts
|
|
17505
18042
|
init_constants();
|
|
17506
18043
|
init_errors();
|
|
17507
|
-
import { CLI_NAME as
|
|
18044
|
+
import { CLI_NAME as CLI_NAME27 } from "@tinycloud/operations/cli-name";
|
|
17508
18045
|
var WAIT_BACKOFF_MS = [3e3, 4500, 6750, 10125];
|
|
17509
18046
|
var WAIT_STEADY_INTERVAL_MS = 15e3;
|
|
17510
18047
|
var DEFAULT_WAIT_TIMEOUT = "5m";
|
|
@@ -17512,7 +18049,7 @@ var MAX_WAIT_TIMEOUT_MS = 9 * 6e4;
|
|
|
17512
18049
|
var MAX_CONSECUTIVE_FAILURES = 5;
|
|
17513
18050
|
var TIMEOUT_RE = /^(0|\d+s|\d+m)$/;
|
|
17514
18051
|
var RETRYABLE_STATUS_ERRORS = { NODE_UNREACHABLE: true, NODE_ERROR: true };
|
|
17515
|
-
var EXPLICIT_KEY_HINT = `${
|
|
18052
|
+
var EXPLICIT_KEY_HINT = `${CLI_NAME27} secrets wait never signs in, and an explicit private key has no stored session. Use a local profile signed in once with ${CLI_NAME27} --profile <profile> auth login --method local, without TC_PRIVATE_KEY.`;
|
|
17516
18053
|
function abortableSleep(ms2, signal) {
|
|
17517
18054
|
return new Promise((resolve8) => {
|
|
17518
18055
|
if (signal.aborted) {
|
|
@@ -17548,7 +18085,7 @@ async function runSecretsWait(request, deps) {
|
|
|
17548
18085
|
let step = 0;
|
|
17549
18086
|
const cancelled = () => new CLIError(
|
|
17550
18087
|
"CANCELLED",
|
|
17551
|
-
`Interrupted: ${
|
|
18088
|
+
`Interrupted: ${CLI_NAME27} secrets wait stopped before the secrets were ready.`,
|
|
17552
18089
|
ExitCode.CANCELLED,
|
|
17553
18090
|
progress === void 0 ? {} : { progress }
|
|
17554
18091
|
);
|
|
@@ -17692,13 +18229,13 @@ function statusError(error, profile, warnings) {
|
|
|
17692
18229
|
if (error.code === "AUTH_REQUIRED" || error.code === "SESSION_NOT_FOUND") {
|
|
17693
18230
|
const recovery = recoveryOf(error, "sign_in");
|
|
17694
18231
|
if (recovery !== void 0) {
|
|
17695
|
-
const hint = recovery.command !== void 0 ? `Sign in once with: ${recovery.command}, then retry.` : `Sign in to profile "${profile}" once with ${
|
|
18232
|
+
const hint = recovery.command !== void 0 ? `Sign in once with: ${recovery.command}, then retry.` : `Sign in to profile "${profile}" once with ${CLI_NAME27} auth login, then retry.`;
|
|
17696
18233
|
return new CLIError("AUTH_REQUIRED", error.message, ExitCode.AUTH_REQUIRED, { hint, recovery, ...extra });
|
|
17697
18234
|
}
|
|
17698
18235
|
}
|
|
17699
18236
|
if (error.code === "PROFILE_MIGRATION_REQUIRED") {
|
|
17700
18237
|
const recovery = recoveryOf(error, "complete_migration");
|
|
17701
|
-
const hint = recovery?.command !== void 0 ? `Run ${recovery.command} once to finish this profile's one-time upgrade, then retry wait.` : `Run ${
|
|
18238
|
+
const hint = recovery?.command !== void 0 ? `Run ${recovery.command} once to finish this profile's one-time upgrade, then retry wait.` : `Run ${CLI_NAME27} auth caps once with profile "${profile}" to finish its one-time upgrade, then retry wait.`;
|
|
17702
18239
|
return new CLIError("PROFILE_MIGRATION_REQUIRED", error.message, ExitCode.ERROR, {
|
|
17703
18240
|
hint,
|
|
17704
18241
|
...recovery === void 0 ? {} : { recovery },
|
|
@@ -17890,11 +18427,11 @@ async function runSecretOperation(params) {
|
|
|
17890
18427
|
}
|
|
17891
18428
|
function secretPermissionReason(action, name) {
|
|
17892
18429
|
const target = name ? ` secret "${name}"` : " secrets";
|
|
17893
|
-
return `Allow \`${
|
|
18430
|
+
return `Allow \`${CLI_NAME28} secrets ${action}${name ? ` ${name}` : ""}\` to access${target} with the required TinyCloud permissions.`;
|
|
17894
18431
|
}
|
|
17895
18432
|
function assertOwnerApprovalPossible(profileName2, profile, action, name, missing = []) {
|
|
17896
18433
|
if (profile.authMethod !== "openkey" || process.stdin.isTTY || process.stderr.isTTY) return;
|
|
17897
|
-
const command = `${
|
|
18434
|
+
const command = `${CLI_NAME28} secrets ${action === "del" ? "delete" : action}${name ? ` ${name}` : ""}`;
|
|
17898
18435
|
throw new CLIError(
|
|
17899
18436
|
"PERMISSION_DENIED",
|
|
17900
18437
|
action === "get" ? missing.length > 0 && missing.every((permission) => permission.service === "tinycloud.encryption" && Array.isArray(permission.actions) && permission.actions.length > 0 && permission.actions.every((capability) => capability === SECRET_DECRYPT_CAPABILITY)) ? `Profile "${profileName2}" lacks the scoped decrypt authority (${SECRET_DECRYPT_CAPABILITY}) needed to read secret "${name}", and requesting it needs an interactive browser approval.` : `Profile "${profileName2}" lacks a scoped read or decrypt grant needed to read secret "${name}", and requesting it needs an interactive browser approval.` : `Profile "${profileName2}" holds no grant for \`${command}\`, and requesting one needs an interactive browser approval.`,
|
|
@@ -17905,7 +18442,7 @@ function assertOwnerApprovalPossible(profileName2, profile, action, name, missin
|
|
|
17905
18442
|
);
|
|
17906
18443
|
}
|
|
17907
18444
|
function scopedSecretLoginHint(profileName2) {
|
|
17908
|
-
return `Have the owner approve a scoped login whose manifest names the secret: ${
|
|
18445
|
+
return `Have the owner approve a scoped login whose manifest names the secret: ${CLI_NAME28} --profile ${profileName2} auth login --method openkey --paste --manifest <manifest.json>. Pass the owner's code on stdin, newline-terminated.`;
|
|
17909
18446
|
}
|
|
17910
18447
|
async function runSecretOperationAttempt(label, operation) {
|
|
17911
18448
|
try {
|
|
@@ -17966,7 +18503,7 @@ async function canonicalSecretGet(params, collect) {
|
|
|
17966
18503
|
};
|
|
17967
18504
|
const invoke = async () => collect(await withSpinner(
|
|
17968
18505
|
params.label,
|
|
17969
|
-
() => auth?.privateKey ? invokeSecretsGetWithLocalAuthorityRetry(target, input) :
|
|
18506
|
+
() => auth?.privateKey ? invokeSecretsGetWithLocalAuthorityRetry(target, input) : invokeOperation3("tinycloud.secrets.get", 1, target, input)
|
|
17970
18507
|
));
|
|
17971
18508
|
let first = await invoke();
|
|
17972
18509
|
if (first.status === "error" && first.error.code === "SESSION_NOT_FOUND" && auth?.privateKey === void 0) {
|
|
@@ -18039,7 +18576,7 @@ ${message}`,
|
|
|
18039
18576
|
"AUTH_REQUIRED",
|
|
18040
18577
|
"Not signed in to TinyCloud.",
|
|
18041
18578
|
ExitCode.AUTH_REQUIRED,
|
|
18042
|
-
{ hint: `Sign in with: ${
|
|
18579
|
+
{ hint: `Sign in with: ${CLI_NAME28} --profile ${result.context.profile} auth login` }
|
|
18043
18580
|
);
|
|
18044
18581
|
}
|
|
18045
18582
|
if (result.error.code === "NODE_UNREACHABLE") {
|
|
@@ -18516,7 +19053,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
18516
19053
|
name: "Encryption network",
|
|
18517
19054
|
ok: false,
|
|
18518
19055
|
detail: `${networkName} not found`,
|
|
18519
|
-
hint: `${
|
|
19056
|
+
hint: `${CLI_NAME28} secrets network init ${networkName}`
|
|
18520
19057
|
}
|
|
18521
19058
|
];
|
|
18522
19059
|
let secret;
|
|
@@ -18562,7 +19099,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
18562
19099
|
name: "Secret access",
|
|
18563
19100
|
ok: false,
|
|
18564
19101
|
detail: notFound ? `${resolved.permissionPaths.vault} not found` : result.error.message,
|
|
18565
|
-
hint: notFound ? local ? `${
|
|
19102
|
+
hint: notFound ? local ? `${CLI_NAME28} secrets put ${resolved.name}${formatSecretScopeFlag(scopeOptions)} --stdin` : `${CLI_NAME28} secrets request ${resolved.name}${formatSecretScopeFlag(scopeOptions)}` : `Ask the owner profile to grant ${secretCapabilityAction("get")} and ${SECRET_DECRYPT_CAPABILITY}.`
|
|
18566
19103
|
});
|
|
18567
19104
|
}
|
|
18568
19105
|
} else {
|
|
@@ -18806,7 +19343,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
18806
19343
|
if (effectiveLocal(profile, options)) {
|
|
18807
19344
|
throw new CLIError(
|
|
18808
19345
|
"LOCAL_OWNER_PROFILE",
|
|
18809
|
-
`Profile "${ctx.profile}" uses a local key, which owns its secrets: store them with ${
|
|
19346
|
+
`Profile "${ctx.profile}" uses a local key, which owns its secrets: store them with ${CLI_NAME28} secrets put NAME --stdin instead of requesting them.`,
|
|
18810
19347
|
ExitCode.USAGE_ERROR
|
|
18811
19348
|
);
|
|
18812
19349
|
}
|
|
@@ -18909,7 +19446,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
18909
19446
|
try {
|
|
18910
19447
|
const ctx = await ProfileManager.resolveContext(cmd.optsWithGlobals());
|
|
18911
19448
|
const privateKey = process.env.TC_PRIVATE_KEY;
|
|
18912
|
-
const result = await
|
|
19449
|
+
const result = await invokeOperation3("tinycloud.secrets.status", 1, {
|
|
18913
19450
|
profile: ctx.profile,
|
|
18914
19451
|
host: ctx.host,
|
|
18915
19452
|
allowOwnerProfile: true,
|
|
@@ -18929,7 +19466,7 @@ function registerSecretsCommand(program, openKeyAcquisition) {
|
|
|
18929
19466
|
|
|
18930
19467
|
// src/commands/share.ts
|
|
18931
19468
|
import { Option } from "commander";
|
|
18932
|
-
import { CLI_NAME as
|
|
19469
|
+
import { CLI_NAME as CLI_NAME29 } from "@tinycloud/operations/cli-name";
|
|
18933
19470
|
|
|
18934
19471
|
// ../share-sdk/dist/artifact.js
|
|
18935
19472
|
init_sha256();
|
|
@@ -29779,14 +30316,14 @@ function assertKey(key32) {
|
|
|
29779
30316
|
throw new TypeError(`key must be ${KEY_LENGTH} bytes, got ${key32.length}`);
|
|
29780
30317
|
}
|
|
29781
30318
|
}
|
|
29782
|
-
async function importAesKey(key32,
|
|
30319
|
+
async function importAesKey(key32, usage3) {
|
|
29783
30320
|
assertKey(key32);
|
|
29784
30321
|
return globalThis.crypto.subtle.importKey(
|
|
29785
30322
|
"raw",
|
|
29786
30323
|
key32,
|
|
29787
30324
|
"AES-GCM",
|
|
29788
30325
|
false,
|
|
29789
|
-
[
|
|
30326
|
+
[usage3]
|
|
29790
30327
|
);
|
|
29791
30328
|
}
|
|
29792
30329
|
async function open11(blob, key32) {
|
|
@@ -37114,12 +37651,12 @@ function shareCliError(error, operation = "publish") {
|
|
|
37114
37651
|
const profileName2 = "profileName" in failure ? failure.profileName : void 0;
|
|
37115
37652
|
const localKey = "localKey" in failure && failure.localKey === true;
|
|
37116
37653
|
const profileHint = profileName2 === void 0 ? "" : `--profile ${profileName2} `;
|
|
37117
|
-
const loginHint = localKey ? `\`${
|
|
37654
|
+
const loginHint = localKey ? `\`${CLI_NAME29} ${profileHint}auth login --method local\`` : `\`${CLI_NAME29} ${profileHint}auth login --device --manifest builtin:share-publishing\` (or \`${CLI_NAME29} ${profileHint}enable share\`)`;
|
|
37118
37655
|
if (failure.kind === "caveated-session") {
|
|
37119
37656
|
const holder = profileName2 === void 0 ? "this session" : `profile ${profileName2}'s session`;
|
|
37120
37657
|
return new CLIError(
|
|
37121
37658
|
"PERMISSION_DENIED",
|
|
37122
|
-
`${holder} carries signed restrictions (caveats) on the authority an anyone-with-link share needs, so it cannot create the share link; nothing was shared. Approve Share publishing without restrictions on a new, dedicated profile (any unused name): \`${
|
|
37659
|
+
`${holder} carries signed restrictions (caveats) on the authority an anyone-with-link share needs, so it cannot create the share link; nothing was shared. Approve Share publishing without restrictions on a new, dedicated profile (any unused name): \`${CLI_NAME29} init --name publisher --key-only && ${CLI_NAME29} --profile publisher enable share\``,
|
|
37123
37660
|
5
|
|
37124
37661
|
);
|
|
37125
37662
|
}
|
|
@@ -37205,7 +37742,7 @@ function shareCliError(error, operation = "publish") {
|
|
|
37205
37742
|
if (nodeCode === "ENOENT") return new CLIError("INVALID_ARGUMENT", "share input was not found", 2);
|
|
37206
37743
|
if (nodeCode === "EISDIR") return new CLIError("INVALID_ARGUMENT", "share input must be a Markdown file", 2);
|
|
37207
37744
|
const mapped = typeof nodeCode === "string" && known[nodeCode] !== void 0 ? known[nodeCode] : known[message];
|
|
37208
|
-
const classified = mapped === void 0 ? classifyShareFailure(error, `\`${
|
|
37745
|
+
const classified = mapped === void 0 ? classifyShareFailure(error, `\`${CLI_NAME29} auth login\``) : void 0;
|
|
37209
37746
|
if (classified !== void 0) return Object.assign(new CLIError(classified.code, classified.reason, classified.exitCode), { cause: error });
|
|
37210
37747
|
if (error instanceof TypeError) return new CLIError("INVALID_ARGUMENT", "share input is invalid", 2);
|
|
37211
37748
|
return new CLIError(mapped?.code ?? "INVALID_ARGUMENT", mapped ? mapped.code : "share operation failed", mapped?.exit ?? 2);
|
|
@@ -37538,7 +38075,7 @@ init_constants();
|
|
|
37538
38075
|
init_sdk();
|
|
37539
38076
|
init_host();
|
|
37540
38077
|
init_theme();
|
|
37541
|
-
import { CLI_NAME as
|
|
38078
|
+
import { CLI_NAME as CLI_NAME30 } from "@tinycloud/operations/cli-name";
|
|
37542
38079
|
import { ACCOUNT_REGISTRY_SPACE } from "@tinycloud/node-sdk";
|
|
37543
38080
|
import { randomBytes as randomBytes9 } from "crypto";
|
|
37544
38081
|
import { mkdir as mkdir7, writeFile as writeFile4 } from "fs/promises";
|
|
@@ -37585,7 +38122,7 @@ function registerSpaceCommand(program) {
|
|
|
37585
38122
|
} else {
|
|
37586
38123
|
const profile = await ProfileManager.getProfile(ctx.profile);
|
|
37587
38124
|
const cap = "tinycloud.kv:account:spaces/:get,list";
|
|
37588
|
-
const hint = resolveProfilePosture(profile) === "delegate-session" ? `Ask the space owner to grant it. Create the request with: ${
|
|
38125
|
+
const hint = resolveProfilePosture(profile) === "delegate-session" ? `Ask the space owner to grant it. Create the request with: ${CLI_NAME30} --profile ${ctx.profile} auth request --cap ${cap} --emit` : `Grant it to this session with: ${CLI_NAME30} --profile ${ctx.profile} auth request --grant --cap ${cap}. The default OpenKey owner sign-in covers only the default space, so it includes neither.`;
|
|
37589
38126
|
throw new CLIError(
|
|
37590
38127
|
"PERMISSION_DENIED",
|
|
37591
38128
|
`Listing spaces needs tinycloud.kv/get and tinycloud.kv/list on the account space's spaces/ registry (${accountSpaceId ?? ACCOUNT_REGISTRY_SPACE}), or tinycloud.space/list. This session holds neither.`,
|
|
@@ -37622,10 +38159,10 @@ function registerSpaceCommand(program) {
|
|
|
37622
38159
|
|
|
37623
38160
|
A delegate cannot host a space \u2014 only its owner (root authority) can. This
|
|
37624
38161
|
emits a tinycloud.host.request artifact naming the space and its owner so you
|
|
37625
|
-
can hand it to the owner; they run \`${
|
|
38162
|
+
can hand it to the owner; they run \`${CLI_NAME30} space host <name>\` and confirm.
|
|
37626
38163
|
|
|
37627
38164
|
If you ARE the owner of the resolved space, this refuses and tells you to host
|
|
37628
|
-
it directly with \`${
|
|
38165
|
+
it directly with \`${CLI_NAME30} space host <name>\` (no request needed).
|
|
37629
38166
|
`).action(async (name, options, cmd) => {
|
|
37630
38167
|
try {
|
|
37631
38168
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -37636,7 +38173,7 @@ it directly with \`${CLI_NAME28} space host <name>\` (no request needed).
|
|
|
37636
38173
|
if (await isRootAuthority(spaceId, ctx.profile)) {
|
|
37637
38174
|
throw new CLIError(
|
|
37638
38175
|
"ALREADY_ROOT_AUTHORITY",
|
|
37639
|
-
`You are the owner of ${spaceId}. Host it directly: ${
|
|
38176
|
+
`You are the owner of ${spaceId}. Host it directly: ${CLI_NAME30} space host ${spaceName}`,
|
|
37640
38177
|
ExitCode.USAGE_ERROR
|
|
37641
38178
|
);
|
|
37642
38179
|
}
|
|
@@ -37722,7 +38259,7 @@ init_sdk();
|
|
|
37722
38259
|
init_space();
|
|
37723
38260
|
init_host();
|
|
37724
38261
|
init_theme();
|
|
37725
|
-
import { CLI_NAME as
|
|
38262
|
+
import { CLI_NAME as CLI_NAME31 } from "@tinycloud/operations/cli-name";
|
|
37726
38263
|
import { writeFile as writeFile5 } from "fs/promises";
|
|
37727
38264
|
import { resolve as resolve7 } from "path";
|
|
37728
38265
|
async function dbHandle(node, dbName, spaceInput, profileName2) {
|
|
@@ -37746,12 +38283,12 @@ database for simple apps, or pass --db to target a named database. Pass
|
|
|
37746
38283
|
--space to target a non-primary space (e.g. the manifest "applications" space).
|
|
37747
38284
|
|
|
37748
38285
|
Common workflows:
|
|
37749
|
-
$ ${
|
|
37750
|
-
$ ${
|
|
37751
|
-
$ ${
|
|
37752
|
-
$ ${
|
|
37753
|
-
$ ${
|
|
37754
|
-
$ ${
|
|
38286
|
+
$ ${CLI_NAME31} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
|
|
38287
|
+
$ ${CLI_NAME31} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["ship docs"]'
|
|
38288
|
+
$ ${CLI_NAME31} sql query "SELECT id, body FROM notes ORDER BY id"
|
|
38289
|
+
$ ${CLI_NAME31} sql query "SELECT * FROM events WHERE type = ?" --db analytics --params '["signup"]'
|
|
38290
|
+
$ ${CLI_NAME31} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
|
|
38291
|
+
$ ${CLI_NAME31} sql export --db analytics --output analytics.db
|
|
37755
38292
|
|
|
37756
38293
|
Commands:
|
|
37757
38294
|
query Read rows with SELECT statements
|
|
@@ -37768,10 +38305,10 @@ Tips:
|
|
|
37768
38305
|
sql.command("query <sql>").description("Run a read-only SELECT query").option("--db <name>", "SQLite database name within the current space", "default").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").option("--params <json>", "Bind parameters as a JSON array for ? placeholders").addHelpText("after", `
|
|
37769
38306
|
|
|
37770
38307
|
Examples:
|
|
37771
|
-
$ ${
|
|
37772
|
-
$ ${
|
|
37773
|
-
$ ${
|
|
37774
|
-
$ ${
|
|
38308
|
+
$ ${CLI_NAME31} sql query "SELECT * FROM notes ORDER BY id"
|
|
38309
|
+
$ ${CLI_NAME31} sql query "SELECT * FROM notes WHERE id = ?" --params '[42]'
|
|
38310
|
+
$ ${CLI_NAME31} sql query "SELECT count(*) AS total FROM events" --db analytics --json
|
|
38311
|
+
$ ${CLI_NAME31} sql query "SELECT count(*) FROM conversation" --space applications --db xyz.tinycloud.listen/conversations
|
|
37775
38312
|
|
|
37776
38313
|
Output:
|
|
37777
38314
|
Human output is formatted as a table. Piped output or --json returns
|
|
@@ -37812,11 +38349,11 @@ ${rowCount} row${rowCount === 1 ? "" : "s"} returned`) + "\n");
|
|
|
37812
38349
|
sql.command("execute <sql>").description("Run a write or schema statement").option("--db <name>", "SQLite database name within the current space", "default").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").option("--params <json>", "Bind parameters as a JSON array for ? placeholders").addHelpText("after", `
|
|
37813
38350
|
|
|
37814
38351
|
Examples:
|
|
37815
|
-
$ ${
|
|
37816
|
-
$ ${
|
|
37817
|
-
$ ${
|
|
37818
|
-
$ ${
|
|
37819
|
-
$ ${
|
|
38352
|
+
$ ${CLI_NAME31} sql execute "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)"
|
|
38353
|
+
$ ${CLI_NAME31} sql execute "INSERT INTO notes (body) VALUES (?)" --params '["first note"]'
|
|
38354
|
+
$ ${CLI_NAME31} sql execute "UPDATE notes SET body = ? WHERE id = ?" --params '["edited", 1]'
|
|
38355
|
+
$ ${CLI_NAME31} sql execute "DROP TABLE old_notes" --db archive
|
|
38356
|
+
$ ${CLI_NAME31} sql execute "DELETE FROM conversation WHERE id = ?" --space applications --db xyz.tinycloud.listen/conversations --params '["abc"]'
|
|
37820
38357
|
|
|
37821
38358
|
Output:
|
|
37822
38359
|
Returns JSON with the changed row count and last inserted row id when available.
|
|
@@ -37845,9 +38382,9 @@ Output:
|
|
|
37845
38382
|
sql.command("export").description("Export a SQLite database as a binary .db file").option("--db <name>", "SQLite database name within the current space", "default").option("--space <name|uri>", "Target a non-primary space (short name or full URI)").option("-o, --output <file>", "Output file path", "export.db").addHelpText("after", `
|
|
37846
38383
|
|
|
37847
38384
|
Examples:
|
|
37848
|
-
$ ${
|
|
37849
|
-
$ ${
|
|
37850
|
-
$ ${
|
|
38385
|
+
$ ${CLI_NAME31} sql export
|
|
38386
|
+
$ ${CLI_NAME31} sql export --db analytics --output analytics.db
|
|
38387
|
+
$ ${CLI_NAME31} sql export --space applications --db xyz.tinycloud.listen/conversations --output listen.db
|
|
37851
38388
|
|
|
37852
38389
|
Output:
|
|
37853
38390
|
Writes the database file locally and returns JSON with the path and size.
|
|
@@ -37880,19 +38417,19 @@ Output:
|
|
|
37880
38417
|
sql.command("copy").description("Copy rows between SQL databases (optionally across spaces)").requiredOption("--from-db <name>", "Source database name").requiredOption("--to-db <name>", "Destination database name").option("--from-space <name|uri>", "Source space (defaults to primary)").option("--to-space <name|uri>", "Destination space (defaults to primary)").option("--table <name...>", "Restrict copy to specific tables (repeat or comma-separated)").option("--dry-run", "Print the plan without writing", false).addHelpText("after", `
|
|
37881
38418
|
|
|
37882
38419
|
Examples:
|
|
37883
|
-
$ ${
|
|
38420
|
+
$ ${CLI_NAME31} sql copy --from-db com.tinycloud.conversation-sync/conversations \\
|
|
37884
38421
|
--to-db xyz.tinycloud.listen/conversations \\
|
|
37885
38422
|
--space applications --dry-run
|
|
37886
|
-
$ ${
|
|
38423
|
+
$ ${CLI_NAME31} sql copy --from-space applications --from-db com.foo/data \\
|
|
37887
38424
|
--to-space applications --to-db com.bar/data \\
|
|
37888
38425
|
--table conversation --table participant
|
|
37889
38426
|
|
|
37890
38427
|
Notes:
|
|
37891
38428
|
- Refuses to run when (resolved space, db) is identical for source and destination.
|
|
37892
|
-
- Does NOT create destination tables. Run the target app once (or use \`${
|
|
38429
|
+
- Does NOT create destination tables. Run the target app once (or use \`${CLI_NAME31} sql execute\`)
|
|
37893
38430
|
to materialize the schema before copying.
|
|
37894
38431
|
- One row at a time; suitable for small/medium datasets. Large copies should
|
|
37895
|
-
use \`${
|
|
38432
|
+
use \`${CLI_NAME31} sql export\` + bulk import.
|
|
37896
38433
|
- Authorization: the active session/delegation must cover sql/read on source
|
|
37897
38434
|
AND sql/write on destination. Otherwise the relevant operation will fail.
|
|
37898
38435
|
`).action(async (options, cmd) => {
|
|
@@ -37992,7 +38529,7 @@ init_errors();
|
|
|
37992
38529
|
init_formatter();
|
|
37993
38530
|
init_theme();
|
|
37994
38531
|
init_permissions();
|
|
37995
|
-
import { CLI_NAME as
|
|
38532
|
+
import { CLI_NAME as CLI_NAME32 } from "@tinycloud/operations/cli-name";
|
|
37996
38533
|
import {
|
|
37997
38534
|
NodeWasmBindings as NodeWasmBindings2
|
|
37998
38535
|
} from "@tinycloud/node-sdk";
|
|
@@ -38266,7 +38803,7 @@ function formatStatus(summary) {
|
|
|
38266
38803
|
lines.push(`Default profile: ${theme.value(summary.defaultProfile)}`);
|
|
38267
38804
|
lines.push("");
|
|
38268
38805
|
if (summary.profiles.length === 0) {
|
|
38269
|
-
lines.push(theme.muted(`No profiles configured. Run: ${
|
|
38806
|
+
lines.push(theme.muted(`No profiles configured. Run: ${CLI_NAME32} init`));
|
|
38270
38807
|
return `${lines.join("\n")}
|
|
38271
38808
|
`;
|
|
38272
38809
|
}
|
|
@@ -38764,6 +39301,7 @@ function registerTinyCloudCommands(program) {
|
|
|
38764
39301
|
registerSqlCommand(program);
|
|
38765
39302
|
registerDuckdbCommand(program);
|
|
38766
39303
|
registerManifestCommand(program);
|
|
39304
|
+
registerKnowledgeCommand(program);
|
|
38767
39305
|
registerUpgradeCommand(program);
|
|
38768
39306
|
registerStatusCommand(program);
|
|
38769
39307
|
registerAccountCommand(program);
|