@grampro/kit 2.7.1 → 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.
- package/.gbs/skills/gbs-components/SKILL.md +4 -2
- package/.gbs/skills/gbs-components/references/install.md +28 -2
- package/CHANGELOG.md +63 -0
- package/README.md +2 -1
- package/index.cjs +148 -21
- package/package.json +1 -1
- package/source/components/accordion/passport.json +1 -1
- package/source/components/alert/passport.json +1 -1
- package/source/components/avatar/passport.json +1 -1
- package/source/components/badge/passport.json +1 -1
- package/source/components/breadcrumb/passport.json +1 -1
- package/source/components/button/passport.json +1 -1
- package/source/components/card/passport.json +1 -1
- package/source/components/checkbox/passport.json +1 -1
- package/source/components/combobox/passport.json +1 -1
- package/source/components/data-grid/passport.json +1 -1
- package/source/components/date-picker/passport.json +1 -1
- package/source/components/dialog/passport.json +1 -1
- package/source/components/file-uploader/passport.json +1 -1
- package/source/components/input/passport.json +1 -1
- package/source/components/menu/passport.json +1 -1
- package/source/components/modal/passport.json +1 -1
- package/source/components/number-input/passport.json +1 -1
- package/source/components/popover/passport.json +1 -1
- package/source/components/progress/passport.json +1 -1
- package/source/components/radio-group/passport.json +1 -1
- package/source/components/skeleton/passport.json +1 -1
- package/source/components/spinner/passport.json +1 -1
- package/source/components/switch/passport.json +1 -1
- package/source/components/tabs/passport.json +1 -1
- package/source/components/textarea/passport.json +1 -1
- package/source/components/toaster/passport.json +1 -1
- package/source/components/tooltip/passport.json +1 -1
- package/tools/doctor/baseline.cjs +227 -0
- package/tools/doctor/contract.cjs +252 -0
- package/tools/doctor/format.cjs +160 -0
- package/tools/doctor/index.cjs +246 -0
- package/tools/doctor/usage.cjs +95 -0
- package/tools/jsx-tags.cjs +120 -0
- package/tools/mcp/server.cjs +47 -40
- package/tools/passport/extract.cjs +47 -20
- 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.
|
|
35
|
-
|
|
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
|
|
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
|
|
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,68 @@
|
|
|
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
|
+
|
|
3
66
|
## 2.7.1
|
|
4
67
|
|
|
5
68
|
### Security
|
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. |
|
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
|
-
|
|
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
|
|
938
|
-
|
|
939
|
-
|
|
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
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
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
|
|
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