storyquery 0.0.1
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 +126 -0
- package/dist/cli.js +1042 -0
- package/dist/cli.js.map +1 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# storyquery
|
|
2
|
+
|
|
3
|
+
A CLI that queries Storybook design system manifests and answers questions about components and documentation. Built for agents (JSON-first output), but handy for humans too.
|
|
4
|
+
|
|
5
|
+
`storyquery` reads two manifest files published by Storybook:
|
|
6
|
+
|
|
7
|
+
- `<base-url>/manifests/components.json` (components, props, stories, imports)
|
|
8
|
+
- `<base-url>/manifests/docs.json` (MDX guideline pages)
|
|
9
|
+
|
|
10
|
+
Output defaults to JSON. Use `--format text` for human-readable output.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
Requires Node.js >= 18.
|
|
15
|
+
|
|
16
|
+
### Run without installing
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npx storyquery query MainButton --url https://your-storybook.example.com
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Global install
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
npm install -g storyquery
|
|
26
|
+
storyquery --version
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
If you want something shorter, add a shell alias: `alias sq='storyquery'`.
|
|
30
|
+
|
|
31
|
+
### As a project dependency
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
npm install --save-dev storyquery
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Then run it via `npx storyquery ...` or a `package.json` script. Put the base URL
|
|
38
|
+
in `./.storyquery.json` so everyone on the project shares it.
|
|
39
|
+
|
|
40
|
+
## Configuration
|
|
41
|
+
|
|
42
|
+
The Storybook base URL is resolved in this order (highest precedence first):
|
|
43
|
+
|
|
44
|
+
1. `--url` flag
|
|
45
|
+
2. `SQ_URL` environment variable
|
|
46
|
+
3. `./.storyquery.json` (project-local)
|
|
47
|
+
4. `<user-config-dir>/storyquery/config.json` (global)
|
|
48
|
+
|
|
49
|
+
Config file format:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"url": "https://your-storybook.example.com",
|
|
54
|
+
"cacheTTL": "1h"
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`cacheTTL` accepts Go-style durations (`ms`, `s`, `m`, `h`), e.g. `"30m"`, `"1h30m"`.
|
|
59
|
+
|
|
60
|
+
Manifests are cached on disk under the OS cache directory. Default TTL is 1 hour.
|
|
61
|
+
Use `--refresh` to force a refetch or `--no-cache` to bypass the cache.
|
|
62
|
+
|
|
63
|
+
## Usage
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
# Search components and docs for a term (JSON by default)
|
|
67
|
+
storyquery query MainButton
|
|
68
|
+
|
|
69
|
+
# Full detail for one component (props, stories, guideline doc)
|
|
70
|
+
storyquery show MainButton
|
|
71
|
+
|
|
72
|
+
# List all components
|
|
73
|
+
storyquery list
|
|
74
|
+
storyquery list --filter button
|
|
75
|
+
|
|
76
|
+
# Search guideline / docs pages
|
|
77
|
+
storyquery docs "getting started"
|
|
78
|
+
|
|
79
|
+
# Human-readable output
|
|
80
|
+
storyquery query MainButton --format text
|
|
81
|
+
|
|
82
|
+
# Point at a specific Storybook instance
|
|
83
|
+
storyquery query Alert --url https://your-storybook.example.com
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Global flags
|
|
87
|
+
|
|
88
|
+
| Flag | Description |
|
|
89
|
+
|--------------|---------------------------------------------------|
|
|
90
|
+
| `--url` | Storybook base URL (overrides env and config) |
|
|
91
|
+
| `--format` | `json` (default) or `text` |
|
|
92
|
+
| `--refresh` | Force a fresh fetch, ignoring cached manifests |
|
|
93
|
+
| `--no-cache` | Bypass the cache entirely |
|
|
94
|
+
|
|
95
|
+
### Command flags
|
|
96
|
+
|
|
97
|
+
| Command | Flag | Description |
|
|
98
|
+
|---------|------------|------------------------------------------|
|
|
99
|
+
| `query` | `--limit` | max results per category (0 = all) |
|
|
100
|
+
| `docs` | `--limit` | max results (0 = all) |
|
|
101
|
+
| `list` | `--filter` | case-insensitive substring on name/id |
|
|
102
|
+
|
|
103
|
+
### Exit codes
|
|
104
|
+
|
|
105
|
+
| Code | Meaning |
|
|
106
|
+
|------|----------------------|
|
|
107
|
+
| 0 | success |
|
|
108
|
+
| 1 | no match / error |
|
|
109
|
+
| 2 | usage (e.g. no URL) |
|
|
110
|
+
| 3 | network/HTTP error |
|
|
111
|
+
|
|
112
|
+
## Development
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
npm install
|
|
116
|
+
npm run build # bundle to dist/ with tsup
|
|
117
|
+
npm run dev # watch build
|
|
118
|
+
npm run typecheck # tsc --noEmit
|
|
119
|
+
npm test # vitest run
|
|
120
|
+
npm run test:watch # vitest
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
The CLI is authored in TypeScript and bundled to a single ESM file in `dist/`.
|
|
124
|
+
Runtime dependencies: [`citty`](https://github.com/unjs/citty) (command tree) and
|
|
125
|
+
[`arktype`](https://arktype.io) (lenient manifest validation). HTTP uses the
|
|
126
|
+
native `fetch` API.
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1042 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { defineCommand, runCommand, showUsage } from "citty";
|
|
3
|
+
import { type } from "arktype";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
|
|
10
|
+
//#region src/output/text.ts
|
|
11
|
+
function firstLine(s) {
|
|
12
|
+
const i = s.indexOf("\n");
|
|
13
|
+
return i >= 0 ? s.slice(0, i) : s;
|
|
14
|
+
}
|
|
15
|
+
function pad(s, width) {
|
|
16
|
+
return s.length >= width ? s : s + " ".repeat(width - s.length);
|
|
17
|
+
}
|
|
18
|
+
function warnings(lines) {
|
|
19
|
+
return (lines ?? []).map((m) => `! ${m}\n`).join("");
|
|
20
|
+
}
|
|
21
|
+
function renderQuery(r) {
|
|
22
|
+
let out = warnings(r.warnings);
|
|
23
|
+
out += `Query: ${r.term}\n`;
|
|
24
|
+
out += `\nComponents (${r.components.length}):\n`;
|
|
25
|
+
for (const c of r.components) {
|
|
26
|
+
out += ` ${pad(c.name, 28)} ${c.id}\n`;
|
|
27
|
+
if (c.description) out += ` ${firstLine(c.description)}\n`;
|
|
28
|
+
}
|
|
29
|
+
out += `\nDocs (${r.docs.length}):\n`;
|
|
30
|
+
for (const d of r.docs) out += ` ${pad(d.title, 28)} ${d.id}\n`;
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
function renderList(r) {
|
|
34
|
+
let out = warnings(r.warnings);
|
|
35
|
+
for (const c of r.components) out += `${pad(c.name, 32)} ${c.id}\n`;
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
function renderDetail(d) {
|
|
39
|
+
let out = warnings(d.warnings);
|
|
40
|
+
out += `${d.name} (${d.id})\n`;
|
|
41
|
+
if (d.description) out += `\n${d.description}\n`;
|
|
42
|
+
if (d.tags && Object.keys(d.tags).length > 0) {
|
|
43
|
+
out += `\nTags:\n`;
|
|
44
|
+
for (const [k, v] of Object.entries(d.tags)) out += ` @${k} ${firstLine(v)}\n`;
|
|
45
|
+
}
|
|
46
|
+
if (d.import) out += `\nImport:\n ${d.import}\n`;
|
|
47
|
+
if (d.sourceFile) out += `Source: ${d.sourceFile}\n`;
|
|
48
|
+
out += `\nProps (${d.props.length}):\n`;
|
|
49
|
+
for (const p of d.props) {
|
|
50
|
+
const req = p.required ? " (required)" : "";
|
|
51
|
+
const def = p.default ? ` = ${p.default}` : "";
|
|
52
|
+
out += ` ${p.name}: ${p.type}${def}${req}\n`;
|
|
53
|
+
if (p.description) out += ` ${firstLine(p.description)}\n`;
|
|
54
|
+
}
|
|
55
|
+
out += `\nStories (${d.stories.length}):\n`;
|
|
56
|
+
for (const s of d.stories) out += ` ${s.name}\n`;
|
|
57
|
+
if (d.guideline) out += `\nGuideline: ${d.guideline.title}\n\n${d.guideline.content}\n`;
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
function renderDocs(r) {
|
|
61
|
+
let out = warnings(r.warnings);
|
|
62
|
+
out += `Query: ${r.term}\n`;
|
|
63
|
+
for (const d of r.docs) out += `\n=== ${d.title} (${d.id}) ===\n${d.content}\n`;
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/manifest/types.ts
|
|
69
|
+
/**
|
|
70
|
+
* The manifest schema version this package was written against. Manifests
|
|
71
|
+
* reporting a different version are still parsed (best effort) but the mismatch
|
|
72
|
+
* is surfaced to callers as a warning.
|
|
73
|
+
*/
|
|
74
|
+
const SCHEMA_VERSION = 0;
|
|
75
|
+
/** The default value of a prop, when one is documented. */
|
|
76
|
+
const propDefault = type({ "value?": "unknown" });
|
|
77
|
+
/** The resolved type of a prop. */
|
|
78
|
+
const propType = type({
|
|
79
|
+
"name?": "string",
|
|
80
|
+
"raw?": "string",
|
|
81
|
+
"value?": "unknown"
|
|
82
|
+
});
|
|
83
|
+
/** A single component property. */
|
|
84
|
+
const prop = type({
|
|
85
|
+
"name?": "string",
|
|
86
|
+
"description?": "string",
|
|
87
|
+
"required?": "boolean",
|
|
88
|
+
"defaultValue?": propDefault.or("null"),
|
|
89
|
+
"type?": propType
|
|
90
|
+
});
|
|
91
|
+
/** The react-docgen-typescript output for a component. */
|
|
92
|
+
const docgenInfo = type({
|
|
93
|
+
"displayName?": "string",
|
|
94
|
+
"description?": "string",
|
|
95
|
+
"exportName?": "string",
|
|
96
|
+
"filePath?": "string",
|
|
97
|
+
"tags?": type.Record("string", "string"),
|
|
98
|
+
"props?": type.Record("string", prop)
|
|
99
|
+
});
|
|
100
|
+
/** A single design-system component. */
|
|
101
|
+
const component = type({
|
|
102
|
+
"id?": "string",
|
|
103
|
+
"name?": "string",
|
|
104
|
+
"path?": "string",
|
|
105
|
+
"import?": "string",
|
|
106
|
+
"description?": "string",
|
|
107
|
+
"jsDocTags?": "unknown",
|
|
108
|
+
"stories?": type({
|
|
109
|
+
"id?": "string",
|
|
110
|
+
"name?": "string",
|
|
111
|
+
"snippet?": "string"
|
|
112
|
+
}).array(),
|
|
113
|
+
"reactDocgenTypescript?": docgenInfo
|
|
114
|
+
});
|
|
115
|
+
/** A single MDX documentation/guideline page. */
|
|
116
|
+
const doc = type({
|
|
117
|
+
"id?": "string",
|
|
118
|
+
"name?": "string",
|
|
119
|
+
"path?": "string",
|
|
120
|
+
"title?": "string",
|
|
121
|
+
"content?": "string"
|
|
122
|
+
});
|
|
123
|
+
/** The parsed representation of components.json. */
|
|
124
|
+
const components = type({
|
|
125
|
+
"v?": "number",
|
|
126
|
+
"components?": type.Record("string", component)
|
|
127
|
+
});
|
|
128
|
+
/** The parsed representation of docs.json. */
|
|
129
|
+
const docs = type({
|
|
130
|
+
"v?": "number",
|
|
131
|
+
"docs?": type.Record("string", doc)
|
|
132
|
+
});
|
|
133
|
+
/** Decodes and validates components.json text. Lenient: never throws on extra keys. */
|
|
134
|
+
function parseComponents(data) {
|
|
135
|
+
const out = components(safeJson(data));
|
|
136
|
+
if (out instanceof type.errors) throw new Error(`parse components manifest: ${out.summary}`);
|
|
137
|
+
if (!out.components) out.components = {};
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
/** Decodes and validates docs.json text. Lenient: never throws on extra keys. */
|
|
141
|
+
function parseDocs(data) {
|
|
142
|
+
const out = docs(safeJson(data));
|
|
143
|
+
if (out instanceof type.errors) throw new Error(`parse docs manifest: ${out.summary}`);
|
|
144
|
+
if (!out.docs) out.docs = {};
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
function safeJson(data) {
|
|
148
|
+
try {
|
|
149
|
+
return JSON.parse(data);
|
|
150
|
+
} catch (err) {
|
|
151
|
+
throw new Error(`parse manifest json: ${err instanceof Error ? err.message : String(err)}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/** Reports whether a components manifest version differs from the supported one. */
|
|
155
|
+
function componentsVersionMismatch(c) {
|
|
156
|
+
return (c.v ?? 0) !== 0;
|
|
157
|
+
}
|
|
158
|
+
/** Reports whether a docs manifest version differs from the supported one. */
|
|
159
|
+
function docsVersionMismatch(d) {
|
|
160
|
+
return (d.v ?? 0) !== 0;
|
|
161
|
+
}
|
|
162
|
+
/** Renders a prop's default value as a readable string. */
|
|
163
|
+
function propDefaultString(d) {
|
|
164
|
+
if (!d || d.value === void 0 || d.value === null) return "";
|
|
165
|
+
if (typeof d.value === "string") return d.value;
|
|
166
|
+
return JSON.stringify(d.value);
|
|
167
|
+
}
|
|
168
|
+
/** Renders a prop type, preferring the raw TypeScript expression. */
|
|
169
|
+
function propTypeString(t) {
|
|
170
|
+
if (!t) return "";
|
|
171
|
+
if (t.raw) return t.raw;
|
|
172
|
+
return t.name ?? "";
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
//#endregion
|
|
176
|
+
//#region src/output/types.ts
|
|
177
|
+
/** Builds a compact summary (score optional; omitted when 0). */
|
|
178
|
+
function summarizeComponent(c, score) {
|
|
179
|
+
const summary = {
|
|
180
|
+
id: c.id ?? "",
|
|
181
|
+
name: c.name ?? ""
|
|
182
|
+
};
|
|
183
|
+
if (c.description) summary.description = c.description;
|
|
184
|
+
if (c.import) summary.import = c.import;
|
|
185
|
+
summary.props = Object.keys(c.reactDocgenTypescript?.props ?? {}).length;
|
|
186
|
+
summary.stories = c.stories?.length ?? 0;
|
|
187
|
+
if (score) summary.score = score;
|
|
188
|
+
return summary;
|
|
189
|
+
}
|
|
190
|
+
/** Builds the full detail view, attaching a guideline doc if set. */
|
|
191
|
+
function detailComponent(c, guideline) {
|
|
192
|
+
const docgen = c.reactDocgenTypescript;
|
|
193
|
+
const detail = {
|
|
194
|
+
id: c.id ?? "",
|
|
195
|
+
name: c.name ?? "",
|
|
196
|
+
props: [],
|
|
197
|
+
stories: []
|
|
198
|
+
};
|
|
199
|
+
if (c.description) detail.description = c.description;
|
|
200
|
+
if (c.import) detail.import = c.import;
|
|
201
|
+
if (c.path) detail.path = c.path;
|
|
202
|
+
if (docgen?.filePath) detail.sourceFile = docgen.filePath;
|
|
203
|
+
const tags = mergeTags(docgen?.tags);
|
|
204
|
+
if (tags) detail.tags = tags;
|
|
205
|
+
for (const p of Object.values(docgen?.props ?? {})) {
|
|
206
|
+
const prop = {
|
|
207
|
+
name: p.name ?? "",
|
|
208
|
+
type: propTypeString(p.type),
|
|
209
|
+
required: p.required ?? false
|
|
210
|
+
};
|
|
211
|
+
const def = propDefaultString(p.defaultValue);
|
|
212
|
+
if (def) prop.default = def;
|
|
213
|
+
if (p.description) prop.description = p.description;
|
|
214
|
+
detail.props.push(prop);
|
|
215
|
+
}
|
|
216
|
+
detail.props.sort((a, b) => a.name.localeCompare(b.name));
|
|
217
|
+
for (const s of c.stories ?? []) {
|
|
218
|
+
const story = {
|
|
219
|
+
id: s.id ?? "",
|
|
220
|
+
name: s.name ?? ""
|
|
221
|
+
};
|
|
222
|
+
if (s.snippet) story.snippet = s.snippet;
|
|
223
|
+
detail.stories.push(story);
|
|
224
|
+
}
|
|
225
|
+
if (guideline) detail.guideline = {
|
|
226
|
+
id: guideline.id ?? "",
|
|
227
|
+
title: guideline.title ?? "",
|
|
228
|
+
content: guideline.content ?? ""
|
|
229
|
+
};
|
|
230
|
+
return detail;
|
|
231
|
+
}
|
|
232
|
+
/** Builds a full doc view. */
|
|
233
|
+
function detailDoc(d) {
|
|
234
|
+
return {
|
|
235
|
+
id: d.id ?? "",
|
|
236
|
+
title: d.title ?? "",
|
|
237
|
+
content: d.content ?? ""
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
/** Combines tag maps, returning undefined when all are empty. */
|
|
241
|
+
function mergeTags(...sources) {
|
|
242
|
+
const merged = {};
|
|
243
|
+
for (const src of sources) {
|
|
244
|
+
if (!src) continue;
|
|
245
|
+
for (const [k, v] of Object.entries(src)) if (v) merged[k] = v;
|
|
246
|
+
}
|
|
247
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
//#endregion
|
|
251
|
+
//#region src/output/index.ts
|
|
252
|
+
/** Validates and normalizes a format string. */
|
|
253
|
+
function parseFormat(s) {
|
|
254
|
+
switch (s.trim().toLowerCase()) {
|
|
255
|
+
case "json": return "json";
|
|
256
|
+
case "text": return "text";
|
|
257
|
+
default: throw new Error(`invalid format "${s}" (want json or text)`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/** Renders a payload in the requested format. */
|
|
261
|
+
function encode(format, payload) {
|
|
262
|
+
if (format === "json") return `${JSON.stringify(payload.value, null, 2)}\n`;
|
|
263
|
+
switch (payload.kind) {
|
|
264
|
+
case "query": return renderQuery(payload.value);
|
|
265
|
+
case "list": return renderList(payload.value);
|
|
266
|
+
case "detail": return renderDetail(payload.value);
|
|
267
|
+
case "docs": return renderDocs(payload.value);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
//#endregion
|
|
272
|
+
//#region src/search.ts
|
|
273
|
+
const SCORE_EXACT_NAME = 1e3;
|
|
274
|
+
const SCORE_EXACT_ID = 900;
|
|
275
|
+
const SCORE_NAME_PREFIX = 700;
|
|
276
|
+
const SCORE_ID_SUBSTR = 500;
|
|
277
|
+
const SCORE_NAME_SUBSTR = 450;
|
|
278
|
+
const SCORE_FUZZY = 100;
|
|
279
|
+
const SCORE_NO_MATCH = 0;
|
|
280
|
+
/**
|
|
281
|
+
* Ranks all components against term, returning matches with a positive score
|
|
282
|
+
* sorted best-first. limit <= 0 returns all matches.
|
|
283
|
+
*/
|
|
284
|
+
function searchComponents(comps, term, limit) {
|
|
285
|
+
const q = term.trim().toLowerCase();
|
|
286
|
+
const out = [];
|
|
287
|
+
for (const component of Object.values(comps)) {
|
|
288
|
+
const score = scoreComponent(component, q);
|
|
289
|
+
if (score > SCORE_NO_MATCH) out.push({
|
|
290
|
+
component,
|
|
291
|
+
score
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
out.sort((a, b) => {
|
|
295
|
+
if (a.score !== b.score) return b.score - a.score;
|
|
296
|
+
return (a.component.name ?? "").localeCompare(b.component.name ?? "");
|
|
297
|
+
});
|
|
298
|
+
return cap(out, limit);
|
|
299
|
+
}
|
|
300
|
+
/** Ranks all docs against term, matching on title, name, id, and content. */
|
|
301
|
+
function searchDocs(docs, term, limit) {
|
|
302
|
+
const q = term.trim().toLowerCase();
|
|
303
|
+
const out = [];
|
|
304
|
+
for (const doc of Object.values(docs)) {
|
|
305
|
+
const score = scoreDoc(doc, q);
|
|
306
|
+
if (score > SCORE_NO_MATCH) out.push({
|
|
307
|
+
doc,
|
|
308
|
+
score
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
out.sort((a, b) => {
|
|
312
|
+
if (a.score !== b.score) return b.score - a.score;
|
|
313
|
+
return (a.doc.title ?? "").localeCompare(b.doc.title ?? "");
|
|
314
|
+
});
|
|
315
|
+
return cap(out, limit);
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Resolves term to a single best component. A direct id or exact name always
|
|
319
|
+
* wins outright; multiple tied top matches are ambiguous.
|
|
320
|
+
*/
|
|
321
|
+
function bestComponent(comps, term) {
|
|
322
|
+
const matches = searchComponents(comps, term, 0);
|
|
323
|
+
if (matches.length === 0) return { kind: "none" };
|
|
324
|
+
if (matches.length === 1) return {
|
|
325
|
+
kind: "found",
|
|
326
|
+
match: matches[0]
|
|
327
|
+
};
|
|
328
|
+
if (matches[0].score > matches[1].score) return {
|
|
329
|
+
kind: "found",
|
|
330
|
+
match: matches[0]
|
|
331
|
+
};
|
|
332
|
+
return {
|
|
333
|
+
kind: "ambiguous",
|
|
334
|
+
match: matches[0]
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
function scoreComponent(c, q) {
|
|
338
|
+
if (q === "") return SCORE_NO_MATCH;
|
|
339
|
+
const name = (c.name ?? "").toLowerCase();
|
|
340
|
+
const id = (c.id ?? "").toLowerCase();
|
|
341
|
+
if (name === q) return SCORE_EXACT_NAME;
|
|
342
|
+
if (id === q) return SCORE_EXACT_ID;
|
|
343
|
+
if (name.startsWith(q)) return SCORE_NAME_PREFIX;
|
|
344
|
+
if (id.includes(q)) return SCORE_ID_SUBSTR;
|
|
345
|
+
if (name.includes(q)) return SCORE_NAME_SUBSTR;
|
|
346
|
+
const nameFuzzy = fuzzyScore(name, q);
|
|
347
|
+
if (nameFuzzy !== null) return SCORE_FUZZY + nameFuzzy;
|
|
348
|
+
const idFuzzy = fuzzyScore(id, q);
|
|
349
|
+
if (idFuzzy !== null) return SCORE_FUZZY + idFuzzy;
|
|
350
|
+
return SCORE_NO_MATCH;
|
|
351
|
+
}
|
|
352
|
+
function scoreDoc(d, q) {
|
|
353
|
+
if (q === "") return SCORE_NO_MATCH;
|
|
354
|
+
const title = (d.title ?? "").toLowerCase();
|
|
355
|
+
const name = (d.name ?? "").toLowerCase();
|
|
356
|
+
const id = (d.id ?? "").toLowerCase();
|
|
357
|
+
if (title === q || name === q) return SCORE_EXACT_NAME;
|
|
358
|
+
if (id === q) return SCORE_EXACT_ID;
|
|
359
|
+
if (title.startsWith(q)) return SCORE_NAME_PREFIX;
|
|
360
|
+
if (id.includes(q)) return SCORE_ID_SUBSTR;
|
|
361
|
+
if (title.includes(q) || name.includes(q)) return SCORE_NAME_SUBSTR;
|
|
362
|
+
const titleFuzzy = fuzzyScore(title, q);
|
|
363
|
+
if (titleFuzzy !== null) return SCORE_FUZZY + titleFuzzy;
|
|
364
|
+
if ((d.content ?? "").toLowerCase().includes(q)) return SCORE_FUZZY / 2;
|
|
365
|
+
return SCORE_NO_MATCH;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Reports whether q is a subsequence of s and, if so, a density bonus that
|
|
369
|
+
* rewards tightly-packed matches over scattered ones. Returns null on no match.
|
|
370
|
+
*/
|
|
371
|
+
function fuzzyScore(s, q) {
|
|
372
|
+
if (q === "") return null;
|
|
373
|
+
const sr = [...s];
|
|
374
|
+
const qr = [...q];
|
|
375
|
+
let si = 0;
|
|
376
|
+
let qi = 0;
|
|
377
|
+
let first = -1;
|
|
378
|
+
let last = -1;
|
|
379
|
+
while (si < sr.length && qi < qr.length) {
|
|
380
|
+
if (sr[si] === qr[qi]) {
|
|
381
|
+
if (first < 0) first = si;
|
|
382
|
+
last = si;
|
|
383
|
+
qi++;
|
|
384
|
+
}
|
|
385
|
+
si++;
|
|
386
|
+
}
|
|
387
|
+
if (qi !== qr.length) return null;
|
|
388
|
+
let span = last - first + 1;
|
|
389
|
+
if (span <= 0) span = 1;
|
|
390
|
+
return Math.trunc(qr.length * 50 / span);
|
|
391
|
+
}
|
|
392
|
+
function cap(items, limit) {
|
|
393
|
+
if (limit > 0 && items.length > limit) return items.slice(0, limit);
|
|
394
|
+
return items;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
//#endregion
|
|
398
|
+
//#region src/cache.ts
|
|
399
|
+
/** A filesystem-backed Store rooted at a directory. */
|
|
400
|
+
var Cache = class {
|
|
401
|
+
dir;
|
|
402
|
+
now;
|
|
403
|
+
ready = null;
|
|
404
|
+
constructor(dir, opts = {}) {
|
|
405
|
+
this.dir = dir;
|
|
406
|
+
this.now = opts.now ?? Date.now;
|
|
407
|
+
}
|
|
408
|
+
ensureDir() {
|
|
409
|
+
if (!this.ready) this.ready = mkdir(this.dir, { recursive: true }).then(() => void 0);
|
|
410
|
+
return this.ready;
|
|
411
|
+
}
|
|
412
|
+
dataPath(key) {
|
|
413
|
+
return join(this.dir, `${key}.data`);
|
|
414
|
+
}
|
|
415
|
+
metaPath(key) {
|
|
416
|
+
return join(this.dir, `${key}.meta.json`);
|
|
417
|
+
}
|
|
418
|
+
async load(key, ttlMs) {
|
|
419
|
+
let data;
|
|
420
|
+
try {
|
|
421
|
+
data = await readFile(this.dataPath(key), "utf8");
|
|
422
|
+
} catch (err) {
|
|
423
|
+
if (isNotFound(err)) return {
|
|
424
|
+
fresh: false,
|
|
425
|
+
ok: false
|
|
426
|
+
};
|
|
427
|
+
throw new Error(`read cache data: ${errMsg$1(err)}`);
|
|
428
|
+
}
|
|
429
|
+
let fresh = false;
|
|
430
|
+
try {
|
|
431
|
+
const raw = await readFile(this.metaPath(key), "utf8");
|
|
432
|
+
const meta = JSON.parse(raw);
|
|
433
|
+
const fetchedAt = Date.parse(meta.fetched_at);
|
|
434
|
+
if (!Number.isNaN(fetchedAt)) fresh = this.now() - fetchedAt < ttlMs;
|
|
435
|
+
} catch {}
|
|
436
|
+
return {
|
|
437
|
+
data,
|
|
438
|
+
fresh,
|
|
439
|
+
ok: true
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
async save(key, data) {
|
|
443
|
+
await this.ensureDir();
|
|
444
|
+
try {
|
|
445
|
+
await writeFile(this.dataPath(key), data, "utf8");
|
|
446
|
+
} catch (err) {
|
|
447
|
+
throw new Error(`write cache data: ${errMsg$1(err)}`);
|
|
448
|
+
}
|
|
449
|
+
const meta = { fetched_at: new Date(this.now()).toISOString() };
|
|
450
|
+
try {
|
|
451
|
+
await writeFile(this.metaPath(key), JSON.stringify(meta), "utf8");
|
|
452
|
+
} catch (err) {
|
|
453
|
+
throw new Error(`write cache meta: ${errMsg$1(err)}`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
/** Derives a stable, filesystem-safe cache key from arbitrary parts. */
|
|
458
|
+
function cacheKey(...parts) {
|
|
459
|
+
const h = createHash("sha256");
|
|
460
|
+
for (const p of parts) {
|
|
461
|
+
h.update(p);
|
|
462
|
+
h.update(Buffer.from([0]));
|
|
463
|
+
}
|
|
464
|
+
return h.digest("hex").slice(0, 32);
|
|
465
|
+
}
|
|
466
|
+
function isNotFound(err) {
|
|
467
|
+
return err?.code === "ENOENT";
|
|
468
|
+
}
|
|
469
|
+
function errMsg$1(err) {
|
|
470
|
+
return err instanceof Error ? err.message : String(err);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
//#endregion
|
|
474
|
+
//#region src/config.ts
|
|
475
|
+
/** The environment variable holding the Storybook base URL. */
|
|
476
|
+
const ENV_URL = "SQ_URL";
|
|
477
|
+
/** The project-local config filename. */
|
|
478
|
+
const PROJECT_FILE = ".storyquery.json";
|
|
479
|
+
/** The cache time-to-live used when none is configured (1 hour, in ms). */
|
|
480
|
+
const DEFAULT_TTL_MS = 3600 * 1e3;
|
|
481
|
+
/** Thrown when no base URL could be resolved from any source. Drives exit code 2. */
|
|
482
|
+
var NoUrlError = class extends Error {
|
|
483
|
+
constructor() {
|
|
484
|
+
super("no storybook url configured: pass --url, set SQ_URL, or add a config file");
|
|
485
|
+
this.name = "NoUrlError";
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
/**
|
|
489
|
+
* Produces a Config from the flag value (may be empty), the environment, and
|
|
490
|
+
* config files. flagUrl takes highest precedence.
|
|
491
|
+
*/
|
|
492
|
+
function resolveConfig(flagUrl) {
|
|
493
|
+
let baseUrl = "";
|
|
494
|
+
let cacheTtlMs = DEFAULT_TTL_MS;
|
|
495
|
+
const apply = (f) => {
|
|
496
|
+
const u = f.url?.trim();
|
|
497
|
+
if (u) baseUrl = u;
|
|
498
|
+
const ttl = f.cacheTTL?.trim();
|
|
499
|
+
if (ttl) {
|
|
500
|
+
const ms = parseDuration(ttl);
|
|
501
|
+
if (ms !== null) cacheTtlMs = ms;
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
const global = loadFile(globalPath());
|
|
505
|
+
if (global) apply(global);
|
|
506
|
+
const project = loadFile(PROJECT_FILE);
|
|
507
|
+
if (project) apply(project);
|
|
508
|
+
const env = process.env[ENV_URL]?.trim();
|
|
509
|
+
if (env) baseUrl = env;
|
|
510
|
+
const flag = flagUrl?.trim();
|
|
511
|
+
if (flag) baseUrl = flag;
|
|
512
|
+
baseUrl = baseUrl.replace(/\/+$/, "");
|
|
513
|
+
if (!baseUrl) throw new NoUrlError();
|
|
514
|
+
return {
|
|
515
|
+
baseUrl,
|
|
516
|
+
cacheTtlMs
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
/** The absolute URL of the components manifest. */
|
|
520
|
+
function componentsUrl(cfg) {
|
|
521
|
+
return `${cfg.baseUrl}/manifests/components.json`;
|
|
522
|
+
}
|
|
523
|
+
/** The absolute URL of the docs manifest. */
|
|
524
|
+
function docsUrl(cfg) {
|
|
525
|
+
return `${cfg.baseUrl}/manifests/docs.json`;
|
|
526
|
+
}
|
|
527
|
+
/** The directory used for the manifest cache. */
|
|
528
|
+
function cacheDir() {
|
|
529
|
+
return join(userCacheDir(), "storyquery");
|
|
530
|
+
}
|
|
531
|
+
function loadFile(path) {
|
|
532
|
+
if (!path) return null;
|
|
533
|
+
try {
|
|
534
|
+
const data = readFileSync(path, "utf8");
|
|
535
|
+
return JSON.parse(data);
|
|
536
|
+
} catch {
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
function globalPath() {
|
|
541
|
+
return join(userConfigDir(), "storyquery", "config.json");
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Parses a Go time.Duration-style string (a subset: s/m/h, optionally combined,
|
|
545
|
+
* e.g. "1h", "30m", "1h30m", "90s") into milliseconds. Returns null on error.
|
|
546
|
+
*/
|
|
547
|
+
function parseDuration(s) {
|
|
548
|
+
const re = /(\d+(?:\.\d+)?)(ms|s|m|h)/g;
|
|
549
|
+
let ms = 0;
|
|
550
|
+
let matched = false;
|
|
551
|
+
let lastIndex = 0;
|
|
552
|
+
for (let m = re.exec(s); m !== null; m = re.exec(s)) {
|
|
553
|
+
if (m.index !== lastIndex) return null;
|
|
554
|
+
matched = true;
|
|
555
|
+
const value = Number(m[1]);
|
|
556
|
+
switch (m[2]) {
|
|
557
|
+
case "ms":
|
|
558
|
+
ms += value;
|
|
559
|
+
break;
|
|
560
|
+
case "s":
|
|
561
|
+
ms += value * 1e3;
|
|
562
|
+
break;
|
|
563
|
+
case "m":
|
|
564
|
+
ms += value * 6e4;
|
|
565
|
+
break;
|
|
566
|
+
case "h":
|
|
567
|
+
ms += value * 36e5;
|
|
568
|
+
break;
|
|
569
|
+
}
|
|
570
|
+
lastIndex = re.lastIndex;
|
|
571
|
+
}
|
|
572
|
+
if (!matched || lastIndex !== s.length) return null;
|
|
573
|
+
return ms;
|
|
574
|
+
}
|
|
575
|
+
function userConfigDir() {
|
|
576
|
+
const home = homedir();
|
|
577
|
+
switch (process.platform) {
|
|
578
|
+
case "win32": return process.env.APPDATA ?? join(home, "AppData", "Roaming");
|
|
579
|
+
case "darwin": return join(home, "Library", "Application Support");
|
|
580
|
+
default: return process.env.XDG_CONFIG_HOME ?? join(home, ".config");
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
function userCacheDir() {
|
|
584
|
+
const home = homedir();
|
|
585
|
+
switch (process.platform) {
|
|
586
|
+
case "win32": return process.env.LOCALAPPDATA ?? join(home, "AppData", "Local");
|
|
587
|
+
case "darwin": return join(home, "Library", "Caches");
|
|
588
|
+
default: return process.env.XDG_CACHE_HOME ?? join(home, ".cache");
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
//#endregion
|
|
593
|
+
//#region src/fetch.ts
|
|
594
|
+
/** Thrown when a fetch receives a non-2xx HTTP response. Drives exit code 3. */
|
|
595
|
+
var HttpStatusError = class extends Error {
|
|
596
|
+
url;
|
|
597
|
+
status;
|
|
598
|
+
constructor(url, status) {
|
|
599
|
+
super(`fetch ${url}: unexpected status ${status}`);
|
|
600
|
+
this.name = "HttpStatusError";
|
|
601
|
+
this.url = url;
|
|
602
|
+
this.status = status;
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
606
|
+
const DEFAULT_MAX_BYTES = 64 << 20;
|
|
607
|
+
/** Builds the default fetch-backed Fetcher. */
|
|
608
|
+
function createFetcher(opts = {}) {
|
|
609
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
610
|
+
const maxBytes = opts.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
611
|
+
return async (url, signal) => {
|
|
612
|
+
const ctrl = new AbortController();
|
|
613
|
+
const onAbort = () => ctrl.abort(signal?.reason);
|
|
614
|
+
if (signal) if (signal.aborted) ctrl.abort(signal.reason);
|
|
615
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
616
|
+
const timer = setTimeout(() => ctrl.abort(/* @__PURE__ */ new Error("request timed out")), timeoutMs);
|
|
617
|
+
try {
|
|
618
|
+
const resp = await fetch(url, {
|
|
619
|
+
method: "GET",
|
|
620
|
+
headers: {
|
|
621
|
+
Accept: "application/json",
|
|
622
|
+
"User-Agent": "storyquery"
|
|
623
|
+
},
|
|
624
|
+
signal: ctrl.signal
|
|
625
|
+
});
|
|
626
|
+
if (resp.status < 200 || resp.status >= 300) throw new HttpStatusError(url, resp.status);
|
|
627
|
+
return await readCapped(resp, url, maxBytes);
|
|
628
|
+
} finally {
|
|
629
|
+
clearTimeout(timer);
|
|
630
|
+
if (signal) signal.removeEventListener("abort", onAbort);
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
/** Reads a response body as text, aborting if it exceeds maxBytes. */
|
|
635
|
+
async function readCapped(resp, url, maxBytes) {
|
|
636
|
+
if (!resp.body) return await resp.text();
|
|
637
|
+
const reader = resp.body.getReader();
|
|
638
|
+
const chunks = [];
|
|
639
|
+
let total = 0;
|
|
640
|
+
try {
|
|
641
|
+
for (;;) {
|
|
642
|
+
const { done, value } = await reader.read();
|
|
643
|
+
if (done) break;
|
|
644
|
+
if (value) {
|
|
645
|
+
total += value.byteLength;
|
|
646
|
+
if (total > maxBytes) throw new Error(`read body from ${url}: response exceeds ${maxBytes} bytes`);
|
|
647
|
+
chunks.push(value);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
} finally {
|
|
651
|
+
reader.releaseLock();
|
|
652
|
+
}
|
|
653
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
//#endregion
|
|
657
|
+
//#region src/manifest/service.ts
|
|
658
|
+
/** Returned when a query matches no component or doc. */
|
|
659
|
+
var NotFoundError = class extends Error {
|
|
660
|
+
constructor(message = "no match found") {
|
|
661
|
+
super(message);
|
|
662
|
+
this.name = "NotFoundError";
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
/** Returned when a lookup expecting a single result matches many. */
|
|
666
|
+
var AmbiguousError = class extends Error {
|
|
667
|
+
candidates;
|
|
668
|
+
constructor(message, candidates = []) {
|
|
669
|
+
super(message);
|
|
670
|
+
this.name = "AmbiguousError";
|
|
671
|
+
this.candidates = candidates;
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
/** Fetches and parses both manifests, fetching them concurrently. */
|
|
675
|
+
async function loadBundle$1(opts, signal) {
|
|
676
|
+
const [comp, docs] = await Promise.all([loadOne(opts, "components", opts.componentsUrl, signal), loadOne(opts, "docs", opts.docsUrl, signal)]);
|
|
677
|
+
const warnings = [...comp.warnings, ...docs.warnings];
|
|
678
|
+
const components = parseComponents(comp.data);
|
|
679
|
+
const parsedDocs = parseDocs(docs.data);
|
|
680
|
+
if (componentsVersionMismatch(components)) warnings.push(`components manifest schema v${components.v ?? 0} differs from supported v${0}`);
|
|
681
|
+
if (docsVersionMismatch(parsedDocs)) warnings.push(`docs manifest schema v${parsedDocs.v ?? 0} differs from supported v${0}`);
|
|
682
|
+
return {
|
|
683
|
+
components,
|
|
684
|
+
docs: parsedDocs,
|
|
685
|
+
warnings
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Resolves a single manifest, using the cache when enabled and falling back to a
|
|
690
|
+
* stale cache entry if the network fetch fails.
|
|
691
|
+
*/
|
|
692
|
+
async function loadOne(opts, label, url, signal) {
|
|
693
|
+
if (!url) throw new Error(`${label} manifest url not configured`);
|
|
694
|
+
const warnings = [];
|
|
695
|
+
if (opts.store) {
|
|
696
|
+
const key = cacheKey(url);
|
|
697
|
+
const entry = await opts.store.load(key, opts.ttlMs);
|
|
698
|
+
if (entry.ok && entry.fresh && !opts.refresh && entry.data !== void 0) return {
|
|
699
|
+
data: entry.data,
|
|
700
|
+
warnings
|
|
701
|
+
};
|
|
702
|
+
try {
|
|
703
|
+
const fetched = await opts.fetcher(url, signal);
|
|
704
|
+
try {
|
|
705
|
+
await opts.store.save(key, fetched);
|
|
706
|
+
} catch (err) {
|
|
707
|
+
warnings.push(`failed to cache ${label} manifest: ${errMsg(err)}`);
|
|
708
|
+
}
|
|
709
|
+
return {
|
|
710
|
+
data: fetched,
|
|
711
|
+
warnings
|
|
712
|
+
};
|
|
713
|
+
} catch (err) {
|
|
714
|
+
if (entry.ok && entry.data !== void 0) {
|
|
715
|
+
warnings.push(`using stale cached ${label} manifest: ${errMsg(err)}`);
|
|
716
|
+
return {
|
|
717
|
+
data: entry.data,
|
|
718
|
+
warnings
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
throw new Error(`fetch ${label} manifest: ${errMsg(err)}`, { cause: err });
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
try {
|
|
725
|
+
return {
|
|
726
|
+
data: await opts.fetcher(url, signal),
|
|
727
|
+
warnings
|
|
728
|
+
};
|
|
729
|
+
} catch (err) {
|
|
730
|
+
throw new Error(`fetch ${label} manifest: ${errMsg(err)}`, { cause: err });
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
function errMsg(err) {
|
|
734
|
+
return err instanceof Error ? err.message : String(err);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
//#endregion
|
|
738
|
+
//#region src/commands/shared.ts
|
|
739
|
+
/** Global args shared by all subcommands (citty has no inherited flags). */
|
|
740
|
+
const globalArgs = {
|
|
741
|
+
url: {
|
|
742
|
+
type: "string",
|
|
743
|
+
description: "Storybook base URL (overrides SQ_URL and config files)"
|
|
744
|
+
},
|
|
745
|
+
format: {
|
|
746
|
+
type: "string",
|
|
747
|
+
description: "output format: json or text",
|
|
748
|
+
default: "json"
|
|
749
|
+
},
|
|
750
|
+
refresh: {
|
|
751
|
+
type: "boolean",
|
|
752
|
+
description: "force a fresh fetch, ignoring cached manifests",
|
|
753
|
+
default: false
|
|
754
|
+
},
|
|
755
|
+
cache: {
|
|
756
|
+
type: "boolean",
|
|
757
|
+
description: "use the on-disk cache (use --no-cache to bypass)",
|
|
758
|
+
default: true
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
/** Resolves the --format flag. */
|
|
762
|
+
function resolveFormat(args) {
|
|
763
|
+
return parseFormat(args.format);
|
|
764
|
+
}
|
|
765
|
+
const LOAD_TIMEOUT_MS = 45e3;
|
|
766
|
+
/**
|
|
767
|
+
* Resolves config, builds the manifest service, and loads both manifests.
|
|
768
|
+
* Shared by every subcommand. Bounds the overall load so a hung server cannot
|
|
769
|
+
* block forever.
|
|
770
|
+
*/
|
|
771
|
+
async function loadBundle(args) {
|
|
772
|
+
const cfg = resolveConfig(args.url);
|
|
773
|
+
const fetcher = createFetcher();
|
|
774
|
+
const store = args.cache ? new Cache(cacheDir()) : void 0;
|
|
775
|
+
const ctrl = new AbortController();
|
|
776
|
+
const timer = setTimeout(() => ctrl.abort(/* @__PURE__ */ new Error("manifest load timed out")), LOAD_TIMEOUT_MS);
|
|
777
|
+
try {
|
|
778
|
+
return await loadBundle$1({
|
|
779
|
+
fetcher,
|
|
780
|
+
componentsUrl: componentsUrl(cfg),
|
|
781
|
+
docsUrl: docsUrl(cfg),
|
|
782
|
+
ttlMs: cfg.cacheTtlMs,
|
|
783
|
+
refresh: args.refresh,
|
|
784
|
+
store
|
|
785
|
+
}, ctrl.signal);
|
|
786
|
+
} finally {
|
|
787
|
+
clearTimeout(timer);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
//#endregion
|
|
792
|
+
//#region src/commands/docs.ts
|
|
793
|
+
const DEFAULT_QUERY_LIMIT$1 = 10;
|
|
794
|
+
const docsCommand = defineCommand({
|
|
795
|
+
meta: {
|
|
796
|
+
name: "docs",
|
|
797
|
+
description: "Search documentation and guideline pages"
|
|
798
|
+
},
|
|
799
|
+
args: {
|
|
800
|
+
term: {
|
|
801
|
+
type: "positional",
|
|
802
|
+
description: "search term",
|
|
803
|
+
required: true
|
|
804
|
+
},
|
|
805
|
+
limit: {
|
|
806
|
+
type: "string",
|
|
807
|
+
description: "maximum results (0 = all)",
|
|
808
|
+
default: String(DEFAULT_QUERY_LIMIT$1)
|
|
809
|
+
},
|
|
810
|
+
...globalArgs
|
|
811
|
+
},
|
|
812
|
+
async run({ args }) {
|
|
813
|
+
const opts = args;
|
|
814
|
+
const format = resolveFormat(opts);
|
|
815
|
+
const bundle = await loadBundle(opts);
|
|
816
|
+
const term = args.term;
|
|
817
|
+
const limit = Number.parseInt(args.limit, 10) || 0;
|
|
818
|
+
const result = {
|
|
819
|
+
term,
|
|
820
|
+
docs: searchDocs(bundle.docs.docs ?? {}, term, limit).map((m) => detailDoc(m.doc))
|
|
821
|
+
};
|
|
822
|
+
if (bundle.warnings.length > 0) result.warnings = bundle.warnings;
|
|
823
|
+
const payload = {
|
|
824
|
+
kind: "docs",
|
|
825
|
+
value: result
|
|
826
|
+
};
|
|
827
|
+
process.stdout.write(encode(format, payload));
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
//#endregion
|
|
832
|
+
//#region src/commands/list.ts
|
|
833
|
+
const listCommand = defineCommand({
|
|
834
|
+
meta: {
|
|
835
|
+
name: "list",
|
|
836
|
+
description: "List all components"
|
|
837
|
+
},
|
|
838
|
+
args: {
|
|
839
|
+
filter: {
|
|
840
|
+
type: "string",
|
|
841
|
+
description: "case-insensitive substring filter on name/id",
|
|
842
|
+
default: ""
|
|
843
|
+
},
|
|
844
|
+
...globalArgs
|
|
845
|
+
},
|
|
846
|
+
async run({ args }) {
|
|
847
|
+
const opts = args;
|
|
848
|
+
const format = resolveFormat(opts);
|
|
849
|
+
const bundle = await loadBundle(opts);
|
|
850
|
+
const needle = args.filter.trim().toLowerCase();
|
|
851
|
+
const result = { components: [] };
|
|
852
|
+
for (const c of Object.values(bundle.components.components ?? {})) {
|
|
853
|
+
const name = (c.name ?? "").toLowerCase();
|
|
854
|
+
const id = (c.id ?? "").toLowerCase();
|
|
855
|
+
if (needle && !name.includes(needle) && !id.includes(needle)) continue;
|
|
856
|
+
result.components.push(summarizeComponent(c, 0));
|
|
857
|
+
}
|
|
858
|
+
result.components.sort((a, b) => a.name.localeCompare(b.name));
|
|
859
|
+
if (bundle.warnings.length > 0) result.warnings = bundle.warnings;
|
|
860
|
+
const payload = {
|
|
861
|
+
kind: "list",
|
|
862
|
+
value: result
|
|
863
|
+
};
|
|
864
|
+
process.stdout.write(encode(format, payload));
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
//#endregion
|
|
869
|
+
//#region src/commands/query.ts
|
|
870
|
+
const DEFAULT_QUERY_LIMIT = 10;
|
|
871
|
+
const queryCommand = defineCommand({
|
|
872
|
+
meta: {
|
|
873
|
+
name: "query",
|
|
874
|
+
description: "Search components and docs for a term"
|
|
875
|
+
},
|
|
876
|
+
args: {
|
|
877
|
+
term: {
|
|
878
|
+
type: "positional",
|
|
879
|
+
description: "search term",
|
|
880
|
+
required: true
|
|
881
|
+
},
|
|
882
|
+
limit: {
|
|
883
|
+
type: "string",
|
|
884
|
+
description: "maximum results per category (0 = all)",
|
|
885
|
+
default: String(DEFAULT_QUERY_LIMIT)
|
|
886
|
+
},
|
|
887
|
+
...globalArgs
|
|
888
|
+
},
|
|
889
|
+
async run({ args }) {
|
|
890
|
+
const opts = args;
|
|
891
|
+
const format = resolveFormat(opts);
|
|
892
|
+
const bundle = await loadBundle(opts);
|
|
893
|
+
const term = args.term;
|
|
894
|
+
const limit = Number.parseInt(args.limit, 10) || 0;
|
|
895
|
+
const result = {
|
|
896
|
+
term,
|
|
897
|
+
components: searchComponents(bundle.components.components ?? {}, term, limit).map((m) => summarizeComponent(m.component, m.score)),
|
|
898
|
+
docs: searchDocs(bundle.docs.docs ?? {}, term, limit).map((m) => ({
|
|
899
|
+
id: m.doc.id ?? "",
|
|
900
|
+
title: m.doc.title ?? "",
|
|
901
|
+
score: m.score
|
|
902
|
+
}))
|
|
903
|
+
};
|
|
904
|
+
if (bundle.warnings.length > 0) result.warnings = bundle.warnings;
|
|
905
|
+
const payload = {
|
|
906
|
+
kind: "query",
|
|
907
|
+
value: result
|
|
908
|
+
};
|
|
909
|
+
process.stdout.write(encode(format, payload));
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
//#endregion
|
|
914
|
+
//#region src/manifest/guideline.ts
|
|
915
|
+
/**
|
|
916
|
+
* Returns the guideline/usage doc associated with a component, if one exists.
|
|
917
|
+
* It matches docs whose id begins with the component's id-derived prefix and
|
|
918
|
+
* looks like a guideline page (e.g. "components-alert-guidelines-usage--docs"
|
|
919
|
+
* for component "components-alert").
|
|
920
|
+
*/
|
|
921
|
+
function guidelineFor(docs, c) {
|
|
922
|
+
const all = docs?.docs;
|
|
923
|
+
if (!all) return void 0;
|
|
924
|
+
const prefix = `${c.id ?? ""}-guidelines`;
|
|
925
|
+
if (c.id) {
|
|
926
|
+
for (const d of Object.values(all)) if (d.id?.startsWith(prefix)) return d;
|
|
927
|
+
}
|
|
928
|
+
const name = (c.name ?? "").toLowerCase();
|
|
929
|
+
if (name) for (const d of Object.values(all)) {
|
|
930
|
+
const id = (d.id ?? "").toLowerCase();
|
|
931
|
+
if (id.includes("guidelines") && id.includes(name)) return d;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
//#endregion
|
|
936
|
+
//#region src/commands/show.ts
|
|
937
|
+
const showCommand = defineCommand({
|
|
938
|
+
meta: {
|
|
939
|
+
name: "show",
|
|
940
|
+
description: "Show full detail for a single component"
|
|
941
|
+
},
|
|
942
|
+
args: {
|
|
943
|
+
term: {
|
|
944
|
+
type: "positional",
|
|
945
|
+
description: "component term or id",
|
|
946
|
+
required: true
|
|
947
|
+
},
|
|
948
|
+
...globalArgs
|
|
949
|
+
},
|
|
950
|
+
async run({ args }) {
|
|
951
|
+
const opts = args;
|
|
952
|
+
const format = resolveFormat(opts);
|
|
953
|
+
const bundle = await loadBundle(opts);
|
|
954
|
+
const term = args.term;
|
|
955
|
+
const comps = bundle.components.components ?? {};
|
|
956
|
+
const result = bestComponent(comps, term);
|
|
957
|
+
if (result.kind === "none") throw new NotFoundError(`"${term}": no match found`);
|
|
958
|
+
if (result.kind === "ambiguous") {
|
|
959
|
+
const candidates = searchComponents(comps, term, 5).map((m) => m.component.id ?? "");
|
|
960
|
+
throw new AmbiguousError(`"${term}": ambiguous match; candidates: ${candidates.join(", ")}`, candidates);
|
|
961
|
+
}
|
|
962
|
+
const component = result.match.component;
|
|
963
|
+
const detail = detailComponent(component, guidelineFor(bundle.docs, component));
|
|
964
|
+
if (bundle.warnings.length > 0) detail.warnings = bundle.warnings;
|
|
965
|
+
const payload = {
|
|
966
|
+
kind: "detail",
|
|
967
|
+
value: detail
|
|
968
|
+
};
|
|
969
|
+
process.stdout.write(encode(format, payload));
|
|
970
|
+
}
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
//#endregion
|
|
974
|
+
//#region src/version.ts
|
|
975
|
+
const VERSION = "0.0.1";
|
|
976
|
+
|
|
977
|
+
//#endregion
|
|
978
|
+
//#region src/cli.ts
|
|
979
|
+
const EXIT_OK = 0;
|
|
980
|
+
const EXIT_NO_MATCH = 1;
|
|
981
|
+
const EXIT_USAGE = 2;
|
|
982
|
+
const EXIT_NETWORK = 3;
|
|
983
|
+
const EXIT_ERROR = 1;
|
|
984
|
+
const main = defineCommand({
|
|
985
|
+
meta: {
|
|
986
|
+
name: "storyquery",
|
|
987
|
+
version: VERSION,
|
|
988
|
+
description: "storyquery fetches a Storybook instance's manifest files and answers agent-friendly queries about components and documentation."
|
|
989
|
+
},
|
|
990
|
+
subCommands: {
|
|
991
|
+
query: queryCommand,
|
|
992
|
+
show: showCommand,
|
|
993
|
+
list: listCommand,
|
|
994
|
+
docs: docsCommand
|
|
995
|
+
}
|
|
996
|
+
});
|
|
997
|
+
function exitCode(err) {
|
|
998
|
+
if (err instanceof NotFoundError) return EXIT_NO_MATCH;
|
|
999
|
+
if (err instanceof AmbiguousError) return EXIT_ERROR;
|
|
1000
|
+
if (err instanceof NoUrlError) return EXIT_USAGE;
|
|
1001
|
+
if (isNetwork(err)) return EXIT_NETWORK;
|
|
1002
|
+
return EXIT_ERROR;
|
|
1003
|
+
}
|
|
1004
|
+
function isNetwork(err) {
|
|
1005
|
+
for (let e = err; e; e = e.cause) {
|
|
1006
|
+
if (e instanceof HttpStatusError) return true;
|
|
1007
|
+
if (e instanceof TypeError) return true;
|
|
1008
|
+
const name = e.name;
|
|
1009
|
+
if (name === "AbortError" || name === "FetchError") return true;
|
|
1010
|
+
}
|
|
1011
|
+
return false;
|
|
1012
|
+
}
|
|
1013
|
+
async function run() {
|
|
1014
|
+
const rawArgs = process.argv.slice(2);
|
|
1015
|
+
if (rawArgs.length === 0 || rawArgs.includes("--help") || rawArgs.includes("-h")) {
|
|
1016
|
+
await showUsage(main);
|
|
1017
|
+
return EXIT_OK;
|
|
1018
|
+
}
|
|
1019
|
+
if (rawArgs.length === 1 && (rawArgs[0] === "--version" || rawArgs[0] === "-v")) {
|
|
1020
|
+
process.stdout.write(`${VERSION}\n`);
|
|
1021
|
+
return EXIT_OK;
|
|
1022
|
+
}
|
|
1023
|
+
try {
|
|
1024
|
+
await runCommand(main, { rawArgs });
|
|
1025
|
+
return EXIT_OK;
|
|
1026
|
+
} catch (err) {
|
|
1027
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1028
|
+
process.stderr.write(`error: ${message}\n`);
|
|
1029
|
+
return exitCode(err);
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
run().then((code) => {
|
|
1033
|
+
process.exitCode = code;
|
|
1034
|
+
}, (err) => {
|
|
1035
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1036
|
+
process.stderr.write(`error: ${message}\n`);
|
|
1037
|
+
process.exitCode = EXIT_ERROR;
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
//#endregion
|
|
1041
|
+
export { };
|
|
1042
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","names":["errMsg","loadBundle","loadManifestBundle","DEFAULT_QUERY_LIMIT"],"sources":["../src/output/text.ts","../src/manifest/types.ts","../src/output/types.ts","../src/output/index.ts","../src/search.ts","../src/cache.ts","../src/config.ts","../src/fetch.ts","../src/manifest/service.ts","../src/commands/shared.ts","../src/commands/docs.ts","../src/commands/list.ts","../src/commands/query.ts","../src/manifest/guideline.ts","../src/commands/show.ts","../src/version.ts","../src/cli.ts"],"sourcesContent":["// Human-friendly text rendering for the known view models.\nimport type { ComponentDetail, DocsResult, ListResult, QueryResult } from \"./types.js\";\n\nfunction firstLine(s: string): string {\n const i = s.indexOf(\"\\n\");\n return i >= 0 ? s.slice(0, i) : s;\n}\n\nfunction pad(s: string, width: number): string {\n return s.length >= width ? s : s + \" \".repeat(width - s.length);\n}\n\nfunction warnings(lines: string[] | undefined): string {\n return (lines ?? []).map((m) => `! ${m}\\n`).join(\"\");\n}\n\nexport function renderQuery(r: QueryResult): string {\n let out = warnings(r.warnings);\n out += `Query: ${r.term}\\n`;\n out += `\\nComponents (${r.components.length}):\\n`;\n for (const c of r.components) {\n out += ` ${pad(c.name, 28)} ${c.id}\\n`;\n if (c.description) out += ` ${firstLine(c.description)}\\n`;\n }\n out += `\\nDocs (${r.docs.length}):\\n`;\n for (const d of r.docs) {\n out += ` ${pad(d.title, 28)} ${d.id}\\n`;\n }\n return out;\n}\n\nexport function renderList(r: ListResult): string {\n let out = warnings(r.warnings);\n for (const c of r.components) {\n out += `${pad(c.name, 32)} ${c.id}\\n`;\n }\n return out;\n}\n\nexport function renderDetail(d: ComponentDetail): string {\n let out = warnings(d.warnings);\n out += `${d.name} (${d.id})\\n`;\n if (d.description) out += `\\n${d.description}\\n`;\n if (d.tags && Object.keys(d.tags).length > 0) {\n out += `\\nTags:\\n`;\n for (const [k, v] of Object.entries(d.tags)) {\n out += ` @${k} ${firstLine(v)}\\n`;\n }\n }\n if (d.import) out += `\\nImport:\\n ${d.import}\\n`;\n if (d.sourceFile) out += `Source: ${d.sourceFile}\\n`;\n out += `\\nProps (${d.props.length}):\\n`;\n for (const p of d.props) {\n const req = p.required ? \" (required)\" : \"\";\n const def = p.default ? ` = ${p.default}` : \"\";\n out += ` ${p.name}: ${p.type}${def}${req}\\n`;\n if (p.description) out += ` ${firstLine(p.description)}\\n`;\n }\n out += `\\nStories (${d.stories.length}):\\n`;\n for (const s of d.stories) {\n out += ` ${s.name}\\n`;\n }\n if (d.guideline) {\n out += `\\nGuideline: ${d.guideline.title}\\n\\n${d.guideline.content}\\n`;\n }\n return out;\n}\n\nexport function renderDocs(r: DocsResult): string {\n let out = warnings(r.warnings);\n out += `Query: ${r.term}\\n`;\n for (const d of r.docs) {\n out += `\\n=== ${d.title} (${d.id}) ===\\n${d.content}\\n`;\n }\n return out;\n}\n","// Models the Storybook design-system manifest files served at\n// <base>/manifests/components.json and <base>/manifests/docs.json.\n//\n// The schema is versioned through the top-level \"v\" field. Parsing is\n// intentionally lenient: arktype ignores undeclared keys by default, so the CLI\n// keeps working as the upstream schema evolves. Validation only narrows the\n// fields we care about; a version mismatch is surfaced as a warning, never an\n// error.\nimport { type } from \"arktype\";\n\n/**\n * The manifest schema version this package was written against. Manifests\n * reporting a different version are still parsed (best effort) but the mismatch\n * is surfaced to callers as a warning.\n */\nexport const SCHEMA_VERSION = 0;\n\n// --- arktype schemas (single source of truth for runtime + static types) ---\n\n/** The default value of a prop, when one is documented. */\nconst propDefault = type({\n \"value?\": \"unknown\",\n});\n\n/** The resolved type of a prop. */\nconst propType = type({\n \"name?\": \"string\",\n \"raw?\": \"string\",\n \"value?\": \"unknown\",\n});\n\n/** A single component property. */\nconst prop = type({\n \"name?\": \"string\",\n \"description?\": \"string\",\n \"required?\": \"boolean\",\n \"defaultValue?\": propDefault.or(\"null\"),\n \"type?\": propType,\n});\n\n/** The react-docgen-typescript output for a component. */\nconst docgenInfo = type({\n \"displayName?\": \"string\",\n \"description?\": \"string\",\n \"exportName?\": \"string\",\n \"filePath?\": \"string\",\n \"tags?\": type.Record(\"string\", \"string\"),\n \"props?\": type.Record(\"string\", prop),\n});\n\n/** A single Storybook story for a component. */\nconst story = type({\n \"id?\": \"string\",\n \"name?\": \"string\",\n \"snippet?\": \"string\",\n});\n\n/** A single design-system component. */\nconst component = type({\n \"id?\": \"string\",\n \"name?\": \"string\",\n \"path?\": \"string\",\n \"import?\": \"string\",\n \"description?\": \"string\",\n \"jsDocTags?\": \"unknown\",\n \"stories?\": story.array(),\n \"reactDocgenTypescript?\": docgenInfo,\n});\n\n/** A single MDX documentation/guideline page. */\nconst doc = type({\n \"id?\": \"string\",\n \"name?\": \"string\",\n \"path?\": \"string\",\n \"title?\": \"string\",\n \"content?\": \"string\",\n});\n\n/** The parsed representation of components.json. */\nconst components = type({\n \"v?\": \"number\",\n \"components?\": type.Record(\"string\", component),\n});\n\n/** The parsed representation of docs.json. */\nconst docs = type({\n \"v?\": \"number\",\n \"docs?\": type.Record(\"string\", doc),\n});\n\n// --- Inferred static types (derived from the schemas above) ---\n\nexport type PropDefault = typeof propDefault.infer;\nexport type PropType = typeof propType.infer;\nexport type Prop = typeof prop.infer;\nexport type DocgenInfo = typeof docgenInfo.infer;\nexport type Story = typeof story.infer;\nexport type Component = typeof component.infer;\nexport type Doc = typeof doc.infer;\nexport type Components = typeof components.infer;\nexport type Docs = typeof docs.infer;\n\n// --- Parsers ---\n\n/** Decodes and validates components.json text. Lenient: never throws on extra keys. */\nexport function parseComponents(data: string): Components {\n const out = components(safeJson(data));\n if (out instanceof type.errors) {\n throw new Error(`parse components manifest: ${out.summary}`);\n }\n if (!out.components) out.components = {};\n return out;\n}\n\n/** Decodes and validates docs.json text. Lenient: never throws on extra keys. */\nexport function parseDocs(data: string): Docs {\n const out = docs(safeJson(data));\n if (out instanceof type.errors) {\n throw new Error(`parse docs manifest: ${out.summary}`);\n }\n if (!out.docs) out.docs = {};\n return out;\n}\n\nfunction safeJson(data: string): unknown {\n try {\n return JSON.parse(data);\n } catch (err) {\n throw new Error(`parse manifest json: ${err instanceof Error ? err.message : String(err)}`);\n }\n}\n\n// --- Version checks ---\n\n/** Reports whether a components manifest version differs from the supported one. */\nexport function componentsVersionMismatch(c: Components): boolean {\n return (c.v ?? 0) !== SCHEMA_VERSION;\n}\n\n/** Reports whether a docs manifest version differs from the supported one. */\nexport function docsVersionMismatch(d: Docs): boolean {\n return (d.v ?? 0) !== SCHEMA_VERSION;\n}\n\n// --- Field renderers (ported from Go String() methods) ---\n\n/** Renders a prop's default value as a readable string. */\nexport function propDefaultString(d: PropDefault | null | undefined): string {\n if (!d || d.value === undefined || d.value === null) return \"\";\n // The value may be a JSON string, number, or bool. Prefer a bare string.\n if (typeof d.value === \"string\") return d.value;\n return JSON.stringify(d.value);\n}\n\n/** Renders a prop type, preferring the raw TypeScript expression. */\nexport function propTypeString(t: PropType | undefined): string {\n if (!t) return \"\";\n if (t.raw) return t.raw;\n return t.name ?? \"\";\n}\n","// Stable view models — the JSON contract (agent-first). Optional fields are\n// omitted from JSON when empty to match the Go `omitempty` behavior.\nimport type { Component, Doc } from \"../manifest/types.js\";\nimport { propDefaultString, propTypeString } from \"../manifest/types.js\";\n\nexport interface ComponentSummary {\n id: string;\n name: string;\n description?: string;\n import?: string;\n props: number;\n stories: number;\n score?: number;\n}\n\nexport interface DocSummary {\n id: string;\n title: string;\n score?: number;\n}\n\nexport interface QueryResult {\n term: string;\n components: ComponentSummary[];\n docs: DocSummary[];\n warnings?: string[];\n}\n\nexport interface PropDetail {\n name: string;\n type: string;\n required: boolean;\n default?: string;\n description?: string;\n}\n\nexport interface StoryDetail {\n id: string;\n name: string;\n snippet?: string;\n}\n\nexport interface DocDetail {\n id: string;\n title: string;\n content: string;\n warnings?: string[];\n}\n\nexport interface ComponentDetail {\n id: string;\n name: string;\n description?: string;\n import?: string;\n path?: string;\n sourceFile?: string;\n tags?: Record<string, string>;\n props: PropDetail[];\n stories: StoryDetail[];\n guideline?: DocDetail;\n warnings?: string[];\n}\n\nexport interface DocsResult {\n term: string;\n docs: DocDetail[];\n warnings?: string[];\n}\n\nexport interface ListResult {\n components: ComponentSummary[];\n warnings?: string[];\n}\n\n// --- Builders ---\n\n/** Builds a compact summary (score optional; omitted when 0). */\nexport function summarizeComponent(c: Component, score: number): ComponentSummary {\n // Field order mirrors the original JSON contract: id, name, description,\n // import, props, stories, score.\n const summary = { id: c.id ?? \"\", name: c.name ?? \"\" } as ComponentSummary;\n if (c.description) summary.description = c.description;\n if (c.import) summary.import = c.import;\n summary.props = Object.keys(c.reactDocgenTypescript?.props ?? {}).length;\n summary.stories = c.stories?.length ?? 0;\n if (score) summary.score = score;\n return summary;\n}\n\n/** Builds the full detail view, attaching a guideline doc if set. */\nexport function detailComponent(c: Component, guideline?: Doc): ComponentDetail {\n const docgen = c.reactDocgenTypescript;\n const detail: ComponentDetail = {\n id: c.id ?? \"\",\n name: c.name ?? \"\",\n props: [],\n stories: [],\n };\n if (c.description) detail.description = c.description;\n if (c.import) detail.import = c.import;\n if (c.path) detail.path = c.path;\n if (docgen?.filePath) detail.sourceFile = docgen.filePath;\n\n const tags = mergeTags(docgen?.tags);\n if (tags) detail.tags = tags;\n\n for (const p of Object.values(docgen?.props ?? {})) {\n const prop: PropDetail = {\n name: p.name ?? \"\",\n type: propTypeString(p.type),\n required: p.required ?? false,\n };\n const def = propDefaultString(p.defaultValue);\n if (def) prop.default = def;\n if (p.description) prop.description = p.description;\n detail.props.push(prop);\n }\n // Stable prop order by name.\n detail.props.sort((a, b) => a.name.localeCompare(b.name));\n\n for (const s of c.stories ?? []) {\n const story: StoryDetail = { id: s.id ?? \"\", name: s.name ?? \"\" };\n if (s.snippet) story.snippet = s.snippet;\n detail.stories.push(story);\n }\n\n if (guideline) {\n detail.guideline = {\n id: guideline.id ?? \"\",\n title: guideline.title ?? \"\",\n content: guideline.content ?? \"\",\n };\n }\n return detail;\n}\n\n/** Builds a full doc view. */\nexport function detailDoc(d: Doc): DocDetail {\n return { id: d.id ?? \"\", title: d.title ?? \"\", content: d.content ?? \"\" };\n}\n\n/** Combines tag maps, returning undefined when all are empty. */\nfunction mergeTags(\n ...sources: (Record<string, string> | undefined)[]\n): Record<string, string> | undefined {\n const merged: Record<string, string> = {};\n for (const src of sources) {\n if (!src) continue;\n for (const [k, v] of Object.entries(src)) {\n if (v) merged[k] = v;\n }\n }\n return Object.keys(merged).length > 0 ? merged : undefined;\n}\n","import { renderDetail, renderDocs, renderList, renderQuery } from \"./text.js\";\n// Renders query results in either machine-readable JSON (the default,\n// agent-first contract) or human-friendly text.\nimport type { ComponentDetail, DocsResult, ListResult, QueryResult } from \"./types.js\";\n\nexport * from \"./types.js\";\n\n/** The rendering style. */\nexport type Format = \"json\" | \"text\";\n\n/**\n * A tagged payload so text rendering can dispatch to the right renderer with\n * full type safety. JSON output ignores the tag and serializes `value`.\n */\nexport type Renderable =\n | { kind: \"query\"; value: QueryResult }\n | { kind: \"list\"; value: ListResult }\n | { kind: \"detail\"; value: ComponentDetail }\n | { kind: \"docs\"; value: DocsResult };\n\n/** Validates and normalizes a format string. */\nexport function parseFormat(s: string): Format {\n switch (s.trim().toLowerCase()) {\n case \"json\":\n return \"json\";\n case \"text\":\n return \"text\";\n default:\n throw new Error(`invalid format \"${s}\" (want json or text)`);\n }\n}\n\n/** Renders a payload in the requested format. */\nexport function encode(format: Format, payload: Renderable): string {\n if (format === \"json\") return `${JSON.stringify(payload.value, null, 2)}\\n`;\n switch (payload.kind) {\n case \"query\":\n return renderQuery(payload.value);\n case \"list\":\n return renderList(payload.value);\n case \"detail\":\n return renderDetail(payload.value);\n case \"docs\":\n return renderDocs(payload.value);\n }\n}\n","// Ranks components and docs against a search term.\nimport type { Component, Doc } from \"./manifest/types.js\";\n\n// Score tiers. Higher is a better match.\nconst SCORE_EXACT_NAME = 1000;\nconst SCORE_EXACT_ID = 900;\nconst SCORE_NAME_PREFIX = 700;\nconst SCORE_ID_SUBSTR = 500;\nconst SCORE_NAME_SUBSTR = 450;\nconst SCORE_FUZZY = 100; // base; fuzzy adds a density bonus on top\nconst SCORE_NO_MATCH = 0;\n\n/** A component paired with its relevance score. */\nexport interface ComponentMatch {\n component: Component;\n score: number;\n}\n\n/** A doc paired with its relevance score. */\nexport interface DocMatch {\n doc: Doc;\n score: number;\n}\n\n/** The outcome of resolving a term to a single best component. */\nexport type BestComponentResult =\n | { kind: \"found\"; match: ComponentMatch }\n | { kind: \"ambiguous\"; match: ComponentMatch }\n | { kind: \"none\" };\n\n/**\n * Ranks all components against term, returning matches with a positive score\n * sorted best-first. limit <= 0 returns all matches.\n */\nexport function searchComponents(\n comps: Record<string, Component>,\n term: string,\n limit: number,\n): ComponentMatch[] {\n const q = term.trim().toLowerCase();\n const out: ComponentMatch[] = [];\n for (const component of Object.values(comps)) {\n const score = scoreComponent(component, q);\n if (score > SCORE_NO_MATCH) out.push({ component, score });\n }\n out.sort((a, b) => {\n if (a.score !== b.score) return b.score - a.score;\n return (a.component.name ?? \"\").localeCompare(b.component.name ?? \"\");\n });\n return cap(out, limit);\n}\n\n/** Ranks all docs against term, matching on title, name, id, and content. */\nexport function searchDocs(docs: Record<string, Doc>, term: string, limit: number): DocMatch[] {\n const q = term.trim().toLowerCase();\n const out: DocMatch[] = [];\n for (const doc of Object.values(docs)) {\n const score = scoreDoc(doc, q);\n if (score > SCORE_NO_MATCH) out.push({ doc, score });\n }\n out.sort((a, b) => {\n if (a.score !== b.score) return b.score - a.score;\n return (a.doc.title ?? \"\").localeCompare(b.doc.title ?? \"\");\n });\n return cap(out, limit);\n}\n\n/**\n * Resolves term to a single best component. A direct id or exact name always\n * wins outright; multiple tied top matches are ambiguous.\n */\nexport function bestComponent(comps: Record<string, Component>, term: string): BestComponentResult {\n const matches = searchComponents(comps, term, 0);\n if (matches.length === 0) return { kind: \"none\" };\n if (matches.length === 1) return { kind: \"found\", match: matches[0]! };\n // A unique top score is a clear winner.\n if (matches[0]!.score > matches[1]!.score) {\n return { kind: \"found\", match: matches[0]! };\n }\n return { kind: \"ambiguous\", match: matches[0]! };\n}\n\nfunction scoreComponent(c: Component, q: string): number {\n if (q === \"\") return SCORE_NO_MATCH;\n const name = (c.name ?? \"\").toLowerCase();\n const id = (c.id ?? \"\").toLowerCase();\n\n if (name === q) return SCORE_EXACT_NAME;\n if (id === q) return SCORE_EXACT_ID;\n if (name.startsWith(q)) return SCORE_NAME_PREFIX;\n if (id.includes(q)) return SCORE_ID_SUBSTR;\n if (name.includes(q)) return SCORE_NAME_SUBSTR;\n\n const nameFuzzy = fuzzyScore(name, q);\n if (nameFuzzy !== null) return SCORE_FUZZY + nameFuzzy;\n const idFuzzy = fuzzyScore(id, q);\n if (idFuzzy !== null) return SCORE_FUZZY + idFuzzy;\n return SCORE_NO_MATCH;\n}\n\nfunction scoreDoc(d: Doc, q: string): number {\n if (q === \"\") return SCORE_NO_MATCH;\n const title = (d.title ?? \"\").toLowerCase();\n const name = (d.name ?? \"\").toLowerCase();\n const id = (d.id ?? \"\").toLowerCase();\n\n if (title === q || name === q) return SCORE_EXACT_NAME;\n if (id === q) return SCORE_EXACT_ID;\n if (title.startsWith(q)) return SCORE_NAME_PREFIX;\n if (id.includes(q)) return SCORE_ID_SUBSTR;\n if (title.includes(q) || name.includes(q)) return SCORE_NAME_SUBSTR;\n\n const titleFuzzy = fuzzyScore(title, q);\n if (titleFuzzy !== null) return SCORE_FUZZY + titleFuzzy;\n // Content match is the weakest signal.\n if ((d.content ?? \"\").toLowerCase().includes(q)) return SCORE_FUZZY / 2;\n return SCORE_NO_MATCH;\n}\n\n/**\n * Reports whether q is a subsequence of s and, if so, a density bonus that\n * rewards tightly-packed matches over scattered ones. Returns null on no match.\n */\nexport function fuzzyScore(s: string, q: string): number | null {\n if (q === \"\") return null;\n const sr = [...s];\n const qr = [...q];\n let si = 0;\n let qi = 0;\n let first = -1;\n let last = -1;\n while (si < sr.length && qi < qr.length) {\n if (sr[si] === qr[qi]) {\n if (first < 0) first = si;\n last = si;\n qi++;\n }\n si++;\n }\n if (qi !== qr.length) return null;\n let span = last - first + 1;\n if (span <= 0) span = 1;\n // Higher bonus when the matched runes are densely packed.\n return Math.trunc((qr.length * 50) / span);\n}\n\nfunction cap<T>(items: T[], limit: number): T[] {\n if (limit > 0 && items.length > limit) return items.slice(0, limit);\n return items;\n}\n","// A small TTL-based disk cache for manifest text.\n//\n// Entries are keyed by an opaque string (typically derived from the source URL).\n// Each entry stores the raw payload plus a sidecar metadata file holding the\n// fetch timestamp, enabling both TTL checks and stale-fallback reads.\nimport { createHash } from \"node:crypto\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nexport interface CacheEntry {\n /** The cached payload, or undefined when the entry is missing (ok=false). */\n data?: string;\n /** Whether the entry is within the TTL window. */\n fresh: boolean;\n /** Whether an entry exists at all. */\n ok: boolean;\n}\n\n/** Loads and saves cached payloads. */\nexport interface Store {\n load(key: string, ttlMs: number): Promise<CacheEntry>;\n save(key: string, data: string): Promise<void>;\n}\n\ninterface Meta {\n fetched_at: string;\n}\n\nexport interface CacheOptions {\n /** Overrides the time source (used in tests). */\n now?: () => number;\n}\n\n/** A filesystem-backed Store rooted at a directory. */\nexport class Cache implements Store {\n private readonly dir: string;\n private readonly now: () => number;\n private ready: Promise<void> | null = null;\n\n constructor(dir: string, opts: CacheOptions = {}) {\n this.dir = dir;\n this.now = opts.now ?? Date.now;\n }\n\n private ensureDir(): Promise<void> {\n if (!this.ready) this.ready = mkdir(this.dir, { recursive: true }).then(() => undefined);\n return this.ready;\n }\n\n private dataPath(key: string): string {\n return join(this.dir, `${key}.data`);\n }\n\n private metaPath(key: string): string {\n return join(this.dir, `${key}.meta.json`);\n }\n\n async load(key: string, ttlMs: number): Promise<CacheEntry> {\n let data: string;\n try {\n data = await readFile(this.dataPath(key), \"utf8\");\n } catch (err) {\n if (isNotFound(err)) return { fresh: false, ok: false };\n throw new Error(`read cache data: ${errMsg(err)}`);\n }\n\n let fresh = false;\n try {\n const raw = await readFile(this.metaPath(key), \"utf8\");\n const meta = JSON.parse(raw) as Meta;\n const fetchedAt = Date.parse(meta.fetched_at);\n if (!Number.isNaN(fetchedAt)) fresh = this.now() - fetchedAt < ttlMs;\n } catch {\n // Missing or unreadable meta: treat as stale.\n }\n\n return { data, fresh, ok: true };\n }\n\n async save(key: string, data: string): Promise<void> {\n await this.ensureDir();\n try {\n await writeFile(this.dataPath(key), data, \"utf8\");\n } catch (err) {\n throw new Error(`write cache data: ${errMsg(err)}`);\n }\n const meta: Meta = { fetched_at: new Date(this.now()).toISOString() };\n try {\n await writeFile(this.metaPath(key), JSON.stringify(meta), \"utf8\");\n } catch (err) {\n throw new Error(`write cache meta: ${errMsg(err)}`);\n }\n }\n}\n\n/** Derives a stable, filesystem-safe cache key from arbitrary parts. */\nexport function cacheKey(...parts: string[]): string {\n const h = createHash(\"sha256\");\n for (const p of parts) {\n h.update(p);\n h.update(Buffer.from([0]));\n }\n return h.digest(\"hex\").slice(0, 32);\n}\n\nfunction isNotFound(err: unknown): boolean {\n return (err as NodeJS.ErrnoException)?.code === \"ENOENT\";\n}\n\nfunction errMsg(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n","// Resolves the storyquery runtime configuration from, in order of precedence:\n// command-line flag, environment variable, a project-local file\n// (./.storyquery.json), and a global file (<userConfigDir>/storyquery/config.json).\nimport { readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\n/** The environment variable holding the Storybook base URL. */\nexport const ENV_URL = \"SQ_URL\";\n\n/** The project-local config filename. */\nexport const PROJECT_FILE = \".storyquery.json\";\n\n/** The cache time-to-live used when none is configured (1 hour, in ms). */\nexport const DEFAULT_TTL_MS = 60 * 60 * 1000;\n\n/** Thrown when no base URL could be resolved from any source. Drives exit code 2. */\nexport class NoUrlError extends Error {\n constructor() {\n super(\"no storybook url configured: pass --url, set SQ_URL, or add a config file\");\n this.name = \"NoUrlError\";\n }\n}\n\n/** The resolved runtime configuration. */\nexport interface Config {\n baseUrl: string;\n cacheTtlMs: number;\n}\n\ninterface FileConfig {\n url?: string;\n cacheTTL?: string;\n}\n\n/**\n * Produces a Config from the flag value (may be empty), the environment, and\n * config files. flagUrl takes highest precedence.\n */\nexport function resolveConfig(flagUrl?: string): Config {\n let baseUrl = \"\";\n let cacheTtlMs = DEFAULT_TTL_MS;\n\n const apply = (f: FileConfig) => {\n const u = f.url?.trim();\n if (u) baseUrl = u;\n const ttl = f.cacheTTL?.trim();\n if (ttl) {\n const ms = parseDuration(ttl);\n if (ms !== null) cacheTtlMs = ms;\n }\n };\n\n // Lowest precedence first; later assignments win.\n const global = loadFile(globalPath());\n if (global) apply(global);\n const project = loadFile(PROJECT_FILE);\n if (project) apply(project);\n\n const env = process.env[ENV_URL]?.trim();\n if (env) baseUrl = env;\n\n const flag = flagUrl?.trim();\n if (flag) baseUrl = flag;\n\n baseUrl = baseUrl.replace(/\\/+$/, \"\");\n if (!baseUrl) throw new NoUrlError();\n\n return { baseUrl, cacheTtlMs };\n}\n\n/** The absolute URL of the components manifest. */\nexport function componentsUrl(cfg: Config): string {\n return `${cfg.baseUrl}/manifests/components.json`;\n}\n\n/** The absolute URL of the docs manifest. */\nexport function docsUrl(cfg: Config): string {\n return `${cfg.baseUrl}/manifests/docs.json`;\n}\n\n/** The directory used for the manifest cache. */\nexport function cacheDir(): string {\n return join(userCacheDir(), \"storyquery\");\n}\n\nfunction loadFile(path: string): FileConfig | null {\n if (!path) return null;\n try {\n const data = readFileSync(path, \"utf8\");\n return JSON.parse(data) as FileConfig;\n } catch {\n return null;\n }\n}\n\nfunction globalPath(): string {\n return join(userConfigDir(), \"storyquery\", \"config.json\");\n}\n\n/**\n * Parses a Go time.Duration-style string (a subset: s/m/h, optionally combined,\n * e.g. \"1h\", \"30m\", \"1h30m\", \"90s\") into milliseconds. Returns null on error.\n */\nexport function parseDuration(s: string): number | null {\n const re = /(\\d+(?:\\.\\d+)?)(ms|s|m|h)/g;\n let ms = 0;\n let matched = false;\n let lastIndex = 0;\n for (let m = re.exec(s); m !== null; m = re.exec(s)) {\n if (m.index !== lastIndex) return null; // gap = junk between units\n matched = true;\n const value = Number(m[1]);\n switch (m[2]) {\n case \"ms\":\n ms += value;\n break;\n case \"s\":\n ms += value * 1000;\n break;\n case \"m\":\n ms += value * 60_000;\n break;\n case \"h\":\n ms += value * 3_600_000;\n break;\n }\n lastIndex = re.lastIndex;\n }\n if (!matched || lastIndex !== s.length) return null;\n return ms;\n}\n\n// --- Platform directories (no dependency) ---\n\nfunction userConfigDir(): string {\n const home = homedir();\n switch (process.platform) {\n case \"win32\":\n return process.env.APPDATA ?? join(home, \"AppData\", \"Roaming\");\n case \"darwin\":\n return join(home, \"Library\", \"Application Support\");\n default:\n return process.env.XDG_CONFIG_HOME ?? join(home, \".config\");\n }\n}\n\nfunction userCacheDir(): string {\n const home = homedir();\n switch (process.platform) {\n case \"win32\":\n return process.env.LOCALAPPDATA ?? join(home, \"AppData\", \"Local\");\n case \"darwin\":\n return join(home, \"Library\", \"Caches\");\n default:\n return process.env.XDG_CACHE_HOME ?? join(home, \".cache\");\n }\n}\n","// Retrieves raw manifest text over HTTP using the native fetch API.\n//\n// The Fetcher type is intentionally minimal so callers can inject fakes in tests\n// without touching the network.\n\n/** Thrown when a fetch receives a non-2xx HTTP response. Drives exit code 3. */\nexport class HttpStatusError extends Error {\n readonly url: string;\n readonly status: number;\n constructor(url: string, status: number) {\n super(`fetch ${url}: unexpected status ${status}`);\n this.name = \"HttpStatusError\";\n this.url = url;\n this.status = status;\n }\n}\n\n/** Retrieves the raw text located at a URL. */\nexport type Fetcher = (url: string, signal?: AbortSignal) => Promise<string>;\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst DEFAULT_MAX_BYTES = 64 << 20; // 64 MiB\n\nexport interface FetchOptions {\n timeoutMs?: number;\n maxBytes?: number;\n}\n\n/** Builds the default fetch-backed Fetcher. */\nexport function createFetcher(opts: FetchOptions = {}): Fetcher {\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const maxBytes = opts.maxBytes ?? DEFAULT_MAX_BYTES;\n\n return async (url: string, signal?: AbortSignal): Promise<string> => {\n const ctrl = new AbortController();\n const onAbort = () => ctrl.abort(signal?.reason);\n if (signal) {\n if (signal.aborted) ctrl.abort(signal.reason);\n else signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n const timer = setTimeout(() => ctrl.abort(new Error(\"request timed out\")), timeoutMs);\n\n try {\n const resp = await fetch(url, {\n method: \"GET\",\n headers: { Accept: \"application/json\", \"User-Agent\": \"storyquery\" },\n signal: ctrl.signal,\n });\n\n if (resp.status < 200 || resp.status >= 300) {\n throw new HttpStatusError(url, resp.status);\n }\n return await readCapped(resp, url, maxBytes);\n } finally {\n clearTimeout(timer);\n if (signal) signal.removeEventListener(\"abort\", onAbort);\n }\n };\n}\n\n/** Reads a response body as text, aborting if it exceeds maxBytes. */\nasync function readCapped(resp: Response, url: string, maxBytes: number): Promise<string> {\n if (!resp.body) return await resp.text();\n\n const reader = resp.body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value) {\n total += value.byteLength;\n if (total > maxBytes) {\n throw new Error(`read body from ${url}: response exceeds ${maxBytes} bytes`);\n }\n chunks.push(value);\n }\n }\n } finally {\n reader.releaseLock();\n }\n\n return Buffer.concat(chunks).toString(\"utf8\");\n}\n","// Fetches, caches, and parses the manifest bundle.\nimport type { Store } from \"../cache.js\";\nimport { cacheKey } from \"../cache.js\";\nimport type { Fetcher } from \"../fetch.js\";\nimport {\n type Components,\n type Docs,\n SCHEMA_VERSION,\n componentsVersionMismatch,\n docsVersionMismatch,\n parseComponents,\n parseDocs,\n} from \"./types.js\";\n\n/** Returned when a query matches no component or doc. */\nexport class NotFoundError extends Error {\n constructor(message = \"no match found\") {\n super(message);\n this.name = \"NotFoundError\";\n }\n}\n\n/** Returned when a lookup expecting a single result matches many. */\nexport class AmbiguousError extends Error {\n readonly candidates: string[];\n constructor(message: string, candidates: string[] = []) {\n super(message);\n this.name = \"AmbiguousError\";\n this.candidates = candidates;\n }\n}\n\n/** Both parsed manifests together with version warnings. */\nexport interface Bundle {\n components: Components;\n docs: Docs;\n /** Non-fatal messages (e.g. schema version mismatch, stale cache). */\n warnings: string[];\n}\n\nexport interface ServiceOptions {\n fetcher: Fetcher;\n componentsUrl: string;\n docsUrl: string;\n ttlMs: number;\n refresh?: boolean;\n /** A cache store; omit to always fetch fresh. */\n store?: Store;\n}\n\ninterface LoadedManifest {\n data: string;\n warnings: string[];\n}\n\n/** Fetches and parses both manifests, fetching them concurrently. */\nexport async function loadBundle(opts: ServiceOptions, signal?: AbortSignal): Promise<Bundle> {\n const [comp, docs] = await Promise.all([\n loadOne(opts, \"components\", opts.componentsUrl, signal),\n loadOne(opts, \"docs\", opts.docsUrl, signal),\n ]);\n\n const warnings = [...comp.warnings, ...docs.warnings];\n\n const components = parseComponents(comp.data);\n const parsedDocs = parseDocs(docs.data);\n\n if (componentsVersionMismatch(components)) {\n warnings.push(\n `components manifest schema v${components.v ?? 0} differs from supported v${SCHEMA_VERSION}`,\n );\n }\n if (docsVersionMismatch(parsedDocs)) {\n warnings.push(\n `docs manifest schema v${parsedDocs.v ?? 0} differs from supported v${SCHEMA_VERSION}`,\n );\n }\n\n return { components, docs: parsedDocs, warnings };\n}\n\n/**\n * Resolves a single manifest, using the cache when enabled and falling back to a\n * stale cache entry if the network fetch fails.\n */\nasync function loadOne(\n opts: ServiceOptions,\n label: string,\n url: string,\n signal?: AbortSignal,\n): Promise<LoadedManifest> {\n if (!url) throw new Error(`${label} manifest url not configured`);\n\n const warnings: string[] = [];\n\n if (opts.store) {\n const key = cacheKey(url);\n const entry = await opts.store.load(key, opts.ttlMs);\n if (entry.ok && entry.fresh && !opts.refresh && entry.data !== undefined) {\n return { data: entry.data, warnings };\n }\n\n // Need to fetch; keep any stale copy for fallback.\n try {\n const fetched = await opts.fetcher(url, signal);\n try {\n await opts.store.save(key, fetched);\n } catch (err) {\n warnings.push(`failed to cache ${label} manifest: ${errMsg(err)}`);\n }\n return { data: fetched, warnings };\n } catch (err) {\n if (entry.ok && entry.data !== undefined) {\n warnings.push(`using stale cached ${label} manifest: ${errMsg(err)}`);\n return { data: entry.data, warnings };\n }\n throw new Error(`fetch ${label} manifest: ${errMsg(err)}`, { cause: err });\n }\n }\n\n try {\n const fetched = await opts.fetcher(url, signal);\n return { data: fetched, warnings };\n } catch (err) {\n throw new Error(`fetch ${label} manifest: ${errMsg(err)}`, { cause: err });\n }\n}\n\nfunction errMsg(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n","// Shared global arguments and the bundle-loading helper used by every command.\nimport { Cache } from \"../cache.js\";\nimport { type Config, cacheDir, componentsUrl, docsUrl, resolveConfig } from \"../config.js\";\nimport { createFetcher } from \"../fetch.js\";\nimport type { Bundle } from \"../manifest/service.js\";\nimport { loadBundle as loadManifestBundle } from \"../manifest/service.js\";\nimport { type Format, parseFormat } from \"../output/index.js\";\n\n/** Global args shared by all subcommands (citty has no inherited flags). */\nexport const globalArgs = {\n url: {\n type: \"string\",\n description: \"Storybook base URL (overrides SQ_URL and config files)\",\n },\n format: {\n type: \"string\",\n description: \"output format: json or text\",\n default: \"json\",\n },\n refresh: {\n type: \"boolean\",\n description: \"force a fresh fetch, ignoring cached manifests\",\n default: false,\n },\n cache: {\n type: \"boolean\",\n description: \"use the on-disk cache (use --no-cache to bypass)\",\n default: true,\n },\n} as const;\n\nexport interface GlobalOpts {\n url?: string;\n format: string;\n refresh: boolean;\n cache: boolean;\n}\n\n/** Resolves the --format flag. */\nexport function resolveFormat(args: GlobalOpts): Format {\n return parseFormat(args.format);\n}\n\nconst LOAD_TIMEOUT_MS = 45_000;\n\n/**\n * Resolves config, builds the manifest service, and loads both manifests.\n * Shared by every subcommand. Bounds the overall load so a hung server cannot\n * block forever.\n */\nexport async function loadBundle(args: GlobalOpts): Promise<Bundle> {\n const cfg: Config = resolveConfig(args.url);\n const fetcher = createFetcher();\n\n const store = args.cache ? new Cache(cacheDir()) : undefined;\n\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(new Error(\"manifest load timed out\")), LOAD_TIMEOUT_MS);\n try {\n return await loadManifestBundle(\n {\n fetcher,\n componentsUrl: componentsUrl(cfg),\n docsUrl: docsUrl(cfg),\n ttlMs: cfg.cacheTtlMs,\n refresh: args.refresh,\n store,\n },\n ctrl.signal,\n );\n } finally {\n clearTimeout(timer);\n }\n}\n","import { defineCommand } from \"citty\";\n\nimport { type Renderable, detailDoc, encode } from \"../output/index.js\";\nimport type { DocsResult } from \"../output/index.js\";\nimport { searchDocs } from \"../search.js\";\nimport { type GlobalOpts, globalArgs, loadBundle, resolveFormat } from \"./shared.js\";\n\nconst DEFAULT_QUERY_LIMIT = 10;\n\nexport const docsCommand = defineCommand({\n meta: { name: \"docs\", description: \"Search documentation and guideline pages\" },\n args: {\n term: { type: \"positional\", description: \"search term\", required: true },\n limit: {\n type: \"string\",\n description: \"maximum results (0 = all)\",\n default: String(DEFAULT_QUERY_LIMIT),\n },\n ...globalArgs,\n },\n async run({ args }) {\n const opts = args as unknown as GlobalOpts;\n const format = resolveFormat(opts);\n const bundle = await loadBundle(opts);\n\n const term = args.term;\n const limit = Number.parseInt(args.limit, 10) || 0;\n\n const result: DocsResult = {\n term,\n docs: searchDocs(bundle.docs.docs ?? {}, term, limit).map((m) => detailDoc(m.doc)),\n };\n if (bundle.warnings.length > 0) result.warnings = bundle.warnings;\n\n const payload: Renderable = { kind: \"docs\", value: result };\n process.stdout.write(encode(format, payload));\n },\n});\n","import { defineCommand } from \"citty\";\n\nimport { type Renderable, encode, summarizeComponent } from \"../output/index.js\";\nimport type { ListResult } from \"../output/index.js\";\nimport { type GlobalOpts, globalArgs, loadBundle, resolveFormat } from \"./shared.js\";\n\nexport const listCommand = defineCommand({\n meta: { name: \"list\", description: \"List all components\" },\n args: {\n filter: {\n type: \"string\",\n description: \"case-insensitive substring filter on name/id\",\n default: \"\",\n },\n ...globalArgs,\n },\n async run({ args }) {\n const opts = args as unknown as GlobalOpts;\n const format = resolveFormat(opts);\n const bundle = await loadBundle(opts);\n\n const needle = args.filter.trim().toLowerCase();\n const result: ListResult = { components: [] };\n for (const c of Object.values(bundle.components.components ?? {})) {\n const name = (c.name ?? \"\").toLowerCase();\n const id = (c.id ?? \"\").toLowerCase();\n if (needle && !name.includes(needle) && !id.includes(needle)) continue;\n result.components.push(summarizeComponent(c, 0));\n }\n result.components.sort((a, b) => a.name.localeCompare(b.name));\n if (bundle.warnings.length > 0) result.warnings = bundle.warnings;\n\n const payload: Renderable = { kind: \"list\", value: result };\n process.stdout.write(encode(format, payload));\n },\n});\n","import { defineCommand } from \"citty\";\n\nimport { type Renderable, encode, summarizeComponent } from \"../output/index.js\";\nimport type { QueryResult } from \"../output/index.js\";\nimport { searchComponents, searchDocs } from \"../search.js\";\nimport { type GlobalOpts, globalArgs, loadBundle, resolveFormat } from \"./shared.js\";\n\nconst DEFAULT_QUERY_LIMIT = 10;\n\nexport const queryCommand = defineCommand({\n meta: { name: \"query\", description: \"Search components and docs for a term\" },\n args: {\n term: { type: \"positional\", description: \"search term\", required: true },\n limit: {\n type: \"string\",\n description: \"maximum results per category (0 = all)\",\n default: String(DEFAULT_QUERY_LIMIT),\n },\n ...globalArgs,\n },\n async run({ args }) {\n const opts = args as unknown as GlobalOpts;\n const format = resolveFormat(opts);\n const bundle = await loadBundle(opts);\n\n const term = args.term;\n const limit = Number.parseInt(args.limit, 10) || 0;\n\n const result: QueryResult = {\n term,\n components: searchComponents(bundle.components.components ?? {}, term, limit).map((m) =>\n summarizeComponent(m.component, m.score),\n ),\n docs: searchDocs(bundle.docs.docs ?? {}, term, limit).map((m) => ({\n id: m.doc.id ?? \"\",\n title: m.doc.title ?? \"\",\n score: m.score,\n })),\n };\n if (bundle.warnings.length > 0) result.warnings = bundle.warnings;\n\n const payload: Renderable = { kind: \"query\", value: result };\n process.stdout.write(encode(format, payload));\n },\n});\n","import type { Component, Doc, Docs } from \"./types.js\";\n\n/**\n * Returns the guideline/usage doc associated with a component, if one exists.\n * It matches docs whose id begins with the component's id-derived prefix and\n * looks like a guideline page (e.g. \"components-alert-guidelines-usage--docs\"\n * for component \"components-alert\").\n */\nexport function guidelineFor(docs: Docs | undefined, c: Component): Doc | undefined {\n const all = docs?.docs;\n if (!all) return undefined;\n\n // Component ids look like \"components-buttons-mainbutton\"; guideline doc ids\n // look like \"components-<name>-guidelines-usage--docs\". Try a direct prefix\n // match first, then a looser name-based match.\n const prefix = `${c.id ?? \"\"}-guidelines`;\n if (c.id) {\n for (const d of Object.values(all)) {\n if (d.id?.startsWith(prefix)) return d;\n }\n }\n\n const name = (c.name ?? \"\").toLowerCase();\n if (name) {\n for (const d of Object.values(all)) {\n const id = (d.id ?? \"\").toLowerCase();\n if (id.includes(\"guidelines\") && id.includes(name)) return d;\n }\n }\n return undefined;\n}\n","import { defineCommand } from \"citty\";\n\nimport { guidelineFor } from \"../manifest/guideline.js\";\nimport { AmbiguousError, NotFoundError } from \"../manifest/service.js\";\nimport { type Renderable, detailComponent, encode } from \"../output/index.js\";\nimport { bestComponent, searchComponents } from \"../search.js\";\nimport { type GlobalOpts, globalArgs, loadBundle, resolveFormat } from \"./shared.js\";\n\nexport const showCommand = defineCommand({\n meta: { name: \"show\", description: \"Show full detail for a single component\" },\n args: {\n term: { type: \"positional\", description: \"component term or id\", required: true },\n ...globalArgs,\n },\n async run({ args }) {\n const opts = args as unknown as GlobalOpts;\n const format = resolveFormat(opts);\n const bundle = await loadBundle(opts);\n\n const term = args.term;\n const comps = bundle.components.components ?? {};\n const result = bestComponent(comps, term);\n\n if (result.kind === \"none\") {\n throw new NotFoundError(`\"${term}\": no match found`);\n }\n if (result.kind === \"ambiguous\") {\n const candidates = searchComponents(comps, term, 5).map((m) => m.component.id ?? \"\");\n throw new AmbiguousError(\n `\"${term}\": ambiguous match; candidates: ${candidates.join(\", \")}`,\n candidates,\n );\n }\n\n const component = result.match.component;\n const guideline = guidelineFor(bundle.docs, component);\n const detail = detailComponent(component, guideline);\n if (bundle.warnings.length > 0) detail.warnings = bundle.warnings;\n\n const payload: Renderable = { kind: \"detail\", value: detail };\n process.stdout.write(encode(format, payload));\n },\n});\n","// Build-time injected version. tsup replaces __STORYQUERY_VERSION__ via `define`;\n// vitest does the same. Falls back to \"dev\" if somehow not replaced.\ndeclare const __STORYQUERY_VERSION__: string;\n\nexport const VERSION: string =\n typeof __STORYQUERY_VERSION__ === \"string\" ? __STORYQUERY_VERSION__ : \"dev\";\n","// Wires the storyquery command tree and maps domain errors to process exit codes.\nimport { defineCommand, runCommand, showUsage } from \"citty\";\n\nimport { docsCommand } from \"./commands/docs.js\";\nimport { listCommand } from \"./commands/list.js\";\nimport { queryCommand } from \"./commands/query.js\";\nimport { showCommand } from \"./commands/show.js\";\nimport { NoUrlError } from \"./config.js\";\nimport { HttpStatusError } from \"./fetch.js\";\nimport { AmbiguousError, NotFoundError } from \"./manifest/service.js\";\nimport { VERSION } from \"./version.js\";\n\n// Exit codes returned by the CLI.\nconst EXIT_OK = 0;\nconst EXIT_NO_MATCH = 1;\nconst EXIT_USAGE = 2;\nconst EXIT_NETWORK = 3;\nconst EXIT_ERROR = 1;\n\nconst main = defineCommand({\n meta: {\n name: \"storyquery\",\n version: VERSION,\n description:\n \"storyquery fetches a Storybook instance's manifest files and answers agent-friendly queries about components and documentation.\",\n },\n subCommands: {\n query: queryCommand,\n show: showCommand,\n list: listCommand,\n docs: docsCommand,\n },\n});\n\nfunction exitCode(err: unknown): number {\n if (err instanceof NotFoundError) return EXIT_NO_MATCH;\n if (err instanceof AmbiguousError) return EXIT_ERROR;\n if (err instanceof NoUrlError) return EXIT_USAGE;\n if (isNetwork(err)) return EXIT_NETWORK;\n return EXIT_ERROR;\n}\n\nfunction isNetwork(err: unknown): boolean {\n // Native fetch failures surface as TypeError with a cause, possibly wrapped by\n // our service layer (\"fetch <label> manifest: ...\").\n for (let e: unknown = err; e; e = (e as { cause?: unknown }).cause) {\n if (e instanceof HttpStatusError) return true;\n if (e instanceof TypeError) return true;\n const name = (e as { name?: string }).name;\n if (name === \"AbortError\" || name === \"FetchError\") return true;\n }\n return false;\n}\n\nasync function run(): Promise<number> {\n const rawArgs = process.argv.slice(2);\n\n // Top-level help / version (citty's runCommand handles per-subcommand help).\n if (rawArgs.length === 0 || rawArgs.includes(\"--help\") || rawArgs.includes(\"-h\")) {\n await showUsage(main);\n return EXIT_OK;\n }\n if (rawArgs.length === 1 && (rawArgs[0] === \"--version\" || rawArgs[0] === \"-v\")) {\n process.stdout.write(`${VERSION}\\n`);\n return EXIT_OK;\n }\n\n try {\n await runCommand(main, { rawArgs });\n return EXIT_OK;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`error: ${message}\\n`);\n return exitCode(err);\n }\n}\n\nrun().then(\n (code) => {\n process.exitCode = code;\n },\n (err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`error: ${message}\\n`);\n process.exitCode = EXIT_ERROR;\n },\n);\n"],"mappings":";;;;;;;;;;AAGA,SAAS,UAAU,GAAmB;CACpC,MAAM,IAAI,EAAE,QAAQ,IAAI;CACxB,OAAO,KAAK,IAAI,EAAE,MAAM,GAAG,CAAC,IAAI;AAClC;AAEA,SAAS,IAAI,GAAW,OAAuB;CAC7C,OAAO,EAAE,UAAU,QAAQ,IAAI,IAAI,IAAI,OAAO,QAAQ,EAAE,MAAM;AAChE;AAEA,SAAS,SAAS,OAAqC;CACrD,QAAQ,SAAS,CAAC,EAAC,CAAE,KAAK,MAAM,KAAK,EAAE,GAAG,CAAC,CAAC,KAAK,EAAE;AACrD;AAEA,SAAgB,YAAY,GAAwB;CAClD,IAAI,MAAM,SAAS,EAAE,QAAQ;CAC7B,OAAO,UAAU,EAAE,KAAK;CACxB,OAAO,iBAAiB,EAAE,WAAW,OAAO;CAC5C,KAAK,MAAM,KAAK,EAAE,YAAY;EAC5B,OAAO,KAAK,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,GAAG;EACpC,IAAI,EAAE,aAAa,OAAO,SAAS,UAAU,EAAE,WAAW,EAAE;CAC9D;CACA,OAAO,WAAW,EAAE,KAAK,OAAO;CAChC,KAAK,MAAM,KAAK,EAAE,MAChB,OAAO,KAAK,IAAI,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,GAAG;CAEvC,OAAO;AACT;AAEA,SAAgB,WAAW,GAAuB;CAChD,IAAI,MAAM,SAAS,EAAE,QAAQ;CAC7B,KAAK,MAAM,KAAK,EAAE,YAChB,OAAO,GAAG,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,GAAG;CAEpC,OAAO;AACT;AAEA,SAAgB,aAAa,GAA4B;CACvD,IAAI,MAAM,SAAS,EAAE,QAAQ;CAC7B,OAAO,GAAG,EAAE,KAAK,IAAI,EAAE,GAAG;CAC1B,IAAI,EAAE,aAAa,OAAO,KAAK,EAAE,YAAY;CAC7C,IAAI,EAAE,QAAQ,OAAO,KAAK,EAAE,IAAI,CAAC,CAAC,SAAS,GAAG;EAC5C,OAAO;EACP,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,EAAE,IAAI,GACxC,OAAO,MAAM,EAAE,GAAG,UAAU,CAAC,EAAE;CAEnC;CACA,IAAI,EAAE,QAAQ,OAAO,gBAAgB,EAAE,OAAO;CAC9C,IAAI,EAAE,YAAY,OAAO,WAAW,EAAE,WAAW;CACjD,OAAO,YAAY,EAAE,MAAM,OAAO;CAClC,KAAK,MAAM,KAAK,EAAE,OAAO;EACvB,MAAM,MAAM,EAAE,WAAW,gBAAgB;EACzC,MAAM,MAAM,EAAE,UAAU,MAAM,EAAE,YAAY;EAC5C,OAAO,KAAK,EAAE,KAAK,IAAI,EAAE,OAAO,MAAM,IAAI;EAC1C,IAAI,EAAE,aAAa,OAAO,SAAS,UAAU,EAAE,WAAW,EAAE;CAC9D;CACA,OAAO,cAAc,EAAE,QAAQ,OAAO;CACtC,KAAK,MAAM,KAAK,EAAE,SAChB,OAAO,KAAK,EAAE,KAAK;CAErB,IAAI,EAAE,WACJ,OAAO,gBAAgB,EAAE,UAAU,MAAM,MAAM,EAAE,UAAU,QAAQ;CAErE,OAAO;AACT;AAEA,SAAgB,WAAW,GAAuB;CAChD,IAAI,MAAM,SAAS,EAAE,QAAQ;CAC7B,OAAO,UAAU,EAAE,KAAK;CACxB,KAAK,MAAM,KAAK,EAAE,MAChB,OAAO,SAAS,EAAE,MAAM,IAAI,EAAE,GAAG,SAAS,EAAE,QAAQ;CAEtD,OAAO;AACT;;;;;;;;;AC5DA,MAAa,iBAAiB;;AAK9B,MAAM,cAAc,KAAK,EACvB,UAAU,UACZ,CAAC;;AAGD,MAAM,WAAW,KAAK;CACpB,SAAS;CACT,QAAQ;CACR,UAAU;AACZ,CAAC;;AAGD,MAAM,OAAO,KAAK;CAChB,SAAS;CACT,gBAAgB;CAChB,aAAa;CACb,iBAAiB,YAAY,GAAG,MAAM;CACtC,SAAS;AACX,CAAC;;AAGD,MAAM,aAAa,KAAK;CACtB,gBAAgB;CAChB,gBAAgB;CAChB,eAAe;CACf,aAAa;CACb,SAAS,KAAK,OAAO,UAAU,QAAQ;CACvC,UAAU,KAAK,OAAO,UAAU,IAAI;AACtC,CAAC;;AAUD,MAAM,YAAY,KAAK;CACrB,OAAO;CACP,SAAS;CACT,SAAS;CACT,WAAW;CACX,gBAAgB;CAChB,cAAc;CACd,YAdY,KAAK;EACjB,OAAO;EACP,SAAS;EACT,YAAY;CACd,CAUkB,CAAC,CAAC,MAAM;CACxB,0BAA0B;AAC5B,CAAC;;AAGD,MAAM,MAAM,KAAK;CACf,OAAO;CACP,SAAS;CACT,SAAS;CACT,UAAU;CACV,YAAY;AACd,CAAC;;AAGD,MAAM,aAAa,KAAK;CACtB,MAAM;CACN,eAAe,KAAK,OAAO,UAAU,SAAS;AAChD,CAAC;;AAGD,MAAM,OAAO,KAAK;CAChB,MAAM;CACN,SAAS,KAAK,OAAO,UAAU,GAAG;AACpC,CAAC;;AAiBD,SAAgB,gBAAgB,MAA0B;CACxD,MAAM,MAAM,WAAW,SAAS,IAAI,CAAC;CACrC,IAAI,eAAe,KAAK,QACtB,MAAM,IAAI,MAAM,8BAA8B,IAAI,SAAS;CAE7D,IAAI,CAAC,IAAI,YAAY,IAAI,aAAa,CAAC;CACvC,OAAO;AACT;;AAGA,SAAgB,UAAU,MAAoB;CAC5C,MAAM,MAAM,KAAK,SAAS,IAAI,CAAC;CAC/B,IAAI,eAAe,KAAK,QACtB,MAAM,IAAI,MAAM,wBAAwB,IAAI,SAAS;CAEvD,IAAI,CAAC,IAAI,MAAM,IAAI,OAAO,CAAC;CAC3B,OAAO;AACT;AAEA,SAAS,SAAS,MAAuB;CACvC,IAAI;EACF,OAAO,KAAK,MAAM,IAAI;CACxB,SAAS,KAAK;EACZ,MAAM,IAAI,MAAM,wBAAwB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,GAAG;CAC5F;AACF;;AAKA,SAAgB,0BAA0B,GAAwB;CAChE,QAAQ,EAAE,KAAK;AACjB;;AAGA,SAAgB,oBAAoB,GAAkB;CACpD,QAAQ,EAAE,KAAK;AACjB;;AAKA,SAAgB,kBAAkB,GAA2C;CAC3E,IAAI,CAAC,KAAK,EAAE,UAAU,UAAa,EAAE,UAAU,MAAM,OAAO;CAE5D,IAAI,OAAO,EAAE,UAAU,UAAU,OAAO,EAAE;CAC1C,OAAO,KAAK,UAAU,EAAE,KAAK;AAC/B;;AAGA,SAAgB,eAAe,GAAiC;CAC9D,IAAI,CAAC,GAAG,OAAO;CACf,IAAI,EAAE,KAAK,OAAO,EAAE;CACpB,OAAO,EAAE,QAAQ;AACnB;;;;;AClFA,SAAgB,mBAAmB,GAAc,OAAiC;CAGhF,MAAM,UAAU;EAAE,IAAI,EAAE,MAAM;EAAI,MAAM,EAAE,QAAQ;CAAG;CACrD,IAAI,EAAE,aAAa,QAAQ,cAAc,EAAE;CAC3C,IAAI,EAAE,QAAQ,QAAQ,SAAS,EAAE;CACjC,QAAQ,QAAQ,OAAO,KAAK,EAAE,uBAAuB,SAAS,CAAC,CAAC,CAAC,CAAC;CAClE,QAAQ,UAAU,EAAE,SAAS,UAAU;CACvC,IAAI,OAAO,QAAQ,QAAQ;CAC3B,OAAO;AACT;;AAGA,SAAgB,gBAAgB,GAAc,WAAkC;CAC9E,MAAM,SAAS,EAAE;CACjB,MAAM,SAA0B;EAC9B,IAAI,EAAE,MAAM;EACZ,MAAM,EAAE,QAAQ;EAChB,OAAO,CAAC;EACR,SAAS,CAAC;CACZ;CACA,IAAI,EAAE,aAAa,OAAO,cAAc,EAAE;CAC1C,IAAI,EAAE,QAAQ,OAAO,SAAS,EAAE;CAChC,IAAI,EAAE,MAAM,OAAO,OAAO,EAAE;CAC5B,IAAI,QAAQ,UAAU,OAAO,aAAa,OAAO;CAEjD,MAAM,OAAO,UAAU,QAAQ,IAAI;CACnC,IAAI,MAAM,OAAO,OAAO;CAExB,KAAK,MAAM,KAAK,OAAO,OAAO,QAAQ,SAAS,CAAC,CAAC,GAAG;EAClD,MAAM,OAAmB;GACvB,MAAM,EAAE,QAAQ;GAChB,MAAM,eAAe,EAAE,IAAI;GAC3B,UAAU,EAAE,YAAY;EAC1B;EACA,MAAM,MAAM,kBAAkB,EAAE,YAAY;EAC5C,IAAI,KAAK,KAAK,UAAU;EACxB,IAAI,EAAE,aAAa,KAAK,cAAc,EAAE;EACxC,OAAO,MAAM,KAAK,IAAI;CACxB;CAEA,OAAO,MAAM,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;CAExD,KAAK,MAAM,KAAK,EAAE,WAAW,CAAC,GAAG;EAC/B,MAAM,QAAqB;GAAE,IAAI,EAAE,MAAM;GAAI,MAAM,EAAE,QAAQ;EAAG;EAChE,IAAI,EAAE,SAAS,MAAM,UAAU,EAAE;EACjC,OAAO,QAAQ,KAAK,KAAK;CAC3B;CAEA,IAAI,WACF,OAAO,YAAY;EACjB,IAAI,UAAU,MAAM;EACpB,OAAO,UAAU,SAAS;EAC1B,SAAS,UAAU,WAAW;CAChC;CAEF,OAAO;AACT;;AAGA,SAAgB,UAAU,GAAmB;CAC3C,OAAO;EAAE,IAAI,EAAE,MAAM;EAAI,OAAO,EAAE,SAAS;EAAI,SAAS,EAAE,WAAW;CAAG;AAC1E;;AAGA,SAAS,UACP,GAAG,SACiC;CACpC,MAAM,SAAiC,CAAC;CACxC,KAAK,MAAM,OAAO,SAAS;EACzB,IAAI,CAAC,KAAK;EACV,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,GAAG,GACrC,IAAI,GAAG,OAAO,KAAK;CAEvB;CACA,OAAO,OAAO,KAAK,MAAM,CAAC,CAAC,SAAS,IAAI,SAAS;AACnD;;;;;ACpIA,SAAgB,YAAY,GAAmB;CAC7C,QAAQ,EAAE,KAAK,CAAC,CAAC,YAAY,GAA7B;EACE,KAAK,QACH,OAAO;EACT,KAAK,QACH,OAAO;EACT,SACE,MAAM,IAAI,MAAM,mBAAmB,EAAE,sBAAsB;CAC/D;AACF;;AAGA,SAAgB,OAAO,QAAgB,SAA6B;CAClE,IAAI,WAAW,QAAQ,OAAO,GAAG,KAAK,UAAU,QAAQ,OAAO,MAAM,CAAC,EAAE;CACxE,QAAQ,QAAQ,MAAhB;EACE,KAAK,SACH,OAAO,YAAY,QAAQ,KAAK;EAClC,KAAK,QACH,OAAO,WAAW,QAAQ,KAAK;EACjC,KAAK,UACH,OAAO,aAAa,QAAQ,KAAK;EACnC,KAAK,QACH,OAAO,WAAW,QAAQ,KAAK;CACnC;AACF;;;;ACzCA,MAAM,mBAAmB;AACzB,MAAM,iBAAiB;AACvB,MAAM,oBAAoB;AAC1B,MAAM,kBAAkB;AACxB,MAAM,oBAAoB;AAC1B,MAAM,cAAc;AACpB,MAAM,iBAAiB;;;;;AAwBvB,SAAgB,iBACd,OACA,MACA,OACkB;CAClB,MAAM,IAAI,KAAK,KAAK,CAAC,CAAC,YAAY;CAClC,MAAM,MAAwB,CAAC;CAC/B,KAAK,MAAM,aAAa,OAAO,OAAO,KAAK,GAAG;EAC5C,MAAM,QAAQ,eAAe,WAAW,CAAC;EACzC,IAAI,QAAQ,gBAAgB,IAAI,KAAK;GAAE;GAAW;EAAM,CAAC;CAC3D;CACA,IAAI,MAAM,GAAG,MAAM;EACjB,IAAI,EAAE,UAAU,EAAE,OAAO,OAAO,EAAE,QAAQ,EAAE;EAC5C,QAAQ,EAAE,UAAU,QAAQ,GAAE,CAAE,cAAc,EAAE,UAAU,QAAQ,EAAE;CACtE,CAAC;CACD,OAAO,IAAI,KAAK,KAAK;AACvB;;AAGA,SAAgB,WAAW,MAA2B,MAAc,OAA2B;CAC7F,MAAM,IAAI,KAAK,KAAK,CAAC,CAAC,YAAY;CAClC,MAAM,MAAkB,CAAC;CACzB,KAAK,MAAM,OAAO,OAAO,OAAO,IAAI,GAAG;EACrC,MAAM,QAAQ,SAAS,KAAK,CAAC;EAC7B,IAAI,QAAQ,gBAAgB,IAAI,KAAK;GAAE;GAAK;EAAM,CAAC;CACrD;CACA,IAAI,MAAM,GAAG,MAAM;EACjB,IAAI,EAAE,UAAU,EAAE,OAAO,OAAO,EAAE,QAAQ,EAAE;EAC5C,QAAQ,EAAE,IAAI,SAAS,GAAE,CAAE,cAAc,EAAE,IAAI,SAAS,EAAE;CAC5D,CAAC;CACD,OAAO,IAAI,KAAK,KAAK;AACvB;;;;;AAMA,SAAgB,cAAc,OAAkC,MAAmC;CACjG,MAAM,UAAU,iBAAiB,OAAO,MAAM,CAAC;CAC/C,IAAI,QAAQ,WAAW,GAAG,OAAO,EAAE,MAAM,OAAO;CAChD,IAAI,QAAQ,WAAW,GAAG,OAAO;EAAE,MAAM;EAAS,OAAO,QAAQ;CAAI;CAErE,IAAI,QAAQ,EAAE,CAAE,QAAQ,QAAQ,EAAE,CAAE,OAClC,OAAO;EAAE,MAAM;EAAS,OAAO,QAAQ;CAAI;CAE7C,OAAO;EAAE,MAAM;EAAa,OAAO,QAAQ;CAAI;AACjD;AAEA,SAAS,eAAe,GAAc,GAAmB;CACvD,IAAI,MAAM,IAAI,OAAO;CACrB,MAAM,QAAQ,EAAE,QAAQ,GAAE,CAAE,YAAY;CACxC,MAAM,MAAM,EAAE,MAAM,GAAE,CAAE,YAAY;CAEpC,IAAI,SAAS,GAAG,OAAO;CACvB,IAAI,OAAO,GAAG,OAAO;CACrB,IAAI,KAAK,WAAW,CAAC,GAAG,OAAO;CAC/B,IAAI,GAAG,SAAS,CAAC,GAAG,OAAO;CAC3B,IAAI,KAAK,SAAS,CAAC,GAAG,OAAO;CAE7B,MAAM,YAAY,WAAW,MAAM,CAAC;CACpC,IAAI,cAAc,MAAM,OAAO,cAAc;CAC7C,MAAM,UAAU,WAAW,IAAI,CAAC;CAChC,IAAI,YAAY,MAAM,OAAO,cAAc;CAC3C,OAAO;AACT;AAEA,SAAS,SAAS,GAAQ,GAAmB;CAC3C,IAAI,MAAM,IAAI,OAAO;CACrB,MAAM,SAAS,EAAE,SAAS,GAAE,CAAE,YAAY;CAC1C,MAAM,QAAQ,EAAE,QAAQ,GAAE,CAAE,YAAY;CACxC,MAAM,MAAM,EAAE,MAAM,GAAE,CAAE,YAAY;CAEpC,IAAI,UAAU,KAAK,SAAS,GAAG,OAAO;CACtC,IAAI,OAAO,GAAG,OAAO;CACrB,IAAI,MAAM,WAAW,CAAC,GAAG,OAAO;CAChC,IAAI,GAAG,SAAS,CAAC,GAAG,OAAO;CAC3B,IAAI,MAAM,SAAS,CAAC,KAAK,KAAK,SAAS,CAAC,GAAG,OAAO;CAElD,MAAM,aAAa,WAAW,OAAO,CAAC;CACtC,IAAI,eAAe,MAAM,OAAO,cAAc;CAE9C,KAAK,EAAE,WAAW,GAAE,CAAE,YAAY,CAAC,CAAC,SAAS,CAAC,GAAG,OAAO,cAAc;CACtE,OAAO;AACT;;;;;AAMA,SAAgB,WAAW,GAAW,GAA0B;CAC9D,IAAI,MAAM,IAAI,OAAO;CACrB,MAAM,KAAK,CAAC,GAAG,CAAC;CAChB,MAAM,KAAK,CAAC,GAAG,CAAC;CAChB,IAAI,KAAK;CACT,IAAI,KAAK;CACT,IAAI,QAAQ;CACZ,IAAI,OAAO;CACX,OAAO,KAAK,GAAG,UAAU,KAAK,GAAG,QAAQ;EACvC,IAAI,GAAG,QAAQ,GAAG,KAAK;GACrB,IAAI,QAAQ,GAAG,QAAQ;GACvB,OAAO;GACP;EACF;EACA;CACF;CACA,IAAI,OAAO,GAAG,QAAQ,OAAO;CAC7B,IAAI,OAAO,OAAO,QAAQ;CAC1B,IAAI,QAAQ,GAAG,OAAO;CAEtB,OAAO,KAAK,MAAO,GAAG,SAAS,KAAM,IAAI;AAC3C;AAEA,SAAS,IAAO,OAAY,OAAoB;CAC9C,IAAI,QAAQ,KAAK,MAAM,SAAS,OAAO,OAAO,MAAM,MAAM,GAAG,KAAK;CAClE,OAAO;AACT;;;;;ACnHA,IAAa,QAAb,MAAoC;CAClC,AAAiB;CACjB,AAAiB;CACjB,AAAQ,QAA8B;CAEtC,YAAY,KAAa,OAAqB,CAAC,GAAG;EAChD,KAAK,MAAM;EACX,KAAK,MAAM,KAAK,OAAO,KAAK;CAC9B;CAEA,AAAQ,YAA2B;EACjC,IAAI,CAAC,KAAK,OAAO,KAAK,QAAQ,MAAM,KAAK,KAAK,EAAE,WAAW,KAAK,CAAC,CAAC,CAAC,WAAW,MAAS;EACvF,OAAO,KAAK;CACd;CAEA,AAAQ,SAAS,KAAqB;EACpC,OAAO,KAAK,KAAK,KAAK,GAAG,IAAI,MAAM;CACrC;CAEA,AAAQ,SAAS,KAAqB;EACpC,OAAO,KAAK,KAAK,KAAK,GAAG,IAAI,WAAW;CAC1C;CAEA,MAAM,KAAK,KAAa,OAAoC;EAC1D,IAAI;EACJ,IAAI;GACF,OAAO,MAAM,SAAS,KAAK,SAAS,GAAG,GAAG,MAAM;EAClD,SAAS,KAAK;GACZ,IAAI,WAAW,GAAG,GAAG,OAAO;IAAE,OAAO;IAAO,IAAI;GAAM;GACtD,MAAM,IAAI,MAAM,oBAAoBA,SAAO,GAAG,GAAG;EACnD;EAEA,IAAI,QAAQ;EACZ,IAAI;GACF,MAAM,MAAM,MAAM,SAAS,KAAK,SAAS,GAAG,GAAG,MAAM;GACrD,MAAM,OAAO,KAAK,MAAM,GAAG;GAC3B,MAAM,YAAY,KAAK,MAAM,KAAK,UAAU;GAC5C,IAAI,CAAC,OAAO,MAAM,SAAS,GAAG,QAAQ,KAAK,IAAI,IAAI,YAAY;EACjE,QAAQ,CAER;EAEA,OAAO;GAAE;GAAM;GAAO,IAAI;EAAK;CACjC;CAEA,MAAM,KAAK,KAAa,MAA6B;EACnD,MAAM,KAAK,UAAU;EACrB,IAAI;GACF,MAAM,UAAU,KAAK,SAAS,GAAG,GAAG,MAAM,MAAM;EAClD,SAAS,KAAK;GACZ,MAAM,IAAI,MAAM,qBAAqBA,SAAO,GAAG,GAAG;EACpD;EACA,MAAM,OAAa,EAAE,YAAY,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,YAAY,EAAE;EACpE,IAAI;GACF,MAAM,UAAU,KAAK,SAAS,GAAG,GAAG,KAAK,UAAU,IAAI,GAAG,MAAM;EAClE,SAAS,KAAK;GACZ,MAAM,IAAI,MAAM,qBAAqBA,SAAO,GAAG,GAAG;EACpD;CACF;AACF;;AAGA,SAAgB,SAAS,GAAG,OAAyB;CACnD,MAAM,IAAI,WAAW,QAAQ;CAC7B,KAAK,MAAM,KAAK,OAAO;EACrB,EAAE,OAAO,CAAC;EACV,EAAE,OAAO,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;CAC3B;CACA,OAAO,EAAE,OAAO,KAAK,CAAC,CAAC,MAAM,GAAG,EAAE;AACpC;AAEA,SAAS,WAAW,KAAuB;CACzC,OAAQ,KAA+B,SAAS;AAClD;AAEA,SAASA,SAAO,KAAsB;CACpC,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AACxD;;;;;ACvGA,MAAa,UAAU;;AAGvB,MAAa,eAAe;;AAG5B,MAAa,iBAAiB,OAAU;;AAGxC,IAAa,aAAb,cAAgC,MAAM;CACpC,cAAc;EACZ,MAAM,2EAA2E;EACjF,KAAK,OAAO;CACd;AACF;;;;;AAiBA,SAAgB,cAAc,SAA0B;CACtD,IAAI,UAAU;CACd,IAAI,aAAa;CAEjB,MAAM,SAAS,MAAkB;EAC/B,MAAM,IAAI,EAAE,KAAK,KAAK;EACtB,IAAI,GAAG,UAAU;EACjB,MAAM,MAAM,EAAE,UAAU,KAAK;EAC7B,IAAI,KAAK;GACP,MAAM,KAAK,cAAc,GAAG;GAC5B,IAAI,OAAO,MAAM,aAAa;EAChC;CACF;CAGA,MAAM,SAAS,SAAS,WAAW,CAAC;CACpC,IAAI,QAAQ,MAAM,MAAM;CACxB,MAAM,UAAU,SAAS,YAAY;CACrC,IAAI,SAAS,MAAM,OAAO;CAE1B,MAAM,MAAM,QAAQ,IAAI,QAAQ,EAAE,KAAK;CACvC,IAAI,KAAK,UAAU;CAEnB,MAAM,OAAO,SAAS,KAAK;CAC3B,IAAI,MAAM,UAAU;CAEpB,UAAU,QAAQ,QAAQ,QAAQ,EAAE;CACpC,IAAI,CAAC,SAAS,MAAM,IAAI,WAAW;CAEnC,OAAO;EAAE;EAAS;CAAW;AAC/B;;AAGA,SAAgB,cAAc,KAAqB;CACjD,OAAO,GAAG,IAAI,QAAQ;AACxB;;AAGA,SAAgB,QAAQ,KAAqB;CAC3C,OAAO,GAAG,IAAI,QAAQ;AACxB;;AAGA,SAAgB,WAAmB;CACjC,OAAO,KAAK,aAAa,GAAG,YAAY;AAC1C;AAEA,SAAS,SAAS,MAAiC;CACjD,IAAI,CAAC,MAAM,OAAO;CAClB,IAAI;EACF,MAAM,OAAO,aAAa,MAAM,MAAM;EACtC,OAAO,KAAK,MAAM,IAAI;CACxB,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,aAAqB;CAC5B,OAAO,KAAK,cAAc,GAAG,cAAc,aAAa;AAC1D;;;;;AAMA,SAAgB,cAAc,GAA0B;CACtD,MAAM,KAAK;CACX,IAAI,KAAK;CACT,IAAI,UAAU;CACd,IAAI,YAAY;CAChB,KAAK,IAAI,IAAI,GAAG,KAAK,CAAC,GAAG,MAAM,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG;EACnD,IAAI,EAAE,UAAU,WAAW,OAAO;EAClC,UAAU;EACV,MAAM,QAAQ,OAAO,EAAE,EAAE;EACzB,QAAQ,EAAE,IAAV;GACE,KAAK;IACH,MAAM;IACN;GACF,KAAK;IACH,MAAM,QAAQ;IACd;GACF,KAAK;IACH,MAAM,QAAQ;IACd;GACF,KAAK;IACH,MAAM,QAAQ;IACd;EACJ;EACA,YAAY,GAAG;CACjB;CACA,IAAI,CAAC,WAAW,cAAc,EAAE,QAAQ,OAAO;CAC/C,OAAO;AACT;AAIA,SAAS,gBAAwB;CAC/B,MAAM,OAAO,QAAQ;CACrB,QAAQ,QAAQ,UAAhB;EACE,KAAK,SACH,OAAO,QAAQ,IAAI,WAAW,KAAK,MAAM,WAAW,SAAS;EAC/D,KAAK,UACH,OAAO,KAAK,MAAM,WAAW,qBAAqB;EACpD,SACE,OAAO,QAAQ,IAAI,mBAAmB,KAAK,MAAM,SAAS;CAC9D;AACF;AAEA,SAAS,eAAuB;CAC9B,MAAM,OAAO,QAAQ;CACrB,QAAQ,QAAQ,UAAhB;EACE,KAAK,SACH,OAAO,QAAQ,IAAI,gBAAgB,KAAK,MAAM,WAAW,OAAO;EAClE,KAAK,UACH,OAAO,KAAK,MAAM,WAAW,QAAQ;EACvC,SACE,OAAO,QAAQ,IAAI,kBAAkB,KAAK,MAAM,QAAQ;CAC5D;AACF;;;;;ACvJA,IAAa,kBAAb,cAAqC,MAAM;CACzC,AAAS;CACT,AAAS;CACT,YAAY,KAAa,QAAgB;EACvC,MAAM,SAAS,IAAI,sBAAsB,QAAQ;EACjD,KAAK,OAAO;EACZ,KAAK,MAAM;EACX,KAAK,SAAS;CAChB;AACF;AAKA,MAAM,qBAAqB;AAC3B,MAAM,oBAAoB,MAAM;;AAQhC,SAAgB,cAAc,OAAqB,CAAC,GAAY;CAC9D,MAAM,YAAY,KAAK,aAAa;CACpC,MAAM,WAAW,KAAK,YAAY;CAElC,OAAO,OAAO,KAAa,WAA0C;EACnE,MAAM,OAAO,IAAI,gBAAgB;EACjC,MAAM,gBAAgB,KAAK,MAAM,QAAQ,MAAM;EAC/C,IAAI,QACF,IAAI,OAAO,SAAS,KAAK,MAAM,OAAO,MAAM;OACvC,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;EAE/D,MAAM,QAAQ,iBAAiB,KAAK,sBAAM,IAAI,MAAM,mBAAmB,CAAC,GAAG,SAAS;EAEpF,IAAI;GACF,MAAM,OAAO,MAAM,MAAM,KAAK;IAC5B,QAAQ;IACR,SAAS;KAAE,QAAQ;KAAoB,cAAc;IAAa;IAClE,QAAQ,KAAK;GACf,CAAC;GAED,IAAI,KAAK,SAAS,OAAO,KAAK,UAAU,KACtC,MAAM,IAAI,gBAAgB,KAAK,KAAK,MAAM;GAE5C,OAAO,MAAM,WAAW,MAAM,KAAK,QAAQ;EAC7C,UAAU;GACR,aAAa,KAAK;GAClB,IAAI,QAAQ,OAAO,oBAAoB,SAAS,OAAO;EACzD;CACF;AACF;;AAGA,eAAe,WAAW,MAAgB,KAAa,UAAmC;CACxF,IAAI,CAAC,KAAK,MAAM,OAAO,MAAM,KAAK,KAAK;CAEvC,MAAM,SAAS,KAAK,KAAK,UAAU;CACnC,MAAM,SAAuB,CAAC;CAC9B,IAAI,QAAQ;CACZ,IAAI;EACF,SAAS;GACP,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,KAAK;GAC1C,IAAI,MAAM;GACV,IAAI,OAAO;IACT,SAAS,MAAM;IACf,IAAI,QAAQ,UACV,MAAM,IAAI,MAAM,kBAAkB,IAAI,qBAAqB,SAAS,OAAO;IAE7E,OAAO,KAAK,KAAK;GACnB;EACF;CACF,UAAU;EACR,OAAO,YAAY;CACrB;CAEA,OAAO,OAAO,OAAO,MAAM,CAAC,CAAC,SAAS,MAAM;AAC9C;;;;;ACrEA,IAAa,gBAAb,cAAmC,MAAM;CACvC,YAAY,UAAU,kBAAkB;EACtC,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF;;AAGA,IAAa,iBAAb,cAAoC,MAAM;CACxC,AAAS;CACT,YAAY,SAAiB,aAAuB,CAAC,GAAG;EACtD,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,aAAa;CACpB;AACF;;AA0BA,eAAsBC,aAAW,MAAsB,QAAuC;CAC5F,MAAM,CAAC,MAAM,QAAQ,MAAM,QAAQ,IAAI,CACrC,QAAQ,MAAM,cAAc,KAAK,eAAe,MAAM,GACtD,QAAQ,MAAM,QAAQ,KAAK,SAAS,MAAM,CAC5C,CAAC;CAED,MAAM,WAAW,CAAC,GAAG,KAAK,UAAU,GAAG,KAAK,QAAQ;CAEpD,MAAM,aAAa,gBAAgB,KAAK,IAAI;CAC5C,MAAM,aAAa,UAAU,KAAK,IAAI;CAEtC,IAAI,0BAA0B,UAAU,GACtC,SAAS,KACP,+BAA+B,WAAW,KAAK,EAAE,8BACnD;CAEF,IAAI,oBAAoB,UAAU,GAChC,SAAS,KACP,yBAAyB,WAAW,KAAK,EAAE,8BAC7C;CAGF,OAAO;EAAE;EAAY,MAAM;EAAY;CAAS;AAClD;;;;;AAMA,eAAe,QACb,MACA,OACA,KACA,QACyB;CACzB,IAAI,CAAC,KAAK,MAAM,IAAI,MAAM,GAAG,MAAM,6BAA6B;CAEhE,MAAM,WAAqB,CAAC;CAE5B,IAAI,KAAK,OAAO;EACd,MAAM,MAAM,SAAS,GAAG;EACxB,MAAM,QAAQ,MAAM,KAAK,MAAM,KAAK,KAAK,KAAK,KAAK;EACnD,IAAI,MAAM,MAAM,MAAM,SAAS,CAAC,KAAK,WAAW,MAAM,SAAS,QAC7D,OAAO;GAAE,MAAM,MAAM;GAAM;EAAS;EAItC,IAAI;GACF,MAAM,UAAU,MAAM,KAAK,QAAQ,KAAK,MAAM;GAC9C,IAAI;IACF,MAAM,KAAK,MAAM,KAAK,KAAK,OAAO;GACpC,SAAS,KAAK;IACZ,SAAS,KAAK,mBAAmB,MAAM,aAAa,OAAO,GAAG,GAAG;GACnE;GACA,OAAO;IAAE,MAAM;IAAS;GAAS;EACnC,SAAS,KAAK;GACZ,IAAI,MAAM,MAAM,MAAM,SAAS,QAAW;IACxC,SAAS,KAAK,sBAAsB,MAAM,aAAa,OAAO,GAAG,GAAG;IACpE,OAAO;KAAE,MAAM,MAAM;KAAM;IAAS;GACtC;GACA,MAAM,IAAI,MAAM,SAAS,MAAM,aAAa,OAAO,GAAG,KAAK,EAAE,OAAO,IAAI,CAAC;EAC3E;CACF;CAEA,IAAI;EAEF,OAAO;GAAE,MAAM,MADO,KAAK,QAAQ,KAAK,MAAM;GACtB;EAAS;CACnC,SAAS,KAAK;EACZ,MAAM,IAAI,MAAM,SAAS,MAAM,aAAa,OAAO,GAAG,KAAK,EAAE,OAAO,IAAI,CAAC;CAC3E;AACF;AAEA,SAAS,OAAO,KAAsB;CACpC,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AACxD;;;;;ACzHA,MAAa,aAAa;CACxB,KAAK;EACH,MAAM;EACN,aAAa;CACf;CACA,QAAQ;EACN,MAAM;EACN,aAAa;EACb,SAAS;CACX;CACA,SAAS;EACP,MAAM;EACN,aAAa;EACb,SAAS;CACX;CACA,OAAO;EACL,MAAM;EACN,aAAa;EACb,SAAS;CACX;AACF;;AAUA,SAAgB,cAAc,MAA0B;CACtD,OAAO,YAAY,KAAK,MAAM;AAChC;AAEA,MAAM,kBAAkB;;;;;;AAOxB,eAAsB,WAAW,MAAmC;CAClE,MAAM,MAAc,cAAc,KAAK,GAAG;CAC1C,MAAM,UAAU,cAAc;CAE9B,MAAM,QAAQ,KAAK,QAAQ,IAAI,MAAM,SAAS,CAAC,IAAI;CAEnD,MAAM,OAAO,IAAI,gBAAgB;CACjC,MAAM,QAAQ,iBAAiB,KAAK,sBAAM,IAAI,MAAM,yBAAyB,CAAC,GAAG,eAAe;CAChG,IAAI;EACF,OAAO,MAAMC,aACX;GACE;GACA,eAAe,cAAc,GAAG;GAChC,SAAS,QAAQ,GAAG;GACpB,OAAO,IAAI;GACX,SAAS,KAAK;GACd;EACF,GACA,KAAK,MACP;CACF,UAAU;EACR,aAAa,KAAK;CACpB;AACF;;;;AClEA,MAAMC,wBAAsB;AAE5B,MAAa,cAAc,cAAc;CACvC,MAAM;EAAE,MAAM;EAAQ,aAAa;CAA2C;CAC9E,MAAM;EACJ,MAAM;GAAE,MAAM;GAAc,aAAa;GAAe,UAAU;EAAK;EACvE,OAAO;GACL,MAAM;GACN,aAAa;GACb,SAAS,OAAOA,qBAAmB;EACrC;EACA,GAAG;CACL;CACA,MAAM,IAAI,EAAE,QAAQ;EAClB,MAAM,OAAO;EACb,MAAM,SAAS,cAAc,IAAI;EACjC,MAAM,SAAS,MAAM,WAAW,IAAI;EAEpC,MAAM,OAAO,KAAK;EAClB,MAAM,QAAQ,OAAO,SAAS,KAAK,OAAO,EAAE,KAAK;EAEjD,MAAM,SAAqB;GACzB;GACA,MAAM,WAAW,OAAO,KAAK,QAAQ,CAAC,GAAG,MAAM,KAAK,CAAC,CAAC,KAAK,MAAM,UAAU,EAAE,GAAG,CAAC;EACnF;EACA,IAAI,OAAO,SAAS,SAAS,GAAG,OAAO,WAAW,OAAO;EAEzD,MAAM,UAAsB;GAAE,MAAM;GAAQ,OAAO;EAAO;EAC1D,QAAQ,OAAO,MAAM,OAAO,QAAQ,OAAO,CAAC;CAC9C;AACF,CAAC;;;;AC/BD,MAAa,cAAc,cAAc;CACvC,MAAM;EAAE,MAAM;EAAQ,aAAa;CAAsB;CACzD,MAAM;EACJ,QAAQ;GACN,MAAM;GACN,aAAa;GACb,SAAS;EACX;EACA,GAAG;CACL;CACA,MAAM,IAAI,EAAE,QAAQ;EAClB,MAAM,OAAO;EACb,MAAM,SAAS,cAAc,IAAI;EACjC,MAAM,SAAS,MAAM,WAAW,IAAI;EAEpC,MAAM,SAAS,KAAK,OAAO,KAAK,CAAC,CAAC,YAAY;EAC9C,MAAM,SAAqB,EAAE,YAAY,CAAC,EAAE;EAC5C,KAAK,MAAM,KAAK,OAAO,OAAO,OAAO,WAAW,cAAc,CAAC,CAAC,GAAG;GACjE,MAAM,QAAQ,EAAE,QAAQ,GAAE,CAAE,YAAY;GACxC,MAAM,MAAM,EAAE,MAAM,GAAE,CAAE,YAAY;GACpC,IAAI,UAAU,CAAC,KAAK,SAAS,MAAM,KAAK,CAAC,GAAG,SAAS,MAAM,GAAG;GAC9D,OAAO,WAAW,KAAK,mBAAmB,GAAG,CAAC,CAAC;EACjD;EACA,OAAO,WAAW,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;EAC7D,IAAI,OAAO,SAAS,SAAS,GAAG,OAAO,WAAW,OAAO;EAEzD,MAAM,UAAsB;GAAE,MAAM;GAAQ,OAAO;EAAO;EAC1D,QAAQ,OAAO,MAAM,OAAO,QAAQ,OAAO,CAAC;CAC9C;AACF,CAAC;;;;AC5BD,MAAM,sBAAsB;AAE5B,MAAa,eAAe,cAAc;CACxC,MAAM;EAAE,MAAM;EAAS,aAAa;CAAwC;CAC5E,MAAM;EACJ,MAAM;GAAE,MAAM;GAAc,aAAa;GAAe,UAAU;EAAK;EACvE,OAAO;GACL,MAAM;GACN,aAAa;GACb,SAAS,OAAO,mBAAmB;EACrC;EACA,GAAG;CACL;CACA,MAAM,IAAI,EAAE,QAAQ;EAClB,MAAM,OAAO;EACb,MAAM,SAAS,cAAc,IAAI;EACjC,MAAM,SAAS,MAAM,WAAW,IAAI;EAEpC,MAAM,OAAO,KAAK;EAClB,MAAM,QAAQ,OAAO,SAAS,KAAK,OAAO,EAAE,KAAK;EAEjD,MAAM,SAAsB;GAC1B;GACA,YAAY,iBAAiB,OAAO,WAAW,cAAc,CAAC,GAAG,MAAM,KAAK,CAAC,CAAC,KAAK,MACjF,mBAAmB,EAAE,WAAW,EAAE,KAAK,CACzC;GACA,MAAM,WAAW,OAAO,KAAK,QAAQ,CAAC,GAAG,MAAM,KAAK,CAAC,CAAC,KAAK,OAAO;IAChE,IAAI,EAAE,IAAI,MAAM;IAChB,OAAO,EAAE,IAAI,SAAS;IACtB,OAAO,EAAE;GACX,EAAE;EACJ;EACA,IAAI,OAAO,SAAS,SAAS,GAAG,OAAO,WAAW,OAAO;EAEzD,MAAM,UAAsB;GAAE,MAAM;GAAS,OAAO;EAAO;EAC3D,QAAQ,OAAO,MAAM,OAAO,QAAQ,OAAO,CAAC;CAC9C;AACF,CAAC;;;;;;;;;;ACpCD,SAAgB,aAAa,MAAwB,GAA+B;CAClF,MAAM,MAAM,MAAM;CAClB,IAAI,CAAC,KAAK,OAAO;CAKjB,MAAM,SAAS,GAAG,EAAE,MAAM,GAAG;CAC7B,IAAI,EAAE,IACJ;OAAK,MAAM,KAAK,OAAO,OAAO,GAAG,GAC/B,IAAI,EAAE,IAAI,WAAW,MAAM,GAAG,OAAO;CACvC;CAGF,MAAM,QAAQ,EAAE,QAAQ,GAAE,CAAE,YAAY;CACxC,IAAI,MACF,KAAK,MAAM,KAAK,OAAO,OAAO,GAAG,GAAG;EAClC,MAAM,MAAM,EAAE,MAAM,GAAE,CAAE,YAAY;EACpC,IAAI,GAAG,SAAS,YAAY,KAAK,GAAG,SAAS,IAAI,GAAG,OAAO;CAC7D;AAGJ;;;;ACtBA,MAAa,cAAc,cAAc;CACvC,MAAM;EAAE,MAAM;EAAQ,aAAa;CAA0C;CAC7E,MAAM;EACJ,MAAM;GAAE,MAAM;GAAc,aAAa;GAAwB,UAAU;EAAK;EAChF,GAAG;CACL;CACA,MAAM,IAAI,EAAE,QAAQ;EAClB,MAAM,OAAO;EACb,MAAM,SAAS,cAAc,IAAI;EACjC,MAAM,SAAS,MAAM,WAAW,IAAI;EAEpC,MAAM,OAAO,KAAK;EAClB,MAAM,QAAQ,OAAO,WAAW,cAAc,CAAC;EAC/C,MAAM,SAAS,cAAc,OAAO,IAAI;EAExC,IAAI,OAAO,SAAS,QAClB,MAAM,IAAI,cAAc,IAAI,KAAK,kBAAkB;EAErD,IAAI,OAAO,SAAS,aAAa;GAC/B,MAAM,aAAa,iBAAiB,OAAO,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,UAAU,MAAM,EAAE;GACnF,MAAM,IAAI,eACR,IAAI,KAAK,kCAAkC,WAAW,KAAK,IAAI,KAC/D,UACF;EACF;EAEA,MAAM,YAAY,OAAO,MAAM;EAE/B,MAAM,SAAS,gBAAgB,WADb,aAAa,OAAO,MAAM,SACM,CAAC;EACnD,IAAI,OAAO,SAAS,SAAS,GAAG,OAAO,WAAW,OAAO;EAEzD,MAAM,UAAsB;GAAE,MAAM;GAAU,OAAO;EAAO;EAC5D,QAAQ,OAAO,MAAM,OAAO,QAAQ,OAAO,CAAC;CAC9C;AACF,CAAC;;;;ACtCD,MAAa;;;;ACSb,MAAM,UAAU;AAChB,MAAM,gBAAgB;AACtB,MAAM,aAAa;AACnB,MAAM,eAAe;AACrB,MAAM,aAAa;AAEnB,MAAM,OAAO,cAAc;CACzB,MAAM;EACJ,MAAM;EACN,SAAS;EACT,aACE;CACJ;CACA,aAAa;EACX,OAAO;EACP,MAAM;EACN,MAAM;EACN,MAAM;CACR;AACF,CAAC;AAED,SAAS,SAAS,KAAsB;CACtC,IAAI,eAAe,eAAe,OAAO;CACzC,IAAI,eAAe,gBAAgB,OAAO;CAC1C,IAAI,eAAe,YAAY,OAAO;CACtC,IAAI,UAAU,GAAG,GAAG,OAAO;CAC3B,OAAO;AACT;AAEA,SAAS,UAAU,KAAuB;CAGxC,KAAK,IAAI,IAAa,KAAK,GAAG,IAAK,EAA0B,OAAO;EAClE,IAAI,aAAa,iBAAiB,OAAO;EACzC,IAAI,aAAa,WAAW,OAAO;EACnC,MAAM,OAAQ,EAAwB;EACtC,IAAI,SAAS,gBAAgB,SAAS,cAAc,OAAO;CAC7D;CACA,OAAO;AACT;AAEA,eAAe,MAAuB;CACpC,MAAM,UAAU,QAAQ,KAAK,MAAM,CAAC;CAGpC,IAAI,QAAQ,WAAW,KAAK,QAAQ,SAAS,QAAQ,KAAK,QAAQ,SAAS,IAAI,GAAG;EAChF,MAAM,UAAU,IAAI;EACpB,OAAO;CACT;CACA,IAAI,QAAQ,WAAW,MAAM,QAAQ,OAAO,eAAe,QAAQ,OAAO,OAAO;EAC/E,QAAQ,OAAO,MAAM,GAAG,QAAQ,GAAG;EACnC,OAAO;CACT;CAEA,IAAI;EACF,MAAM,WAAW,MAAM,EAAE,QAAQ,CAAC;EAClC,OAAO;CACT,SAAS,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;EAC/D,QAAQ,OAAO,MAAM,UAAU,QAAQ,GAAG;EAC1C,OAAO,SAAS,GAAG;CACrB;AACF;AAEA,IAAI,CAAC,CAAC,MACH,SAAS;CACR,QAAQ,WAAW;AACrB,IACC,QAAiB;CAChB,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;CAC/D,QAAQ,OAAO,MAAM,UAAU,QAAQ,GAAG;CAC1C,QAAQ,WAAW;AACrB,CACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "storyquery",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A CLI that queries Storybook design-system manifests and answers agent-friendly questions about components and documentation.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"storybook",
|
|
7
|
+
"design-system",
|
|
8
|
+
"cli",
|
|
9
|
+
"manifest",
|
|
10
|
+
"components",
|
|
11
|
+
"agents"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "adriankarlen",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/adriankarlen/storyquery.git"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"bin": {
|
|
21
|
+
"storyquery": "dist/cli.js"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=20"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"arktype": "^2.2.0",
|
|
31
|
+
"citty": "^0.2.2"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^25.0.0",
|
|
35
|
+
"oxfmt": "^0.54.0",
|
|
36
|
+
"oxlint": "^1.69.0",
|
|
37
|
+
"tsdown": "^0.22.2",
|
|
38
|
+
"typescript": "^5.7.0",
|
|
39
|
+
"vitest": "^4.1.8"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsdown",
|
|
43
|
+
"dev": "tsdown --watch",
|
|
44
|
+
"typecheck": "tsc --noEmit",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"test:watch": "vitest",
|
|
47
|
+
"lint": "oxlint src test",
|
|
48
|
+
"lint:fix": "oxlint --fix src test",
|
|
49
|
+
"format": "oxfmt src test",
|
|
50
|
+
"format:check": "oxfmt --check src test"
|
|
51
|
+
}
|
|
52
|
+
}
|