@kaleabdenbel/llmweb 1.0.4 → 1.0.6

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/dist/cli.js CHANGED
@@ -35,57 +35,89 @@ var import_fs = __toESM(require("fs"));
35
35
  var import_path = __toESM(require("path"));
36
36
  async function generateConfig(config) {
37
37
  const cwd = process.cwd();
38
- let publicData = [];
39
- if (typeof config.public === "string") {
40
- publicData = config.public.split(",").map((s) => s.trim()).filter(Boolean);
41
- } else if (Array.isArray(config.public)) {
42
- publicData = config.public;
43
- }
44
- let dynamicData = {};
45
- if (typeof config.dynamic === "string") {
46
- config.dynamic.split(",").forEach((pair) => {
47
- const [key, value] = pair.split("=").map((s) => s.trim());
48
- if (key && value) {
49
- dynamicData[key] = value;
50
- }
51
- });
52
- } else if (typeof config.dynamic === "object") {
53
- dynamicData = config.dynamic;
54
- }
55
- const llmWebConfig = {
56
- name: config.name,
57
- description: config.description || "",
58
- type: config.type,
59
- public: publicData,
60
- dynamic: dynamicData,
61
- version: "1.0.0"
38
+ const staticMetadata = {
39
+ identity: {
40
+ name: config.identity?.name || config.name || "My Awesome Site",
41
+ description: config.identity?.description || config.description || "",
42
+ brand: config.identity?.brand || "",
43
+ locale: config.identity?.locale || "en-US"
44
+ },
45
+ navigation: config.navigation || { pages: [] },
46
+ business: config.business || {},
47
+ seo: config.seo || {},
48
+ contentTypes: config.contentTypes || [],
49
+ type: config.type || "marketing",
50
+ version: "1.2.0"
62
51
  };
63
- const jsonPath = import_path.default.join(cwd, "llmweb.json");
64
- import_fs.default.writeFileSync(jsonPath, JSON.stringify(llmWebConfig, null, 2));
65
- console.log(`Created: ${jsonPath}`);
52
+ const staticJsonPath = import_path.default.join(cwd, "llmweb.json");
53
+ import_fs.default.writeFileSync(staticJsonPath, JSON.stringify(staticMetadata, null, 2));
54
+ console.log(`Created static metadata: ${staticJsonPath}`);
55
+ const isTS = import_fs.default.existsSync(import_path.default.join(cwd, "tsconfig.json"));
56
+ const configExt = isTS ? "ts" : "js";
57
+ const configPath = import_path.default.join(cwd, `llmweb.config.${configExt}`);
58
+ const dynamicEntries = Object.entries(config.dynamic || {});
59
+ const dynamicConfigStr = dynamicEntries.map(([key, source]) => {
60
+ const from = source.from || {};
61
+ let fromStr = "";
62
+ if (from.type === "fetch") {
63
+ fromStr = `{ type: 'fetch', url: '${from.url}' }`;
64
+ } else if (from.type === "fn") {
65
+ const fnName = from.call.replace("[INTERNAL:", "").replace("]", "");
66
+ fromStr = `{ type: 'fn', call: ${fnName} }`;
67
+ } else {
68
+ fromStr = `{ type: 'other' }`;
69
+ }
70
+ return ` ${key}: {
71
+ from: ${fromStr}
72
+ }`;
73
+ }).join(",\n");
74
+ const serverActionImports = dynamicEntries.filter(([_, source]) => source.from?.type === "fn").map(([_, source]) => {
75
+ const fnName = source.from.call.replace("[INTERNAL:", "").replace("]", "");
76
+ return `// import { ${fnName} } from './actions';`;
77
+ }).join("\n");
78
+ const configContent = `${isTS ? "import { LLMConfig } from '@kaleabdenbel/llmweb';" : ""}
79
+ import staticData from './llmweb.json';
80
+ ${serverActionImports}
81
+
82
+ export const config${isTS ? ": LLMConfig" : ""} = {
83
+ static: staticData,
84
+ dynamic: {
85
+ ${dynamicConfigStr}
86
+ }
87
+ };
88
+ `;
89
+ import_fs.default.writeFileSync(configPath, configContent);
90
+ console.log(`Created orchestrator: ${configPath}`);
66
91
  const mdPath = import_path.default.join(cwd, "llm.md");
67
- const mdContent = `# ${config.name}
92
+ const mdContent = `# ${staticMetadata.identity.name}
68
93
 
69
- > ${config.description || "No description provided."}
94
+ > ${staticMetadata.identity.description || "No description provided."}
70
95
 
71
96
  This file is a machine-readable summary of the site structure for LLMs and Agents.
72
97
 
73
- ## Type
74
- ${config.type}
98
+ ## Identity
99
+ - **Brand**: ${staticMetadata.identity.brand || "N/A"}
100
+ - **Locale**: ${staticMetadata.identity.locale}
101
+ - **Type**: ${staticMetadata.type}
102
+
103
+ ## Navigation
104
+ ${staticMetadata.navigation.pages.map((p) => `- [${p.label}](${p.path})`).join("\n") || "- None"}
75
105
 
76
- ## Public Data
77
- The following data is statically available:
78
- ${publicData.map((item) => `- ${item}`).join("\n") || "- None"}
106
+ ## Content Types
107
+ ${staticMetadata.contentTypes.map((t) => `- ${t}`).join("\n") || "- None"}
79
108
 
80
- ## Dynamic Data
81
- The following data changes frequently and should be fetched from the API:
82
- ${Object.entries(dynamicData).map(([key, value]) => `- **${key}**: \`${value}\``).join("\n") || "- None"}
109
+ ## Dynamic Data Sources
110
+ ${dynamicEntries.map(([key, source]) => {
111
+ const from = source.from || {};
112
+ const info = from.type === "fetch" ? `API: \`${from.url}\`` : from.type === "fn" ? `Server Action: \`${from.call}\`` : "Other";
113
+ return `- **${key}**: ${info}`;
114
+ }).join("\n") || "- None"}
83
115
 
84
116
  ---
85
117
  Generated by [llmweb](https://github.com/kaleabdenbel/llmweb)
86
118
  `;
87
119
  import_fs.default.writeFileSync(mdPath, mdContent);
88
- console.log(`Created: ${mdPath}`);
120
+ console.log(`Created documentation: ${mdPath}`);
89
121
  }
90
122
 
91
123
  // src/generators/frameworks.ts
@@ -96,95 +128,408 @@ async function generateFrameworkGlue(config) {
96
128
  const framework = config.framework || "vite";
97
129
  console.log(`Detected framework: ${framework}`);
98
130
  if (framework === "vite" || framework === "react") {
99
- const setupContent = `import { LLMSource } from '@kaleabdenbel/llmweb/adapters/react';
100
- import config from './llmweb.json';
131
+ const setupContent = `import { LLMJson } from '@kaleabdenbel/llmweb/adapters/react';
132
+ import { config } from './llmweb.config';
101
133
 
102
134
  /**
103
135
  * Use this component in your root layout or App component.
104
136
  * It will inject the machine-readable identity into your page.
105
137
  */
106
138
  export function LLMIdentity() {
107
- return <LLMSource config={config as any} />;
139
+ return <LLMJson config={config as any} mode="ldjson" />;
108
140
  }
109
141
  `;
110
142
  const setupPath = import_path2.default.join(cwd, "llmweb.setup.tsx");
111
143
  import_fs2.default.writeFileSync(setupPath, setupContent);
112
144
  console.log(`Created: ${setupPath}`);
113
- console.log("\nTIP: Import and use <LLMIdentity /> in your App.tsx or main.tsx\n");
145
+ console.log(
146
+ "\nTIP: Import and use <LLMIdentity /> in your App.tsx or main.tsx"
147
+ );
148
+ console.log(
149
+ "Snippet: \n <header>\n <LLMIdentity />\n ...\n </header>\n"
150
+ );
114
151
  } else if (framework === "next") {
115
- const setupContent = `import { LLMSource } from '@kaleabdenbel/llmweb/adapters/next';
116
- import config from './llmweb.json';
152
+ const appDirPath = import_path2.default.join(cwd, "app");
153
+ const srcAppDirPath = import_path2.default.join(cwd, "src", "app");
154
+ let targetAppDir = "";
155
+ if (import_fs2.default.existsSync(appDirPath)) {
156
+ targetAppDir = appDirPath;
157
+ } else if (import_fs2.default.existsSync(srcAppDirPath)) {
158
+ targetAppDir = srcAppDirPath;
159
+ }
160
+ if (targetAppDir) {
161
+ const llmDirPath = import_path2.default.join(targetAppDir, "llm");
162
+ if (!import_fs2.default.existsSync(llmDirPath)) {
163
+ import_fs2.default.mkdirSync(llmDirPath, { recursive: true });
164
+ }
165
+ const pagePath = import_path2.default.join(llmDirPath, "page.tsx");
166
+ const pageContent = `import { LLMJson } from '@kaleabdenbel/llmweb/adapters/next';
167
+ import { config } from '../../llmweb.config';
168
+
169
+ /**
170
+ * Machine-readable identity page.
171
+ * Accessible at /llm
172
+ */
173
+ export default async function LLMPage() {
174
+ return (
175
+ <div style={{ padding: '2rem', fontFamily: 'sans-serif' }}>
176
+ <h1>LLM Perspective</h1>
177
+ <p>This page provides a machine-readable view of the site structure and dynamic data.</p>
178
+
179
+ <LLMJson config={config as any} mode="ldjson" />
180
+
181
+ <section style={{ marginTop: '2rem' }}>
182
+ <h2>Raw Configuration</h2>
183
+ <pre style={{ background: '#f4f4f4', padding: '1rem', borderRadius: '4px', overflow: 'auto' }}>
184
+ {JSON.stringify(config, null, 2)}
185
+ </pre>
186
+ </section>
187
+ </div>
188
+ );
189
+ }
190
+ `;
191
+ import_fs2.default.writeFileSync(pagePath, pageContent);
192
+ console.log(`Created: ${pagePath}`);
193
+ console.log(
194
+ "\nTIP: Your machine-readable identity is now available at /llm\n"
195
+ );
196
+ } else {
197
+ const setupContent = `import { LLMJson } from '@kaleabdenbel/llmweb/adapters/next';
198
+ import { config } from './llmweb.config';
117
199
 
118
200
  /**
119
- * Add this to your root layout.tsx
201
+ * Add this to your root layout.tsx or a dedicated page.
120
202
  */
121
203
  export function LLMIdentity() {
122
- return <LLMSource config={config as any} />;
204
+ return <LLMJson config={config as any} mode="ldjson" />;
123
205
  }
124
206
  `;
125
- const setupPath = import_path2.default.join(cwd, "llmweb.setup.tsx");
126
- import_fs2.default.writeFileSync(setupPath, setupContent);
127
- console.log(`Created: ${setupPath}`);
207
+ const setupPath = import_path2.default.join(cwd, "llmweb.setup.tsx");
208
+ import_fs2.default.writeFileSync(setupPath, setupContent);
209
+ console.log(`Created: ${setupPath}`);
210
+ }
211
+ } else if (framework === "static") {
212
+ const indexPath = import_path2.default.join(cwd, "index.html");
213
+ if (import_fs2.default.existsSync(indexPath)) {
214
+ let content = import_fs2.default.readFileSync(indexPath, "utf-8");
215
+ const ldJsonSnippet = `
216
+ <!-- llmweb AI identity -->
217
+ <script type="application/ld+json">
218
+ {
219
+ "@context": "https://schema.org",
220
+ "@type": "WebSite",
221
+ "name": "${config.identity?.name || "My Site"}",
222
+ "description": "${config.identity?.description || ""}"
223
+ }
224
+ </script>`;
225
+ if (content.includes("</head>")) {
226
+ content = content.replace("</head>", `${ldJsonSnippet}
227
+ </head>`);
228
+ } else {
229
+ content = ldJsonSnippet + "\n" + content;
230
+ }
231
+ import_fs2.default.writeFileSync(indexPath, content);
232
+ console.log(`Injected JSON-LD into: ${indexPath}`);
233
+ } else {
234
+ console.log(
235
+ "\n[llmweb] Warning: index.html not found for static injection."
236
+ );
237
+ console.log("Please add the following to your <head>:");
238
+ console.log(
239
+ `
240
+ <script type="application/ld+json">
241
+ { "@context": "https://schema.org", "@type": "WebSite", "name": "${config.identity?.name}" }
242
+ </script>
243
+ `
244
+ );
245
+ }
128
246
  } else {
129
- console.log("Generic setup: Ensure `llmweb.json` and `llm.md` are served at the root of your site.");
247
+ console.log(
248
+ "Generic setup: Ensure `llmweb.json` and `llm.md` are served at the root of your site."
249
+ );
130
250
  }
131
251
  }
132
252
 
133
253
  // src/cli/init.ts
134
- var initCommand = new import_commander.Command("init").description("Initialize llmweb configuration for your project").option("--name <name>", "Project name").option("--type <type>", "Project type (marketing, docs, saas, content)").option("--public <items>", "Comma-separated list of public data types").option("--dynamic <items>", "Comma-separated list of dynamic data types (key=path)").option("--inject <type>", "Injection method (script, route, component)").option("--framework <framework>", "Framework (vite, next, vue)").action(async (options) => {
254
+ var initCommand = new import_commander.Command("init").description("Initialize llmweb configuration for your project").option("--name <name>", "Project name").option("--description <desc>", "Project description").option("--brand <brand>", "Brand name").option("--locale <locale>", "Locale (e.g. en-US)").option("--type <type>", "Project type (marketing, docs, saas, content)").option("--address <address>", "Business address").option("--email <email>", "Contact email").option("--twitter <url>", "Twitter URL").option("--linkedin <url>", "LinkedIn URL").option("--title-pattern <pattern>", "SEO title pattern").option("--desc-pattern <pattern>", "SEO description pattern").option("--public <items>", "Comma-separated list of public data types").option(
255
+ "--dynamic <items>",
256
+ "Comma-separated list of dynamic data types (key=path)"
257
+ ).option("--inject <type>", "Injection method (script, route, component)").option("--framework <framework>", "Framework (vite, next, vue)").option("--silent", "Run in non-interactive mode").action(async (options) => {
135
258
  let config = { ...options };
136
- if (!config.name || !config.type) {
137
- const response = await (0, import_prompts.default)([
138
- {
139
- type: config.name ? null : "text",
140
- name: "name",
141
- message: "What is the name of your site?",
142
- initial: "My Awesome Site"
143
- },
144
- {
145
- type: "text",
146
- name: "description",
147
- message: "One sentence describing the product:"
148
- },
149
- {
150
- type: config.type ? null : "select",
151
- name: "type",
152
- message: "Is this marketing, docs, SaaS, or content?",
153
- choices: [
154
- { title: "Marketing", value: "marketing" },
155
- { title: "Docs", value: "docs" },
156
- { title: "SaaS", value: "saas" },
157
- { title: "Content", value: "content" }
158
- ]
159
- },
160
- {
161
- type: "list",
162
- name: "public",
163
- message: "What data is always public? (comma separated)",
164
- initial: "",
165
- separator: ","
166
- },
167
- {
168
- type: "text",
169
- name: "dynamic",
170
- message: "What data changes frequently? (format: key=route)",
171
- hint: "announcements=/api/announcements"
172
- },
173
- {
174
- type: "select",
175
- name: "framework",
176
- message: "Which framework are you using?",
177
- choices: [
178
- { title: "Vite (React/Vue/Vanilla)", value: "vite" },
179
- { title: "Next.js", value: "next" },
180
- { title: "Other", value: "other" }
181
- ]
259
+ const silent = !!config.silent;
260
+ const identity = silent && config.name ? {
261
+ name: config.name,
262
+ description: config.description || "",
263
+ brand: config.brand || "",
264
+ locale: config.locale || "en-US"
265
+ } : await (0, import_prompts.default)([
266
+ {
267
+ type: config.name ? null : "text",
268
+ name: "name",
269
+ message: "What is the name of your site?",
270
+ initial: "My Awesome Site"
271
+ },
272
+ {
273
+ type: config.description ? null : "text",
274
+ name: "description",
275
+ message: "One sentence describing the product/site:",
276
+ initial: config.description
277
+ },
278
+ {
279
+ type: config.brand ? null : "text",
280
+ name: "brand",
281
+ message: "Brand/organization name?",
282
+ initial: config.brand
283
+ },
284
+ {
285
+ type: config.locale ? null : "text",
286
+ name: "locale",
287
+ message: "Primary language/locale?",
288
+ initial: config.locale || "en-US"
289
+ }
290
+ ]);
291
+ if (config.name) identity.name = config.name;
292
+ if (config.description) identity.description = config.description;
293
+ if (config.brand) identity.brand = config.brand;
294
+ if (config.locale) identity.locale = config.locale || "en-US";
295
+ const navigation = { pages: [] };
296
+ const { hasNav } = silent ? { hasNav: false } : await (0, import_prompts.default)({
297
+ type: "confirm",
298
+ name: "hasNav",
299
+ message: "Do you want to declare main navigation pages?",
300
+ initial: true
301
+ });
302
+ if (hasNav) {
303
+ let addPage = true;
304
+ while (addPage) {
305
+ const page = await (0, import_prompts.default)([
306
+ {
307
+ type: "text",
308
+ name: "label",
309
+ message: "Page label (e.g. Home, Pricing):"
310
+ },
311
+ {
312
+ type: "text",
313
+ name: "path",
314
+ message: "Page path (e.g. /, /pricing):"
315
+ },
316
+ {
317
+ type: "confirm",
318
+ name: "more",
319
+ message: "Add another page?",
320
+ initial: false
321
+ }
322
+ ]);
323
+ if (page.label && page.path) {
324
+ navigation.pages.push({ label: page.label, path: page.path });
182
325
  }
183
- ]);
184
- config = { ...config, ...response };
326
+ addPage = page.more;
327
+ }
328
+ }
329
+ const business = silent ? {
330
+ address: config.address || "",
331
+ email: config.email || "",
332
+ twitter: config.twitter || "",
333
+ linkedin: config.linkedin || ""
334
+ } : await (0, import_prompts.default)([
335
+ {
336
+ type: config.address ? null : "text",
337
+ name: "address",
338
+ message: "Company/organization address?",
339
+ initial: config.address
340
+ },
341
+ {
342
+ type: config.email ? null : "text",
343
+ name: "email",
344
+ message: "Contact email?",
345
+ initial: config.email
346
+ },
347
+ {
348
+ type: config.twitter ? null : "text",
349
+ name: "twitter",
350
+ message: "Twitter URL?",
351
+ initial: config.twitter
352
+ },
353
+ {
354
+ type: config.linkedin ? null : "text",
355
+ name: "linkedin",
356
+ message: "LinkedIn URL?",
357
+ initial: config.linkedin
358
+ }
359
+ ]);
360
+ if (config.address) business.address = config.address;
361
+ if (config.email) business.email = config.email;
362
+ if (config.twitter) business.twitter = config.twitter;
363
+ if (config.linkedin) business.linkedin = config.linkedin;
364
+ const seo = silent ? {
365
+ titlePattern: config.titlePattern || "%s | Site Name",
366
+ descriptionPattern: config.descPattern || "",
367
+ multilingual: !!config.multilingual
368
+ } : await (0, import_prompts.default)([
369
+ {
370
+ type: config.titlePattern ? null : "text",
371
+ name: "titlePattern",
372
+ message: "Default meta title pattern?",
373
+ initial: config.titlePattern || "%s | Site Name"
374
+ },
375
+ {
376
+ type: config.descPattern ? null : "text",
377
+ name: "descriptionPattern",
378
+ message: "Default meta description pattern?",
379
+ initial: config.descPattern
380
+ },
381
+ {
382
+ type: config.multilingual !== void 0 ? null : "confirm",
383
+ name: "multilingual",
384
+ message: "Do you want multilingual content?",
385
+ initial: false
386
+ }
387
+ ]);
388
+ if (config.titlePattern) seo.titlePattern = config.titlePattern;
389
+ if (config.descPattern) seo.descriptionPattern = config.descPattern;
390
+ if (config.multilingual !== void 0)
391
+ seo.multilingual = config.multilingual;
392
+ const { contentTypes } = silent && config.name ? { contentTypes: [] } : await (0, import_prompts.default)({
393
+ type: "multiselect",
394
+ name: "contentTypes",
395
+ message: "Which content types do you have?",
396
+ choices: [
397
+ { title: "Blog", value: "blog" },
398
+ { title: "Articles", value: "articles" },
399
+ { title: "FAQ", value: "faq" },
400
+ { title: "Tutorials", value: "tutorials" },
401
+ { title: "Products", value: "products" },
402
+ { title: "Portfolio", value: "portfolio" },
403
+ { title: "Events", value: "events" }
404
+ ],
405
+ hint: "- Space to select. Return to submit"
406
+ });
407
+ const frameworkInfo = silent && config.name ? {
408
+ type: config.type || "saas",
409
+ framework: config.framework || "next"
410
+ } : await (0, import_prompts.default)([
411
+ {
412
+ type: config.type ? null : "select",
413
+ name: "type",
414
+ message: "Is this marketing, docs, SaaS, or content?",
415
+ choices: [
416
+ { title: "Marketing", value: "marketing" },
417
+ { title: "Docs", value: "docs" },
418
+ { title: "SaaS", value: "saas" },
419
+ { title: "Content", value: "content" }
420
+ ]
421
+ },
422
+ {
423
+ type: config.framework ? null : "select",
424
+ name: "framework",
425
+ message: "Which framework are you using?",
426
+ choices: [
427
+ { title: "Vite (React/Vue/Vanilla)", value: "vite" },
428
+ { title: "Next.js", value: "next" },
429
+ { title: "Static (HTML)", value: "static" },
430
+ { title: "Other", value: "other" }
431
+ ]
432
+ }
433
+ ]);
434
+ config = {
435
+ ...config,
436
+ identity,
437
+ navigation,
438
+ business,
439
+ seo,
440
+ contentTypes,
441
+ type: frameworkInfo.type || config.type || "saas",
442
+ framework: frameworkInfo.framework || config.framework || "next"
443
+ };
444
+ const dynamicSources = {};
445
+ const { confirmDynamic } = silent || typeof config.dynamic === "string" && config.dynamic.length > 0 ? { confirmDynamic: false } : await (0, import_prompts.default)({
446
+ type: "confirm",
447
+ name: "confirmDynamic",
448
+ message: "Do you have dynamic data to declare? (API, Server Actions)",
449
+ initial: false
450
+ });
451
+ if (confirmDynamic) {
452
+ let addDynamic = true;
453
+ while (addDynamic) {
454
+ const dynamicEntry = await (0, import_prompts.default)([
455
+ {
456
+ type: "text",
457
+ name: "key",
458
+ message: "What is the data key? (e.g. announcements, user)"
459
+ },
460
+ {
461
+ type: "select",
462
+ name: "mode",
463
+ message: "How is this data fetched?",
464
+ choices: (prev, values) => {
465
+ const base = [
466
+ { title: "API Endpoint", value: "api" },
467
+ { title: "Database/Other", value: "other" }
468
+ ];
469
+ if (config.framework === "next") {
470
+ base.splice(1, 0, {
471
+ title: "Server Action (Next.js)",
472
+ value: "action"
473
+ });
474
+ }
475
+ return base;
476
+ }
477
+ },
478
+ {
479
+ type: (prev) => prev === "api" ? "text" : null,
480
+ name: "url",
481
+ message: "API URL:",
482
+ initial: (prev, values) => `/api/${values.key}`
483
+ },
484
+ {
485
+ type: (prev) => prev === "action" ? "text" : null,
486
+ name: "actionFn",
487
+ message: "Server Action function name:",
488
+ initial: (prev, values) => `get${values.key.charAt(0).toUpperCase() + values.key.slice(1)}`
489
+ },
490
+ {
491
+ type: "confirm",
492
+ name: "more",
493
+ message: "Add another dynamic source?",
494
+ initial: false
495
+ }
496
+ ]);
497
+ if (dynamicEntry.key) {
498
+ if (dynamicEntry.mode === "api") {
499
+ dynamicSources[dynamicEntry.key] = {
500
+ from: { type: "fetch", url: dynamicEntry.url }
501
+ };
502
+ } else if (dynamicEntry.mode === "action") {
503
+ dynamicSources[dynamicEntry.key] = {
504
+ from: { type: "fn", call: `[INTERNAL:${dynamicEntry.actionFn}]` }
505
+ };
506
+ } else {
507
+ dynamicSources[dynamicEntry.key] = { from: { type: "other" } };
508
+ }
509
+ }
510
+ addDynamic = dynamicEntry.more;
511
+ }
185
512
  }
186
513
  if (typeof config.dynamic === "string" && config.dynamic.length > 0) {
514
+ const flagSources = {};
515
+ config.dynamic.split(",").forEach((pair) => {
516
+ const [key, value] = pair.split("=").map((s) => s.trim());
517
+ if (key && value) {
518
+ if (value.startsWith("action:")) {
519
+ flagSources[key] = {
520
+ from: {
521
+ type: "fn",
522
+ call: `[INTERNAL:${value.replace("action:", "")}]`
523
+ }
524
+ };
525
+ } else {
526
+ flagSources[key] = { from: { type: "fetch", url: value } };
527
+ }
528
+ }
529
+ });
530
+ Object.assign(dynamicSources, flagSources);
187
531
  }
532
+ config.dynamic = dynamicSources;
188
533
  console.log("Generating configuration...");
189
534
  await generateConfig(config);
190
535
  console.log("Generating framework glue...");
@@ -195,7 +540,7 @@ var initCommand = new import_commander.Command("init").description("Initialize l
195
540
  // package.json
196
541
  var package_default = {
197
542
  name: "@kaleabdenbel/llmweb",
198
- version: "1.0.4",
543
+ version: "1.0.5",
199
544
  description: "A compiler for LLM-readable truth from static and dynamic sources.",
200
545
  publishConfig: {
201
546
  access: "public"
@@ -243,8 +588,8 @@ var package_default = {
243
588
  scripts: {
244
589
  build: "npm run build:lib && npm run build:cli",
245
590
  "build:lib": "tsup src/index.ts src/index.browser.ts src/adapters/next.tsx src/adapters/react.tsx src/adapters/vanilla.ts src/adapters/express.ts --format cjs,esm --dts --clean --no-splitting",
246
- "build:cli": "tsup src/cli.ts --format cjs --no-dts --clean --no-splitting",
247
- dev: "npm run build:lib && npm run build:cli",
591
+ "build:cli": "tsup src/cli.ts --format cjs --no-dts --no-splitting",
592
+ dev: "npm run build",
248
593
  lint: "tsc",
249
594
  test: "vitest run"
250
595
  },
@@ -0,0 +1,56 @@
1
+ type SourceType = "fetch" | "fn";
2
+ interface SourceDefinition {
3
+ type: SourceType;
4
+ url?: string;
5
+ call?: (...args: any[]) => Promise<any> | any;
6
+ }
7
+ interface MapSchema {
8
+ [targetKey: string]: string | ((sourceData: any) => any) | MapSchema;
9
+ }
10
+ interface DynamicSource {
11
+ from: SourceDefinition;
12
+ map?: MapSchema;
13
+ }
14
+ interface LLMConfig {
15
+ identity?: {
16
+ name: string;
17
+ description?: string;
18
+ brand?: string;
19
+ locale?: string;
20
+ };
21
+ navigation?: {
22
+ pages: Array<{
23
+ label: string;
24
+ path: string;
25
+ }>;
26
+ };
27
+ business?: {
28
+ address?: string;
29
+ email?: string;
30
+ twitter?: string;
31
+ linkedin?: string;
32
+ };
33
+ seo?: {
34
+ titlePattern?: string;
35
+ descriptionPattern?: string;
36
+ multilingual?: boolean;
37
+ };
38
+ contentTypes?: string[];
39
+ type?: string;
40
+ static?: string | Record<string, any>;
41
+ dynamic?: {
42
+ [key: string]: DynamicSource;
43
+ };
44
+ }
45
+ interface TransformerOptions {
46
+ failLoudly?: boolean;
47
+ timeout?: number;
48
+ }
49
+ interface LLMJsonProps {
50
+ config: LLMConfig;
51
+ mode?: "script" | "window" | "pre" | "ldjson";
52
+ targetKey?: string;
53
+ className?: string;
54
+ }
55
+
56
+ export type { DynamicSource as D, LLMConfig as L, MapSchema as M, SourceDefinition as S, TransformerOptions as T, LLMJsonProps as a, SourceType as b };