pi-checkup 0.0.0-stage → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +343 -2
- package/bin/checkup.js +233 -0
- package/package.json +47 -4
- package/scripts/lint.js +43 -0
- package/src/checks/binaries.js +355 -0
- package/src/checks/config.js +312 -0
- package/src/checks/index.js +165 -0
- package/src/checks/network.js +543 -0
- package/src/checks/packages.js +367 -0
- package/src/checks/registry.js +338 -0
- package/src/checks/runtime.js +360 -0
- package/src/context.js +79 -0
- package/src/index.js +109 -0
- package/src/render.js +159 -0
- package/src/repair.js +367 -0
- package/src/util.js +839 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 xinghe-labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,344 @@
|
|
|
1
|
-
#
|
|
1
|
+
# pi-checkup
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**`pi list` tells you what you installed. `pi-checkup` tells you what actually works.**
|
|
4
|
+
|
|
5
|
+
A health-check extension for [Pi](https://pi.dev). It inspects the *running* session — not just your `settings.json` — and reports the failures that produce no error message anywhere: tools silently shadowed by another package, binaries that resolve to a completely different program, packages that are installed but registered nothing, endpoints that stopped answering.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pi install npm:pi-checkup
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Start a new session afterwards (or run `/reload` in a running one): a package
|
|
14
|
+
installed mid-session is not loaded until Pi re-reads its package list.
|
|
15
|
+
|
|
16
|
+
Then, in a session:
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
/checkup run every check
|
|
20
|
+
/checkup verbose same, plus what to do about each finding
|
|
21
|
+
/checkup json machine-readable report
|
|
22
|
+
/checkup fix apply the available repairs (asks first)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## The problem
|
|
26
|
+
|
|
27
|
+
A Pi install accumulates packages. Each one registers tools, commands, hooks, and sometimes shell binaries. When one of them stops working, nothing tells you:
|
|
28
|
+
|
|
29
|
+
- Two packages claimed the same tool name, and the second one silently won. The first is now unreachable.
|
|
30
|
+
- A package is in `settings.json`, its directory exists, but its manifest points at a file that isn't there. It loaded nothing and logged nothing.
|
|
31
|
+
- `ast-grep` ships a binary named `sg`. So does the shadow-utils package. Typing `sg` starts the wrong one.
|
|
32
|
+
- A blocked `postinstall` left `node_modules/.bin/playwright` as a dangling symlink.
|
|
33
|
+
- Your provider endpoint moved. The only symptom is a request that hangs, then fails.
|
|
34
|
+
|
|
35
|
+
`pi list` prints paths. `pi --verbose` prints warnings you have to read line by line. Neither answers *"is my setup actually coherent right now?"*
|
|
36
|
+
|
|
37
|
+
## The standalone CLI
|
|
38
|
+
|
|
39
|
+
The in-session command is useless in the one case you need it most: Pi is installed but will not boot. The same checks also ship as a CLI that needs no session, no Pi, and no `node_modules` — which is also what you want in CI, or on a machine you are about to hand over.
|
|
40
|
+
|
|
41
|
+
One difference to know before you read the first CLI report: the registry section — collisions, silent packages, attribution — is read from the live process, so the CLI marks it skipped. Everything else runs without a session: packages, binaries, network, config, runtime. On a machine where Pi has never been started, most rows have nothing to inspect and say so rather than passing.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx pi-checkup # full report
|
|
45
|
+
npx pi-checkup --verbose # same report, plus the hint under every finding
|
|
46
|
+
npx pi-checkup --offline # skip every network probe
|
|
47
|
+
npx pi-checkup --json # for scripts
|
|
48
|
+
npx pi-checkup --fix # list the available repairs
|
|
49
|
+
npx pi-checkup --fix -y # apply them (nothing is written without -y)
|
|
50
|
+
npx pi-checkup --version # print the version
|
|
51
|
+
npx pi-checkup --help # every flag and exit code
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Requires Node 20 or newer (`engines` in `package.json`); no dependencies, no `node_modules`.
|
|
55
|
+
|
|
56
|
+
It exits `1` when any check fails, so it works as a pre-flight step in CI or a shell profile. After a repair it re-runs the checks and exits on the new result, so `npx pi-checkup --fix -y && start-pi` does what it looks like it does. `--json` cannot be combined with `--fix`: the JSON is a single snapshot, and a repair changes what the snapshot describes.
|
|
57
|
+
|
|
58
|
+
The registry section reports skipped — auditing loaded tools genuinely requires a live process — and the CLI says so instead of implying a clean bill of health.
|
|
59
|
+
|
|
60
|
+
Exit codes: `0` nothing failed, `1` at least one check failed, `2` bad usage or a crashed run. The `--json` payload is the same report the text renderer prints, minus the repair metadata.
|
|
61
|
+
|
|
62
|
+
| Variable | Effect |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `PI_CODING_AGENT_DIR` | Check a different Pi install than `~/.pi/agent`. The CLI needs no session, so this is how you check a container, a second profile, or a Pi that will not boot at all |
|
|
65
|
+
| `PI_CHECKUP_IGNORE` | Suppress rows you have decided to live with — see below |
|
|
66
|
+
| `PI_CHECKUP_PROBE_URL` | Test real proxy *forwarding* with `curl`, instead of only proving the port is open. Only meaningful when a proxy variable below is set — without one there is nothing to forward through |
|
|
67
|
+
| `https_proxy`, `HTTPS_PROXY`, `http_proxy`, `HTTP_PROXY`, `all_proxy`, `ALL_PROXY` | The first one set is used by the provider probe (through `curl`, because Node's `fetch` ignores these) and reported redacted under Runtime. The probe runs with `--noproxy ''`, so a `NO_PROXY` entry cannot route it around the proxy it is meant to test — and Pi's own `fetch` ignores `NO_PROXY` too |
|
|
68
|
+
|
|
69
|
+
```text
|
|
70
|
+
$ npx pi-checkup
|
|
71
|
+
Registry
|
|
72
|
+
· live registry no Pi session — registry checks need a running process
|
|
73
|
+
Packages
|
|
74
|
+
✓ package dirs 14 present
|
|
75
|
+
✓ manifests 14 readable
|
|
76
|
+
✓ entry files 13 manifest(s) resolve
|
|
77
|
+
Binaries
|
|
78
|
+
✓ dangling links 19 links all resolve
|
|
79
|
+
· shadowed names no package command resolved on PATH
|
|
80
|
+
! unreachable bins 6 package command(s) not on PATH: pi-web-access-mcp,
|
|
81
|
+
pi-subagents, pi-mcp-adapter, pi-lens, pi-lens-mcp,
|
|
82
|
+
pi-lens-analyze
|
|
83
|
+
Network
|
|
84
|
+
✓ providers 2 endpoint(s) reachable
|
|
85
|
+
✓ dns 1 provider host(s) resolve normally
|
|
86
|
+
Config
|
|
87
|
+
✓ settings.json valid JSON
|
|
88
|
+
✓ models.json valid JSON
|
|
89
|
+
· project settings none
|
|
90
|
+
✓ backups 2 file(s) backed up; edited since the newest backup:
|
|
91
|
+
settings.json
|
|
92
|
+
✓ duplicate packages none
|
|
93
|
+
· env references no $VAR reference in models.json credentials
|
|
94
|
+
Runtime
|
|
95
|
+
✓ node v22.23.3
|
|
96
|
+
✓ pi launcher /root/.pi/agent/bin/pi
|
|
97
|
+
✓ disk 6% used on /, 950.7 GB free
|
|
98
|
+
✓ proxy http://127.0.0.1:10808 accepting connections
|
|
99
|
+
|
|
100
|
+
19 checks: 14 ok, 1 warning, 0 failed, 4 skipped
|
|
101
|
+
Run `npx pi-checkup --verbose` to show what to do about each finding.
|
|
102
|
+
|
|
103
|
+
! 1 warning, 4 skipped · run /checkup inside Pi for registry checks
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Both captures below and above come from a real machine, taken with pi-checkup 0.1.0; free space, Node versions and which packages are installed are properties of that machine, not of the tool, and the row counts grow when checks are added.
|
|
107
|
+
|
|
108
|
+
## Real output
|
|
109
|
+
|
|
110
|
+
This is an actual run on a working install, copied out of the TUI at 80 columns — not a mock-up. The two `Network` rows describe the moment it ran; everything else is a property of the install. 20 of 24 checks pass, three are skipped because there is genuinely nothing to look at, and the one warning is the expected state on this machine — the package commands are installed but their directory is not on `PATH`, which the hint explains and `PI_CHECKUP_IGNORE=unreachable` silences.
|
|
111
|
+
|
|
112
|
+
```text
|
|
113
|
+
Registry
|
|
114
|
+
✓ tools 49 registered (39 extension, 10 builtin) across 12
|
|
115
|
+
sources
|
|
116
|
+
✓ commands 95 registered across 21 sources, 0 MCP server(s)
|
|
117
|
+
✓ attribution 49 tool(s) from 12 source(s): pi-lens (13), builtin (10),
|
|
118
|
+
pi-web-access (5), pi-subagents (5), ... 8 more
|
|
119
|
+
✓ tool collisions none
|
|
120
|
+
✓ command collisions none
|
|
121
|
+
✓ silent packages none
|
|
122
|
+
Packages
|
|
123
|
+
✓ package dirs 14 present
|
|
124
|
+
✓ manifests 14 readable
|
|
125
|
+
✓ entry files 13 manifest(s) resolve
|
|
126
|
+
Binaries
|
|
127
|
+
✓ dangling links 19 links all resolve
|
|
128
|
+
· shadowed names no package command resolved on PATH
|
|
129
|
+
! unreachable bins 6 package command(s) not on PATH: pi-web-access-mcp,
|
|
130
|
+
pi-subagents, pi-mcp-adapter, pi-lens, pi-lens-mcp,
|
|
131
|
+
pi-lens-analyze
|
|
132
|
+
Network
|
|
133
|
+
✓ providers 2 endpoint(s) reachable
|
|
134
|
+
✓ dns 1 provider host(s) resolve normally
|
|
135
|
+
Config
|
|
136
|
+
✓ settings.json valid JSON (values read from the running session)
|
|
137
|
+
✓ models.json valid JSON
|
|
138
|
+
· project settings none
|
|
139
|
+
✓ backups 2 file(s) backed up; edited since the newest backup:
|
|
140
|
+
settings.json
|
|
141
|
+
✓ duplicate packages none
|
|
142
|
+
· env references no $VAR reference in models.json credentials
|
|
143
|
+
Runtime
|
|
144
|
+
✓ node v22.23.3
|
|
145
|
+
✓ pi launcher /root/.pi/agent/bin/pi
|
|
146
|
+
✓ disk 6% used on /, 950.7 GB free
|
|
147
|
+
✓ proxy http://127.0.0.1:10808 accepting connections
|
|
148
|
+
|
|
149
|
+
24 checks: 20 ok, 1 warning, 0 failed, 3 skipped
|
|
150
|
+
Run `/checkup verbose` to show what to do about each finding.
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The `attribution` row is the part no other tool gives you: which package owns which tools, counted from the live registry rather than from your config. Both blocks above share the same 23-column detail column, because `duplicate packages` and `command collisions` are both 18 characters wide and the renderer sizes the column to the longest title in the report it is printing, up to a 22-character cap after which the detail moves to its own line.
|
|
154
|
+
|
|
155
|
+
## What it checks
|
|
156
|
+
|
|
157
|
+
| Group | Finding | Why it matters |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| **Registry** | Live registry | No Pi session — the registry checks need a running process, so the whole section collapses to this one skipped row |
|
|
160
|
+
| | Tool and command collisions | One extension silently stops being reachable; the later registration wins with no error |
|
|
161
|
+
| | Silent packages | Declared an extension but registered no tool or command — either by design or a load failure |
|
|
162
|
+
| | Loaded tools and commands | How many registered, split by extension and builtin, and how many distinct sources they came from |
|
|
163
|
+
| | Attribution | Which package owns each of your tools, by name |
|
|
164
|
+
| **Packages** | Package list | Whether `settings.json` could be read at all — a hand-edited file that does not parse is reported as unreadable, not as "no packages" |
|
|
165
|
+
| | Missing directories | In `settings.json` but gone from `node_modules`. A version pin (`npm:name@1.2.3`) is resolved to the directory it installs into, not taken as part of the name |
|
|
166
|
+
| | Version pins | `settings.json` pins an exact version and a different one is installed — the code that runs is not the code that was asked for. Ranges and tags are not compared, because the installer is free to satisfy them |
|
|
167
|
+
| | Corrupt manifests | A `package.json` that parses as nothing — reported as a failure, because the package cannot load |
|
|
168
|
+
| | Missing manifests | The package directory exists but holds no `package.json` — an interrupted install, or a leftover folder. Its own row, because telling someone to check ownership of a directory that has no manifest sends them to fix the wrong thing |
|
|
169
|
+
| | Unreadable manifests | A `package.json` this process could not read (permissions, or larger than the 4 MB limit). Reported as a warning: unproven, not broken |
|
|
170
|
+
| | Missing entry files | The manifest points at a build output that was never shipped |
|
|
171
|
+
| | Not a package | A `packages` entry that points at a file instead of a directory. Reported without a repair: there is nothing to delete |
|
|
172
|
+
| | Unverifiable sources | `git:` and `https:` packages, which Pi resolves internally and no static check can follow |
|
|
173
|
+
| | Malformed entries | A `packages` entry that is not a source string — `null`, a number, an object without `source` |
|
|
174
|
+
| | Unreadable directories | A package directory that exists but cannot be listed, which is a permission problem rather than a missing install |
|
|
175
|
+
| | Manifest shape | A `pi.extensions` entry that is not a string. The string entries are still checked; only the malformed ones are left out |
|
|
176
|
+
| **Binaries** | Bin directory | Whether `.bin` could be listed at all — a missing `.bin` is a `skip` that still says how many commands it should have held, an unreadable one is a warning |
|
|
177
|
+
| | Dangling links | A symlink whose target is gone — a blocked `postinstall`, or an optional native dependency skipped for this platform. Only links are counted, so a Windows `.bin` of plain shim files reports `skip` instead of a green tick over nothing |
|
|
178
|
+
| | Unreadable links | A link that exists but cannot be followed (permissions, a loop) — reported, not called broken |
|
|
179
|
+
| | Install leftovers | `.<name>-<random>` staging links in `.bin`: an install is running right now, or was interrupted. Its own finding, not a broken package |
|
|
180
|
+
| | Missing shims | The package declares a `bin` but npm never linked it into `.bin` — usually an install run with `--ignore-scripts` |
|
|
181
|
+
| | Shadowed names | A package's command name resolves to an unrelated program earlier on `PATH`. Skipped when no declared command resolved at all: there is nothing to compare, and a green tick there would sit next to a row saying the commands are missing |
|
|
182
|
+
| | Unreachable bins | A package ships a CLI you cannot invoke by name |
|
|
183
|
+
| **Network** | Endpoint reachability | Probes `<baseUrl>/models` for every provider. `401`/`403` counts as alive — this never validates your key and never spends tokens. An endpoint that answers with something other than life — a refused connection, a rejected certificate, an HTTP error — is a failure, and fails the run; only a probe that was cut off locally by its own timeout stays a warning, because the deadline is this process's, not the endpoint's |
|
|
184
|
+
| | Cleartext auth | A provider on `http://` to another machine with a credential configured — an `apiKey`, a password embedded in the `baseUrl`, a credential-named header value, or a query string, which cannot be inspected for secrets. The `apiKey` and `baseUrl` credentials are withheld from the probe; a query string is never sent, so that endpoint is reported unprobed. Loopback hosts are exempt |
|
|
185
|
+
| | baseUrl shape | A `baseUrl` that is not a string or an object. A string that is not a URL — including the `localhost:11434/v1` paste with no scheme — is reported as an unhealthy provider, and also named in this row, because the endpoint is what would have to be probed |
|
|
186
|
+
| | DNS | Provider hosts that do not resolve, or resolve only to loopback |
|
|
187
|
+
| **Config** | Invalid JSON | With a backup on disk, `/checkup fix` can restore it |
|
|
188
|
+
| | Duplicate package entries | The package loads twice and the second registration can shadow the first |
|
|
189
|
+
| | Unset env references | `$NAME` / `${NAME}` in the `models.json` fields Pi actually interpolates (`apiKey`, header values) with nothing in the environment |
|
|
190
|
+
| | Backups | Whether a broken config could be recovered at all |
|
|
191
|
+
| **Runtime** | Agent directory | Whether the directory being inspected exists and can be listed — without it most other rows skip, which would otherwise read as a clean install |
|
|
192
|
+
| | Node version, launcher, disk, proxy | The host conditions Pi itself needs |
|
|
193
|
+
|
|
194
|
+
The groups above are the ones that appear in a report, in that order. Independent of them: a check that throws, or runs past 30 seconds, is reported as a failure naming the module, and every other section still prints.
|
|
195
|
+
|
|
196
|
+
## `/checkup fix`
|
|
197
|
+
|
|
198
|
+
Only reversible repairs are automated, and inside a session it normally offers only the first:
|
|
199
|
+
|
|
200
|
+
1. **Restore a config file** that is no longer valid JSON, from its newest sibling backup. The broken copy is kept.
|
|
201
|
+
|
|
202
|
+
2. **Collapse duplicate `packages` entries** in `settings.json`. Only byte-identical repeats are dropped: `"npm:a"` next to `{"source": "npm:a", "enabled": false}` carries different intent and is left alone.
|
|
203
|
+
3. **Prune `packages` entries whose directory is gone** — the ones a reinstall did not bring back. Sources resolved by Pi itself (`git:`, `https:`) are never touched, and a prune that would leave the list empty is refused and never even offered: that pattern means the agent directory is wrong, not that every package vanished.
|
|
204
|
+
|
|
205
|
+
Repairs 2 and 3 rewrite `settings.json`, so they need a settings file checkup actually read. Inside a session Pi hands checkup the settings from the *running process*, not the file on disk, and checkup will not rewrite a file it never read — so `/checkup fix` in-session offers repair 1, plus the other two only when Pi returned no settings and checkup read the file itself. If `getSettings()` threw, the fallback is still unverified and neither rewrite is offered. Stop Pi and run the CLI to be sure:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
npx pi-checkup --fix --yes
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Every write is preceded by a timestamped backup, and neither path writes on its own: `/checkup fix` asks in the TUI, and the CLI refuses to write unless you pass `--yes`. Anything that would delete a package, disable an extension, or edit your shell environment is reported with a suggestion and left alone — a diagnostic tool should not be able to break your setup in the process of describing it.
|
|
212
|
+
|
|
213
|
+
A repair that rewrites `settings.json` re-serialises it with two-space indentation, so expect a larger diff than the change itself. Nothing is repaired unless a check found something wrong, so a healthy install writes nothing at all.
|
|
214
|
+
|
|
215
|
+
## Ignoring a finding you have decided to live with
|
|
216
|
+
|
|
217
|
+
Some warnings are correct and permanent: a launcher you deliberately keep off `PATH`, an event-only package that registers no tool. A health check you have learned to ignore is worse than no health check, so rows can be suppressed by name.
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
PI_CHECKUP_IGNORE=unreachable,Config:env npx pi-checkup
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Each entry matches a row's `id`, its `Group:id` form, or its title. A suppressed row is downgraded to `skip`, keeps its original detail, and says what it replaced:
|
|
224
|
+
|
|
225
|
+
```text
|
|
226
|
+
· unreachable bins ignored via PI_CHECKUP_IGNORE (would be warn): 6 package
|
|
227
|
+
command(s) not on PATH: pi-web-access-mcp, pi-subagents,
|
|
228
|
+
pi-mcp-adapter, pi-lens, pi-lens-mcp, pi-lens-analyze
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Suppression is never silent, and it drops the row's repair offer so a hidden finding cannot be fixed behind your back. It applies to failures too: a suppressed `fail` no longer affects the exit code, which is the point of the variable — use it for a state you have decided to accept, not to turn a red build green. Export it from your shell profile if you want it to apply inside Pi too.
|
|
232
|
+
|
|
233
|
+
## JSON output
|
|
234
|
+
|
|
235
|
+
`/checkup json` prints the report as one object, delivered as a TUI notification — good for pasting into a bug report. For a pipe, use `npx pi-checkup --json`, which writes the same object to stdout, stamped with the version and platform that produced it. The excerpt below is from `/checkup json` inside a session — 24 entries, because the registry section has something to read there; in the CLI that section collapses to a single skipped row. Two of the entries, and the summary, are shown here:
|
|
236
|
+
|
|
237
|
+
```json
|
|
238
|
+
{
|
|
239
|
+
"summary": {
|
|
240
|
+
"total": 24,
|
|
241
|
+
"ok": 20,
|
|
242
|
+
"warn": 1,
|
|
243
|
+
"fail": 0,
|
|
244
|
+
"skip": 3
|
|
245
|
+
},
|
|
246
|
+
"version": "0.1.0",
|
|
247
|
+
"node": "22.23.3",
|
|
248
|
+
"platform": "linux",
|
|
249
|
+
"checks": [
|
|
250
|
+
{
|
|
251
|
+
"group": "Binaries",
|
|
252
|
+
"id": "unreachable",
|
|
253
|
+
"title": "unreachable bins",
|
|
254
|
+
"status": "warn",
|
|
255
|
+
"detail": "6 package command(s) not on PATH: pi-web-access-mcp, pi-subagents, pi-mcp-adapter, pi-lens, pi-lens-mcp, pi-lens-analyze",
|
|
256
|
+
"hint": "Pi installs package commands into /root/.pi/agent/npm/node_modules/.bin without adding it to PATH. Extensions that call them by full path still work; to use them by name, add that directory to PATH or run them with npx. If this is the state you want, silence the row with PI_CHECKUP_IGNORE=unreachable."
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
"group": "Config",
|
|
260
|
+
"id": "env",
|
|
261
|
+
"title": "env references",
|
|
262
|
+
"status": "skip",
|
|
263
|
+
"detail": "no $VAR reference in models.json credentials"
|
|
264
|
+
}
|
|
265
|
+
]
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
The `checks` array always has one entry per row of the text report, in the same order. The excerpt above omits the other 22 entries; real output is printed by `JSON.stringify(payload, null, 2)`.
|
|
270
|
+
|
|
271
|
+
Exit-worthy statuses are `fail`; `warn` means something needs attention without by itself failing the run — a package command that is not on `PATH`, or an `apiKey` that no HTTP request can carry. Repair metadata is stripped from the JSON payload.
|
|
272
|
+
|
|
273
|
+
## Design principles
|
|
274
|
+
|
|
275
|
+
- **No traffic to anyone but your own provider.** The only outbound requests are `<baseUrl>/models` for providers already in `models.json`, plus a DNS lookup of those same hosts. The proxy check is a plain TCP connect to the proxy's own address — set `PI_CHECKUP_PROBE_URL` if you want a real end-to-end forwarding test.
|
|
276
|
+
- **No secrets printed, no keys validated.** A provider probe sends the `apiKey` — or the `baseUrl` credentials, moved into an `Authorization` header — that your config defines, and ignores the response body. Custom `headers` are not replayed. A key on a plain-`http://` endpoint that is not on your own machine is never sent, and neither is a query string there — `?key=…` gateways are a real shape, and a query cannot be inspected for secrets. A proxy URL is reduced to scheme, host and port, and a value that is nothing but a credential prints `(configured)`; a `git:` source carrying a token is scrubbed the same way: corporate proxies and private repos both keep credentials in the URL, and both are the kind of string that ends up in a CI log. A `baseUrl` with credentials in it is moved into a header before the probe, so no error message can quote it back; over plain `http://` to another machine it is dropped instead, and the row says so. A header value that cannot be sent at all — a newline, a character above Latin-1 — is never sent through either path and is never quoted back: the row says the request could not be built and points at `models.json`.
|
|
277
|
+
- **Non-blocking.** Every check runs concurrently, and every outbound call has its own timeout — 6s for a curl probe, 8s for a fetch probe, 4s for DNS, 3s for a TCP connect — under a 30s per-module backstop. A hung endpoint costs one timeout, or two when a configured proxy fails and the direct path is tried as well, and never the whole command.
|
|
278
|
+
- **A crashing check is a finding, not a crash.** If a check module throws, the report says which one, marks it failed, and still shows everything else.
|
|
279
|
+
- **A check with nothing to inspect says so.** `skip` means nothing was inspected — an absent optional config file, an empty `.bin`, a provider list with no host to resolve, or a row you suppressed with `PI_CHECKUP_IGNORE` (whose detail says what it replaced). Zero packages present reports `skip`, never a green tick over nothing, and the verdict counts them: `✓ 3 ok, 2 skipped` rather than a bare "all clear".
|
|
280
|
+
- **Cross-platform.** Node built-ins only. `PATH` is resolved in-process against `PATHEXT` on Windows and the executable bit on POSIX, so a minimal container without `which` still works; `/dev/null` vs `NUL` for curl output, symlinked package stores, and path separators are handled. A check that finds nothing to inspect is marked skipped rather than failed — but a check module that throws or runs past its 30s backstop is a failure, because something was supposed to be inspected and was not.
|
|
281
|
+
- **Zero dependencies.** Nothing to audit, nothing to break on a Pi upgrade.
|
|
282
|
+
|
|
283
|
+
## Known limitations
|
|
284
|
+
|
|
285
|
+
Being explicit about what this cannot see:
|
|
286
|
+
|
|
287
|
+
- **Extension load errors are not exposed to extensions.** Pi's own `LoadExtensionsResult.errors` is not reachable from the extension API, so "which extension threw during load" is inferred statically (declared but unregistered), not read directly.
|
|
288
|
+
- **`git:` and URL package sources are not inspected.** Pi resolves them internally; static probing cannot follow them, so they are reported as `unverifiable` rather than guessed at.
|
|
289
|
+
- **Only the personal agent directory is audited.** A project's `.pi/settings.json` is checked for parseability, but the packages it declares are not resolved: where a project installs them is Pi's decision, and guessing the path produced "missing" claims that were not true.
|
|
290
|
+
- **The registry section needs a running session.** Tools and commands are read from the live process; the CLI reports that section as skipped rather than guessing from disk.
|
|
291
|
+
- **Tool collisions are reported, not resolved.** Pi's registry keeps the last registration; checkup tells you it happened.
|
|
292
|
+
- **The proxy check proves the port is open, not that forwarding works.** A TCP connect tells you the process is running; it says nothing about whether it can reach a given target. Set `PI_CHECKUP_PROBE_URL` for the full test, which uses `curl`.
|
|
293
|
+
- **A proxied provider probe needs `curl`.** Node's `fetch` ignores `https_proxy`, so when a proxy is configured the probe shells out to `curl`, which honours it. Without `curl` on `PATH` that row reports `skip` with the reason instead of inventing a reachability failure.
|
|
294
|
+
- **Static checks describe the current session.** Install a package and the registry section is stale until `/reload`.
|
|
295
|
+
- **A DNS lookup that hangs can outlive the report.** `resolveHost` races the lookup against a 4s deadline so the report is never held hostage, but Node's pending `getaddrinfo` request can keep the process alive for a while after the verdict is printed. The report is complete and correct; the exit is merely later than it looks.
|
|
296
|
+
- **A run that overlaps an install describes a moving target.** Half-extracted packages and npm's staging links in `.bin` are visible for a second or two; checkup reports them as install leftovers and suggests re-running once the install finishes, but it cannot wait for it.
|
|
297
|
+
|
|
298
|
+
## Development
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
git clone https://github.com/xinghe-labs/pi-checkup
|
|
302
|
+
cd pi-checkup
|
|
303
|
+
npm run lint # node --check over every source file
|
|
304
|
+
node --test # the whole suite; no Pi session needed
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
To try a local checkout without installing:
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
pi -e ./src/index.js
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Layout:
|
|
314
|
+
|
|
315
|
+
```text
|
|
316
|
+
bin/
|
|
317
|
+
checkup.js standalone CLI entry
|
|
318
|
+
src/
|
|
319
|
+
index.js in-session command registration, fix flow
|
|
320
|
+
context.js where a check gets its inputs (session vs disk)
|
|
321
|
+
render.js one formatter for text and JSON
|
|
322
|
+
repair.js the only module that writes to user files
|
|
323
|
+
util.js cross-platform helpers, no dependencies
|
|
324
|
+
checks/
|
|
325
|
+
index.js the check registry, shared by command and CLI
|
|
326
|
+
registry.js what is loaded, who owns it, what collided
|
|
327
|
+
packages.js installed vs present vs loadable
|
|
328
|
+
binaries.js PATH reachability and shadowing
|
|
329
|
+
network.js provider endpoints and DNS
|
|
330
|
+
config.js JSON validity, backups, duplicates, env refs
|
|
331
|
+
runtime.js node, launcher, disk, proxy
|
|
332
|
+
test/
|
|
333
|
+
*.test.js node --test, no Pi session needed
|
|
334
|
+
scripts/
|
|
335
|
+
lint.js node --check over every source file (`npm run lint`)
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Adding a check: export `group` and `run(ctx)`, return an array of `{ group, id, title, status, detail, hint? }`, and add the module to the `CHECKS` array in `src/checks/index.js`.
|
|
339
|
+
|
|
340
|
+
`ctx` carries everything a check may read — `agentDir`, `settingsFile`, `projectSettingsFile`, `modelsFile`, `binDir`, `settings`, `settingsLoaded`, `settingsSource`, `env`, `offline`, `hasRuntime`, `agentDirExplicit`, `pi`, `cwd` — so no check reads the process environment or a path on its own. Tests point `agentDir` at a temp directory; see `test/checks.test.js`.
|
|
341
|
+
|
|
342
|
+
## License
|
|
343
|
+
|
|
344
|
+
MIT
|
package/bin/checkup.js
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Standalone CLI — for the case the in-session command cannot cover: Pi is
|
|
3
|
+
// installed but will not start, so there is no session to run /checkup in.
|
|
4
|
+
//
|
|
5
|
+
// The registry checks need a live process and report skipped here. Everything
|
|
6
|
+
// else (packages, binaries, config, runtime, and network unless --offline) runs
|
|
7
|
+
// exactly as it does inside Pi.
|
|
8
|
+
import { runChecks } from "../src/checks/index.js";
|
|
9
|
+
import { buildContext } from "../src/context.js";
|
|
10
|
+
import { renderReport, renderVerdict, toJson } from "../src/render.js";
|
|
11
|
+
import { applyRepairs } from "../src/repair.js";
|
|
12
|
+
import { packageVersion, scrubCredentials } from "../src/util.js";
|
|
13
|
+
|
|
14
|
+
const VERSION = packageVersion();
|
|
15
|
+
|
|
16
|
+
const HELP = `pi-checkup ${VERSION} — health checks for a Pi installation
|
|
17
|
+
|
|
18
|
+
Usage
|
|
19
|
+
pi-checkup [options]
|
|
20
|
+
|
|
21
|
+
Options
|
|
22
|
+
-v, --verbose show how to fix each finding
|
|
23
|
+
--json print the report as JSON
|
|
24
|
+
--offline skip every network probe
|
|
25
|
+
--fix list the available repairs, and apply them with --yes
|
|
26
|
+
-y, --yes apply the repairs instead of only listing them
|
|
27
|
+
-h, --help show this help
|
|
28
|
+
--version print the version
|
|
29
|
+
|
|
30
|
+
Exit codes
|
|
31
|
+
0 no failed checks (including after a successful repair)
|
|
32
|
+
1 at least one check failed
|
|
33
|
+
2 usage error, or the run itself crashed
|
|
34
|
+
|
|
35
|
+
--json and --fix cannot be combined: --json is a single machine-readable
|
|
36
|
+
snapshot, and a repair changes what the snapshot describes.
|
|
37
|
+
|
|
38
|
+
The registry section needs a running Pi session; use /checkup inside Pi to
|
|
39
|
+
audit loaded tools and commands.
|
|
40
|
+
|
|
41
|
+
Environment
|
|
42
|
+
PI_CODING_AGENT_DIR agent directory to inspect (default: ~/.pi/agent)
|
|
43
|
+
PI_CHECKUP_IGNORE comma-separated rows to downgrade to skipped, by id,
|
|
44
|
+
Group:id, or title (e.g. "unreachable,Config:env")
|
|
45
|
+
PI_CHECKUP_PROBE_URL opt in to a real forwarding test through the proxy
|
|
46
|
+
https_proxy, HTTPS_PROXY, http_proxy, HTTP_PROXY, all_proxy, ALL_PROXY
|
|
47
|
+
the first one set is tested for reachability; never
|
|
48
|
+
printed with credentials`;
|
|
49
|
+
|
|
50
|
+
function parseArgs(argv) {
|
|
51
|
+
const opts = { verbose: false, json: false, offline: false, fix: false, yes: false, help: false, version: false };
|
|
52
|
+
for (const arg of argv) {
|
|
53
|
+
switch (arg) {
|
|
54
|
+
case "-v":
|
|
55
|
+
case "--verbose":
|
|
56
|
+
opts.verbose = true;
|
|
57
|
+
break;
|
|
58
|
+
case "--json":
|
|
59
|
+
opts.json = true;
|
|
60
|
+
break;
|
|
61
|
+
case "--offline":
|
|
62
|
+
opts.offline = true;
|
|
63
|
+
break;
|
|
64
|
+
case "--fix":
|
|
65
|
+
opts.fix = true;
|
|
66
|
+
break;
|
|
67
|
+
case "--yes":
|
|
68
|
+
case "-y":
|
|
69
|
+
opts.yes = true;
|
|
70
|
+
break;
|
|
71
|
+
case "-h":
|
|
72
|
+
case "--help":
|
|
73
|
+
opts.help = true;
|
|
74
|
+
break;
|
|
75
|
+
case "--version":
|
|
76
|
+
opts.version = true;
|
|
77
|
+
break;
|
|
78
|
+
default:
|
|
79
|
+
throw new Error(`unknown option: ${arg}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Silently ignoring one of these two would run no repairs and say nothing.
|
|
83
|
+
if (opts.json && opts.fix) throw new Error("--json cannot be combined with --fix");
|
|
84
|
+
return opts;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function main() {
|
|
88
|
+
let opts;
|
|
89
|
+
try {
|
|
90
|
+
opts = parseArgs(process.argv.slice(2));
|
|
91
|
+
} catch (e) {
|
|
92
|
+
process.stderr.write(`${e.message}\n\n${HELP}\n`);
|
|
93
|
+
return 2;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (opts.help) {
|
|
97
|
+
process.stdout.write(`${HELP}\n`);
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
if (opts.version) {
|
|
101
|
+
process.stdout.write(`${VERSION}\n`);
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// `--yes` on its own reads as "apply the repairs" and does nothing. Say so
|
|
106
|
+
// rather than returning a normal report that silently ignored a flag.
|
|
107
|
+
if (opts.yes && !opts.fix) process.stderr.write("pi-checkup: --yes only means something with --fix\n");
|
|
108
|
+
|
|
109
|
+
// Whether the agent directory was named explicitly matters to the Runtime
|
|
110
|
+
// group: a path the user typed and got wrong is a fault in this run, while an
|
|
111
|
+
// absent default location only means Pi has not been started on this machine.
|
|
112
|
+
const ctx = buildContext({
|
|
113
|
+
cwd: process.cwd(),
|
|
114
|
+
offline: opts.offline,
|
|
115
|
+
agentDirExplicit: Boolean(process.env.PI_CODING_AGENT_DIR),
|
|
116
|
+
});
|
|
117
|
+
const results = await runChecks(ctx);
|
|
118
|
+
|
|
119
|
+
if (opts.json) {
|
|
120
|
+
// Stamped: a JSON report pasted into an issue has to say what produced it,
|
|
121
|
+
// and a version or a platform is the first thing anyone asks for.
|
|
122
|
+
const payload = toJson(results, { version: VERSION, node: process.versions.node, platform: process.platform });
|
|
123
|
+
// Set before the write, like the text path: a consumer that closes the pipe
|
|
124
|
+
// mid-payload must still see the code the report implies.
|
|
125
|
+
process.exitCode = results.some((r) => r.status === "fail") ? 1 : 0;
|
|
126
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
127
|
+
return process.exitCode;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const report = renderReport(results, {
|
|
131
|
+
verbose: opts.verbose,
|
|
132
|
+
// `pi-checkup` alone only resolves after a global install the README does
|
|
133
|
+
// not require, so the hint names the form that always works.
|
|
134
|
+
hintCommand: "npx pi-checkup --verbose",
|
|
135
|
+
// Piped output has no columns; the renderer's default keeps logs stable.
|
|
136
|
+
width: process.stdout.isTTY ? process.stdout.columns || 80 : 80,
|
|
137
|
+
});
|
|
138
|
+
// Set before the first byte of the report, so a consumer that closes the pipe
|
|
139
|
+
// mid-report (`pi-checkup | head -3`) still exits with the code the report
|
|
140
|
+
// implies instead of a misleading 0.
|
|
141
|
+
process.exitCode = results.some((r) => r.status === "fail") ? 1 : 0;
|
|
142
|
+
process.stdout.write(`${report}\n`);
|
|
143
|
+
const fixable = results.filter((r) => r.fix && r.fixTarget);
|
|
144
|
+
let finalResults = results;
|
|
145
|
+
if (opts.fix) {
|
|
146
|
+
if (!fixable.length) {
|
|
147
|
+
process.stdout.write("\nNothing to repair automatically.\n");
|
|
148
|
+
} else if (!opts.yes) {
|
|
149
|
+
process.stdout.write(`\n${fixable.length} repair(s) available — each rewrites a config file after backing it up:\n`);
|
|
150
|
+
for (const f of fixable) process.stdout.write(` • ${f.title}: ${f.detail}\n`);
|
|
151
|
+
process.stdout.write("\nRe-run with --fix --yes to apply them. Every file is backed up first.\n");
|
|
152
|
+
} else {
|
|
153
|
+
const applied = await applyRepairs(fixable, { agentDir: ctx.agentDir });
|
|
154
|
+
process.stdout.write("\nRepairs\n");
|
|
155
|
+
for (const a of applied) process.stdout.write(` ${a.ok ? "✓" : "✗"} ${a.message}\n`);
|
|
156
|
+
// Only when something was actually written: a refused repair, or one
|
|
157
|
+
// that found nothing to do by the time it ran, changed nothing, so
|
|
158
|
+
// telling the user to restart Pi is noise.
|
|
159
|
+
const wrote = applied.some((a) => a.changed);
|
|
160
|
+
if (wrote) process.stdout.write("\nRestart Pi so it picks up the changes.\n");
|
|
161
|
+
// Re-check: the exit code has to describe the tree as it is now, not as
|
|
162
|
+
// it was before the repair. Exiting 1 right after fixing the only
|
|
163
|
+
// failure breaks every `&&` chain that calls this.
|
|
164
|
+
//
|
|
165
|
+
// The context has to be rebuilt, not reused: it holds the settings it
|
|
166
|
+
// parsed on construction, so re-running the checks against the old one
|
|
167
|
+
// reports the entries the repair just removed. That is how a successful
|
|
168
|
+
// prune still exited 1.
|
|
169
|
+
if (wrote) {
|
|
170
|
+
finalResults = await runChecks(
|
|
171
|
+
buildContext({
|
|
172
|
+
cwd: process.cwd(),
|
|
173
|
+
offline: opts.offline,
|
|
174
|
+
agentDirExplicit: Boolean(process.env.PI_CODING_AGENT_DIR),
|
|
175
|
+
}),
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
} else if (fixable.length) {
|
|
180
|
+
process.stdout.write(`\n${fixable.length} finding(s) can be repaired with --fix.\n`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Discoverability: the CLI cannot audit the registry, and saying so is more
|
|
184
|
+
// useful than letting the skipped section look like a clean bill of health.
|
|
185
|
+
// Labelled, because an unlabelled verdict printed under a pre-repair report
|
|
186
|
+
// reads as a contradiction: "1 failed" then "✓ 23 ok".
|
|
187
|
+
const after = finalResults === results ? "" : " · re-checked after repair";
|
|
188
|
+
process.stdout.write(`\n${renderVerdict(finalResults)}${after} · run /checkup inside Pi for registry checks\n`);
|
|
189
|
+
return finalResults.some((r) => r.status === "fail") ? 1 : 0;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// `npx pi-checkup | head -3` closes stdout while the report is still being
|
|
193
|
+
// written. Node surfaces that as an unhandled EPIPE rejection, so a truncated
|
|
194
|
+
// pipe printed "pi-checkup crashed" with a stack for something that is not a
|
|
195
|
+
// failure of the check at all. A consumer that stopped reading is a normal exit.
|
|
196
|
+
// Anything else is a real write failure, and it goes through the same scrubbing
|
|
197
|
+
// as a caught exception: an uncaught throw from an event listener printed a raw
|
|
198
|
+
// stack, which is the one place a config value can slip out unredacted.
|
|
199
|
+
let reported = false;
|
|
200
|
+
const writeFailure = (e) => {
|
|
201
|
+
// A write error on stderr is itself reported to stderr, so without this guard
|
|
202
|
+
// a broken stderr re-enters the handler instead of exiting.
|
|
203
|
+
if (reported) return;
|
|
204
|
+
reported = true;
|
|
205
|
+
process.stderr.write(`pi-checkup crashed: ${scrubCredentials(String(e?.stack ?? e))}\n`);
|
|
206
|
+
process.exitCode = 2;
|
|
207
|
+
};
|
|
208
|
+
process.stdout.on("error", (e) => {
|
|
209
|
+
// A consumer that stopped reading is a normal exit, but not a successful one:
|
|
210
|
+
// `pi-checkup | head -1` over failed checks must still exit 1.
|
|
211
|
+
if (e?.code === "EPIPE") process.exit(process.exitCode ?? 0);
|
|
212
|
+
writeFailure(e);
|
|
213
|
+
});
|
|
214
|
+
process.stderr.on("error", (e) => {
|
|
215
|
+
if (e?.code !== "EPIPE") writeFailure(e);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// `process.exit()` can truncate a pipe that has not drained, and a --json report
|
|
219
|
+
// on a slow consumer is exactly the payload large enough to hit that. Setting
|
|
220
|
+
// exitCode lets Node flush and exit on its own.
|
|
221
|
+
main().then(
|
|
222
|
+
(code) => {
|
|
223
|
+
// A write failure already set 2, and "2 = the run itself crashed" is the
|
|
224
|
+
// one thing that must not be overwritten by a normal return.
|
|
225
|
+
if (!reported) process.exitCode = code;
|
|
226
|
+
},
|
|
227
|
+
(e) => {
|
|
228
|
+
// Scrubbed: an uncaught error can quote the URL or the config value it
|
|
229
|
+
// choked on, and a crash report is exactly where nobody looks twice.
|
|
230
|
+
process.stderr.write(`pi-checkup crashed: ${scrubCredentials(String(e?.stack ?? e))}\n`);
|
|
231
|
+
process.exitCode = 2;
|
|
232
|
+
},
|
|
233
|
+
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,49 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-checkup",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Health checks for your Pi setup: tool/command conflicts, broken packages, shadowed binaries, dead endpoints.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "xinghe-labs",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=20"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"pi-package",
|
|
13
|
+
"pi-coding-agent",
|
|
14
|
+
"pi",
|
|
15
|
+
"extension",
|
|
16
|
+
"healthcheck",
|
|
17
|
+
"diagnostics",
|
|
18
|
+
"doctor",
|
|
19
|
+
"troubleshooting"
|
|
20
|
+
],
|
|
21
|
+
"pi": {
|
|
22
|
+
"extensions": [
|
|
23
|
+
"./src/index.js"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"bin": {
|
|
27
|
+
"pi-checkup": "bin/checkup.js"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"src/",
|
|
31
|
+
"bin/",
|
|
32
|
+
"scripts/",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"test": "node --test",
|
|
38
|
+
"lint": "node scripts/lint.js",
|
|
39
|
+
"checkup": "node ./bin/checkup.js"
|
|
40
|
+
},
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/xinghe-labs/pi-checkup.git"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/xinghe-labs/pi-checkup/issues"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/xinghe-labs/pi-checkup#readme"
|
|
49
|
+
}
|