saturndocs 0.1.0

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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/dist/auth-FRCFGNYT.js +14 -0
  3. package/dist/brokenLinks-TTOBP4P7.js +48 -0
  4. package/dist/build-F5PWGP57.js +39 -0
  5. package/dist/chunk-3P2OFTXR.js +71 -0
  6. package/dist/chunk-3V753MRI.js +100 -0
  7. package/dist/chunk-5KX4ZK5E.js +104 -0
  8. package/dist/chunk-CMNX5DLQ.js +119 -0
  9. package/dist/chunk-GYRVGWDM.js +3499 -0
  10. package/dist/chunk-I6U4V7B4.js +1552 -0
  11. package/dist/chunk-J6P3VGGE.js +21 -0
  12. package/dist/chunk-KGJESHJC.js +37 -0
  13. package/dist/chunk-M2BZEDAS.js +57 -0
  14. package/dist/chunk-N7QPZMLP.js +82 -0
  15. package/dist/chunk-OEAEX5YW.js +51 -0
  16. package/dist/chunk-OZWTXMO3.js +584 -0
  17. package/dist/chunk-QLFODLVN.js +46 -0
  18. package/dist/chunk-R3X2DPFF.js +568 -0
  19. package/dist/chunk-S45UWCML.js +588 -0
  20. package/dist/chunk-SABK3R2P.js +2452 -0
  21. package/dist/chunk-STCCGFKC.js +246 -0
  22. package/dist/chunk-TKISMN3P.js +39 -0
  23. package/dist/chunk-U7PKMSB3.js +465 -0
  24. package/dist/chunk-VNYDYHXM.js +22 -0
  25. package/dist/chunk-VXEUNRVA.js +26 -0
  26. package/dist/chunk-XO4CUC7V.js +99 -0
  27. package/dist/cli.d.ts +1 -0
  28. package/dist/cli.js +95 -0
  29. package/dist/deploy-QU7DCKWU.js +11 -0
  30. package/dist/dev-VDSDCBO2.js +43 -0
  31. package/dist/index.d.ts +382 -0
  32. package/dist/index.js +191 -0
  33. package/dist/init-7T5TEPBH.js +14 -0
  34. package/dist/manage-MKH27U3B.js +12 -0
  35. package/dist/openapiCheck-KGBQJTIQ.js +66 -0
  36. package/dist/pages-HIPXRLSY.js +15 -0
  37. package/dist/read-DMSJUEDA.js +160 -0
  38. package/dist/read-ZEVUZNNB.js +146 -0
  39. package/dist/requests-H5MIGBIS.js +13 -0
  40. package/dist/schema-XJUEZSIW.js +13 -0
  41. package/dist/sites-I4WI2MPG.js +14 -0
  42. package/dist/status-7R3NOI5H.js +14 -0
  43. package/dist/suggestions-4WFQ3APX.js +739 -0
  44. package/dist/validate-VXL4PL42.js +30 -0
  45. package/package.json +68 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SaturnDocs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,14 @@
