@de-otio/bibcheck 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 (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +147 -0
  3. package/dist/cache/fs-cache.d.ts +55 -0
  4. package/dist/cache/fs-cache.d.ts.map +1 -0
  5. package/dist/cache/fs-cache.js +264 -0
  6. package/dist/cache/fs-cache.js.map +1 -0
  7. package/dist/canonical.d.ts +29 -0
  8. package/dist/canonical.d.ts.map +1 -0
  9. package/dist/canonical.js +132 -0
  10. package/dist/canonical.js.map +1 -0
  11. package/dist/check.d.ts +140 -0
  12. package/dist/check.d.ts.map +1 -0
  13. package/dist/check.js +646 -0
  14. package/dist/check.js.map +1 -0
  15. package/dist/cli.d.ts +19 -0
  16. package/dist/cli.d.ts.map +1 -0
  17. package/dist/cli.js +357 -0
  18. package/dist/cli.js.map +1 -0
  19. package/dist/config.d.ts +175 -0
  20. package/dist/config.d.ts.map +1 -0
  21. package/dist/config.js +180 -0
  22. package/dist/config.js.map +1 -0
  23. package/dist/databases/crossref.d.ts +53 -0
  24. package/dist/databases/crossref.d.ts.map +1 -0
  25. package/dist/databases/crossref.js +138 -0
  26. package/dist/databases/crossref.js.map +1 -0
  27. package/dist/databases/index.d.ts +12 -0
  28. package/dist/databases/index.d.ts.map +1 -0
  29. package/dist/databases/index.js +9 -0
  30. package/dist/databases/index.js.map +1 -0
  31. package/dist/databases/openalex.d.ts +29 -0
  32. package/dist/databases/openalex.d.ts.map +1 -0
  33. package/dist/databases/openalex.js +117 -0
  34. package/dist/databases/openalex.js.map +1 -0
  35. package/dist/databases/openlibrary.d.ts +26 -0
  36. package/dist/databases/openlibrary.d.ts.map +1 -0
  37. package/dist/databases/openlibrary.js +79 -0
  38. package/dist/databases/openlibrary.js.map +1 -0
  39. package/dist/databases/worldcat.d.ts +33 -0
  40. package/dist/databases/worldcat.d.ts.map +1 -0
  41. package/dist/databases/worldcat.js +145 -0
  42. package/dist/databases/worldcat.js.map +1 -0
  43. package/dist/doctor.d.ts +44 -0
  44. package/dist/doctor.d.ts.map +1 -0
  45. package/dist/doctor.js +386 -0
  46. package/dist/doctor.js.map +1 -0
  47. package/dist/existence.d.ts +70 -0
  48. package/dist/existence.d.ts.map +1 -0
  49. package/dist/existence.js +308 -0
  50. package/dist/existence.js.map +1 -0
  51. package/dist/http.d.ts +97 -0
  52. package/dist/http.d.ts.map +1 -0
  53. package/dist/http.js +543 -0
  54. package/dist/http.js.map +1 -0
  55. package/dist/identifiers.d.ts +44 -0
  56. package/dist/identifiers.d.ts.map +1 -0
  57. package/dist/identifiers.js +111 -0
  58. package/dist/identifiers.js.map +1 -0
  59. package/dist/index.d.ts +9 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +8 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/linkage.d.ts +29 -0
  64. package/dist/linkage.d.ts.map +1 -0
  65. package/dist/linkage.js +73 -0
  66. package/dist/linkage.js.map +1 -0
  67. package/dist/markdown/blocks.d.ts +19 -0
  68. package/dist/markdown/blocks.d.ts.map +1 -0
  69. package/dist/markdown/blocks.js +69 -0
  70. package/dist/markdown/blocks.js.map +1 -0
  71. package/dist/markdown/citekeys.d.ts +22 -0
  72. package/dist/markdown/citekeys.d.ts.map +1 -0
  73. package/dist/markdown/citekeys.js +100 -0
  74. package/dist/markdown/citekeys.js.map +1 -0
  75. package/dist/markdown/glob.d.ts +18 -0
  76. package/dist/markdown/glob.d.ts.map +1 -0
  77. package/dist/markdown/glob.js +26 -0
  78. package/dist/markdown/glob.js.map +1 -0
  79. package/dist/markdown/prose.d.ts +19 -0
  80. package/dist/markdown/prose.d.ts.map +1 -0
  81. package/dist/markdown/prose.js +81 -0
  82. package/dist/markdown/prose.js.map +1 -0
  83. package/dist/output/json.d.ts +21 -0
  84. package/dist/output/json.d.ts.map +1 -0
  85. package/dist/output/json.js +24 -0
  86. package/dist/output/json.js.map +1 -0
  87. package/dist/output/markdown.d.ts +21 -0
  88. package/dist/output/markdown.d.ts.map +1 -0
  89. package/dist/output/markdown.js +194 -0
  90. package/dist/output/markdown.js.map +1 -0
  91. package/dist/output/sarif.d.ts +31 -0
  92. package/dist/output/sarif.d.ts.map +1 -0
  93. package/dist/output/sarif.js +322 -0
  94. package/dist/output/sarif.js.map +1 -0
  95. package/dist/output/text.d.ts +27 -0
  96. package/dist/output/text.d.ts.map +1 -0
  97. package/dist/output/text.js +212 -0
  98. package/dist/output/text.js.map +1 -0
  99. package/dist/phrases/load.d.ts +34 -0
  100. package/dist/phrases/load.d.ts.map +1 -0
  101. package/dist/phrases/load.js +148 -0
  102. package/dist/phrases/load.js.map +1 -0
  103. package/dist/phrases.d.ts +27 -0
  104. package/dist/phrases.d.ts.map +1 -0
  105. package/dist/phrases.js +116 -0
  106. package/dist/phrases.js.map +1 -0
  107. package/dist/schema/csl.d.ts +429 -0
  108. package/dist/schema/csl.d.ts.map +1 -0
  109. package/dist/schema/csl.js +101 -0
  110. package/dist/schema/csl.js.map +1 -0
  111. package/dist/schema/output.d.ts +1116 -0
  112. package/dist/schema/output.d.ts.map +1 -0
  113. package/dist/schema/output.js +419 -0
  114. package/dist/schema/output.js.map +1 -0
  115. package/dist/suppression.d.ts +106 -0
  116. package/dist/suppression.d.ts.map +1 -0
  117. package/dist/suppression.js +134 -0
  118. package/dist/suppression.js.map +1 -0
  119. package/dist/version.d.ts +11 -0
  120. package/dist/version.d.ts.map +1 -0
  121. package/dist/version.js +14 -0
  122. package/dist/version.js.map +1 -0
  123. package/dist/worklist.d.ts +32 -0
  124. package/dist/worklist.d.ts.map +1 -0
  125. package/dist/worklist.js +211 -0
  126. package/dist/worklist.js.map +1 -0
  127. package/package.json +82 -0
@@ -0,0 +1,79 @@
1
+ /**
2
+ * OpenLibrary ISBN lookup client.
3
+ *
4
+ * Queries https://openlibrary.org/api/books?bibkeys=ISBN:<isbn>&format=json&jscmd=data
5
+ * and returns normalised DatabaseLookupResult metadata. Results are cached at
6
+ * the default TTL (30 days).
7
+ *
8
+ * An empty response object {} indicates no match (found: false). The client
9
+ * does not use a polite-pool email (OpenLibrary has no such mechanism).
10
+ */
11
+ import { trimTrailingSlash } from './crossref.js';
12
+ const DEFAULT_OPENLIBRARY_BASE = 'https://openlibrary.org';
13
+ // ---------------------------------------------------------------------------
14
+ // Helpers
15
+ // ---------------------------------------------------------------------------
16
+ function isOpenLibraryResponse(body) {
17
+ return typeof body === 'object' && body !== null && !Array.isArray(body);
18
+ }
19
+ function mapBookMetadata(book, isbn) {
20
+ const authors = book.authors?.map((a) => a.name ?? '').filter((n) => n.length > 0);
21
+ const publisher = book.publishers?.[0]?.name;
22
+ // publish_date is a free-form string like "January 2001" or "2001"; extract year.
23
+ let issued;
24
+ if (book.publish_date) {
25
+ const match = /\b(\d{4})\b/.exec(book.publish_date);
26
+ if (match?.[1] !== undefined) {
27
+ const parsed = parseInt(match[1], 10);
28
+ if (!isNaN(parsed)) {
29
+ issued = parsed;
30
+ }
31
+ }
32
+ }
33
+ return {
34
+ title: book.title,
35
+ authors: authors && authors.length > 0 ? authors : undefined,
36
+ issued,
37
+ publisher,
38
+ isbn,
39
+ };
40
+ }
41
+ // ---------------------------------------------------------------------------
42
+ // Factory
43
+ // ---------------------------------------------------------------------------
44
+ export function createOpenLibraryClient(opts) {
45
+ const { http, cache } = opts;
46
+ const base = trimTrailingSlash(opts.baseUrl ?? DEFAULT_OPENLIBRARY_BASE);
47
+ async function lookupByIsbn(isbn, signal) {
48
+ const cacheKey = `openlibrary:lookupByIsbn:${isbn}`;
49
+ const cached = await cache.get(cacheKey, signal);
50
+ if (cached !== null) {
51
+ return cached;
52
+ }
53
+ const url = `${base}/api/books?bibkeys=ISBN:${encodeURIComponent(isbn)}&format=json&jscmd=data`;
54
+ let response;
55
+ response = await http.get(url, { signal });
56
+ if (response.status === 404) {
57
+ return { found: false, metadata: null, raw: response.body };
58
+ }
59
+ if (!isOpenLibraryResponse(response.body)) {
60
+ throw new Error(`OpenLibrary: unexpected response body for ISBN "${isbn}"`);
61
+ }
62
+ const bookData = response.body[`ISBN:${isbn}`];
63
+ if (bookData === undefined) {
64
+ // Empty response or no matching entry.
65
+ const result = { found: false, metadata: null, raw: response.body };
66
+ await cache.set(cacheKey, result);
67
+ return result;
68
+ }
69
+ const metadata = mapBookMetadata(bookData, isbn);
70
+ const result = { found: true, metadata, raw: response.body };
71
+ await cache.set(cacheKey, result);
72
+ return result;
73
+ }
74
+ return {
75
+ name: 'openlibrary',
76
+ lookupByIsbn,
77
+ };
78
+ }
79
+ //# sourceMappingURL=openlibrary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openlibrary.js","sourceRoot":"","sources":["../../src/databases/openlibrary.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * WorldCat ISBN lookup client (v0.1: OCLC Classify legacy endpoint).
3
+ *
4
+ * v0.1 uses the OCLC Classify API at:
5
+ * http://classify.oclc.org/classify2/api?isbn=<isbn>&summary=true
6
+ *
7
+ * IMPORTANT: This endpoint uses HTTP (not HTTPS). The response is bibliographic
8
+ * metadata only and contains no credentials, but response integrity is not
9
+ * guaranteed over a plaintext connection. This is an accepted v0.1 limitation.
10
+ * Migrate to the OCLC Discovery API (HTTPS + OAuth2) in v0.2 when apiKey
11
+ * support is implemented.
12
+ *
13
+ * The Classify API returns XML by default. We request JSON via Accept header.
14
+ * If the response is nonetheless XML (string), we perform a minimal regex parse
15
+ * for <work title="..." author="..."> — acceptable for v0.1 (documented kludge).
16
+ */
17
+ import type { HttpClient } from '../http.js';
18
+ import type { Cache } from '../cache/fs-cache.js';
19
+ import type { DatabaseLookupResult, DatabaseClient } from './crossref.js';
20
+ export type { DatabaseLookupResult, DatabaseClient };
21
+ export interface WorldCatClientOptions {
22
+ http: HttpClient;
23
+ cache: Cache;
24
+ apiKey?: string | null;
25
+ /** Base URL for the Classify endpoint. Defaults to the public endpoint. */
26
+ baseUrl?: string;
27
+ }
28
+ export interface WorldCatClient extends DatabaseClient {
29
+ readonly name: 'worldcat';
30
+ lookupByIsbn(isbn: string, signal?: AbortSignal): Promise<DatabaseLookupResult>;
31
+ }
32
+ export declare function createWorldCatClient(opts: WorldCatClientOptions): WorldCatClient;
33
+ //# sourceMappingURL=worldcat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worldcat.d.ts","sourceRoot":"","sources":["../../src/databases/worldcat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAgB,MAAM,YAAY,CAAC;AAC3D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,KAAK,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG1E,YAAY,EAAE,oBAAoB,EAAE,cAAc,EAAE,CAAC;AAQrD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAe,SAAQ,cAAc;IACpD,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;CACjF;AA6FD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,qBAAqB,GAAG,cAAc,CA2EhF"}
@@ -0,0 +1,145 @@
1
+ /**
2
+ * WorldCat ISBN lookup client (v0.1: OCLC Classify legacy endpoint).
3
+ *
4
+ * v0.1 uses the OCLC Classify API at:
5
+ * http://classify.oclc.org/classify2/api?isbn=<isbn>&summary=true
6
+ *
7
+ * IMPORTANT: This endpoint uses HTTP (not HTTPS). The response is bibliographic
8
+ * metadata only and contains no credentials, but response integrity is not
9
+ * guaranteed over a plaintext connection. This is an accepted v0.1 limitation.
10
+ * Migrate to the OCLC Discovery API (HTTPS + OAuth2) in v0.2 when apiKey
11
+ * support is implemented.
12
+ *
13
+ * The Classify API returns XML by default. We request JSON via Accept header.
14
+ * If the response is nonetheless XML (string), we perform a minimal regex parse
15
+ * for <work title="..." author="..."> — acceptable for v0.1 (documented kludge).
16
+ */
17
+ import { trimTrailingSlash } from './crossref.js';
18
+ const DEFAULT_WORLDCAT_BASE = 'http://classify.oclc.org';
19
+ // ---------------------------------------------------------------------------
20
+ // Helpers
21
+ // ---------------------------------------------------------------------------
22
+ function isClassifyJsonResponse(body) {
23
+ return typeof body === 'object' && body !== null;
24
+ }
25
+ /**
26
+ * Minimal XML parse for OCLC Classify XML responses.
27
+ * Extracts title and author from the first <work title="..." author="..."> element.
28
+ * This is a v0.1 kludge; full XML parsing is deferred to v0.2.
29
+ */
30
+ function parseClassifyXml(xml) {
31
+ // Match both <work ... title="..." author="..."> and with attributes in any order.
32
+ const workMatch = /<work\b([^>]*)>/i.exec(xml);
33
+ if (!workMatch?.[1])
34
+ return null;
35
+ const attrs = workMatch[1];
36
+ const titleMatch = /\btitle="([^"]*)"/.exec(attrs);
37
+ const authorMatch = /\bauthor="([^"]*)"/.exec(attrs);
38
+ if (!titleMatch && !authorMatch)
39
+ return null;
40
+ return {
41
+ title: titleMatch?.[1],
42
+ author: authorMatch?.[1],
43
+ };
44
+ }
45
+ /**
46
+ * Determine whether there are zero works in a Classify JSON response.
47
+ * The Classify API returns an empty work list or a specific responseCode when
48
+ * no records are found.
49
+ */
50
+ function hasClassifyWork(body) {
51
+ const classify = body.classify;
52
+ if (!classify)
53
+ return null;
54
+ // Single-work response.
55
+ if (classify.work && typeof classify.work === 'object') {
56
+ return classify.work;
57
+ }
58
+ // Multi-work summary: works.work may be an array or single object.
59
+ if (classify.works?.work) {
60
+ const w = classify.works.work;
61
+ if (Array.isArray(w)) {
62
+ return w[0] ?? null;
63
+ }
64
+ return w;
65
+ }
66
+ return null;
67
+ }
68
+ function mapClassifyMetadata(work, isbn) {
69
+ const authors = work.author ? [work.author] : undefined;
70
+ return {
71
+ title: work.title,
72
+ authors,
73
+ isbn,
74
+ };
75
+ }
76
+ // ---------------------------------------------------------------------------
77
+ // Factory
78
+ // ---------------------------------------------------------------------------
79
+ export function createWorldCatClient(opts) {
80
+ const { http, cache } = opts;
81
+ // apiKey is reserved for v0.2; ignored in v0.1.
82
+ const base = trimTrailingSlash(opts.baseUrl ?? DEFAULT_WORLDCAT_BASE);
83
+ async function lookupByIsbn(isbn, signal) {
84
+ const cacheKey = `worldcat:lookupByIsbn:${isbn}`;
85
+ const cached = await cache.get(cacheKey, signal);
86
+ if (cached !== null) {
87
+ return cached;
88
+ }
89
+ // v0.1: HTTP (not HTTPS) — legacy Classify endpoint does not support HTTPS.
90
+ const url = `${base}/classify2/api?isbn=${encodeURIComponent(isbn)}&summary=true`;
91
+ let response;
92
+ try {
93
+ response = await http.get(url, {
94
+ signal,
95
+ headers: { Accept: 'application/json' },
96
+ });
97
+ }
98
+ catch (err) {
99
+ throw err;
100
+ }
101
+ if (response.status === 404) {
102
+ return { found: false, metadata: null, raw: response.body };
103
+ }
104
+ // Handle XML fallback (response body is a string when Content-Type is not JSON).
105
+ if (typeof response.body === 'string') {
106
+ const parsed = parseClassifyXml(response.body);
107
+ if (!parsed || (!parsed.title && !parsed.author)) {
108
+ const result = {
109
+ found: false,
110
+ metadata: null,
111
+ raw: response.body,
112
+ };
113
+ await cache.set(cacheKey, result);
114
+ return result;
115
+ }
116
+ const metadata = mapClassifyMetadata(parsed, isbn);
117
+ const result = { found: true, metadata, raw: response.body };
118
+ await cache.set(cacheKey, result);
119
+ return result;
120
+ }
121
+ // JSON response path.
122
+ if (!isClassifyJsonResponse(response.body)) {
123
+ throw new Error(`WorldCat: unexpected response body for ISBN "${isbn}"`);
124
+ }
125
+ const work = hasClassifyWork(response.body);
126
+ if (!work) {
127
+ const result = {
128
+ found: false,
129
+ metadata: null,
130
+ raw: response.body,
131
+ };
132
+ await cache.set(cacheKey, result);
133
+ return result;
134
+ }
135
+ const metadata = mapClassifyMetadata(work, isbn);
136
+ const result = { found: true, metadata, raw: response.body };
137
+ await cache.set(cacheKey, result);
138
+ return result;
139
+ }
140
+ return {
141
+ name: 'worldcat',
142
+ lookupByIsbn,
143
+ };
144
+ }
145
+ //# sourceMappingURL=worldcat.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worldcat.js","sourceRoot":"","sources":["../../src/databases/worldcat.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * bibcheck doctor — diagnostic runner.
3
+ *
4
+ * Runs a series of environment and configuration checks and returns a
5
+ * structured result. Each check emits a DoctorCheck entry with a name,
6
+ * status, and human-readable message. The CLI (T15) is responsible for
7
+ * rendering the output; this module does no I/O beyond what is needed
8
+ * to perform the checks.
9
+ *
10
+ * SECURITY: API keys, polite-pool email addresses, and full request URLs
11
+ * (which might contain ?mailto= query params) MUST NOT appear in any
12
+ * DoctorCheck.message or .details value.
13
+ */
14
+ import type { Config } from './config.js';
15
+ import type { HttpClient } from './http.js';
16
+ export interface DoctorCheck {
17
+ name: string;
18
+ status: 'ok' | 'warn' | 'fail';
19
+ message: string;
20
+ details?: Record<string, unknown>;
21
+ }
22
+ export interface RunDoctorDeps {
23
+ config: Config;
24
+ cwd: string;
25
+ http: HttpClient;
26
+ signal: AbortSignal;
27
+ /** Injectable for tests */
28
+ fs?: {
29
+ stat: (path: string) => Promise<{
30
+ size: number;
31
+ isDirectory: () => boolean;
32
+ }>;
33
+ access: (path: string) => Promise<void>;
34
+ readdir: (path: string) => Promise<string[]>;
35
+ };
36
+ /** Optional: clear cache directory (called by --clear-cache flag) */
37
+ clearCache?: boolean;
38
+ }
39
+ export interface RunDoctorResult {
40
+ checks: DoctorCheck[];
41
+ ok: boolean;
42
+ }
43
+ export declare function runDoctor(deps: RunDoctorDeps): Promise<RunDoctorResult>;
44
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAU5C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC;IACpB,2BAA2B;IAC3B,EAAE,CAAC,EAAE;QACH,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,OAAO,CAAA;SAAE,CAAC,CAAC;QAC9E,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;QACxC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;KAC9C,CAAC;IACF,qEAAqE;IACrE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,EAAE,EAAE,OAAO,CAAC;CACb;AA+HD,wBAAsB,SAAS,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,CAAC,CAsS7E"}