@grampro/kit 2.7.0 → 2.7.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.
Files changed (43) hide show
  1. package/.gbs/skills/gbs-components/SKILL.md +4 -2
  2. package/.gbs/skills/gbs-components/references/install.md +28 -2
  3. package/CHANGELOG.md +87 -0
  4. package/README.md +9 -2
  5. package/index.cjs +148 -21
  6. package/package.json +2 -2
  7. package/source/components/accordion/passport.json +1 -1
  8. package/source/components/alert/passport.json +1 -1
  9. package/source/components/avatar/passport.json +1 -1
  10. package/source/components/badge/passport.json +1 -1
  11. package/source/components/breadcrumb/passport.json +1 -1
  12. package/source/components/button/passport.json +1 -1
  13. package/source/components/card/passport.json +1 -1
  14. package/source/components/checkbox/passport.json +1 -1
  15. package/source/components/combobox/passport.json +1 -1
  16. package/source/components/data-grid/passport.json +1 -1
  17. package/source/components/date-picker/passport.json +1 -1
  18. package/source/components/dialog/passport.json +1 -1
  19. package/source/components/file-uploader/passport.json +1 -1
  20. package/source/components/input/passport.json +1 -1
  21. package/source/components/menu/passport.json +1 -1
  22. package/source/components/modal/passport.json +1 -1
  23. package/source/components/number-input/passport.json +1 -1
  24. package/source/components/popover/passport.json +1 -1
  25. package/source/components/progress/passport.json +1 -1
  26. package/source/components/radio-group/passport.json +1 -1
  27. package/source/components/skeleton/passport.json +1 -1
  28. package/source/components/spinner/passport.json +1 -1
  29. package/source/components/switch/passport.json +1 -1
  30. package/source/components/tabs/passport.json +1 -1
  31. package/source/components/textarea/passport.json +1 -1
  32. package/source/components/toaster/passport.json +1 -1
  33. package/source/components/tooltip/passport.json +1 -1
  34. package/tools/doctor/baseline.cjs +227 -0
  35. package/tools/doctor/contract.cjs +252 -0
  36. package/tools/doctor/format.cjs +160 -0
  37. package/tools/doctor/index.cjs +246 -0
  38. package/tools/doctor/usage.cjs +95 -0
  39. package/tools/jsx-tags.cjs +120 -0
  40. package/tools/mcp/README.md +23 -0
  41. package/tools/mcp/server.cjs +63 -40
  42. package/tools/passport/extract.cjs +47 -20
  43. package/tools/passport/hash.cjs +39 -0
@@ -31,8 +31,10 @@ npx @grampro/kit add Button,Input,Modal
31
31
  ```
32
32
 
33
33
  Writes `component-lib/<folder>/` plus `component-lib/shared/`. No flag is
34
- needed: the 2.x components are the default. Import the barrel in your
35
- component:
34
+ needed: the 2.x components are the default. A component the project has
35
+ edited is never overwritten — if `add` says it kept one, tell the user and
36
+ suggest `npx @grampro/kit doctor <Component>`; **never retry with `--force`**,
37
+ which discards their work. Import the barrel in your component:
36
38
 
37
39
  ```ts
38
40
  import { Button } from "component-lib/button";
@@ -20,7 +20,8 @@ npx @grampro/kit add Button # one component
20
20
  npx @grampro/kit add Button,Input,Modal # several
21
21
  npx @grampro/kit -i # interactive picker
22
22
  npx @grampro/kit -l # list available
23
- npx @grampro/kit add Button --force # overwrite edited shared/ files
23
+ npx @grampro/kit doctor # what changed locally and upstream
24
+ npx @grampro/kit doctor Button --json # one component, for reading programmatically
24
25
  npx @grampro/kit skill # install this skill
25
26
  npx @grampro/kit skill --for claude # only the Claude Code copy
