layro-cli 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +13 -11
  2. package/dist/layro.mjs +82 -9
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # layro-cli
2
2
 
3
- Publish React components to [Layro](https://layro.dev) from your terminal.
3
+ Find and publish React components on [Layro](https://layro.dev) from your terminal.
4
4
 
5
5
  ```bash
6
+ npx layro-cli search pricing toggle
6
7
  npx layro-cli login
7
8
  npx layro-cli publish components/pricing-card.tsx
8
9
  ```
@@ -11,23 +12,24 @@ Requires Node 20 or newer. No dependencies.
11
12
 
12
13
  ## Commands
13
14
 
14
- | Command | Does |
15
- | -------------------------- | -------------------------------------------------------------------------------------------------------- |
16
- | `layro login` | Sign in with GitHub in your browser (no API keys). `--no-browser` prints the link. |
17
- | `layro whoami` | Who you're signed in as, and submissions left today. |
18
- | `layro publish <files…>` | Send a new component, or a new version of one you already have (same name). The first file is the entry. |
19
- | `layro status [component]` | Where your submissions stand, with reviewers' reasons. `component` is a slug or `author/slug`. |
20
- | `layro add <author/slug>` | Prints the shadcn command that installs a component. |
21
- | `layro logout` | Revokes the session and forgets it on this computer. |
15
+ | Command | Does |
16
+ | -------------------------- | -------------------------------------------------------------------------------------------------------------- |
17
+ | `layro search <words…>` | Find components, best match first (typos are fine, no sign-in). `--category`, `--author`, `--limit`, `--json`. |
18
+ | `layro login` | Sign in with GitHub in your browser (no API keys). `--no-browser` prints the link. |
19
+ | `layro whoami` | Who you're signed in as, and submissions left today. |
20
+ | `layro publish <files…>` | Send a new component, or a new version of one you already have (same name). The first file is the entry. |
21
+ | `layro status [component]` | Where your submissions stand, with reviewers' reasons. `component` is a slug or `author/slug`. |
22
+ | `layro add <author/slug>` | Prints the shadcn command that installs a component. |
23
+ | `layro logout` | Revokes the session and forgets it on this computer. |
22
24
 
23
25
  ### Publish options
24
26
 
25
- `--name`, `--description` (20 to 200 characters), `--category` (a slug from layro.dev/components), `--license` (MIT, Apache-2.0, ISC or BSD-3-Clause; default MIT), `--version` (new versions: defaults to a patch bump), `--notes` (what changed), `--dark-only`, `--preview <plan.json>` (how to film the preview), `-y, --yes` (don't ask; for CI).
27
+ `--name`, `--description` (20 to 200 characters), `--category` (a slug from layro.dev/components), `--license` (MIT, Apache-2.0, ISC or BSD-3-Clause; default MIT), `--version` (new versions: defaults to a patch bump), `--notes` (what changed), `--dark-only`, `--preview <plan.json>` (how to film the preview), `--if-changed` (new versions: unchanged code or a version still in review exits 0 with "Nothing sent"; for CI), `-y, --yes` (don't ask; for CI).
26
28
 
27
29
  Everything goes through review on Layro before it's live. The CLI and coding agents (MCP) share one daily allowance of submissions per account.
28
30
 
29
31
  ## Where tokens live
30
32
 
31
- `~/.config/layro/credentials.json` (or `$XDG_CONFIG_HOME/layro`), readable only by you. Access tokens last an hour and renew themselves. `LAYRO_TOKEN` uses an access token instead; `LAYRO_URL` points at another Layro server.
33
+ `~/.config/layro/credentials.json` (or `$XDG_CONFIG_HOME/layro`), readable only by you. Access tokens last an hour and renew themselves. `LAYRO_TOKEN` uses a token instead: for CI, make a long-lived one at https://layro.dev/settings/tokens (publish only; a GitHub Actions example is at https://layro.dev/docs/cli); `LAYRO_URL` points at another Layro server.
32
34
 
33
35
  Docs: https://layro.dev/docs/cli
package/dist/layro.mjs CHANGED
@@ -340,7 +340,7 @@ async function logout(server2) {
340
340
  }
341
341
 
342
342
  // src/api.ts
343
- var VERSION = true ? "0.1.0" : "dev";
343
+ var VERSION = true ? "0.3.0" : "dev";
344
344
  var ApiFailure = class extends CliError {
345
345
  constructor(detail) {
346
346
  super(detail.error);
@@ -376,13 +376,16 @@ async function api(server2, path, init = {}) {
376
376
  if (response.ok && body) return body;
377
377
  if (response.status === 401) {
378
378
  if (process.env.LAYRO_TOKEN)
379
- throw new CliError("LAYRO_TOKEN was refused: it's invalid or expired.");
379
+ throw new CliError(
380
+ `LAYRO_TOKEN was refused: it's invalid, revoked or expired. Make a new one at ${server2}/settings/tokens.`
381
+ );
380
382
  throw notSignedIn();
381
383
  }
382
384
  throw new ApiFailure({
383
385
  status: response.status,
384
386
  error: body?.error ?? `${server2} answered ${response.status} for ${path}.`,
385
- errors: body?.errors
387
+ errors: body?.errors,
388
+ code: body?.code
386
389
  });
387
390
  }
388
391
 
@@ -461,14 +464,25 @@ async function publish(server2, paths, flags) {
461
464
  const notes = flags.notes ?? (interactive ? await ask("What changed", "") : "");
462
465
  if (interactive && !await confirm(`Send ${existing.id} ${version} for review?`))
463
466
  return say("Nothing sent.");
464
- result = await api(
467
+ const sent = await api(
465
468
  server2,
466
469
  `/components/${existing.id}/versions`,
467
470
  {
468
471
  method: "POST",
469
472
  body: { version, notes, files, preview }
470
473
  }
471
- ).catch(explain);
474
+ ).catch((error) => {
475
+ if (flags.ifChanged && error instanceof ApiFailure && (error.detail.code === "unchanged" || error.detail.code === "pending"))
476
+ return error;
477
+ return explain(error);
478
+ });
479
+ if (sent instanceof ApiFailure) {
480
+ say(
481
+ `Nothing sent: ${Object.values(sent.detail.errors ?? {})[0] ?? sent.message}`
482
+ );
483
+ return;
484
+ }
485
+ result = sent;
472
486
  } else {
473
487
  if (flags.version && flags.version !== "1.0.0")
474
488
  warn(
@@ -520,6 +534,36 @@ async function publish(server2, paths, flags) {
520
534
  say(dim(` Submissions left today: ${result.submissionsLeftToday}`));
521
535
  }
522
536
 
537
+ // src/search.ts
538
+ async function search(server2, words, flags) {
539
+ const q = words.join(" ").trim();
540
+ if (!q) throw new CliError("Give some words: layro search <words\u2026>");
541
+ const params = new URLSearchParams({ q });
542
+ if (flags.category) params.set("category", flags.category);
543
+ if (flags.author) params.set("author", flags.author);
544
+ if (flags.limit) params.set("limit", flags.limit);
545
+ const found = await api(server2, `/search?${params}`, { auth: false });
546
+ if (flags.json) return say(JSON.stringify(found, null, 2));
547
+ if (found.results.length === 0) {
548
+ say(`Nothing matches "${q}".`);
549
+ if (found.suggestion)
550
+ say(
551
+ `The closest category is ${bold(found.suggestion.name)}: ${found.suggestion.url}`
552
+ );
553
+ return;
554
+ }
555
+ for (const c of found.results) {
556
+ say(`${bold(c.name)} ${dim(c.id)}`);
557
+ say(` ${c.description}`);
558
+ say(dim(` ${c.install}`));
559
+ }
560
+ say(
561
+ dim(
562
+ `${found.results.length} ${found.results.length === 1 ? "result" : "results"}. Install one with the command under it.`
563
+ )
564
+ );
565
+ }
566
+
523
567
  // src/status.ts
524
568
  var LABEL = {
525
569
  live: "live",
@@ -574,9 +618,10 @@ async function status(server2, target) {
574
618
  }
575
619
 
576
620
  // src/index.ts
577
- var HELP = `${bold("layro")} ${dim(VERSION)}: publish React components to Layro
621
+ var HELP = `${bold("layro")} ${dim(VERSION)}: find and publish React components on Layro
578
622
 
579
623
  Usage
624
+ layro search <words\u2026> Find components (typos are fine; no sign-in needed)
580
625
  layro login Sign in with GitHub in your browser
581
626
  layro whoami Who you're signed in as
582
627
  layro publish <files\u2026> Send a new component, or a new version of yours
@@ -593,13 +638,21 @@ Publish options
593
638
  --notes <text> New versions: what changed
594
639
  --dark-only It only works on a dark page
595
640
  --preview <plan.json> How to film its preview (see the docs)
641
+ --if-changed Unchanged code, or a version still in review: say so, exit 0 (CI)
596
642
  -y, --yes Don't ask; send what's given (for CI)
597
643
 
644
+ Search options
645
+ --category <slug> Only this category
646
+ --author <handle> Only this author's components
647
+ --limit <n> 1 to 50 (default 20)
648
+ --json Print the API's JSON
649
+
598
650
  Login options
599
651
  --no-browser Print the sign-in link instead of opening it
600
652
 
601
653
  Environment
602
- LAYRO_TOKEN An access token to use instead of signing in
654
+ LAYRO_TOKEN A token to use instead of signing in: an access
655
+ token from https://layro.dev/settings/tokens (CI)
603
656
  LAYRO_URL Another Layro server (default https://layro.dev)
604
657
 
605
658
  Docs: https://layro.dev/docs/cli`;
@@ -661,14 +714,34 @@ async function main(argv) {
661
714
  notes: { type: "string" },
662
715
  preview: { type: "string" },
663
716
  "dark-only": { type: "boolean" },
717
+ "if-changed": { type: "boolean" },
664
718
  yes: { type: "boolean", short: "y" }
665
719
  }
666
720
  });
667
- const { "dark-only": darkOnly, ...flags } = values;
721
+ const {
722
+ "dark-only": darkOnly,
723
+ "if-changed": ifChanged,
724
+ ...flags
725
+ } = values;
668
726
  await publish(server, positionals, {
669
727
  ...flags,
670
- darkOnly
728
+ darkOnly,
729
+ ifChanged
730
+ });
731
+ return;
732
+ }
733
+ case "search": {
734
+ const { values, positionals } = parseArgs({
735
+ args: rest,
736
+ allowPositionals: true,
737
+ options: {
738
+ category: { type: "string" },
739
+ author: { type: "string" },
740
+ limit: { type: "string" },
741
+ json: { type: "boolean" }
742
+ }
671
743
  });
744
+ await search(server, positionals, values);
672
745
  return;
673
746
  }
674
747
  case "status": {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "layro-cli",
3
- "version": "0.1.0",
4
- "description": "Publish React components to Layro (layro.dev) from your terminal.",
3
+ "version": "0.3.0",
4
+ "description": "Find and publish React components on Layro (layro.dev) from your terminal.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {