@ctcstack/ctcdocs-core 0.22.0 → 0.22.2

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.
@@ -0,0 +1,42 @@
1
+ /**
2
+ * What a published page is made from, said once for the sync, the site and
3
+ * the Worker: a Google Doc, a PDF file (ADR-027), a spreadsheet (ADR-046), or
4
+ * a video or audio file (ADR-047).
5
+ *
6
+ * The same fact travels under three names. The page's frontmatter records its
7
+ * `sourceType`, the generated index and a run's report its format, and the
8
+ * MCP server its own `format`, which calls a Google Doc `doc` (ADR-044). Each
9
+ * is derived here from the format, so a new kind of page is added in one
10
+ * place and every list that names the kinds follows.
11
+ */
12
+ /** The kinds of page a Drive file is published as. */
13
+ export declare const DOCUMENT_FORMATS: readonly ["google-doc", "pdf", "sheet", "video", "audio"];
14
+ export type DocumentFormat = (typeof DOCUMENT_FORMATS)[number];
15
+ /**
16
+ * The frontmatter `sourceType` of a page published from a Drive file. A video
17
+ * and an audio file share one; the page's `media.kind` tells them apart.
18
+ */
19
+ export declare const DOCUMENT_SOURCE_TYPES: readonly ["google-doc", "drive-pdf", "drive-sheet", "drive-media"];
20
+ export type DocumentSourceType = (typeof DOCUMENT_SOURCE_TYPES)[number];
21
+ /** The `sourceType` a page of `format` records. */
22
+ export declare function documentSourceType(format: DocumentFormat): DocumentSourceType;
23
+ /** Whether a page's `sourceType` says it was published from a Drive file. */
24
+ export declare function isDocumentSourceType(value: string | undefined): value is DocumentSourceType;
25
+ /**
26
+ * The format of a page, from its `sourceType` and, for a recording, the kind
27
+ * its frontmatter records; `undefined` for a page no Drive file made.
28
+ */
29
+ export declare function documentFormatOf(sourceType: string | undefined, mediaKind?: 'video' | 'audio'): DocumentFormat | undefined;
30
+ /**
31
+ * The mark beside a page that is not a Google Doc, in the sidebar and in a
32
+ * run's list of pages.
33
+ */
34
+ export declare const DOCUMENT_FORMAT_BADGES: Readonly<Record<DocumentFormat, string | undefined>>;
35
+ /**
36
+ * How `llms.txt` names a page that is not a Google Doc after its title, as in
37
+ * `Price list (spreadsheet)`.
38
+ */
39
+ export declare const DOCUMENT_FORMAT_NOUNS: Readonly<Record<DocumentFormat, string | undefined>>;
40
+ /** The format the MCP server reports: a Google Doc is `doc` (ADR-044). */
41
+ export type AgentDocumentFormat = Exclude<DocumentFormat, 'google-doc'> | 'doc';
42
+ export declare function agentDocumentFormat(format: DocumentFormat): AgentDocumentFormat;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * What a published page is made from, said once for the sync, the site and
3
+ * the Worker: a Google Doc, a PDF file (ADR-027), a spreadsheet (ADR-046), or
4
+ * a video or audio file (ADR-047).
5
+ *
6
+ * The same fact travels under three names. The page's frontmatter records its
7
+ * `sourceType`, the generated index and a run's report its format, and the
8
+ * MCP server its own `format`, which calls a Google Doc `doc` (ADR-044). Each
9
+ * is derived here from the format, so a new kind of page is added in one
10
+ * place and every list that names the kinds follows.
11
+ */
12
+ /** The kinds of page a Drive file is published as. */
13
+ export const DOCUMENT_FORMATS = [
14
+ 'google-doc',
15
+ 'pdf',
16
+ 'sheet',
17
+ 'video',
18
+ 'audio',
19
+ ];
20
+ /**
21
+ * The frontmatter `sourceType` of a page published from a Drive file. A video
22
+ * and an audio file share one; the page's `media.kind` tells them apart.
23
+ */
24
+ export const DOCUMENT_SOURCE_TYPES = [
25
+ 'google-doc',
26
+ 'drive-pdf',
27
+ 'drive-sheet',
28
+ 'drive-media',
29
+ ];
30
+ const SOURCE_TYPES = {
31
+ 'google-doc': 'google-doc',
32
+ pdf: 'drive-pdf',
33
+ sheet: 'drive-sheet',
34
+ video: 'drive-media',
35
+ audio: 'drive-media',
36
+ };
37
+ /** The `sourceType` a page of `format` records. */
38
+ export function documentSourceType(format) {
39
+ return SOURCE_TYPES[format];
40
+ }
41
+ /** Whether a page's `sourceType` says it was published from a Drive file. */
42
+ export function isDocumentSourceType(value) {
43
+ return DOCUMENT_SOURCE_TYPES.includes(value);
44
+ }
45
+ /**
46
+ * The format of a page, from its `sourceType` and, for a recording, the kind
47
+ * its frontmatter records; `undefined` for a page no Drive file made.
48
+ */
49
+ export function documentFormatOf(sourceType, mediaKind) {
50
+ switch (sourceType) {
51
+ case 'google-doc':
52
+ return 'google-doc';
53
+ case 'drive-pdf':
54
+ return 'pdf';
55
+ case 'drive-sheet':
56
+ return 'sheet';
57
+ case 'drive-media':
58
+ return mediaKind;
59
+ default:
60
+ return undefined;
61
+ }
62
+ }
63
+ /**
64
+ * The mark beside a page that is not a Google Doc, in the sidebar and in a
65
+ * run's list of pages.
66
+ */
67
+ export const DOCUMENT_FORMAT_BADGES = {
68
+ 'google-doc': undefined,
69
+ pdf: 'PDF',
70
+ sheet: 'Sheet',
71
+ video: 'Video',
72
+ audio: 'Audio',
73
+ };
74
+ /**
75
+ * How `llms.txt` names a page that is not a Google Doc after its title, as in
76
+ * `Price list (spreadsheet)`.
77
+ */
78
+ export const DOCUMENT_FORMAT_NOUNS = {
79
+ 'google-doc': undefined,
80
+ pdf: 'PDF',
81
+ sheet: 'spreadsheet',
82
+ video: 'video',
83
+ audio: 'audio',
84
+ };
85
+ export function agentDocumentFormat(format) {
86
+ return format === 'google-doc' ? 'doc' : format;
87
+ }
package/dist/index.d.ts CHANGED
@@ -12,3 +12,5 @@ export { accessFindings } from './access-findings.js';
12
12
  export type { AccessFinding } from './access-findings.js';