26
27
  ```
@@ -33,7 +34,32 @@ npx @grampro/kit skill --for claude # only the Claude Code copy
33
34
  | `--legacy` | — | Install from the 1.x set instead. Unmaintained; see below. |
34
35
  | `--skill` | — | Install this skill at the project root. `-skill` and `-a skill` also work. |
35
36
  | `--for` | — | With `--skill`: which agents to write adapters for (`claude`, `codex`, `antigravity`, or `none`). Default: all. |
36
- | `--force` | — | Replace files you have edited locally in `shared/` or `.gbs/`. |
37
+ | `--force` | — | Replace a component, `shared/` or `.gbs/` even though the project has edited it. Never pass it on the user's behalf. |
38
+ | `--json` | — | With `doctor`: print the report as JSON. |
39
+
40
+ ## Components the project has changed
41
+
42
+ Copied components are meant to be edited, so `add` treats an edited one as
43
+ the project's code, not the kit's:
44
+
45
+ - **Untouched** — `add` updates it in place.
46
+ - **Edited** since it was installed — `add` leaves it alone, prints which
47
+ files changed, and exits 1. The component is still installed and usable.
48
+
49
+ When that happens, **stop and tell the user.** Do not rerun with `--force`:
50
+ it discards their changes, and replacing their code is their decision, not
51
+ yours. Show them `npx @grampro/kit doctor <Component>`, which reports:
52
+
53
+ - which files they changed;
54
+ - what they changed in the component's contract (props, values, handlers);
55
+ - what the new version changes, and where the two conflict or merge;
56
+ - the exact call sites in their code an update would break.
57
+
58
+ Run `doctor` yourself before proposing an update. `doctor --json` gives the
59
+ same report in a form you can read; it never writes anything.
60
+
61
+ The MCP server's `install_component` follows the same rule and returns the
62
+ components it kept under `kept`, with the reason.
37
63
 
38
64
  **Pass no flag.** The 2.x components are what `add` installs. `--beta` used
39
65
  to opt in to them and now does nothing but print a notice, so do not pass it.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,92 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.7.2
4
+
5
+ ### Fixed
6
+
7
+ **`add` overwrote components you had edited, and said it had succeeded.**
8
+ Components are copied into your project so that you can change them, but
9
+ running `add` on one that was already installed replaced the whole folder
10
+ unconditionally. It printed "installed successfully" and the edits were gone.
11
+ The same happened through the MCP server: `install_component` runs `add`, so an
12
+ agent asked to "install Button" in a project with a customised Button
13
+ discarded the customisation.
14
+
15
+ Now every install records what it wrote — a hash of each file and a copy of
16
+ the component's contract, in `.install-manifest.json` beside the component —
17
+ and `add` checks that record first. An untouched component is updated. An
18
+ edited one is left in place: `add` lists the files that changed, prints how to
19
+ see more, and exits 1, while the other components in the same command still
20
+ install. Only `--force` replaces an edited component. `shared/` has worked this
21
+ way since it was introduced; components never did.
22
+
23
+ Components installed before this release have no record. An untouched one is
24
+ recognised from its passport's `sourceHash` — recomputed over your copy with
25
+ the labels the kit hashed it with, which a test holds to the shipped value for
26
+ all 27 — and is updated normally. One that differs from this version cannot be
27
+ told apart from an older untouched copy, so it is kept, not guessed at.
28
+
29
+ **`install_component` told an agent nothing when it was refused.** The
30
+ installer explains a refusal on stderr, and the tool only returned stdout — so
31
+ the agent got "Install failed" followed by stylesheet instructions. It now
32
+ returns what was installed, what was kept and why, and advice not to force it.
33
+
34
+ **`passport` crashed in any project on TypeScript 7.** TypeScript 7 is a
35
+ native rewrite whose npm package does not expose the compiler API passports
36
+ are generated with, and it is what `npm i -D typescript` installs today. The
37
+ command failed with `Cannot read properties of undefined (reading
38
+ 'ReactJSX')`. It now checks for the API rather than for the package, falls
39
+ back to a TypeScript 6 installed under an alias, and otherwise says exactly
40
+ that, with the one-line fix.
41
+
42
+ **`validate_usage` invented props from inside expressions.** Attribute names
43
+ were matched anywhere in a tag, so `title={ok ? first : second}` reported
44
+ `first` and `second` as unknown props. The tag scanner now reads names only at
45
+ the top level, outside braces and quotes. It is shared with `doctor`, and stays
46
+ linear on hostile input.
47
+
48
+ ### Added
49
+
50
+ **`npx @grampro/kit doctor`** — what you changed in your copies, what the new
51
+ version changes, and which call sites in your code an update would break.
52
+
53
+ It compares three versions of each component: the one you installed, your copy
54
+ now, and the one the CLI carries. It compares them as contracts — props,
55
+ accepted values, handlers, exports, agent operations — because a text diff of
56
+ `Button.tsx` shows which lines moved, not that `variant` stopped accepting
57
+ `"ghost"`. Edits from both sides to the same value set are merged rather than
58
+ called a conflict; a conflict is an incompatible pair, such as you removing a
59
+ prop that upstream changed. Then it scans your source and names the call sites
60
+ that pass something the replacement would not accept.
61
+
62
+ It never writes. `--json` gives the same report to agents and CI. The agent
63
+ skill now tells agents to run it, and never to retry `add` with `--force` on
64
+ someone else's edits. A new docs page, *Updating components*, covers all of it.
65
+
66
+ ## 2.7.1
67
+
68
+ ### Security
69
+
70
+ **get_guidance could read any .md file on disk.** It appended `.md` to a
71
+ caller-supplied `topic` and joined it onto the references folder with no
72
+ traversal guard, so `topic = "../../../../CHANGELOG"` returned the repo
73
+ CHANGELOG and the same trick reached any `.md` file the server process could
74
+ open. Found by a VAPT pass driving the tool with traversal payloads. The
75
+ server runs with your own authority, so it is a confused-deputy hole rather
76
+ than privilege escalation — but a tool that advertises nine fixed topics
77
+ should not become a file-exfiltration primitive for a prompt-injected agent.
78
+ It now refuses any `topic` that is not one of the shipped guidance names, with
79
+ a resolved-prefix check behind the allowlist. A regression test drives the
80
+ same payloads.
81
+
82
+ **Affected: 2.4.4 through 2.7.0** — every release that has shipped the MCP
83
+ server. Each was checked by running the same payload against its published
84
+ tarball. 2.4.3 and earlier have no MCP server and are not affected. A project
85
+ that installed the package locally needs `npm i -D @grampro/kit@latest`. One
86
+ configured with `npx -y @grampro/kit mcp` may keep running a cached copy; check
87
+ with `npx -y @grampro/kit --version`, and if it is not 2.7.1, run
88
+ `npx -y @grampro/kit@2.7.1 mcp` once to replace it.
89
+
3
90
  ## 2.7.0
4
91
 
5
92
  ### Added
package/README.md CHANGED
@@ -84,7 +84,8 @@ genuinely headless, and the component READMEs document it under "Headless use".
84
84
 
85
85
  | | |
86
86
  | --- | --- |
87
- | `add <Component>...` | Copy components into the project. `--dest` chooses where; `--legacy` reaches the 1.x set. |
87
+ | `add <Component>...` | Copy components into the project, or update ones already there. A component you have edited is left alone unless you pass `--force`. `--dest` chooses where; `--legacy` reaches the 1.x set. |
88
+ | `doctor [Component...]` | What you changed in your copies, what the new version changes, and which of your call sites an update would break. `--json` for agents and CI. |
88
89
  | `list` | What is available to add. |
89
90
  | `skill` | Install the agent skill. `--for claude,codex,antigravity` narrows it. |
90
91
  | `mcp` | Run the MCP server. Agents start this themselves; you rarely type it. |
@@ -135,7 +136,13 @@ anything first.
135
136
  No dependencies, and it starts in about the time an empty Node process
136
137
  takes. Full walkthrough, including a prompt to paste:
137
138
  [kit.gramproindia.com/mcp-server](https://kit.gramproindia.com/mcp-server).
138
- Details in [`tools/mcp/README.md`](tools/mcp/README.md).
139
+ Details in [`tools/mcp/README.md`](tools/mcp/README.md), including what a tool
140
+ call can and cannot reach.
141
+
142
+ **Use 2.7.1 or later.** From 2.4.4 through 2.7.0, a crafted `get_guidance`
143
+ call could read any `.md` file the server could open. The
144
+ [changelog](CHANGELOG.md#271) explains how to check which version your agent
145
+ is running.
139
146
 
140
147
  ## Figma plugin
141
148
 
package/index.cjs CHANGED
@@ -833,6 +833,45 @@ const parseSkillTargets = (value) => {
833
833
  return wanted;
834
834
  };
835
835
 
836
+ /* ---------------------------------------------------------------- doctor */
837
+
838
+ /**
839
+ * Report on the components installed here: what was changed since install,
840
+ * what this version of the kit changes, and which call sites an update would
841
+ * break. Read-only. See tools/doctor/index.cjs.
842
+ */
843
+ const runDoctorCommand = ({ only = [], dest: requestedDest, json = false }) => {
844
+ const dest = resolveDestPath(requestedDest);
845
+ if (!fs.existsSync(dest)) {
846
+ console.error(
847
+ `No ${path.basename(dest)}/ folder here, so there is nothing to check.` +
848
+ `${NL} Install a component first: npx ${CLI_NAME} add Button`,
849
+ );
850
+ process.exitCode = 1;
851
+ return;
852
+ }
853
+
854
+ const { runDoctor } = require("./tools/doctor/index.cjs");
855
+ const { formatReport, toJson } = require("./tools/doctor/format.cjs");
856
+ const report = runDoctor({
857
+ cwd: process.cwd(),
858
+ dest,
859
+ kitRoot: SOURCE_PATH,
860
+ kitVersion: PACKAGE_VERSION,
861
+ only,
862
+ });
863
+
864
+ if (report.rows.length === 0 && report.notInstalled.length === 0) {
865
+ console.log(`No ${CLI_NAME} components found in ${path.relative(process.cwd(), dest) || dest}/.`);
866
+ return;
867
+ }
868
+
869
+ const shown = { ...report, dest: `${(path.relative(process.cwd(), dest) || ".").split(path.sep).join("/")}/` };
870
+ console.log(json ? JSON.stringify(toJson(shown), null, 2) : formatReport(shown));
871
+ // A component someone asked about that is not here is the one failure worth an exit code.
872
+ if (report.notInstalled.length) process.exitCode = 1;
873
+ };
874
+
836
875
  /* ------------------------------------------------------------- passports */
837
876
 
838
877
  /**
@@ -901,7 +940,7 @@ const runPassport = async ({ check: checkOnly, dest: requestedDest }) => {
901
940
  );
902
941
  if (errors.length > 0) process.exit(1);
903
942
  } catch (error) {
904
- if (error.code === "ETYPESCRIPT_MISSING" || error.code === "EBADJSON") {
943
+ if (error.code === "ETYPESCRIPT_MISSING" || error.code === "ETYPESCRIPT_API" || error.code === "EBADJSON") {
905
944
  console.error(error.message);
906
945
  process.exit(1);
907
946
  }
@@ -932,13 +971,27 @@ const checkComponentExists = (component, destPath, current = false) => {
932
971
  return fs.existsSync(componentPath);
933
972
  };
934
973
 
935
- const copyComponent = async (component, destPath, current = false) => {
974
+ /*
975
+ * Copy one component into the project — unless that would destroy edits.
976
+ *
977
+ * Components are copied in to be edited; that is what owning the code means.
978
+ * Until 2.7.2 this overwrote the folder unconditionally, so running `add` on a
979
+ * component that was already there — by hand, from a script, or by an agent
980
+ * through the MCP server's install_component — silently replaced whatever had
981
+ * been changed in it, and printed a success message while doing so.
982
+ *
983
+ * Now an installed component is compared with the record written when it was
984
+ * installed. Untouched, it is updated. Changed — or installed before there
985
+ * were records, and different from this version — it is left alone unless
986
+ * `--force` is passed, and the user is told what differs and how to see more.
987
+ *
988
+ * Returns "installed", "updated", "unchanged" or "kept".
989
+ */
990
+ const copyComponent = async (component, destPath, current = false, { force = false } = {}) => {
936
991
  try {
937
- const componentSrc = path.join(
938
- getSourcePath(current),
939
- folderFor(component, current),
940
- );
941
- const componentDest = path.join(destPath, folderFor(component, current));
992
+ const folder = folderFor(component, current);
993
+ const componentSrc = path.join(getSourcePath(current), folder);
994
+ const componentDest = path.join(destPath, folder);
942
995
 
943
996
  if (!fs.existsSync(componentSrc)) {
944
997
  throw new Error(
@@ -946,6 +999,25 @@ const copyComponent = async (component, destPath, current = false) => {
946
999
  );
947
1000
  }
948
1001
 
1002
+ let outcome = "installed";
1003
+ let previous = null;
1004
+ // The 1.x set is unmaintained and keeps its old behaviour.
1005
+ if (current && fs.existsSync(componentDest)) {
1006
+ const status = doctorBaseline().inspect(componentDest, {
1007
+ folder,
1008
+ kitDir: componentSrc,
1009
+ kitVersion: PACKAGE_VERSION,
1010
+ });
1011
+ previous = status.installedVersion;
1012
+
1013
+ if (status.destructive && !force) {
1014
+ reportKept(component, status);
1015
+ process.exitCode = 1;
1016
+ return "kept";
1017
+ }
1018
+ outcome = status.state === "pristine" && status.installedVersion === PACKAGE_VERSION ? "unchanged" : "updated";
1019
+ }
1020
+
949
1021
  // The whole folder travels, which includes passport.json and
950
1022
  // passport.manual.json. The passport is part of the component contract,
951
1023
  // not an add-on — a contract you opt into is not a contract.
@@ -953,23 +1025,65 @@ const copyComponent = async (component, destPath, current = false) => {
953
1025
  overwrite: true,
954
1026
  filter: (source) => !isTestPath(path.relative(componentSrc, source)),
955
1027
  });
956
- console.log(
957
- `✓ Component ${component} installed successfully ${
958
- component === "Grid"
959
- ? "This Version of Grid will be deprecated soon. Please Install The New Data Grid Component"
960
- : ""
961
- }`,
962
- );
1028
+
1029
+ // The record `doctor` and the next `add` compare against.
1030
+ if (current) {
1031
+ doctorBaseline().writeManifest(componentDest, {
1032
+ component,
1033
+ version: PACKAGE_VERSION,
1034
+ sourceDir: componentSrc,
1035
+ });
1036
+ }
1037
+
1038
+ if (outcome === "updated") {
1039
+ console.log(
1040
+ previous && previous !== PACKAGE_VERSION
1041
+ ? `✓ Component ${component} updated ${previous} → ${PACKAGE_VERSION}`
1042
+ : `✓ Component ${component} replaced with ${PACKAGE_VERSION}${force ? " (--force)" : ""}`,
1043
+ );
1044
+ } else if (outcome === "unchanged") {
1045
+ console.log(`✓ Component ${component} already up to date (${PACKAGE_VERSION})`);
1046
+ } else {
1047
+ console.log(
1048
+ `✓ Component ${component} installed successfully ${
1049
+ component === "Grid"
1050
+ ? "This Version of Grid will be deprecated soon. Please Install The New Data Grid Component"
1051
+ : ""
1052
+ }`,
1053
+ );
1054
+ }
1055
+ return outcome;
963
1056
  } catch (error) {
964
1057
  console.error(`Error installing component ${component}:`, error.message);
965
1058
  process.exit(1);
966
1059
  }
967
1060
  };
968
1061
 
1062
+ /* Loaded on first use, so commands that never touch components don't pay for it. */
1063
+ let baselineModule = null;
1064
+ const doctorBaseline = () => (baselineModule ??= require("./tools/doctor/baseline.cjs"));
1065
+
1066
+ /** Why a component was left alone, and the two ways forward. */
1067
+ const reportKept = (component, status) => {
1068
+ const list = (label, files) =>
1069
+ files.length ? ` ${label}: ${files.slice(0, 6).join(", ")}${files.length > 6 ? `, +${files.length - 6} more` : ""}` : null;
1070
+ console.error(
1071
+ status.state === "unknown"
1072
+ ? `✗ ${component} was not replaced: it was installed before 2.7.2 and differs from ${PACKAGE_VERSION}, so it may hold your changes.`
1073
+ : `✗ ${component} was not replaced: it has changes made since it was installed.`,
1074
+ );
1075
+ for (const line of [list("changed", status.changed), list("deleted", status.missing)].filter(Boolean)) {
1076
+ console.error(line);
1077
+ }
1078
+ console.error(` See what you changed and what ${PACKAGE_VERSION} changes: npx ${CLI_NAME} doctor ${component}`);
1079
+ console.error(` Replace it anyway, losing those changes: npx ${CLI_NAME} add ${component} --force`);
1080
+ };
1081
+
969
1082
  const installComponentWithDependencies = async (
970
1083
  component,
971
1084
  destPath,
972
1085
  current = false,
1086
+ options = {},
973
1087
  ) => {
974
1088
  // Get dependencies for the component
975
1089
  const dependencies = CONFIG.dependencies[component] || [];
@@ -991,7 +1105,7 @@ const installComponentWithDependencies = async (
991
1105
 
992
1106
  // Install all pending components
993
1107
  for (const comp of pendingInstalls) {
994
- await copyComponent(comp, destPath, current);
1108
+ await copyComponent(comp, destPath, current, options);
995
1109
  }
996
1110
 
997
1111
  if (dependencies.length > 0) {
@@ -1010,6 +1124,7 @@ const installMultipleComponents = async (
1010
1124
  components,
1011
1125
  destPath,
1012
1126
  current = false,
1127
+ options = {},
1013
1128
  ) => {
1014
1129
  const allComponentsToInstall = new Set();
1015
1130
 
@@ -1038,7 +1153,7 @@ const installMultipleComponents = async (
1038
1153
 
1039
1154
  // Install all pending components
1040
1155
  for (const comp of pendingInstalls) {
1041
- await copyComponent(comp, destPath, current);
1156
+ await copyComponent(comp, destPath, current, options);
1042
1157
  }
1043
1158
 
1044
1159
  // Show dependency information
@@ -1190,7 +1305,7 @@ const validateComponents = (components, current = false) => {
1190
1305
  * projects a week install this; breaking their scripts to tidy an argument
1191
1306
  * parser is not a trade worth making.
1192
1307
  */
1193
- const COMMANDS = new Set(["add", "list", "skill", "passport", "mcp", "secret", "design"]);
1308
+ const COMMANDS = new Set(["add", "list", "skill", "passport", "mcp", "secret", "design", "doctor"]);
1194
1309
 
1195
1310
  /**
1196
1311
  * Flags that take a separate value, so the token after one is that value and
@@ -1281,7 +1396,12 @@ const main = async () => {
1281
1396
  type: "string",
1282
1397
  })
1283
1398
  .option("force", {
1284
- describe: "Replace files you have edited locally in shared/ or the skill",
1399
+ describe: "Replace files you have edited locally: a component, shared/ or the skill",
1400
+ type: "boolean",
1401
+ default: false,
1402
+ })
1403
+ .option("json", {
1404
+ describe: "With doctor: print the report as JSON, for agents and CI",
1285
1405
  type: "boolean",
1286
1406
  default: false,
1287
1407
  })
@@ -1291,6 +1411,8 @@ const main = async () => {
1291
1411
  .example("$0 skill", "Install the agent skill for every supported agent")
1292
1412
  .example("$0 skill --for claude", "Only the Claude Code copy")
1293
1413
  .example("$0 passport --check", "Fail if a passport is stale (CI)")
1414
+ .example("$0 doctor", "What you changed, what upstream changed, what an update breaks")
1415
+ .example("$0 doctor DataGrid --json", "One component, as JSON")
1294
1416
  .example("$0 add Select --legacy", "Install from the 1.x set")
1295
1417
  .example("$0 add Button --dest app/ui", "Choose where they go")
1296
1418
  .example("$0 mcp", "Serve the catalogue to a coding agent over MCP")
@@ -1364,6 +1486,11 @@ const main = async () => {
1364
1486
  return;
1365
1487
  }
1366
1488
 
1489
+ if (parsed && parsed.command === "doctor") {
1490
+ runDoctorCommand({ only: parsed.operands, dest: argv.dest, json: argv.json });
1491
+ return;
1492
+ }
1493
+
1367
1494
  if (parsed) {
1368
1495
  if (parsed.command === "list") argv.list = true;
1369
1496
  if (parsed.command === "skill") argv.skill = true;
@@ -1419,7 +1546,7 @@ const main = async () => {
1419
1546
  await copySupportFiles(destPath, current, { force: argv.force });
1420
1547
 
1421
1548
  // Install selected components
1422
- await installMultipleComponents(selectedComponents, destPath, current);
1549
+ await installMultipleComponents(selectedComponents, destPath, current, { force: argv.force });
1423
1550
  return;
1424
1551
  }
1425
1552
 
@@ -1470,10 +1597,10 @@ const main = async () => {
1470
1597
  // Install components
1471
1598
  if (components.length === 1) {
1472
1599
  // Single component installation (existing behavior)
1473
- await installComponentWithDependencies(components[0], destPath, current);
1600
+ await installComponentWithDependencies(components[0], destPath, current, { force: argv.force });
1474
1601
  } else {
1475
1602
  // Multiple components installation
1476
- await installMultipleComponents(components, destPath, current);
1603
+ await installMultipleComponents(components, destPath, current, { force: argv.force });
1477
1604
  }
1478
1605
  };
1479
1606
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@grampro/kit",
3
- "version": "2.7.0",
4
- "description": "GramproKit \u2014 an agent-native UI runtime for React. Copy-in components you own, each publishing a contract that software can read.",
3
+ "version": "2.7.2",
4
+ "description": "GramproKit — an agent-native UI runtime for React. Copy-in components you own, each publishing a contract that software can read.",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "index.cjs",
@@ -10,7 +10,7 @@
10
10
  ]
11
11
  },
12
12
  "source": {
13
- "libraryVersion": "2.7.0",
13
+ "libraryVersion": "2.7.2",
14
14
  "files": [
15
15
  "core/index.ts",
16
16
  "core/open.ts",
@@ -8,7 +8,7 @@
8
8
  ]
9
9
  },
10
10
  "source": {
11
- "libraryVersion": "2.7.0",
11
+ "libraryVersion": "2.7.2",
12
12
  "files": [
13
13
  "core/index.ts",
14
14
  "core/live.ts",
@@ -9,7 +9,7 @@
9
9
  ]
10
10
  },
11
11
  "source": {
12
- "libraryVersion": "2.7.0",
12
+ "libraryVersion": "2.7.2",
13
13
  "files": [
14
14
  "core/identity.ts",
15
15
  "core/index.ts",
@@ -9,7 +9,7 @@
9
9
  ]
10
10
  },
11
11
  "source": {
12
- "libraryVersion": "2.7.0",
12
+ "libraryVersion": "2.7.2",
13
13
  "files": [
14
14
  "core/count.ts",
15
15
  "core/index.ts",
@@ -8,7 +8,7 @@
8
8
  ]
9
9
  },
10
10
  "source": {
11
- "libraryVersion": "2.7.0",
11
+ "libraryVersion": "2.7.2",
12
12
  "files": [
13
13
  "core/index.ts",
14
14
  "core/trail.ts",
@@ -8,7 +8,7 @@
8
8
  ]
9
9
  },
10
10
  "source": {
11
- "libraryVersion": "2.7.0",
11
+ "libraryVersion": "2.7.2",
12
12
  "files": [
13
13
  "core/index.ts",
14
14
  "core/state.ts",
@@ -12,7 +12,7 @@
12
12
  ]
13
13
  },
14
14
  "source": {
15
- "libraryVersion": "2.7.0",
15
+ "libraryVersion": "2.7.2",
16
16
  "files": [
17
17
  "core/index.ts",
18
18
  "core/trend.ts",
@@ -10,7 +10,7 @@
10
10
  ]
11
11
  },
12
12
  "source": {
13
- "libraryVersion": "2.7.0",
13
+ "libraryVersion": "2.7.2",
14
14
  "files": [
15
15
  "core/agent.ts",
16
16
  "core/group.ts",
@@ -9,7 +9,7 @@
9
9
  ]
10
10
  },
11
11
  "source": {
12
- "libraryVersion": "2.7.0",
12
+ "libraryVersion": "2.7.2",
13
13
  "files": [
14
14
  "core/agent.ts",
15
15
  "core/filter.ts",
@@ -14,7 +14,7 @@
14
14
  ]
15
15
  },
16
16
  "source": {
17
- "libraryVersion": "2.7.0",
17
+ "libraryVersion": "2.7.2",
18
18
  "files": [
19
19
  "agent/coerce.ts",
20
20
  "agent/contract.ts",
@@ -10,7 +10,7 @@
10
10
  ]
11
11
  },
12
12
  "source": {
13
- "libraryVersion": "2.7.0",
13
+ "libraryVersion": "2.7.2",
14
14
  "files": [
15
15
  "core/agent.ts",
16
16
  "core/calendar.ts",
@@ -9,7 +9,7 @@
9
9
  ]
10
10
  },
11
11
  "source": {
12
- "libraryVersion": "2.7.0",
12
+ "libraryVersion": "2.7.2",
13
13
  "files": [
14
14
  "core/api.ts",
15
15
  "core/dialog.ts",
@@ -13,7 +13,7 @@
13
13
  ]
14
14
  },
15
15
  "source": {
16
- "libraryVersion": "2.7.0",
16
+ "libraryVersion": "2.7.2",
17
17
  "files": [
18
18
  "core/format.ts",
19
19
  "core/index.ts",
@@ -9,7 +9,7 @@
9
9
  ]
10
10
  },
11
11
  "source": {
12
- "libraryVersion": "2.7.0",
12
+ "libraryVersion": "2.7.2",
13
13
  "files": [
14
14
  "core/agent.ts",
15
15
  "core/count.ts",
@@ -16,7 +16,7 @@
16
16
  ]
17
17
  },
18
18
  "source": {
19
- "libraryVersion": "2.7.0",
19
+ "libraryVersion": "2.7.2",
20
20
  "files": [
21
21
  "core/index.ts",
22
22
  "core/navigation.ts",
@@ -8,7 +8,7 @@
8
8
  ]
9
9
  },
10
10
  "source": {
11
- "libraryVersion": "2.7.0",
11
+ "libraryVersion": "2.7.2",
12
12
  "files": [
13
13
  "core/dismiss.ts",
14
14
  "core/index.ts",
@@ -8,7 +8,7 @@
8
8
  ]
9
9
  },
10
10
  "source": {
11
- "libraryVersion": "2.7.0",
11
+ "libraryVersion": "2.7.2",
12
12
  "files": [
13
13
  "core/agent.ts",
14
14
  "core/format.ts",
@@ -8,7 +8,7 @@
8
8
  ]
9
9
  },
10
10
  "source": {
11
- "libraryVersion": "2.7.0",
11
+ "libraryVersion": "2.7.2",
12
12
  "files": [
13
13
  "core/index.ts",
14
14
  "core/types.ts",
@@ -9,7 +9,7 @@
9
9
  ]
10
10
  },
11
11
  "source": {
12
- "libraryVersion": "2.7.0",
12
+ "libraryVersion": "2.7.2",
13
13
  "files": [
14
14
  "core/index.ts",
15
15
  "core/progress.ts",