ply-ui-cli 1.1.140 → 1.1.141

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 CHANGED
@@ -26,7 +26,7 @@ The old npm name `base-ui-cli` is **deprecated** (name collision with [MUI Base
26
26
  - **progress-button** (free) — button with a stable-width loading spinner: the label fades out, the spinner takes its place, and the button keeps its exact size and color while the async action runs.
27
27
  - **shell** (Pro) — unified page/dashboard application shell with optional footer, push drawers, and mini rail.
28
28
  - **AI agents** — copy-in install is cheaper in tokens than generating a widget. Walkthrough (simulation, not a lab benchmark): [rich text editor](https://ply-ui.com/learn/ai-agent-tokens-rich-text-editor/).
29
- - **UI design skill** — `npx ply-ui-cli init` writes `.cursor/skills/ply-ui-design/` when that folder is missing. Walkthrough: [Why Ply installs a UI design skill when you run init](https://ply-ui.com/learn/ply-ui-design-skill/).
29
+ - **UI design skill** — `npx ply-ui-cli init` writes `.cursor/skills/ply-ui-design/` and `.agents/skills/ply-ui-design/` when those folders are missing. `npx ply-ui-cli init --refresh-editor-kit` replaces a kit that is already there. Walkthrough: [Why Ply installs a UI design skill when you run init](https://ply-ui.com/learn/ply-ui-design-skill/).
30
30
 
31
31
  Full history: [ply-ui.com/changelog](https://ply-ui.com/changelog)
32
32
 
@@ -272,10 +272,10 @@ Run npx ply-ui-cli doctor. It checks ply-ui.json, the Angular workspace, @angula
272
272
  Open Theme Studio at https://ply-ui.com/theme/ or the docs customizer, pick a preset or hue, then copy the Tailwind v4 @theme block into src/tailwind.css. ply-ui.css points --ply-primary and Tailwind blue-* utilities at that palette (--ply-brand and --ply-scale-*). The live preview shows buttons, tabs, forms, and overlays together.
273
273
 
274
274
  **Can AI agents install Ply?**
275
- Yes. npx -y ply-ui-mcp runs over stdio in Cursor, VS Code, Claude Code, Claude Desktop, Codex, Antigravity, Grok Build, Kimi Code, Qwen Code, Windsurf, and other local hosts. ChatGPT and Gemini chat apps use the Streamable HTTP connector at https://mcp.ply-ui.com/mcp (catalog tools) or a local npx -y ply-ui-mcp --http without --read-only so add/init/update can see the project. Pro add uses PLY_LICENSE_KEY on the MCP process. Setup for each host: https://ply-ui.com/getting-started/#ai-agents-mcp. Editors that skip MCP can copy the Cursor rule and snippet pack from https://ply-ui.com/getting-started/#editor-rules.
275
+ Yes. npx -y ply-ui-mcp runs over stdio in Cursor, VS Code, Claude Code, Claude Desktop, Codex, Antigravity, Grok Build, Kimi Code, Qwen Code, Windsurf, and other local hosts. ChatGPT and Gemini chat apps use the Streamable HTTP connector at https://mcp.ply-ui.com/mcp (catalog tools) or a local npx -y ply-ui-mcp --http without --read-only so add/init/update can see the project. doctor, add_components, and update_components return structuredContent: checks, missing packages with the install command, and one result per file. Hosts that can ask confirm an overwrite before replace. init_project accepts refreshEditorKit. When PLY_LICENSE_KEY is already set, leave it in the environment and out of the tool call. Setup for each host: https://ply-ui.com/getting-started/#ai-agents-mcp. Editors that skip MCP can copy the Cursor rule and snippet pack from https://ply-ui.com/getting-started/#editor-rules.
276
276
 
277
277
  **What does init install for AI agents?**
278
- npx ply-ui-cli init writes ply-ui.json, ply-ui.css, icon sprites, .cursor/rules/ply-ui.mdc, .vscode/ply-ui.code-snippets, and the UI design skill at .cursor/skills/ply-ui-design/ (SKILL.md, reference.md, examples.md) when those files are missing. The skill tells the agent to reuse ply-button, ply-card, and ply-input-group, keep one accent, and include loading, error, empty, and data states. It does not install components. Published copy: https://ply-ui.com/docs/ai/skills/ply-ui-design/SKILL.md. Walkthrough: https://ply-ui.com/learn/ply-ui-design-skill/.
278
+ npx ply-ui-cli init writes ply-ui.json, ply-ui.css, icon sprites, .cursor/rules/ply-ui.mdc, .vscode/ply-ui.code-snippets, and the UI design skill at .cursor/skills/ply-ui-design/ and .agents/skills/ply-ui-design/ (SKILL.md, reference.md, examples.md) when those files are missing. npx ply-ui-cli init --refresh-editor-kit replaces a kit that is already there. The skill tells the agent to reuse ply-button, ply-card, and ply-input-group, keep one accent, and include loading, error, empty, and data states. It does not install components. Published copy: https://ply-ui.com/docs/ai/skills/ply-ui-design/SKILL.md. Walkthrough: https://ply-ui.com/learn/ply-ui-design-skill/.
279
279
 
280
280
  **Do AI agents use fewer tokens with Ply?**
281
281
  Usually, yes. With Ply the agent copies a finished component (CLI or MCP) and wires it, instead of generating a toolbar, selection model, sanitizer, and SSR guards from a blank file. Token use still depends on the prompt and the model. A reconstructed Claude/Grok session for the rich text editor is at https://ply-ui.com/learn/ai-agent-tokens-rich-text-editor/ — that is a teaching simulation, not a lab benchmark.
@@ -32,7 +32,8 @@ var init_exports = {};
32
32
  __export(init_exports, {
33
33
  discoverProjectLayout: () => discoverProjectLayout,
34
34
  init: () => init,
35
- insertComponentSource: () => insertComponentSource
35
+ insertComponentSource: () => insertComponentSource,
36
+ writeEditorKit: () => writeEditorKit
36
37
  });
37
38
  module.exports = __toCommonJS(init_exports);
38
39
  var import_fs3 = __toESM(require("fs"));
@@ -55,6 +56,7 @@ var LEGACY_LOCKFILE_NAME = "base-ui-lock.json";
55
56
  var CURSOR_RULE_REL = ".cursor/rules/ply-ui.mdc";
56
57
  var SNIPPETS_REL = ".vscode/ply-ui.code-snippets";
57
58
  var DESIGN_SKILL_DIR = ".cursor/skills/ply-ui-design";
59
+ var AGENTS_SKILL_DIR = ".agents/skills/ply-ui-design";
58
60
  var DOCS_CURSOR_RULE = "docs/ai/ply-ui.mdc";
59
61
  var DOCS_SNIPPETS = "docs/ai/ply-ui.code-snippets";
60
62
  var DOCS_DESIGN_SKILL = "docs/ai/skills/ply-ui-design";
@@ -281,8 +283,8 @@ async function downloadSprite(name, targetDir) {
281
283
  return false;
282
284
  }
283
285
  }
284
- function writeEditorFile(dest, content, label, docsPath = DOCS_CURSOR_RULE) {
285
- if (import_fs3.default.existsSync(dest)) return;
286
+ function writeEditorFile(dest, content, label, docsPath = DOCS_CURSOR_RULE, refresh = false) {
287
+ if (import_fs3.default.existsSync(dest) && !refresh) return;
286
288
  try {
287
289
  import_fs3.default.mkdirSync(import_path2.default.dirname(dest), { recursive: true });
288
290
  import_fs3.default.writeFileSync(dest, content, "utf8");
@@ -293,6 +295,34 @@ function writeEditorFile(dest, content, label, docsPath = DOCS_CURSOR_RULE) {
293
295
  );
294
296
  }
295
297
  }
298
+ function writeEditorKit(cwd, options = {}) {
299
+ const refresh = options.refresh === true;
300
+ writeEditorFile(
301
+ import_path2.default.resolve(cwd, CURSOR_RULE_REL),
302
+ CURSOR_RULE,
303
+ CURSOR_RULE_REL,
304
+ DOCS_CURSOR_RULE,
305
+ refresh
306
+ );
307
+ writeEditorFile(
308
+ import_path2.default.resolve(cwd, SNIPPETS_REL),
309
+ CODE_SNIPPETS,
310
+ SNIPPETS_REL,
311
+ DOCS_SNIPPETS,
312
+ refresh
313
+ );
314
+ for (const dir of [DESIGN_SKILL_DIR, AGENTS_SKILL_DIR]) {
315
+ for (const [name, content] of Object.entries(DESIGN_SKILL_FILES)) {
316
+ writeEditorFile(
317
+ import_path2.default.resolve(cwd, dir, name),
318
+ content,
319
+ `${dir}/${name}`,
320
+ `${DOCS_DESIGN_SKILL}/${name}`,
321
+ refresh
322
+ );
323
+ }
324
+ }
325
+ }
296
326
  var INIT_DEFAULTS = {
297
327
  componentsAlias: "src/app/components",
298
328
  styles: "src/styles.scss",
@@ -519,32 +549,14 @@ ${stylesContent}`, "utf8");
519
549
  for (const sprite of ICON_SPRITES) {
520
550
  await downloadSprite(sprite, assetsDir);
521
551
  }
522
- spinner.text = "Writing editor rules, snippets, and the UI design skill...";
523
- writeEditorFile(
524
- import_path2.default.resolve(process.cwd(), CURSOR_RULE_REL),
525
- CURSOR_RULE,
526
- CURSOR_RULE_REL,
527
- DOCS_CURSOR_RULE
528
- );
529
- writeEditorFile(
530
- import_path2.default.resolve(process.cwd(), SNIPPETS_REL),
531
- CODE_SNIPPETS,
532
- SNIPPETS_REL,
533
- DOCS_SNIPPETS
534
- );
535
- for (const [name, content] of Object.entries(DESIGN_SKILL_FILES)) {
536
- writeEditorFile(
537
- import_path2.default.resolve(process.cwd(), DESIGN_SKILL_DIR, name),
538
- content,
539
- `${DESIGN_SKILL_DIR}/${name}`,
540
- `${DOCS_DESIGN_SKILL}/${name}`
541
- );
542
- }
552
+ spinner.text = options.refreshEditorKit ? "Refreshing editor rules, snippets, and the UI design skill..." : "Writing editor rules, snippets, and the UI design skill...";
553
+ writeEditorKit(process.cwd(), { refresh: options.refreshEditorKit });
543
554
  spinner.succeed(`Project initialized! Add your first component with "${CLI_NPX} add button".`);
544
555
  }
545
556
  // Annotate the CommonJS export names for ESM import in node:
546
557
  0 && (module.exports = {
547
558
  discoverProjectLayout,
548
559
  init,
549
- insertComponentSource
560
+ insertComponentSource,
561
+ writeEditorKit
550
562
  });
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ function printHelp() {
34
34
  console.log(`Usage: ${CLI_BIN} <command> [options]
35
35
 
36
36
  Commands:
37
- init Initialize your project (ply-ui.json, styles, icons, Cursor rules, snippets)
37
+ init Initialize your project (ply-ui.json, styles, icons, editor kit)
38
38
  add [components...] Add components; omit names to pick from the catalog
39
39
  list List all available components (free and pro)
40
40
  diff [components...] Show upstream changes for installed components since you added them
@@ -48,6 +48,7 @@ Options:
48
48
  -f, --force On conflict, take the upstream version (update)
49
49
  --overwrite-local On update, also revert files you edited that upstream did not change
50
50
  --merge On conflict, 3-way merge using the installed snapshot as ancestor
51
+ --refresh-editor-kit Replace the Cursor rule, snippets, and design skill (.cursor/skills and .agents/skills)
51
52
  -V, --version Print version
52
53
  -h, --help Show help
53
54
 
@@ -62,6 +63,7 @@ function parseArgs(argv) {
62
63
  force: false,
63
64
  overwriteLocal: false,
64
65
  merge: false,
66
+ refreshEditorKit: false,
65
67
  help: false,
66
68
  version: false
67
69
  };
@@ -86,6 +88,9 @@ function parseArgs(argv) {
86
88
  case "--merge":
87
89
  flags.merge = true;
88
90
  break;
91
+ case "--refresh-editor-kit":
92
+ flags.refreshEditorKit = true;
93
+ break;
89
94
  case "-h":
90
95
  case "--help":
91
96
  flags.help = true;
@@ -108,7 +113,7 @@ function parseArgs(argv) {
108
113
  async function main() {
109
114
  const { command, positionals, flags } = parseArgs(process.argv.slice(2));
110
115
  if (flags.version && !command) {
111
- console.log("1.1.140");
116
+ console.log("1.1.141");
112
117
  return;
113
118
  }
114
119
  if (flags.help || !command) {
@@ -118,7 +123,7 @@ async function main() {
118
123
  switch (command) {
119
124
  case "init": {
120
125
  const { init } = await import("./commands/init.js");
121
- await init({ yes: flags.yes });
126
+ await init({ yes: flags.yes, refreshEditorKit: flags.refreshEditorKit });
122
127
  return;
123
128
  }
124
129
  case "add": {
@@ -158,7 +163,7 @@ async function main() {
158
163
  }
159
164
  case "--version":
160
165
  case "-V":
161
- console.log("1.1.140");
166
+ console.log("1.1.141");
162
167
  return;
163
168
  default:
164
169
  console.error(`Unknown command: ${command}`);
package/dist/vendor.js CHANGED
@@ -13148,6 +13148,7 @@ __export(util_exports, {
13148
13148
  required: () => required,
13149
13149
  safeExtend: () => safeExtend,
13150
13150
  shallowClone: () => shallowClone,
13151
+ sizeCaps: () => sizeCaps,
13151
13152
  slugify: () => slugify,
13152
13153
  stringifyPrimitive: () => stringifyPrimitive,
13153
13154
  toZod: () => toZod,
@@ -13667,6 +13668,18 @@ function prefixIssues(path, issues) {
13667
13668
  function unwrapMessage(message) {
13668
13669
  return typeof message === "string" ? message : message?.message;
13669
13670
  }
13671
+ function sizeCaps(defChecks) {
13672
+ const caps = [];
13673
+ for (const ch of defChecks ?? []) {
13674
+ const d = ch._zod.def;
13675
+ if (!/_(length|size)$|_equals$/.test(d.check))
13676
+ break;
13677
+ const max = d.maximum ?? d.length ?? d.size;
13678
+ if (max >= 0)
13679
+ caps.push([max, ch]);
13680
+ }
13681
+ return caps;
13682
+ }
13670
13683
  function attachSchema(issues, start, inst) {
13671
13684
  var _a3;
13672
13685
  for (let i = start; i < issues.length; i++) {
@@ -15087,7 +15100,7 @@ ${content.join("\n")}
15087
15100
  var version = {
15088
15101
  major: 4,
15089
15102
  minor: 6,
15090
- patch: 5
15103
+ patch: 7
15091
15104
  };
15092
15105
 
15093
15106
  // node_modules/zod/v4/core/schemas.js
@@ -15843,6 +15856,31 @@ var $ZodDate = /* @__PURE__ */ $constructor("$ZodDate", (inst, def) => {
15843
15856
  return payload;
15844
15857
  };
15845
15858
  });
15859
+ function capSize(inst, size) {
15860
+ const caps = sizeCaps(inst._zod.def.checks);
15861
+ if (!caps.length)
15862
+ return;
15863
+ const cap = Math.min(...caps.map((c) => c[0]));
15864
+ const parse3 = inst._zod.parse;
15865
+ inst._zod.parse = (payload, ctx) => {
15866
+ const n = size(payload.value);
15867
+ if (n > cap && !ctx?.skipChecks && ctx?.direction !== "backward") {
15868
+ for (const [max, ch] of sizeCaps(inst._zod.def.checks)) {
15869
+ if (n > max && ch._zod.def.when(payload)) {
15870
+ const start = payload.issues.length;
15871
+ ch._zod.check(payload);
15872
+ payload.issues[start].continue = false;
15873
+ attachSchema(payload.issues, start, inst);
15874
+ return payload;
15875
+ }
15876
+ }
15877
+ }
15878
+ return parse3(payload, ctx);
15879
+ };
15880
+ }
15881
+ var arraySize = (input2) => Array.isArray(input2) ? input2.length : -1;
15882
+ var mapSize = (input2) => input2 instanceof Map ? input2.size : -1;
15883
+ var setSize = (input2) => input2 instanceof Set ? input2.size : -1;
15846
15884
  function handleArrayResult(result, final, index) {
15847
15885
  if (result.issues.length) {
15848
15886
  final.issues.push(...prefixIssues(index, result.issues));
@@ -15886,6 +15924,7 @@ var $ZodArray = /* @__PURE__ */ $constructor("$ZodArray", (inst, def) => {
15886
15924
  }
15887
15925
  return payload;
15888
15926
  };
15927
+ capSize(inst, arraySize);
15889
15928
  });
15890
15929
  function handlePropertyResult(result, final, key, input2, optin, optout) {
15891
15930
  const isPresent = key in input2;
@@ -16842,6 +16881,7 @@ var $ZodMap = /* @__PURE__ */ $constructor("$ZodMap", (inst, def) => {
16842
16881
  return Promise.all(proms).then(() => payload);
16843
16882
  return payload;
16844
16883
  };
16884
+ capSize(inst, mapSize);
16845
16885
  });
16846
16886
  function handleMapResult(keyResult, valueResult, final, key, input2, inst, ctx) {
16847
16887
  if (keyResult.issues.length) {
@@ -16908,6 +16948,7 @@ var $ZodSet = /* @__PURE__ */ $constructor("$ZodSet", (inst, def) => {
16908
16948
  return Promise.all(proms).then(() => payload);
16909
16949
  return payload;
16910
16950
  };
16951
+ capSize(inst, setSize);
16911
16952
  });
16912
16953
  function handleSetResult(result, final) {
16913
16954
  if (result.issues.length) {
@@ -26438,9 +26479,15 @@ function generateNullableCheck(doc, ctx, schema, accessor, buildsValue = true) {
26438
26479
  doc.write(`}`);
26439
26480
  return outputVar;
26440
26481
  }
26482
+ function generateSizeGate(doc, schema, size) {
26483
+ for (const [max, check2] of sizeCaps(schema._zod.def.checks)) {
26484
+ doc.write(`if (${size} > ${numericOperand(max, check2._zod.def.check)}) return INVALID;`);
26485
+ }
26486
+ }
26441
26487
  function generateArrayCheck(doc, ctx, schema, accessor, buildsValue = true) {
26442
26488
  const def = schema._zod.def;
26443
26489
  doc.write(`if (!Array.isArray(${accessor})) return INVALID;`);
26490
+ generateSizeGate(doc, schema, `${accessor}.length`);
26444
26491
  const outputVar = buildsValue ? newVar(ctx) : null;
26445
26492
  const iVar = newVar(ctx);
26446
26493
  const elemVar = newVar(ctx);
@@ -26840,6 +26887,7 @@ function literalPropertyKey(ctx, key) {
26840
26887
  function generateMapCheck(doc, ctx, schema, accessor) {
26841
26888
  const def = schema._zod.def;
26842
26889
  doc.write(`if (!(${accessor} instanceof Map)) return INVALID;`);
26890
+ generateSizeGate(doc, schema, `${accessor}.size`);
26843
26891
  const outputVar = newVar(ctx);
26844
26892
  const kVar = newVar(ctx);
26845
26893
  const valVar = newVar(ctx);
@@ -26856,6 +26904,7 @@ function generateMapCheck(doc, ctx, schema, accessor) {
26856
26904
  function generateSetCheck(doc, ctx, schema, accessor) {
26857
26905
  const def = schema._zod.def;
26858
26906
  doc.write(`if (!(${accessor} instanceof Set)) return INVALID;`);
26907
+ generateSizeGate(doc, schema, `${accessor}.size`);
26859
26908
  const outputVar = newVar(ctx);
26860
26909
  const valVar = newVar(ctx);
26861
26910
  doc.write(`const ${outputVar} = new Set();`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ply-ui-cli",
3
- "version": "1.1.140",
3
+ "version": "1.1.141",
4
4
  "description": "Ply CLI — add Angular + Tailwind CSS components to your project (shadcn-style, code is copied in and owned by you). Not MUI Base UI.",
5
5
  "main": "dist/index.js",
6
6
  "publishConfig": {