13
13
  export { fetchCharacterLimit, loadSiteConfiguration, MCP_DEFAULTS, parseSiteConfiguration, SiteConfigurationError, } from './site-configuration.js';
14
14
  export type { AddressPolicy, BrandConfiguration, DeploymentConfiguration, DeploymentEnvironmentConfiguration, DeploymentEnvironmentConfigurations, DeploymentVisibility, HomeConfiguration, McpConfiguration, McpRecentConfiguration, McpSearchConfiguration, NavigationConfiguration, SignInConfiguration, SiteConfiguration, SyncConfigurationDefaults, } from './site-configuration.js';
15
+ export { agentDocumentFormat, DOCUMENT_FORMAT_BADGES, DOCUMENT_FORMAT_NOUNS, DOCUMENT_FORMATS, DOCUMENT_SOURCE_TYPES, documentFormatOf, documentSourceType, isDocumentSourceType, } from './document-format.js';
16
+ export type { AgentDocumentFormat, DocumentFormat, DocumentSourceType, } from './document-format.js';
package/dist/index.js CHANGED
@@ -7,3 +7,4 @@ export { CorpusStructureError, EMPTY_CORPUS, parseCorpusStructure, readCorpusStr
7
7
  export { ADMINS_CLASS, chainReaders, chainRules, classIdentifiers, computeAccessModel, documentClass, folderChain, folderClass, intersectReaders, MEMBERS_CLASS, nextPublishedReaders, widensReaders, } from './access-classes.js';
8
8
  export { accessFindings } from './access-findings.js';
9
9
  export { fetchCharacterLimit, loadSiteConfiguration, MCP_DEFAULTS, parseSiteConfiguration, SiteConfigurationError, } from './site-configuration.js';
10
+ export { agentDocumentFormat, DOCUMENT_FORMAT_BADGES, DOCUMENT_FORMAT_NOUNS, DOCUMENT_FORMATS, DOCUMENT_SOURCE_TYPES, documentFormatOf, documentSourceType, isDocumentSourceType, } from './document-format.js';
@@ -6,8 +6,17 @@ export interface BrandConfiguration {
6
6
  readonly siteTitle: string;
7
7
  /** One sentence, used as the site-wide meta description. */
8
8
  readonly siteDescription: string;
9
- /** Site-root-relative path of the favicon served from `public/`. */
9
+ /**
10
+ * Site-root-relative path of the favicon served from `public/`, which is
11
+ * also the mark in the header.
12
+ */
10
13
  readonly faviconPath: string;
14
+ /**
15
+ * The same mark drawn for a dark ground, when the light one would not show
16
+ * on it: the header shows it under the dark theme, and a browser whose
17
+ * interface is dark shows it in the tab.
18
+ */
19
+ readonly faviconDarkPath?: string;
11
20
  }
12
21
  export interface HomeConfiguration {
13
22
  /**
@@ -333,8 +333,15 @@ export function parseSiteConfiguration(input) {
333
333
  if (!faviconPath.startsWith('/')) {
334
334
  fail('brand.faviconPath', 'must be a site-root-relative path');
335
335
  }
336
+ const faviconDarkPath = brandSource.faviconDarkPath === undefined
337
+ ? undefined
338
+ : text(brandSource, 'faviconDarkPath', 'brand.faviconDarkPath');
339
+ if (faviconDarkPath !== undefined && !faviconDarkPath.startsWith('/')) {
340
+ fail('brand.faviconDarkPath', 'must be a site-root-relative path');
341
+ }
336
342
  const brand = {
337
343
  faviconPath,
344
+ ...(faviconDarkPath === undefined ? {} : { faviconDarkPath }),
338
345
  name: text(brandSource, 'name', 'brand.name'),
339
346
  siteDescription: text(brandSource, 'siteDescription', 'brand.siteDescription'),
340
347
  siteTitle: text(brandSource, 'siteTitle', 'brand.siteTitle'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ctcstack/ctcdocs-core",
3
- "version": "0.22.0",
3
+ "version": "0.22.2",
4
4
  "description": "Configuration schema, project layout, generated-path allowlist and the published Markdown form shared by the CTCDocs platform packages.",
5
5
  "keywords": [
6
6
  "ctcdocs",
@@ -31,6 +31,10 @@
31
31
  "./published-markdown": {
32
32
  "types": "./dist/published-markdown.d.ts",
33
33
  "default": "./dist/published-markdown.js"
34
+ },
35
+ "./document-format": {
36
+ "types": "./dist/document-format.d.ts",
37
+ "default": "./dist/document-format.js"
34
38
  }
35
39
  },
36
40
  "files": [
@@ -0,0 +1,116 @@
1
+ /**
2
+ * What a published page is made from, said once for the sync, the site and
3
+ * the Worker: a Google Doc, a PDF file (ADR-027), a spreadsheet (ADR-046), or
4
+ * a video or audio file (ADR-047).
5
+ *
6
+ * The same fact travels under three names. The page's frontmatter records its
7
+ * `sourceType`, the generated index and a run's report its format, and the
8
+ * MCP server its own `format`, which calls a Google Doc `doc` (ADR-044). Each
9
+ * is derived here from the format, so a new kind of page is added in one
10
+ * place and every list that names the kinds follows.
11
+ */
12
+
13
+ /** The kinds of page a Drive file is published as. */
14
+ export const DOCUMENT_FORMATS = [
15
+ 'google-doc',
16
+ 'pdf',
17
+ 'sheet',
18
+ 'video',
19
+ 'audio',
20
+ ] as const;
21
+
22
+ export type DocumentFormat = (typeof DOCUMENT_FORMATS)[number];
23
+
24
+ /**
25
+ * The frontmatter `sourceType` of a page published from a Drive file. A video
26
+ * and an audio file share one; the page's `media.kind` tells them apart.
27
+ */
28
+ export const DOCUMENT_SOURCE_TYPES = [
29
+ 'google-doc',
30
+ 'drive-pdf',
31
+ 'drive-sheet',
32
+ 'drive-media',
33
+ ] as const;
34
+
35
+ export type DocumentSourceType = (typeof DOCUMENT_SOURCE_TYPES)[number];
36
+
37
+ const SOURCE_TYPES: Readonly<Record<DocumentFormat, DocumentSourceType>> = {
38
+ 'google-doc': 'google-doc',
39
+ pdf: 'drive-pdf',
40
+ sheet: 'drive-sheet',
41
+ video: 'drive-media',
42
+ audio: 'drive-media',
43
+ };
44
+
45
+ /** The `sourceType` a page of `format` records. */
46
+ export function documentSourceType(format: DocumentFormat): DocumentSourceType {
47
+ return SOURCE_TYPES[format];
48
+ }
49
+
50
+ /** Whether a page's `sourceType` says it was published from a Drive file. */
51
+ export function isDocumentSourceType(
52
+ value: string | undefined,
53
+ ): value is DocumentSourceType {
54
+ return (DOCUMENT_SOURCE_TYPES as readonly (string | undefined)[]).includes(
55
+ value,
56
+ );
57
+ }
58
+
59
+ /**
60
+ * The format of a page, from its `sourceType` and, for a recording, the kind
61
+ * its frontmatter records; `undefined` for a page no Drive file made.
62
+ */
63
+ export function documentFormatOf(
64
+ sourceType: string | undefined,
65
+ mediaKind?: 'video' | 'audio',
66
+ ): DocumentFormat | undefined {
67
+ switch (sourceType) {
68
+ case 'google-doc':
69
+ return 'google-doc';
70
+ case 'drive-pdf':
71
+ return 'pdf';
72
+ case 'drive-sheet':
73
+ return 'sheet';
74
+ case 'drive-media':
75
+ return mediaKind;
76
+ default:
77
+ return undefined;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * The mark beside a page that is not a Google Doc, in the sidebar and in a
83
+ * run's list of pages.
84
+ */
85
+ export const DOCUMENT_FORMAT_BADGES: Readonly<
86
+ Record<DocumentFormat, string | undefined>
87
+ > = {
88
+ 'google-doc': undefined,
89
+ pdf: 'PDF',
90
+ sheet: 'Sheet',
91
+ video: 'Video',
92
+ audio: 'Audio',
93
+ };
94
+
95
+ /**
96
+ * How `llms.txt` names a page that is not a Google Doc after its title, as in
97
+ * `Price list (spreadsheet)`.
98
+ */
99
+ export const DOCUMENT_FORMAT_NOUNS: Readonly<
100
+ Record<DocumentFormat, string | undefined>
101
+ > = {
102
+ 'google-doc': undefined,
103
+ pdf: 'PDF',
104
+ sheet: 'spreadsheet',
105
+ video: 'video',
106
+ audio: 'audio',
107
+ };
108
+
109
+ /** The format the MCP server reports: a Google Doc is `doc` (ADR-044). */
110
+ export type AgentDocumentFormat = Exclude<DocumentFormat, 'google-doc'> | 'doc';
111
+
112
+ export function agentDocumentFormat(
113
+ format: DocumentFormat,
114
+ ): AgentDocumentFormat {
115
+ return format === 'google-doc' ? 'doc' : format;
116
+ }
package/src/index.ts CHANGED
@@ -87,3 +87,18 @@ export type {
87
87
  SiteConfiguration,
88
88
  SyncConfigurationDefaults,
89
89
  } from './site-configuration.js';
90
+ export {
91
+ agentDocumentFormat,
92
+ DOCUMENT_FORMAT_BADGES,
93
+ DOCUMENT_FORMAT_NOUNS,
94
+ DOCUMENT_FORMATS,
95
+ DOCUMENT_SOURCE_TYPES,
96
+ documentFormatOf,
97
+ documentSourceType,
98
+ isDocumentSourceType,
99
+ } from './document-format.js';
100
+ export type {
101
+ AgentDocumentFormat,
102
+ DocumentFormat,
103
+ DocumentSourceType,
104
+ } from './document-format.js';
@@ -27,8 +27,17 @@ export interface BrandConfiguration {
27
27
  readonly siteTitle: string;
28
28
  /** One sentence, used as the site-wide meta description. */
29
29
  readonly siteDescription: string;
30
- /** Site-root-relative path of the favicon served from `public/`. */
30
+ /**
31
+ * Site-root-relative path of the favicon served from `public/`, which is
32
+ * also the mark in the header.
33
+ */
31
34
  readonly faviconPath: string;
35
+ /**
36
+ * The same mark drawn for a dark ground, when the light one would not show
37
+ * on it: the header shows it under the dark theme, and a browser whose
38
+ * interface is dark shows it in the tab.
39
+ */
40
+ readonly faviconDarkPath?: string;
32
41
  }
33
42
 
34
43
  export interface HomeConfiguration {
@@ -737,8 +746,16 @@ export function parseSiteConfiguration(input: unknown): SiteConfiguration {
737
746
  if (!faviconPath.startsWith('/')) {
738
747
  fail('brand.faviconPath', 'must be a site-root-relative path');
739
748
  }
749
+ const faviconDarkPath =
750
+ brandSource.faviconDarkPath === undefined
751
+ ? undefined
752
+ : text(brandSource, 'faviconDarkPath', 'brand.faviconDarkPath');
753
+ if (faviconDarkPath !== undefined && !faviconDarkPath.startsWith('/')) {
754
+ fail('brand.faviconDarkPath', 'must be a site-root-relative path');
755
+ }
740
756
  const brand: BrandConfiguration = {
741
757
  faviconPath,
758
+ ...(faviconDarkPath === undefined ? {} : { faviconDarkPath }),
742
759
  name: text(brandSource, 'name', 'brand.name'),
743
760
  siteDescription: text(
744
761
  brandSource,