pdfx-cli 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # pdfx-cli
2
+
3
+ > The official CLI tool for [PDFx](https://pdfx.akashpise.dev), professional pre-built PDF React components.
4
+
5
+ Create beautiful, dynamic, and perfectly typed PDFs in React using standard Tailwind-like utility classes and strict property interfaces. PDFx provides a library of copy-pasteable blocks (Invoices, Reports, Receipts) that you fully own and customize inside your project.
6
+
7
+ Built on top of [@react-pdf/renderer](https://react-pdf.org/).
8
+
9
+ ## Installation
10
+
11
+ Initialize the PDFx configuration and setup in your project:
12
+
13
+ ```bash
14
+ npx pdfx-cli@latest init
15
+ ```
16
+
17
+ ## Adding Components
18
+
19
+ Add specific PDFx components directly into your local codebase. You own the code!
20
+
21
+ ```bash
22
+ npx pdfx-cli@latest add badge
23
+ npx pdfx-cli@latest add table form qrcode
24
+ ```
25
+
26
+ ## Available Components
27
+ - `alert`
28
+ - `badge`
29
+ - `card`
30
+ - `data-table`
31
+ - `divider`
32
+ - `form`
33
+ - `heading`
34
+ - `keep-together`
35
+ - `key-value`
36
+ - `link`
37
+ - `list`
38
+ - `page-break`
39
+ - `page-footer`
40
+ - `page-header`
41
+ - `page-number`
42
+ - `pdf-image`
43
+ - `qrcode`
44
+ - `section`
45
+ - `signature`
46
+ - `stack`
47
+ - `table`
48
+ - `text`
49
+ - `watermark`
50
+
51
+ ## Pre-Composed Blocks
52
+ Start with full, gorgeous templates.
53
+
54
+ ```bash
55
+ npx pdfx-cli@latest block add invoice-modern
56
+ npx pdfx-cli@latest block add report-financial
57
+ ```
58
+
59
+ ## MCP & AI Agents
60
+
61
+ PDFx comes with first-class AI Agent integration via MCP (Model Context Protocol). Connect your AI IDE or Agent directly to the PDFx registry to instantly gain fluent context about PDFx component structures.
62
+
63
+ ```bash
64
+ npx pdfx-cli@latest mcp init --client cursor
65
+ ```
66
+
67
+ ## Documentation
68
+
69
+ Full documentation, real-time PDF previews, and block gallery available at [pdfx.akashpise.dev](https://pdfx.akashpise.dev).
70
+
71
+ ## License
72
+
73
+ MIT
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { readFileSync as readFileSync2 } from "fs";
5
5
  import { dirname, join } from "path";
6
6
  import { fileURLToPath } from "url";
7
- import chalk10 from "chalk";
7
+ import chalk11 from "chalk";
8
8
  import { Command as Command3 } from "commander";
9
9
 
10
10
  // src/commands/add.ts
@@ -2218,6 +2218,7 @@ import { existsSync } from "fs";
2218
2218
  import { mkdir, readFile, writeFile as writeFile2 } from "fs/promises";
2219
2219
  import path11 from "path";
2220
2220
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2221
+ import chalk8 from "chalk";
2221
2222
  import { Command } from "commander";
2222
2223
  import prompts5 from "prompts";
2223
2224
 
@@ -2385,8 +2386,21 @@ async function getAuditChecklist() {
2385
2386
  \`\`\`
2386
2387
 
2387
2388
  ### "Invalid hook call"
2388
- PDFx components render to PDF, not to the DOM — React hooks are not supported inside them.
2389
- Move hook calls to the parent component and pass data down as props.
2389
+ \`@react-pdf/renderer\` renders synchronously. Hooks that depend on browser APIs or
2390
+ async side effects do not work in the PDF render path.
2391
+
2392
+ **Do NOT use inside a PDFx component:**
2393
+ - \`useEffect\` / \`useLayoutEffect\` — no browser lifecycle in PDF rendering
2394
+ - \`useRef\` for DOM nodes — no DOM exists in the PDF render tree
2395
+ - Any hook that calls browser globals (\`window\`, \`document\`, \`navigator\`)
2396
+
2397
+ **These ARE valid inside PDFx components:**
2398
+ - PDFx framework hooks: \`usePdfxTheme()\`, \`useSafeMemo()\`
2399
+ - Custom hooks that are pure functions with no browser-API dependencies
2400
+ - Data passed down as props from a parent client component
2401
+
2402
+ If you see this error in a wrapper component you wrote, move the browser hook
2403
+ to the nearest client component and pass the result down as a prop.
2390
2404
 
2391
2405
  ### "Text strings must be rendered inside \`<Text>\` component"
2392
2406
  Wrap all string literals in \`<Text>\` from \`@react-pdf/renderer\`:
@@ -2586,7 +2600,7 @@ async function getComponent(args) {
2586
2600
  const primaryContent = item.files[0]?.content ?? "";
2587
2601
  const primaryPath = item.files[0]?.path ?? "";
2588
2602
  const exportNames = extractAllExportNames(primaryContent);
2589
- const mainExport = extractExportName(primaryContent);
2603
+ const mainExport = extractExportName(primaryContent, args.component);
2590
2604
  const exportSection = exportNames.length > 0 ? dedent4`
2591
2605
  ## Exports
2592
2606
  **Main component export:** \`${mainExport ?? exportNames[0]}\`
@@ -2631,11 +2645,18 @@ async function getComponent(args) {
2631
2645
  ${fileSources}
2632
2646
  `);
2633
2647
  }
2634
- function extractExportName(source) {
2648
+ function extractExportName(source, componentName) {
2635
2649
  if (!source) return null;
2636
- const matches = [...source.matchAll(/export\s+(?:function|const)\s+([A-Z][A-Za-z0-9]*)/g)];
2637
- if (matches.length === 0) return null;
2638
- return matches[0][1] ?? null;
2650
+ const matches = [...source.matchAll(/export\s+function\s+([A-Z][A-Za-z0-9]*)/g)];
2651
+ const names = matches.map((m) => m[1]).filter(Boolean);
2652
+ if (names.length === 0) return null;
2653
+ if (!componentName) return names[0];
2654
+ const norm = componentName.replace(/-/g, "").toLowerCase();
2655
+ const exact = names.find((n) => n.toLowerCase() === norm);
2656
+ if (exact) return exact;
2657
+ const suffix = names.find((n) => n.toLowerCase().endsWith(norm));
2658
+ if (suffix) return suffix;
2659
+ return names[0];
2639
2660
  }
2640
2661
  function extractAllExportNames(source) {
2641
2662
  const seen = /* @__PURE__ */ new Set();
@@ -2844,7 +2865,7 @@ async function getInstallation(args) {
2844
2865
  | TypeScript errors on \`@react-pdf/renderer\` | \`${installCmd(pm, "@react-pdf/types", true)}\` |
2845
2866
  | "Cannot find module @/components/pdfx/..." | Run \`npx pdfx-cli@latest add <component>\` to install it |
2846
2867
  | PDF renders blank | Ensure root returns \`<Document><Page>...</Page></Document>\` |
2847
- | "Invalid hook call" | PDFx components cannot use React hooks — pass data as props |
2868
+ | "Invalid hook call" | Browser-only hooks (\`useEffect\`, \`useRef\` for DOM) don't work in the PDF render path move them to a parent client component and pass the result as a prop. PDFx framework hooks (\`usePdfxTheme\`, \`useSafeMemo\`) are valid inside PDF components. |
2848
2869
 
2849
2870
  ---
2850
2871
  Next: call \`get_audit_checklist\` to verify your setup is correct.
@@ -2968,14 +2989,13 @@ async function getTheme(args) {
2968
2989
 
2969
2990
  ## Usage in Components
2970
2991
  \`\`\`tsx
2971
- // Access theme values in a PDFx component
2972
- import type { PdfxTheme } from '@pdfx/shared';
2973
-
2974
- interface Props {
2975
- theme: PdfxTheme;
2976
- }
2992
+ // Access theme values in a PDFx component via the usePdfxTheme hook.
2993
+ // @pdfx/shared is workspace-only do NOT import from it in your components.
2994
+ import { View, Text } from '@react-pdf/renderer';
2995
+ import { usePdfxTheme } from '../lib/pdfx-theme-context';
2977
2996
 
2978
- export function MyComponent({ theme }: Props) {
2997
+ export function MyComponent() {
2998
+ const theme = usePdfxTheme();
2979
2999
  return (
2980
3000
  <View style={{ backgroundColor: theme.colors.background }}>
2981
3001
  <Text style={{ color: theme.colors.foreground, fontSize: theme.typography.body.fontSize }}>
@@ -3213,6 +3233,14 @@ function isPdfxAlreadyConfigured(config) {
3213
3233
  return withMcpServers.mcpServers?.pdfx !== void 0 || withServers.servers?.pdfx !== void 0 || withMcp.mcp?.pdfx !== void 0;
3214
3234
  }
3215
3235
  async function initMcpConfig(opts) {
3236
+ const preFlightResult = runPreFlightChecks();
3237
+ displayPreFlightResults(preFlightResult);
3238
+ if (!preFlightResult.canProceed) {
3239
+ console.error(
3240
+ chalk8.red("\n Cannot proceed due to blocking issues. Please fix them and try again.\n")
3241
+ );
3242
+ process.exit(1);
3243
+ }
3216
3244
  let clientName = opts.client;
3217
3245
  if (!clientName) {
3218
3246
  const response = await prompts5({
@@ -3294,7 +3322,7 @@ mcpCommand.command("init").description("Add PDFx MCP server config to your AI ed
3294
3322
  import { existsSync as existsSync2, readFileSync } from "fs";
3295
3323
  import { mkdir as mkdir2, writeFile as writeFile3 } from "fs/promises";
3296
3324
  import path12 from "path";
3297
- import chalk8 from "chalk";
3325
+ import chalk9 from "chalk";
3298
3326
  import { Command as Command2 } from "commander";
3299
3327
  import prompts6 from "prompts";
3300
3328
 
@@ -3997,6 +4025,14 @@ function fileHasPdfxContent(filePath) {
3997
4025
  }
3998
4026
  }
3999
4027
  async function skillsInit(opts) {
4028
+ const preFlightResult = runPreFlightChecks();
4029
+ displayPreFlightResults(preFlightResult);
4030
+ if (!preFlightResult.canProceed) {
4031
+ console.error(
4032
+ chalk9.red("\n Cannot proceed due to blocking issues. Please fix them and try again.\n")
4033
+ );
4034
+ process.exit(1);
4035
+ }
4000
4036
  let platformName = opts.platform;
4001
4037
  if (!platformName) {
4002
4038
  const response = await prompts6({
@@ -4016,10 +4052,10 @@ async function skillsInit(opts) {
4016
4052
  }
4017
4053
  const platform = PLATFORMS.find((p) => p.name === platformName);
4018
4054
  if (!platform) {
4019
- process.stderr.write(chalk8.red(`\u2716 Unknown platform: "${platformName}"
4055
+ process.stderr.write(chalk9.red(`\u2716 Unknown platform: "${platformName}"
4020
4056
  `));
4021
4057
  process.stderr.write(
4022
- chalk8.dim(` Valid options: ${PLATFORMS.map((p) => p.name).join(", ")}
4058
+ chalk9.dim(` Valid options: ${PLATFORMS.map((p) => p.name).join(", ")}
4023
4059
  `)
4024
4060
  );
4025
4061
  process.exit(1);
@@ -4043,7 +4079,7 @@ async function skillsInit(opts) {
4043
4079
  ]
4044
4080
  });
4045
4081
  if (!action2 || action2 === "skip") {
4046
- process.stdout.write(chalk8.dim("\nSkipped \u2014 existing file kept.\n\n"));
4082
+ process.stdout.write(chalk9.dim("\nSkipped \u2014 existing file kept.\n\n"));
4047
4083
  return;
4048
4084
  }
4049
4085
  } else {
@@ -4061,7 +4097,7 @@ async function skillsInit(opts) {
4061
4097
  ]
4062
4098
  });
4063
4099
  if (!action2 || action2 === "skip") {
4064
- process.stdout.write(chalk8.dim("\nSkipped \u2014 existing file kept.\n\n"));
4100
+ process.stdout.write(chalk9.dim("\nSkipped \u2014 existing file kept.\n\n"));
4065
4101
  return;
4066
4102
  }
4067
4103
  if (action2 === "append") {
@@ -4088,15 +4124,15 @@ ${PDFX_SKILLS_CONTENT}`;
4088
4124
  }
4089
4125
  const action = shouldAppend && alreadyExists ? "Appended PDFx context to" : "Wrote";
4090
4126
  process.stdout.write(`
4091
- ${chalk8.green("\u2713")} ${action} ${chalk8.cyan(relativeFile)}
4127
+ ${chalk9.green("\u2713")} ${action} ${chalk9.cyan(relativeFile)}
4092
4128
  `);
4093
4129
  process.stdout.write(`
4094
- ${chalk8.dim(platform.verifyStep)}
4130
+ ${chalk9.dim(platform.verifyStep)}
4095
4131
 
4096
4132
  `);
4097
4133
  if (platform.name === "claude") {
4098
4134
  process.stdout.write(
4099
- chalk8.dim(
4135
+ chalk9.dim(
4100
4136
  "Tip: if you also use the MCP server, CLAUDE.md + MCP gives the AI\nthe best possible PDFx knowledge \u2014 static props reference + live registry.\n\n"
4101
4137
  )
4102
4138
  );
@@ -4109,34 +4145,34 @@ skillsCommand.command("init").description("Write the PDFx skills file to your AI
4109
4145
  ).option("-y, --yes", "Overwrite existing file without prompting").option("-a, --append", "Append PDFx context to an existing file instead of overwriting").action(skillsInit);
4110
4146
  skillsCommand.command("list").description("List all supported AI editor platforms").action(() => {
4111
4147
  process.stdout.write("\n");
4112
- process.stdout.write(chalk8.bold(" Supported platforms\n\n"));
4148
+ process.stdout.write(chalk9.bold(" Supported platforms\n\n"));
4113
4149
  for (const p of PLATFORMS) {
4114
- process.stdout.write(` ${chalk8.cyan(p.name.padEnd(12))} ${chalk8.dim("\u2192")} ${p.file}
4150
+ process.stdout.write(` ${chalk9.cyan(p.name.padEnd(12))} ${chalk9.dim("\u2192")} ${p.file}
4115
4151
  `);
4116
4152
  }
4117
4153
  process.stdout.write("\n");
4118
4154
  process.stdout.write(
4119
- chalk8.dim(" Usage: npx pdfx-cli@latest skills init --platform <name>\n\n")
4155
+ chalk9.dim(" Usage: npx pdfx-cli@latest skills init --platform <name>\n\n")
4120
4156
  );
4121
4157
  });
4122
4158
 
4123
4159
  // src/commands/theme.ts
4124
4160
  import fs8 from "fs";
4125
4161
  import path13 from "path";
4126
- import chalk9 from "chalk";
4162
+ import chalk10 from "chalk";
4127
4163
  import ora7 from "ora";
4128
4164
  import prompts7 from "prompts";
4129
4165
  import ts from "typescript";
4130
4166
  async function themeInit() {
4131
4167
  const configPath = path13.join(process.cwd(), "pdfx.json");
4132
4168
  if (!checkFileExists(configPath)) {
4133
- console.error(chalk9.red("\nError: pdfx.json not found"));
4134
- console.log(chalk9.yellow("\n PDFx is not initialized in this project.\n"));
4135
- console.log(chalk9.cyan(" Run: pdfx init"));
4136
- console.log(chalk9.dim(" This will set up your project configuration and theme.\n"));
4169
+ console.error(chalk10.red("\nError: pdfx.json not found"));
4170
+ console.log(chalk10.yellow("\n PDFx is not initialized in this project.\n"));
4171
+ console.log(chalk10.cyan(" Run: pdfx init"));
4172
+ console.log(chalk10.dim(" This will set up your project configuration and theme.\n"));
4137
4173
  process.exit(1);
4138
4174
  }
4139
- console.log(chalk9.bold.cyan("\n PDFx Theme Setup\n"));
4175
+ console.log(chalk10.bold.cyan("\n PDFx Theme Setup\n"));
4140
4176
  const answers = await prompts7(
4141
4177
  [
4142
4178
  {
@@ -4173,13 +4209,13 @@ async function themeInit() {
4173
4209
  ],
4174
4210
  {
4175
4211
  onCancel: () => {
4176
- console.log(chalk9.yellow("\nTheme setup cancelled."));
4212
+ console.log(chalk10.yellow("\nTheme setup cancelled."));
4177
4213
  process.exit(0);
4178
4214
  }
4179
4215
  }
4180
4216
  );
4181
4217
  if (!answers.preset || !answers.themePath) {
4182
- console.error(chalk9.red("Missing required fields."));
4218
+ console.error(chalk10.red("Missing required fields."));
4183
4219
  process.exit(1);
4184
4220
  }
4185
4221
  const presetName = answers.preset;
@@ -4199,19 +4235,19 @@ async function themeInit() {
4199
4235
  if (result.success) {
4200
4236
  const updatedConfig = { ...result.data, theme: themePath };
4201
4237
  writeFile(configPath, JSON.stringify(updatedConfig, null, 2));
4202
- console.log(chalk9.green(" Updated pdfx.json with theme path"));
4238
+ console.log(chalk10.green(" Updated pdfx.json with theme path"));
4203
4239
  }
4204
4240
  } catch {
4205
- console.log(chalk9.yellow(' Could not update pdfx.json \u2014 add "theme" field manually'));
4241
+ console.log(chalk10.yellow(' Could not update pdfx.json \u2014 add "theme" field manually'));
4206
4242
  }
4207
4243
  }
4208
- console.log(chalk9.dim(`
4244
+ console.log(chalk10.dim(`
4209
4245
  Edit ${themePath} to customize your theme.
4210
4246
  `));
4211
4247
  } catch (error) {
4212
4248
  spinner.fail("Failed to create theme file");
4213
4249
  const message = error instanceof Error ? error.message : String(error);
4214
- console.error(chalk9.dim(` ${message}`));
4250
+ console.error(chalk10.dim(` ${message}`));
4215
4251
  process.exit(1);
4216
4252
  }
4217
4253
  }
@@ -4219,28 +4255,28 @@ async function themeSwitch(presetName) {
4219
4255
  const resolvedPreset = presetName === "default" ? "professional" : presetName;
4220
4256
  const validPresets = Object.keys(themePresets);
4221
4257
  if (!validPresets.includes(resolvedPreset)) {
4222
- console.error(chalk9.red(`\u2716 Invalid theme preset: "${presetName}"`));
4223
- console.log(chalk9.dim(` Available presets: ${validPresets.join(", ")}, default
4258
+ console.error(chalk10.red(`\u2716 Invalid theme preset: "${presetName}"`));
4259
+ console.log(chalk10.dim(` Available presets: ${validPresets.join(", ")}, default
4224
4260
  `));
4225
- console.log(chalk9.dim(" Usage: pdfx theme switch <preset>"));
4261
+ console.log(chalk10.dim(" Usage: pdfx theme switch <preset>"));
4226
4262
  process.exit(1);
4227
4263
  }
4228
4264
  const validatedPreset = resolvedPreset;
4229
4265
  const configPath = path13.join(process.cwd(), "pdfx.json");
4230
4266
  if (!checkFileExists(configPath)) {
4231
- console.error(chalk9.red('No pdfx.json found. Run "npx pdfx-cli@latest init" first.'));
4267
+ console.error(chalk10.red('No pdfx.json found. Run "npx pdfx-cli@latest init" first.'));
4232
4268
  process.exit(1);
4233
4269
  }
4234
4270
  const rawConfig = readJsonFile(configPath);
4235
4271
  const result = configSchema.safeParse(rawConfig);
4236
4272
  if (!result.success) {
4237
- console.error(chalk9.red("Invalid pdfx.json configuration."));
4273
+ console.error(chalk10.red("Invalid pdfx.json configuration."));
4238
4274
  process.exit(1);
4239
4275
  }
4240
4276
  const config = result.data;
4241
4277
  if (!config.theme) {
4242
4278
  console.error(
4243
- chalk9.red(
4279
+ chalk10.red(
4244
4280
  'No theme path in pdfx.json. Run "npx pdfx-cli@latest theme init" to set up theming.'
4245
4281
  )
4246
4282
  );
@@ -4253,7 +4289,7 @@ async function themeSwitch(presetName) {
4253
4289
  initial: false
4254
4290
  });
4255
4291
  if (!answer.confirm) {
4256
- console.log(chalk9.yellow("Cancelled."));
4292
+ console.log(chalk10.yellow("Cancelled."));
4257
4293
  return;
4258
4294
  }
4259
4295
  const spinner = ora7(`Switching to ${validatedPreset} theme...`).start();
@@ -4269,7 +4305,7 @@ async function themeSwitch(presetName) {
4269
4305
  } catch (error) {
4270
4306
  spinner.fail("Failed to switch theme");
4271
4307
  const message = error instanceof Error ? error.message : String(error);
4272
- console.error(chalk9.dim(` ${message}`));
4308
+ console.error(chalk10.dim(` ${message}`));
4273
4309
  process.exit(1);
4274
4310
  }
4275
4311
  }
@@ -4339,18 +4375,18 @@ function parseThemeObject(themePath) {
4339
4375
  async function themeValidate() {
4340
4376
  const configPath = path13.join(process.cwd(), "pdfx.json");
4341
4377
  if (!checkFileExists(configPath)) {
4342
- console.error(chalk9.red('No pdfx.json found. Run "npx pdfx-cli@latest init" first.'));
4378
+ console.error(chalk10.red('No pdfx.json found. Run "npx pdfx-cli@latest init" first.'));
4343
4379
  process.exit(1);
4344
4380
  }
4345
4381
  const rawConfig = readJsonFile(configPath);
4346
4382
  const configResult = configSchema.safeParse(rawConfig);
4347
4383
  if (!configResult.success) {
4348
- console.error(chalk9.red("Invalid pdfx.json configuration."));
4384
+ console.error(chalk10.red("Invalid pdfx.json configuration."));
4349
4385
  process.exit(1);
4350
4386
  }
4351
4387
  if (!configResult.data.theme) {
4352
4388
  console.error(
4353
- chalk9.red(
4389
+ chalk10.red(
4354
4390
  'No theme path in pdfx.json. Run "npx pdfx-cli@latest theme init" to set up theming.'
4355
4391
  )
4356
4392
  );
@@ -4358,7 +4394,7 @@ async function themeValidate() {
4358
4394
  }
4359
4395
  const absThemePath = path13.resolve(process.cwd(), configResult.data.theme);
4360
4396
  if (!checkFileExists(absThemePath)) {
4361
- console.error(chalk9.red(`Theme file not found: ${configResult.data.theme}`));
4397
+ console.error(chalk10.red(`Theme file not found: ${configResult.data.theme}`));
4362
4398
  process.exit(1);
4363
4399
  }
4364
4400
  const spinner = ora7("Validating theme file...").start();
@@ -4366,21 +4402,21 @@ async function themeValidate() {
4366
4402
  const parsedTheme = parseThemeObject(absThemePath);
4367
4403
  const result = themeSchema.safeParse(parsedTheme);
4368
4404
  if (!result.success) {
4369
- const issues = result.error.issues.map((issue) => ` \u2192 ${chalk9.yellow(issue.path.join("."))}: ${issue.message}`).join("\n");
4405
+ const issues = result.error.issues.map((issue) => ` \u2192 ${chalk10.yellow(issue.path.join("."))}: ${issue.message}`).join("\n");
4370
4406
  spinner.fail("Theme validation failed");
4371
- console.log(chalk9.red("\n Missing or invalid fields:\n"));
4407
+ console.log(chalk10.red("\n Missing or invalid fields:\n"));
4372
4408
  console.log(issues);
4373
- console.log(chalk9.dim("\n Fix these fields in your theme file and run validate again.\n"));
4409
+ console.log(chalk10.dim("\n Fix these fields in your theme file and run validate again.\n"));
4374
4410
  process.exit(1);
4375
4411
  }
4376
4412
  spinner.succeed("Theme file is valid");
4377
- console.log(chalk9.dim(`
4413
+ console.log(chalk10.dim(`
4378
4414
  Validated: ${configResult.data.theme}
4379
4415
  `));
4380
4416
  } catch (error) {
4381
4417
  spinner.fail("Failed to validate theme");
4382
4418
  const message = error instanceof Error ? error.message : String(error);
4383
- console.error(chalk9.dim(` ${message}`));
4419
+ console.error(chalk10.dim(` ${message}`));
4384
4420
  process.exit(1);
4385
4421
  }
4386
4422
  }
@@ -4400,7 +4436,7 @@ program.name("pdfx").description("CLI for PDFx components").version(getVersion()
4400
4436
  program.configureOutput({
4401
4437
  writeErr: (str) => {
4402
4438
  const message = str.replace(/^error:\s*/i, "").trimEnd();
4403
- process.stderr.write(chalk10.red(`\u2716 ${message}
4439
+ process.stderr.write(chalk11.red(`\u2716 ${message}
4404
4440
  `));
4405
4441
  }
4406
4442
  });
@@ -4423,7 +4459,7 @@ try {
4423
4459
  await program.parseAsync();
4424
4460
  } catch (err) {
4425
4461
  const message = err instanceof Error ? err.message : String(err);
4426
- process.stderr.write(chalk10.red(`\u2716 ${message}
4462
+ process.stderr.write(chalk11.red(`\u2716 ${message}
4427
4463
  `));
4428
4464
  process.exitCode = 1;
4429
4465
  }
package/dist/mcp/index.js CHANGED
@@ -570,8 +570,21 @@ async function getAuditChecklist() {
570
570
  \`\`\`
571
571
 
572
572
  ### "Invalid hook call"
573
- PDFx components render to PDF, not to the DOM — React hooks are not supported inside them.
574
- Move hook calls to the parent component and pass data down as props.
573
+ \`@react-pdf/renderer\` renders synchronously. Hooks that depend on browser APIs or
574
+ async side effects do not work in the PDF render path.
575
+
576
+ **Do NOT use inside a PDFx component:**
577
+ - \`useEffect\` / \`useLayoutEffect\` — no browser lifecycle in PDF rendering
578
+ - \`useRef\` for DOM nodes — no DOM exists in the PDF render tree
579
+ - Any hook that calls browser globals (\`window\`, \`document\`, \`navigator\`)
580
+
581
+ **These ARE valid inside PDFx components:**
582
+ - PDFx framework hooks: \`usePdfxTheme()\`, \`useSafeMemo()\`
583
+ - Custom hooks that are pure functions with no browser-API dependencies
584
+ - Data passed down as props from a parent client component
585
+
586
+ If you see this error in a wrapper component you wrote, move the browser hook
587
+ to the nearest client component and pass the result down as a prop.
575
588
 
576
589
  ### "Text strings must be rendered inside \`<Text>\` component"
577
590
  Wrap all string literals in \`<Text>\` from \`@react-pdf/renderer\`:
@@ -771,7 +784,7 @@ async function getComponent(args) {
771
784
  const primaryContent = item.files[0]?.content ?? "";
772
785
  const primaryPath = item.files[0]?.path ?? "";
773
786
  const exportNames = extractAllExportNames(primaryContent);
774
- const mainExport = extractExportName(primaryContent);
787
+ const mainExport = extractExportName(primaryContent, args.component);
775
788
  const exportSection = exportNames.length > 0 ? dedent4`
776
789
  ## Exports
777
790
  **Main component export:** \`${mainExport ?? exportNames[0]}\`
@@ -816,11 +829,18 @@ async function getComponent(args) {
816
829
  ${fileSources}
817
830
  `);
818
831
  }
819
- function extractExportName(source) {
832
+ function extractExportName(source, componentName) {
820
833
  if (!source) return null;
821
- const matches = [...source.matchAll(/export\s+(?:function|const)\s+([A-Z][A-Za-z0-9]*)/g)];
822
- if (matches.length === 0) return null;
823
- return matches[0][1] ?? null;
834
+ const matches = [...source.matchAll(/export\s+function\s+([A-Z][A-Za-z0-9]*)/g)];
835
+ const names = matches.map((m) => m[1]).filter(Boolean);
836
+ if (names.length === 0) return null;
837
+ if (!componentName) return names[0];
838
+ const norm = componentName.replace(/-/g, "").toLowerCase();
839
+ const exact = names.find((n) => n.toLowerCase() === norm);
840
+ if (exact) return exact;
841
+ const suffix = names.find((n) => n.toLowerCase().endsWith(norm));
842
+ if (suffix) return suffix;
843
+ return names[0];
824
844
  }
825
845
  function extractAllExportNames(source) {
826
846
  const seen = /* @__PURE__ */ new Set();
@@ -1029,7 +1049,7 @@ async function getInstallation(args) {
1029
1049
  | TypeScript errors on \`@react-pdf/renderer\` | \`${installCmd(pm, "@react-pdf/types", true)}\` |
1030
1050
  | "Cannot find module @/components/pdfx/..." | Run \`npx pdfx-cli@latest add <component>\` to install it |
1031
1051
  | PDF renders blank | Ensure root returns \`<Document><Page>...</Page></Document>\` |
1032
- | "Invalid hook call" | PDFx components cannot use React hooks — pass data as props |
1052
+ | "Invalid hook call" | Browser-only hooks (\`useEffect\`, \`useRef\` for DOM) don't work in the PDF render path move them to a parent client component and pass the result as a prop. PDFx framework hooks (\`usePdfxTheme\`, \`useSafeMemo\`) are valid inside PDF components. |
1033
1053
 
1034
1054
  ---
1035
1055
  Next: call \`get_audit_checklist\` to verify your setup is correct.
@@ -1153,14 +1173,13 @@ async function getTheme(args) {
1153
1173
 
1154
1174
  ## Usage in Components
1155
1175
  \`\`\`tsx
1156
- // Access theme values in a PDFx component
1157
- import type { PdfxTheme } from '@pdfx/shared';
1158
-
1159
- interface Props {
1160
- theme: PdfxTheme;
1161
- }
1176
+ // Access theme values in a PDFx component via the usePdfxTheme hook.
1177
+ // @pdfx/shared is workspace-only do NOT import from it in your components.
1178
+ import { View, Text } from '@react-pdf/renderer';
1179
+ import { usePdfxTheme } from '../lib/pdfx-theme-context';
1162
1180
 
1163
- export function MyComponent({ theme }: Props) {
1181
+ export function MyComponent() {
1182
+ const theme = usePdfxTheme();
1164
1183
  return (
1165
1184
  <View style={{ backgroundColor: theme.colors.background }}>
1166
1185
  <Text style={{ color: theme.colors.foreground, fontSize: theme.typography.body.fontSize }}>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pdfx-cli",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "CLI for PDFx components",
5
5
  "type": "module",
6
6
  "bin": {