1
+ import {
2
+ AUTH_METADATA,
3
+ authStatusOf,
4
+ registerAuth
5
+ } from "./chunk-3P2OFTXR.js";
6
+ import "./chunk-TKISMN3P.js";
7
+ import "./chunk-KGJESHJC.js";
8
+ import "./chunk-M2BZEDAS.js";
9
+ import "./chunk-GYRVGWDM.js";
10
+ export {
11
+ AUTH_METADATA,
12
+ authStatusOf,
13
+ registerAuth
14
+ };
@@ -0,0 +1,48 @@
1
+ import {
2
+ scanBrokenLinks
3
+ } from "./chunk-OEAEX5YW.js";
4
+ import {
5
+ findWebDir
6
+ } from "./chunk-VXEUNRVA.js";
7
+ import {
8
+ messageOf,
9
+ resolveDocsDir
10
+ } from "./chunk-VNYDYHXM.js";
11
+
12
+ // src/commands/brokenLinks.ts
13
+ import { existsSync } from "fs";
14
+ import { join } from "path";
15
+ import pc from "picocolors";
16
+ function registerBrokenLinks(program, cliDir) {
17
+ program.command("broken-links").description("Scan the built site for broken internal links").argument("[dir]", "docs directory (contains docs.json)").action(async (dir) => {
18
+ resolveDocsDir(dir);
19
+ let webDir;
20
+ try {
21
+ webDir = findWebDir(cliDir);
22
+ } catch (error) {
23
+ console.error(pc.red(`error: ${messageOf(error)}`));
24
+ process.exit(1);
25
+ }
26
+ let distDir = join(webDir, "dist");
27
+ if (!existsSync(distDir)) {
28
+ console.error(
29
+ pc.red(`error: ${distDir} does not exist. Run "saturndocs build" first.`)
30
+ );
31
+ process.exit(1);
32
+ }
33
+ const clientDir = join(distDir, "client");
34
+ if (existsSync(clientDir)) distDir = clientDir;
35
+ const misses = await scanBrokenLinks(distDir);
36
+ if (misses.length > 0) {
37
+ console.error(pc.red(`\u2716 ${misses.length} broken internal link(s):`));
38
+ for (const miss of misses) {
39
+ console.error(pc.red(` ${miss.source} -> ${miss.href}`));
40
+ }
41
+ process.exit(1);
42
+ }
43
+ console.log(pc.green(`\u2713 no broken internal links in ${distDir}.`));
44
+ });
45
+ }
46
+ export {
47
+ registerBrokenLinks
48
+ };
@@ -0,0 +1,39 @@
1
+ import {
2
+ findWebDir
3
+ } from "./chunk-VXEUNRVA.js";
4
+ import {
5
+ messageOf,
6
+ resolveDocsDir
7
+ } from "./chunk-VNYDYHXM.js";
8
+
9
+ // src/commands/build.ts
10
+ import { execa } from "execa";
11
+ import pc from "picocolors";
12
+ function registerBuild(program, cliDir) {
13
+ program.command("build").description("Build the static site for a docs directory").argument("[dir]", "docs directory (contains docs.json)").action(async (dir) => {
14
+ const docsDir = resolveDocsDir(dir);
15
+ let webDir;
16
+ try {
17
+ webDir = findWebDir(cliDir);
18
+ } catch (error) {
19
+ console.error(pc.red(`error: ${messageOf(error)}`));
20
+ process.exit(1);
21
+ }
22
+ console.log(pc.dim(`saturndocs build: astro build in ${webDir}`));
23
+ try {
24
+ await execa("astro", ["build"], {
25
+ cwd: webDir,
26
+ preferLocal: true,
27
+ localDir: webDir,
28
+ stdio: "inherit",
29
+ env: { SATURNDOCS_DOCS_DIR: docsDir }
30
+ });
31
+ } catch (error) {
32
+ console.error(pc.red(`astro build failed: ${messageOf(error)}`));
33
+ process.exit(1);
34
+ }
35
+ });
36
+ }
37
+ export {
38
+ registerBuild
39
+ };
@@ -0,0 +1,71 @@
1
+ import {
2
+ asCliError,
3
+ clientFor
4
+ } from "./chunk-TKISMN3P.js";
5
+ import {
6
+ applyCommandHelp
7
+ } from "./chunk-M2BZEDAS.js";
8
+ import {
9
+ commandMetadata,
10
+ createResultWriter,
11
+ failureResult,
12
+ invalidInput,
13
+ listSites,
14
+ resolveGlobalOptions,
15
+ successResult
16
+ } from "./chunk-GYRVGWDM.js";
17
+
18
+ // src/commands/auth.ts
19
+ var AUTH_METADATA = Object.freeze([
20
+ commandMetadata("auth status", {
21
+ description: "Read GET /v1/sites with the credential in SATURNDOCS_MANAGEMENT_TOKEN and report the site it resolves to. One read succeeding proves read access and nothing else: the server never returns the credential's scopes, so this command does not list them.",
22
+ target: { arguments: [] },
23
+ effect: "Nothing. One read of the site listing.",
24
+ output: { fields: ["current", "sites", "read_verified"], ids: [], limitations: [] },
25
+ errors: ["invalid_input", "unauthorized", "forbidden", "rate_limited", "service_unavailable", "transport_failure", "interrupted"],
26
+ examples: [
27
+ { command: "saturndocs auth status", description: "Check that the credential in the environment can read, and see which site it acts on." },
28
+ { command: "saturndocs auth status --json --no-input", description: "The same check as one result object, for an agent." }
29
+ ]
30
+ })
31
+ ]);
32
+ function authStatusOf(discovery) {
33
+ return { current: discovery.current, sites: discovery.sites, read_verified: true };
34
+ }
35
+ function statusText(status) {
36
+ const others = status.sites.map((site) => site.site_id).filter((id) => id !== status.current);
37
+ const reach = others.length === 0 ? "" : `
38
+ Also reached: ${others.join(", ")}.`;
39
+ return [
40
+ `Read access verified: GET /v1/sites answered.`,
41
+ `This credential acts on ${status.current}.${reach}`,
42
+ "The server does not report a credential's permissions, so nothing here says whether this one may write or approve."
43
+ ].join("\n");
44
+ }
45
+ function registerAuth(program, _cliDir, environment = {}) {
46
+ const auth = program.command("auth").description("Check the management credential in the environment");
47
+ const status = auth.command("status").action(async (_options, self) => {
48
+ const globals = resolveGlobalOptions(self);
49
+ const writer = createResultWriter({ json: globals.json });
50
+ const context = { command: "auth status", siteId: null };
51
+ try {
52
+ const client = clientFor(globals, environment);
53
+ const discovery = await listSites(client);
54
+ const data = authStatusOf(discovery);
55
+ context.siteId = data.current;
56
+ if (globals.site !== null && globals.site !== data.current && !data.sites.some((site) => site.site_id === globals.site)) {
57
+ throw invalidInput(`This credential acts on ${data.current}, not ${globals.site}.`);
58
+ }
59
+ process.exitCode = writer.write(successResult(context, data), { text: statusText(data) });
60
+ } catch (error) {
61
+ process.exitCode = writer.write(failureResult(asCliError(error), context));
62
+ }
63
+ });
64
+ applyCommandHelp(status, AUTH_METADATA[0]);
65
+ }
66
+
67
+ export {
68
+ AUTH_METADATA,
69
+ authStatusOf,
70
+ registerAuth
71
+ };
@@ -0,0 +1,100 @@
1
+ import {
2
+ asCliError,
3
+ clientFor
4
+ } from "./chunk-TKISMN3P.js";
5
+ import {
6
+ applyCommandHelp
7
+ } from "./chunk-M2BZEDAS.js";
8
+ import {
9
+ CliError,
10
+ commandMetadata,
11
+ createResultWriter,
12
+ failureResult,
13
+ invalidInput,
14
+ readSiteStatus,
15
+ resolveGlobalOptions,
16
+ resolveTarget,
17
+ statusBlocker,
18
+ statusIds,
19
+ statusLimitations,
20
+ statusRecovery,
21
+ statusSections,
22
+ successResult
23
+ } from "./chunk-GYRVGWDM.js";
24
+
25
+ // src/commands/status.ts
26
+ var STATUS_METADATA = Object.freeze([
27
+ commandMetadata("status", {
28
+ description: "A bounded briefing on one site, composed from the reads that already exist: the site itself, one page of conversations, one page of the pending suggestion tab, and one page of activity. There is no status route on the server and nothing here pages on your behalf. Each section reports available, empty, or unavailable on its own, so a route that answered 503 leaves the other sections' data and identities untouched instead of blanking them. Every number comes from a field the server sent \u2014 the request failed and waiting tallies and the suggestion counts \u2014 and never from the length of a page. A credential the server refuses fails the command rather than being reported as a quiet site.",
29
+ target: { arguments: [] },
30
+ effect: "Nothing. Four independent reads of one site.",
31
+ output: {
32
+ fields: ["site", "requests", "suggestions", "activity", "counts", "attention"],
33
+ ids: ["thread_id", "job_id", "attempt"],
34
+ limitations: ["section_unavailable", "page_bounded"]
35
+ },
36
+ errors: ["invalid_input", "unauthorized", "forbidden", "not_found", "rate_limited", "service_unavailable", "transport_failure", "interrupted"],
37
+ examples: [
38
+ { command: "saturndocs status --site acme", description: "The briefing as lines of text, one per section." },
39
+ { command: "saturndocs status --site acme --json", description: "The same briefing as one result object, with each section's state and server error." }
40
+ ]
41
+ })
42
+ ]);
43
+ function sectionLine(part) {
44
+ if (part.state === "unavailable") {
45
+ return `${part.section}: unavailable \u2014 ${part.error?.message ?? "the read failed and said nothing."} ${part.recovery?.command ?? ""}`.trimEnd();
46
+ }
47
+ return `${part.section}: ${part.state}`;
48
+ }
49
+ function countLine(status) {
50
+ const counts = status.counts;
51
+ const parts = [];
52
+ if (counts.requests_failed !== null) parts.push(`${counts.requests_failed} failed request(s)`);
53
+ if (counts.requests_waiting !== null) parts.push(`${counts.requests_waiting} conversation(s) waiting`);
54
+ if (counts.suggestions !== null) parts.push(`${counts.suggestions.pending} pending suggestion(s)`);
55
+ return parts.length === 0 ? "counts: none supplied by the server. What is listed above is one page, not a total." : `counts: ${parts.join(", ")}`;
56
+ }
57
+ function statusText(status) {
58
+ const lines = [`site: ${status.site_id}`, ...statusSections(status).map(sectionLine), countLine(status)];
59
+ for (const item of status.attention) {
60
+ lines.push(`${item.summary} ${item.recovery.command ?? ""}`.trimEnd());
61
+ }
62
+ return lines.join("\n");
63
+ }
64
+ function namedSite(site) {
65
+ if (site === null || site.trim() === "") {
66
+ throw invalidInput("status reads one site, so it needs --site <site-id>. Run saturndocs sites list to see the site this credential reaches.");
67
+ }
68
+ return site;
69
+ }
70
+ function registerStatus(program, _cliDir, environment = {}) {
71
+ const command = program.command("status").action(async (_options, self) => {
72
+ const globals = resolveGlobalOptions(self);
73
+ const writer = createResultWriter({ json: globals.json });
74
+ const context = { command: "status", siteId: globals.site };
75
+ try {
76
+ const site = namedSite(globals.site);
77
+ const client = clientFor(globals, environment);
78
+ const target = await resolveTarget(client, { site });
79
+ context.siteId = target.site_id;
80
+ const status = await readSiteStatus(client, target.site_id);
81
+ context.limitations = statusLimitations(status);
82
+ context.ids = statusIds(status);
83
+ context.recovery = statusRecovery(status);
84
+ const blocker = statusBlocker(status);
85
+ if (blocker !== null) {
86
+ throw new CliError(blocker.code, blocker.message, { server: blocker.server, data: status });
87
+ }
88
+ process.exitCode = writer.write(successResult(context, status), { text: statusText(status) });
89
+ } catch (error) {
90
+ process.exitCode = writer.write(failureResult(asCliError(error), context));
91
+ }
92
+ });
93
+ applyCommandHelp(command, STATUS_METADATA[0]);
94
+ }
95
+
96
+ export {
97
+ STATUS_METADATA,
98
+ statusText,
99
+ registerStatus
100
+ };
@@ -0,0 +1,104 @@
1
+ import {
2
+ COMMAND_GROUPS
3
+ } from "./chunk-QLFODLVN.js";
4
+ import {
5
+ applyCommandHelp
6
+ } from "./chunk-M2BZEDAS.js";
7
+ import {
8
+ CLIENT_ERROR_CATEGORIES,
9
+ CLIENT_ERROR_CODES,
10
+ CLIENT_SCHEMA_VERSION,
11
+ COMMAND_SCHEMA_VERSION,
12
+ EXIT,
13
+ EXIT_MEANINGS,
14
+ GLOBAL_FLAGS,
15
+ commandMetadata,
16
+ createResultWriter,
17
+ failureResult,
18
+ invalidInput,
19
+ resolveGlobalOptions,
20
+ selectorFor,
21
+ successResult
22
+ } from "./chunk-GYRVGWDM.js";
23
+
24
+ // src/commands/schema.ts
25
+ var SCHEMA_METADATA = Object.freeze([
26
+ commandMetadata("schema", {
27
+ description: "Print this CLI's own command metadata. With no selector it lists the registered groups and the versions, exit codes, and error codes every command shares; with a selector it prints that one command's target rules, permissions, text sources, flags, output, and examples.",
28
+ target: { arguments: [{ name: "selector", required: false, description: "A group or a command, spoken with dots: requests, or requests.create." }] },
29
+ effect: "Nothing. The document is built in this process from the command registry.",
30
+ output: {
31
+ fields: ["schema_version", "result_schema_version", "groups", "global_flags", "exits", "errors", "group", "commands", "command"],
32
+ ids: [],
33
+ limitations: []
34
+ },
35
+ errors: ["invalid_input", "interrupted"],
36
+ examples: [
37
+ { command: "saturndocs schema", description: "Every registered group and the shared contract." },
38
+ { command: "saturndocs schema requests.create", description: "One command's full metadata." },
39
+ { command: "saturndocs schema --json", description: "The same document inside the result envelope every command writes." }
40
+ ]
41
+ })
42
+ ]);
43
+ async function metadataOf(group) {
44
+ return group.metadata === void 0 ? [] : await group.metadata();
45
+ }
46
+ function sharedContract() {
47
+ return {
48
+ schema_version: COMMAND_SCHEMA_VERSION,
49
+ result_schema_version: CLIENT_SCHEMA_VERSION,
50
+ global_flags: GLOBAL_FLAGS,
51
+ exits: Object.entries(EXIT).map(([name, code]) => ({ name, code, meaning: EXIT_MEANINGS[name] })),
52
+ errors: CLIENT_ERROR_CODES.map((code) => ({ code, ...CLIENT_ERROR_CATEGORIES[code] }))
53
+ };
54
+ }
55
+ async function rootDocument(groups) {
56
+ const listed = await Promise.all(
57
+ groups.map(async (group) => ({
58
+ name: group.name,
59
+ summary: group.summary,
60
+ commands: (await metadataOf(group)).map((entry) => entry.selector)
61
+ }))
62
+ );
63
+ return { ...sharedContract(), groups: listed };
64
+ }
65
+ function unknownSelector(selector, known) {
66
+ throw invalidInput(
67
+ `saturndocs schema does not know ${selector}. Run saturndocs schema for the list. Known here: ${known.join(", ")}.`
68
+ );
69
+ }
70
+ async function schemaDocument(selector, groups = COMMAND_GROUPS) {
71
+ if (selector === null || selector === "") return rootDocument(groups);
72
+ const normalized = selectorFor(selector.trim());
73
+ const groupName = normalized.split(".")[0];
74
+ const group = groups.find((candidate) => candidate.name === groupName);
75
+ if (group === void 0) unknownSelector(normalized, groups.map((candidate) => candidate.name));
76
+ const commands = await metadataOf(group);
77
+ const command = commands.find((entry) => entry.selector === normalized);
78
+ if (command !== void 0) return { ...sharedContract(), command };
79
+ if (normalized === groupName && commands.length > 0) {
80
+ return { ...sharedContract(), group: group.name, summary: group.summary, commands };
81
+ }
82
+ unknownSelector(normalized, commands.length === 0 ? groups.filter((candidate) => candidate.metadata !== void 0).map((candidate) => candidate.name) : commands.map((entry) => entry.selector));
83
+ }
84
+ function registerSchema(program) {
85
+ const metadata = SCHEMA_METADATA[0];
86
+ const command = program.command("schema").argument("[selector]", "A group or a command, spoken with dots: requests.create.").action(async (selector, _options, self) => {
87
+ const globals = resolveGlobalOptions(self);
88
+ const writer = createResultWriter({ json: globals.json });
89
+ const context = { command: "schema", siteId: null };
90
+ try {
91
+ const document = await schemaDocument(selector ?? null);
92
+ process.exitCode = writer.write(successResult(context, document), { text: JSON.stringify(document, null, 2) });
93
+ } catch (error) {
94
+ process.exitCode = writer.write(failureResult(error, context));
95
+ }
96
+ });
97
+ applyCommandHelp(command, metadata);
98
+ }
99
+
100
+ export {
101
+ SCHEMA_METADATA,
102
+ schemaDocument,
103
+ registerSchema
104
+ };
@@ -0,0 +1,119 @@
1
+ import {
2
+ asCliError,
3
+ clientFor
4
+ } from "./chunk-TKISMN3P.js";
5
+ import {
6
+ applyCommandHelp
7
+ } from "./chunk-M2BZEDAS.js";
8
+ import {
9
+ commandMetadata,
10
+ createResultWriter,
11
+ failureResult,
12
+ invalidInput,
13
+ listSites,
14
+ readSiteDetail,
15
+ resolveGlobalOptions,
16
+ resolveTarget,
17
+ successResult
18
+ } from "./chunk-GYRVGWDM.js";
19
+
20
+ // src/commands/sites.ts
21
+ var SITES_METADATA = Object.freeze([
22
+ commandMetadata("sites list", {
23
+ description: "List the sites GET /v1/sites returns for the credential in SATURNDOCS_MANAGEMENT_TOKEN. A management credential is bound to one site, so this is that site and not an account-wide listing; primary_host is null on that answer and is reported as null.",
24
+ target: { arguments: [] },
25
+ effect: "Nothing. One read of the site listing.",
26
+ output: { fields: ["current", "sites"], ids: [], limitations: [] },
27
+ errors: ["invalid_input", "unauthorized", "forbidden", "rate_limited", "service_unavailable", "transport_failure", "interrupted"],
28
+ examples: [
29
+ { command: "saturndocs sites list", description: "The sites this credential reaches." },
30
+ { command: "saturndocs sites list --json", description: "The same listing as one result object." }
31
+ ]
32
+ }),
33
+ commandMetadata("sites get", {
34
+ description: "Read one site: its hostnames, the release readers are seeing, its reader status, its latest job, and the usage meters the server records. With --connections it also reads the documentation repository and the watched product repositories. Whatever the server leaves out is reported as absent rather than filled in.",
35
+ target: { arguments: [{ name: "site-id", required: false, description: "The site to read. Defaults to the site the credential resolves to." }] },
36
+ effect: "Nothing. One read of the site, and two more when --connections is given.",
37
+ output: {
38
+ fields: ["site_id", "site.hostnames", "site.active_release", "site.reader_status", "site.latest_job", "site.usage", "connections.docs_repo", "connections.product_connections"],
39
+ ids: ["release_id"],
40
+ limitations: []
41
+ },
42
+ errors: ["invalid_input", "unauthorized", "forbidden", "not_found", "rate_limited", "service_unavailable", "transport_failure", "interrupted"],
43
+ examples: [
44
+ { command: "saturndocs sites get", description: "The site the credential resolves to." },
45
+ { command: "saturndocs sites get acme --connections", description: "One site with its documentation repository and watched repositories." },
46
+ { command: "saturndocs sites get --json", description: "The site read as one result object, with the resolved site_id in the envelope." }
47
+ ]
48
+ })
49
+ ]);
50
+ function requestedSite(positional, flag) {
51
+ const named = positional ?? flag ?? void 0;
52
+ if (positional !== void 0 && flag !== null && positional !== flag) {
53
+ throw invalidInput(`This command was given two sites: ${positional} and --site ${flag}. Name one.`);
54
+ }
55
+ return named;
56
+ }
57
+ function listText(discovery) {
58
+ const lines = discovery.sites.map((site) => `${site.site_id}${site.site_id === discovery.current ? " (current)" : ""} ${site.primary_host ?? "no primary host returned"}`);
59
+ return [...lines, "A management credential reaches only its own site, so this is not a full account listing."].join("\n");
60
+ }
61
+ function detailText(detail) {
62
+ const site = detail.site;
63
+ const release = site.active_release;
64
+ const lines = [
65
+ `site: ${detail.site_id}`,
66
+ `hostnames: ${site.hostnames.length === 0 ? "none returned" : site.hostnames.join(", ")}`,
67
+ `reader status: ${site.reader_status ?? "not reported"}`,
68
+ release === null ? "active release: none" : `active release: ${release.release_id} (${release.state}), activated ${release.activated_at ?? "at an unrecorded time"}`,
69
+ site.latest_job === null ? "latest job: none" : `latest job: ${site.latest_job.state}${site.latest_job.error_code === null ? "" : ` (${site.latest_job.error_code})`}`,
70
+ `usage: ${site.usage === void 0 || Object.keys(site.usage).length === 0 ? "nothing measured" : Object.entries(site.usage).map(([name, meter]) => `${name} ${JSON.stringify(meter)}`).join("; ")}`
71
+ ];
72
+ if (detail.connections !== null) {
73
+ const docs = detail.connections.docs_repo;
74
+ lines.push(
75
+ docs.docs_repo === null ? "docs repo: not connected" : `docs repo: ${docs.docs_repo.repository ?? "repository name not recorded"} (${docs.docs_repo.state}), ${docs.pages_count === null ? "page count unavailable" : `${docs.pages_count} pages`}`,
76
+ `watched repositories: ${detail.connections.product_connections.product_connections.length}`
77
+ );
78
+ }
79
+ return lines.join("\n");
80
+ }
81
+ function registerSites(program, _cliDir, environment = {}) {
82
+ const sites = program.command("sites").description("Discover and inspect the sites this credential reaches");
83
+ const list = sites.command("list").action(async (_options, self) => {
84
+ const globals = resolveGlobalOptions(self);
85
+ const writer = createResultWriter({ json: globals.json });
86
+ const context = { command: "sites list", siteId: null };
87
+ try {
88
+ const discovery = await listSites(clientFor(globals, environment));
89
+ context.siteId = discovery.current;
90
+ process.exitCode = writer.write(successResult(context, discovery), { text: listText(discovery) });
91
+ } catch (error) {
92
+ process.exitCode = writer.write(failureResult(asCliError(error), context));
93
+ }
94
+ });
95
+ applyCommandHelp(list, SITES_METADATA[0]);
96
+ const get = sites.command("get").argument("[site-id]", "The site to read. Defaults to the site the credential resolves to.").action(async (siteId, options, self) => {
97
+ const globals = resolveGlobalOptions(self);
98
+ const writer = createResultWriter({ json: globals.json });
99
+ const context = { command: "sites get", siteId: null };
100
+ try {
101
+ const client = clientFor(globals, environment);
102
+ const named = requestedSite(siteId, globals.site);
103
+ const target = await resolveTarget(client, named === void 0 ? {} : { site: named });
104
+ context.siteId = target.site_id;
105
+ const detail = await readSiteDetail(client, target.site_id, { connections: options.connections === true });
106
+ if (detail.site.active_release !== null) context.ids = { release_id: detail.site.active_release.release_id };
107
+ process.exitCode = writer.write(successResult(context, detail), { text: detailText(detail) });
108
+ } catch (error) {
109
+ process.exitCode = writer.write(failureResult(asCliError(error), context));
110
+ }
111
+ });
112
+ applyCommandHelp(get, SITES_METADATA[1]);
113
+ }
114
+
115
+ export {
116
+ SITES_METADATA,
117
+ requestedSite,
118
+ registerSites
119
+ };