@atlassian/chippy 0.0.1-beta.1 → 0.0.1-beta.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/README.md +142 -54
- package/bin/chippy.mjs +53 -95
- package/bin/pack.mjs +20 -0
- package/bin/skill-dir.mjs +15 -0
- package/lib/avalanche.js +54 -23
- package/lib/avalanche.js.map +2 -2
- package/lib/bootstrap.js +20 -1
- package/lib/bootstrap.js.map +3 -3
- package/lib/extension/avalanche.es.js +173 -34
- package/lib/extension/avalanche.es.js.map +1 -1
- package/lib/extension/chippy-page-bridge.js +164 -44
- package/lib/mcp-skill/README.md +2 -4
- package/lib/mcp-skill/SKILL.md +21 -16
- package/lib/mcp-skill/extension/armed-reload-startup.js +83 -0
- package/lib/mcp-skill/extension/avalanche.es.js +173 -34
- package/lib/mcp-skill/extension/avalanche.es.js.map +1 -1
- package/lib/mcp-skill/extension/avalanche.iife.js +173 -34
- package/lib/mcp-skill/extension/avalanche.iife.js.map +1 -1
- package/lib/mcp-skill/extension/background.js +2 -0
- package/lib/mcp-skill/extension/chippy-page-bridge.js +164 -44
- package/lib/mcp-skill/extension/content-script.js +23 -0
- package/lib/mcp-skill/extension/devtools-hook-shim.js +18 -1
- package/lib/mcp-skill/extension/early-bootstrap.js +3 -1
- package/lib/mcp-skill/extension/manifest.json +1 -1
- package/lib/mcp-skill/extension/panel-settings.js +2 -20
- package/lib/mcp-skill/extension/startup-handoff.js +30 -0
- package/lib/mcp-skill/references/README.md +2 -2
- package/lib/mcp-skill/references/avalanche-detector.md +3 -3
- package/lib/mcp-skill/references/browser-runtime-api.md +4 -4
- package/lib/mcp-skill/references/command-output-formats.md +12 -2
- package/lib/mcp-skill/references/command-surface.md +6 -6
- package/lib/mcp-skill/references/mode-a-injection.md +43 -9
- package/lib/mcp-skill/references/playwright-cli-setup.md +5 -5
- package/lib/mcp-skill/references/playwright-probes.md +1 -1
- package/lib/mcp-skill/references/recipes.txt +1 -1
- package/lib/mcp-skill/references/stability-render.md +4 -4
- package/lib/mcp-skill/references/test-mode-playbook.md +2 -1
- package/lib/mcp-skill/references/troubleshooting.md +6 -1
- package/lib/mcp-skill/scripts/chippy-helper-server.js +116 -58
- package/lib/mcp-skill/scripts/chippy.sh +41 -7
- package/lib/mcp-skill/scripts/helper-access.js +20 -6
- package/lib/mcp-skill/scripts/helper-proxy.js +87 -0
- package/lib/mcp-skill/scripts/page-socket-trust.js +81 -0
- package/lib/plugin.js.map +2 -2
- package/lib/storybook/connection-fallback.jsx +1 -1
- package/lib/storybook/manager-owner.js +42 -0
- package/lib/storybook/manager.jsx +22 -0
- package/lib/storybook/preview.js +53 -18
- package/lib/storybook/setup.js +15 -4
- package/lib/types/chippy-bridge/fiber-source.d.ts +14 -3
- package/lib/types/chippy-bridge/react-renderer.d.ts +30 -5
- package/lib/types/chippy-bridge/recorded-source.d.ts +44 -0
- package/lib/types/debug-render/core.d.ts +6 -0
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-

|
|
1
|
+

|
|
2
2
|
|
|
3
3
|
# Chippy
|
|
4
4
|
|
|
5
|
-
[](LICENSE) [](CONTRIBUTING.md)
|
|
5
|
+
[](https://github.com/atlassian-labs/chippy/blob/main/LICENSE) [](https://github.com/atlassian-labs/chippy/blob/main/CONTRIBUTING.md)
|
|
6
6
|
|
|
7
7
|
**A friend who helps you understand what’s happening, find the cause, and fix it.**
|
|
8
8
|
|
|
@@ -18,7 +18,7 @@ on the page and leave it a note.
|
|
|
18
18
|
|
|
19
19
|
Chippy works with standard React and needs no patched packages. Development
|
|
20
20
|
builds give the most detail; production builds show less, and every answer says
|
|
21
|
-
what was available. See [React compatibility](docs/react-compatibility.md).
|
|
21
|
+
what was available. See [React compatibility](https://github.com/atlassian-labs/chippy/blob/main/docs/react-compatibility.md).
|
|
22
22
|
|
|
23
23
|
## What you can ask
|
|
24
24
|
|
|
@@ -29,38 +29,43 @@ what was available. See [React compatibility](docs/react-compatibility.md).
|
|
|
29
29
|
|
|
30
30
|
## Install
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
Chippy has three parts:
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
- The **agent skill** tells your coding agent how to run Chippy.
|
|
35
|
+
- The **local helper** is a small server on `127.0.0.1:7823`. The agent starts
|
|
36
|
+
it when it needs it.
|
|
37
|
+
- The **page runtime** runs inside the page and reads React.
|
|
38
|
+
|
|
39
|
+
The skill and the helper are the same in every setup. You choose how the page
|
|
40
|
+
runtime gets into the page; see [Connect a page](#connect-a-page).
|
|
41
|
+
|
|
42
|
+
You need Node.js 22.20 or later, which the skills CLI requires.
|
|
43
|
+
|
|
44
|
+
1. Install the agent skill with the [skills CLI](https://github.com/vercel-labs/skills):
|
|
35
45
|
|
|
36
46
|
```sh
|
|
37
|
-
npx
|
|
47
|
+
npx skills add atlassian-labs/chippy --skill chippy -g
|
|
38
48
|
```
|
|
39
49
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
`
|
|
44
|
-
|
|
50
|
+
Choose your coding agents when it asks, or name them with `-a`, for example
|
|
51
|
+
`-a claude-code`. With `-g`, every project can use the skill, and its files
|
|
52
|
+
live in `~/.agents/skills/chippy`; each agent's skills folder links to that
|
|
53
|
+
copy. Leave out `-g` to install it in the current project's
|
|
54
|
+
`.agents/skills/chippy` instead. Run `npx skills update chippy` to update it.
|
|
45
55
|
|
|
46
|
-
2.
|
|
47
|
-
**Developer mode**, choose **Load unpacked**, and select the `extension`
|
|
48
|
-
folder the installer printed (`~/.agents/skills/chippy/extension` by
|
|
49
|
-
default). The folder picker hides dot-folders, so paste the path instead of
|
|
50
|
-
browsing: press **Cmd+Shift+G** on macOS or **Ctrl+L** on Linux, or use the
|
|
51
|
-
address bar on Windows.
|
|
56
|
+
2. Connect a page with one of the routes in [Connect a page](#connect-a-page).
|
|
52
57
|
|
|
53
|
-
3.
|
|
54
|
-
page's origin.
|
|
55
|
-
|
|
56
|
-
4. Ask your agent to use Chippy on the page.
|
|
58
|
+
3. Ask your agent to use Chippy on the page.
|
|
57
59
|
|
|
58
60
|
The agent runs Chippy through the skill's `chippy.sh` script, which needs bash
|
|
59
61
|
(macOS, Linux, or WSL on Windows). You don't start anything yourself: the agent
|
|
60
62
|
starts a local helper on `127.0.0.1:7823` when it needs one. A sandboxed agent
|
|
61
63
|
asks for permission to reach it; approve the prompt or allow `chippy.sh`. If
|
|
62
|
-
your agent runs in a container or a remote machine,
|
|
63
|
-
|
|
64
|
+
your agent runs in a container or on a remote machine, the browser must reach
|
|
65
|
+
the helper's port through an SSH tunnel, a published container port, or other
|
|
66
|
+
forwarding your environment provides; Chippy does not set that up.
|
|
67
|
+
[Local and wide binds](https://github.com/atlassian-labs/chippy/blob/main/docs/trust-boundary.md#local-and-wide-binds)
|
|
68
|
+
lists what to set for each setup.
|
|
64
69
|
|
|
65
70
|
**Optional: connect over MCP.** If your agent supports MCP, it can call Chippy
|
|
66
71
|
as tools instead of running a script. Ask it:
|
|
@@ -69,27 +74,112 @@ as tools instead of running a script. Ask it:
|
|
|
69
74
|
|
|
70
75
|
The agent adds the Chippy server to its own MCP configuration and checks the
|
|
71
76
|
connection. Some agents need a restart before the new tools appear. To set it
|
|
72
|
-
up yourself, see [MCP setup](.agents/skills/chippy/references/mcp.md).
|
|
77
|
+
up yourself, see [MCP setup](https://github.com/atlassian-labs/chippy/blob/main/.agents/skills/chippy/references/mcp.md).
|
|
78
|
+
|
|
79
|
+
## Connect a page
|
|
80
|
+
|
|
81
|
+
Every route loads the same page runtime and talks to the same helper. The
|
|
82
|
+
routes differ in what you set up, whether Chippy stays after a reload, and
|
|
83
|
+
whether Chippy can record the initial page load.
|
|
84
|
+
|
|
85
|
+
| Route | What you set up | After a reload | Records the initial load |
|
|
86
|
+
| --- | --- | --- | --- |
|
|
87
|
+
| [Browser extension](#browser-extension) | Load the extension once, then enable it for each site | Chippy loads again | Yes |
|
|
88
|
+
| [Agent-driven browser](#agent-driven-browser) | Nothing. The agent opens its own Playwright browser | Chippy loads again | Yes, when the agent loads Chippy before the page |
|
|
89
|
+
| [Local proxy](#local-proxy) | Nothing. The agent starts the proxy, and you open the proxy address | Chippy loads again | Yes |
|
|
90
|
+
| [Storybook addon](#storybook-addon) | Add the addon to your Storybook configuration | Chippy remounts the story and stays | The story's mount |
|
|
91
|
+
| [Console import](#console-import) | Paste one line into the page's console | Chippy is removed. Paste the line again | No |
|
|
92
|
+
| [Your app's code](#your-apps-code) | Install the npm package and call `connectChippy()` | Call it again | No |
|
|
93
|
+
|
|
94
|
+
"Records the initial load" means that the agent can reload the page and record
|
|
95
|
+
the renders from the first commit. Every route records renders that happen
|
|
96
|
+
after Chippy connects.
|
|
97
|
+
|
|
98
|
+
### Browser extension
|
|
99
|
+
|
|
100
|
+
Use the extension for day-to-day work in your own Chrome or Chromium browser.
|
|
101
|
+
|
|
102
|
+
1. Open `chrome://extensions`, turn on **Developer mode**, and choose
|
|
103
|
+
**Load unpacked**. Select the skill's `extension` folder:
|
|
104
|
+
`~/.agents/skills/chippy/extension`, or `.agents/skills/chippy/extension`
|
|
105
|
+
in your project if you installed without `-g`. The folder picker hides
|
|
106
|
+
folders whose names start with a dot, so type the path instead of browsing.
|
|
107
|
+
On macOS, press **Cmd+Shift+G**, paste the path, press **Return**, and
|
|
108
|
+
choose **Select**. On Linux, press **Ctrl+L** and paste the path. On
|
|
109
|
+
Windows, paste it into the address bar. After `npx skills update`, click
|
|
110
|
+
**Reload** on the Chippy card in `chrome://extensions`.
|
|
111
|
+
2. Open your app, click the Chippy extension icon, and enable Chippy for the
|
|
112
|
+
page's origin.
|
|
113
|
+
|
|
114
|
+
### Agent-driven browser
|
|
115
|
+
|
|
116
|
+
The agent opens a Playwright browser, loads Chippy into the page, and drives
|
|
117
|
+
the page itself. No extension is needed. Use this route for repeatable flows,
|
|
118
|
+
headless checks, and CI. Ask your agent to open the page with Chippy in
|
|
119
|
+
Playwright; the skill has the setup steps.
|
|
120
|
+
|
|
121
|
+
### Local proxy
|
|
73
122
|
|
|
74
|
-
|
|
123
|
+
The helper can sit in front of a dev server on your machine and add Chippy to
|
|
124
|
+
the page before the page's own scripts run. No extension is needed, and this
|
|
125
|
+
route works in any browser, including one that your agent drives. Ask your
|
|
126
|
+
agent to start the Chippy proxy for your page, then open the address that it
|
|
127
|
+
gives you on `127.0.0.1:7823`. The proxy forwards only to servers on this
|
|
128
|
+
machine, and it adds Chippy only to the page address that the agent gave it.
|
|
129
|
+
It removes the page's Content Security Policy headers, so it also works for
|
|
130
|
+
pages that block the console import.
|
|
75
131
|
|
|
76
|
-
|
|
77
|
-
test ID, source file, or prop, reads their state, clicks through the page, and
|
|
78
|
-
records renders.
|
|
79
|
-
- **From the page:** click the Chippy button in the page corner and drag a probe
|
|
80
|
-
marker (P1, or P2 to compare two elements) onto an element. Choose whether you
|
|
81
|
-
want it explained, investigated, or changed, and add a note. Then ask the agent
|
|
82
|
-
to read it, or to keep watching for new notes while you click around.
|
|
132
|
+
### Storybook addon
|
|
83
133
|
|
|
84
|
-
|
|
85
|
-
|
|
134
|
+
In Storybook 10.3 or later, add the addon to your Storybook configuration:
|
|
135
|
+
|
|
136
|
+
```js
|
|
137
|
+
export default {
|
|
138
|
+
addons: ['@atlassian/chippy/storybook'],
|
|
139
|
+
};
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The addon adds Chippy panels to Storybook and connects each story to your agent.
|
|
143
|
+
When the agent reloads the page, Storybook remounts the current story instead of
|
|
144
|
+
reloading the preview. See the
|
|
145
|
+
[package documentation](https://github.com/atlassian-labs/chippy/blob/main/docs/npm-package.md#storybook)
|
|
146
|
+
for build targets.
|
|
147
|
+
|
|
148
|
+
### Console import
|
|
149
|
+
|
|
150
|
+
Use this for a quick look at a page where you cannot install the extension.
|
|
151
|
+
Once the agent has started the helper, paste this into the page's console:
|
|
86
152
|
|
|
87
153
|
```js
|
|
88
154
|
import('http://127.0.0.1:7823/__chippy/inject.mjs')
|
|
89
155
|
```
|
|
90
156
|
|
|
91
|
-
|
|
92
|
-
|
|
157
|
+
A reload removes Chippy, so paste the line again after every reload or
|
|
158
|
+
navigation. Sites with a strict Content Security Policy block the import.
|
|
159
|
+
|
|
160
|
+
### Your app's code
|
|
161
|
+
|
|
162
|
+
For pages that the extension cannot reach, such as an embedded browser, call
|
|
163
|
+
`connectChippy()` from `@atlassian/chippy/browser` in the page. It loads the
|
|
164
|
+
same runtime as the console import. See
|
|
165
|
+
[Connect a browser page to an agent](https://github.com/atlassian-labs/chippy/blob/main/docs/npm-package.md#connect-a-browser-page-to-an-agent).
|
|
166
|
+
|
|
167
|
+
## Ask the agent, or point at the page
|
|
168
|
+
|
|
169
|
+
- **Ask the agent** about the page. It finds components by name, text, test ID,
|
|
170
|
+
source file, or prop, reads their state, clicks through the page, and records
|
|
171
|
+
renders.
|
|
172
|
+
- **Point at the page:** click the Chippy button in the page corner and drag a
|
|
173
|
+
probe marker (P1, or P2 to compare two elements) onto an element. Choose
|
|
174
|
+
whether you want it explained, investigated, or changed, and add a note. Then
|
|
175
|
+
ask the agent to read it, or to keep watching for new notes while you click
|
|
176
|
+
around. The extension, the local proxy, and the Storybook addon show the
|
|
177
|
+
Chippy button. The console import does not.
|
|
178
|
+
|
|
179
|
+
<p align="center">
|
|
180
|
+
<a href="assets/chippy-call.mp4"><img src="assets/chippy-call.svg" width="900" alt="Animated clip: the asks roll in and Jira keeps them in line, so you call a chippy. Chippy gives your coding agent a hand in the running app: it finds the component and source line for a button to move, reads live props and state to show why a button stays disabled, compares before and after, finds what set off a slow click, connects the page, React, and your code, and turns the page into an experimentation lab."></a>
|
|
181
|
+
<br><a href="assets/chippy-call.mp4">Watch with sound</a>
|
|
182
|
+
</p>
|
|
93
183
|
|
|
94
184
|
## Evidence, then edits
|
|
95
185
|
|
|
@@ -99,20 +189,18 @@ side effects during render, say so and the agent uses read-only inspection
|
|
|
99
189
|
instead. Chippy changes your source only when you ask it to, and checks the fix
|
|
100
190
|
against the same page.
|
|
101
191
|
|
|
102
|
-
## Use Chippy
|
|
192
|
+
## Use Chippy without an agent
|
|
103
193
|
|
|
104
|
-
|
|
194
|
+
The `@atlassian/chippy` npm package also works with no agent, helper, or
|
|
195
|
+
extension:
|
|
105
196
|
|
|
106
197
|
- **Catch re-render regressions in tests.** Record which components rendered
|
|
107
|
-
during an interaction, and why, in a test or a script
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
test runner, or connect from your app's startup code so Chippy sees the first
|
|
111
|
-
render.
|
|
112
|
-
- **Add Chippy to Storybook** as an addon.
|
|
198
|
+
during an interaction, and why, in a test or a script. Install the recorder in
|
|
199
|
+
your app's entry module before React DOM loads, and it records from the first
|
|
200
|
+
commit.
|
|
113
201
|
- **Ship your own build** with extra plugins for your stack.
|
|
114
202
|
|
|
115
|
-
See the [package documentation](docs/npm-package.md) for setup.
|
|
203
|
+
See the [package documentation](https://github.com/atlassian-labs/chippy/blob/main/docs/npm-package.md) for setup.
|
|
116
204
|
|
|
117
205
|
## How Chippy compares
|
|
118
206
|
|
|
@@ -171,19 +259,19 @@ change is verified.
|
|
|
171
259
|
|
|
172
260
|
## Learn more
|
|
173
261
|
|
|
174
|
-
- [Agent skill](.agents/skills/chippy/SKILL.md) and its [references](.agents/skills/chippy/references/README.md)
|
|
175
|
-
- [React compatibility](docs/react-compatibility.md)
|
|
176
|
-
- [Plugins](docs/plugins.md) for app-specific knowledge
|
|
177
|
-
- [Trust boundary](docs/trust-boundary.md): what page data Chippy sees and who can reach the helper
|
|
178
|
-
- [Security policy](SECURITY.md)
|
|
179
|
-
- [Changelog](CHANGELOG.md)
|
|
262
|
+
- [Agent skill](https://github.com/atlassian-labs/chippy/blob/main/.agents/skills/chippy/SKILL.md) and its [references](https://github.com/atlassian-labs/chippy/blob/main/.agents/skills/chippy/references/README.md)
|
|
263
|
+
- [React compatibility](https://github.com/atlassian-labs/chippy/blob/main/docs/react-compatibility.md)
|
|
264
|
+
- [Plugins](https://github.com/atlassian-labs/chippy/blob/main/docs/plugins.md) for app-specific knowledge
|
|
265
|
+
- [Trust boundary](https://github.com/atlassian-labs/chippy/blob/main/docs/trust-boundary.md): what page data Chippy sees and who can reach the helper
|
|
266
|
+
- [Security policy](https://github.com/atlassian-labs/chippy/blob/main/SECURITY.md)
|
|
267
|
+
- [Changelog](https://github.com/atlassian-labs/chippy/blob/main/CHANGELOG.md)
|
|
180
268
|
|
|
181
|
-
To build, test, or change Chippy, see [Contributing](CONTRIBUTING.md) and the
|
|
182
|
-
[code of conduct](CODE_OF_CONDUCT.md).
|
|
269
|
+
To build, test, or change Chippy, see [Contributing](https://github.com/atlassian-labs/chippy/blob/main/CONTRIBUTING.md) and the
|
|
270
|
+
[code of conduct](https://github.com/atlassian-labs/chippy/blob/main/CODE_OF_CONDUCT.md).
|
|
183
271
|
|
|
184
272
|
## License
|
|
185
273
|
|
|
186
274
|
Copyright (c) 2026 Atlassian US., Inc.
|
|
187
|
-
Apache 2.0 licensed, see [LICENSE](LICENSE) file.
|
|
275
|
+
Apache 2.0 licensed, see [LICENSE](https://github.com/atlassian-labs/chippy/blob/main/LICENSE) file.
|
|
188
276
|
|
|
189
277
|
[](https://www.atlassian.com)
|
package/bin/chippy.mjs
CHANGED
|
@@ -1,121 +1,79 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// npx @atlassian/chippy install [--dir <path>] [--force]
|
|
2
|
+
// Packs a custom Chippy build with extra plugins (see pack.mjs). The agent
|
|
3
|
+
// skill is installed with the skills CLI, not by this package.
|
|
5
4
|
// npx @atlassian/chippy pack [--config <file>]
|
|
6
|
-
import {
|
|
7
|
-
import { homedir } from 'node:os';
|
|
5
|
+
import { readFileSync, realpathSync } from 'node:fs';
|
|
8
6
|
import { dirname, join, resolve } from 'node:path';
|
|
9
7
|
import { fileURLToPath } from 'node:url';
|
|
10
8
|
|
|
11
9
|
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
function
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
10
|
+
export const SKILL_INSTALL_COMMAND = 'npx skills add atlassian-labs/chippy --skill chippy -g';
|
|
11
|
+
|
|
12
|
+
const COMMAND_OPTIONS = { pack: ['--config'] };
|
|
13
|
+
const HELP = new Set(['--help', '-h', 'help']);
|
|
14
|
+
const VERSION = new Set(['--version', '-v']);
|
|
15
|
+
|
|
16
|
+
export function parseArgs(argv) {
|
|
17
|
+
const args = { command: argv[0], config: 'chippy.config.mjs', help: false, version: false };
|
|
18
|
+
if (HELP.has(args.command)) return { ...args, command: undefined, help: true };
|
|
19
|
+
if (VERSION.has(args.command)) return { ...args, command: undefined, version: true };
|
|
20
|
+
if (args.command === 'install') return args;
|
|
21
|
+
const allowed = COMMAND_OPTIONS[args.command] ?? [];
|
|
22
|
+
const value = (i) => {
|
|
23
|
+
if (i >= argv.length || argv[i].startsWith('-')) throw new Error(`${argv[i - 1]} needs a value`);
|
|
24
|
+
return argv[i];
|
|
25
|
+
};
|
|
26
|
+
for (let i = 1; i < argv.length; i++) {
|
|
27
|
+
if (HELP.has(argv[i])) args.help = true;
|
|
28
|
+
else if (VERSION.has(argv[i])) args.version = true;
|
|
29
|
+
else if (!allowed.includes(argv[i])) throw new Error(`Unknown option for ${args.command}: ${argv[i]}`);
|
|
30
|
+
else if (argv[i] === '--config') args.config = value(++i);
|
|
33
31
|
}
|
|
34
|
-
|
|
35
|
-
rmSync(destination, { recursive: true, force: true });
|
|
36
|
-
cpSync(source, destination, { recursive: true });
|
|
37
|
-
return destination;
|
|
32
|
+
return args;
|
|
38
33
|
}
|
|
39
34
|
|
|
40
|
-
|
|
41
|
-
return
|
|
42
|
-
}
|
|
35
|
+
function usage(name) {
|
|
36
|
+
return `Usage: npx ${name} pack [--config <file>]
|
|
37
|
+
npx ${name} --version
|
|
43
38
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
}
|
|
39
|
+
pack Builds a custom skill and extension with extra plugins from a config
|
|
40
|
+
file (default chippy.config.mjs). See the package README.
|
|
41
|
+
|
|
42
|
+
Install the Chippy agent skill with the skills CLI:
|
|
43
|
+
${SKILL_INSTALL_COMMAND}
|
|
44
|
+
`;
|
|
51
45
|
}
|
|
52
46
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
return args;
|
|
47
|
+
// The skill used to be installed by this package; point earlier instructions
|
|
48
|
+
// at the skills CLI instead of failing with a bare usage message.
|
|
49
|
+
function installMoved() {
|
|
50
|
+
return `This package no longer installs the Chippy skill. Install it with the skills CLI:
|
|
51
|
+
${SKILL_INSTALL_COMMAND}
|
|
52
|
+
Drop -g to install it in the current project only.
|
|
53
|
+
`;
|
|
62
54
|
}
|
|
63
55
|
|
|
64
56
|
async function main(argv) {
|
|
65
|
-
const { name } = JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8'));
|
|
57
|
+
const { name, version } = JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8'));
|
|
66
58
|
const args = parseArgs(argv);
|
|
67
|
-
if (args.
|
|
68
|
-
|
|
59
|
+
if (args.version) {
|
|
60
|
+
process.stdout.write(`${version}\n`);
|
|
69
61
|
return;
|
|
70
62
|
}
|
|
71
|
-
if (args.command
|
|
72
|
-
process.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
install Installs the Chippy agent skill (default ${defaultSkillDir()}).
|
|
76
|
-
pack Builds a custom skill and extension with extra plugins from a config
|
|
77
|
-
file (default chippy.config.mjs). See the package README.
|
|
78
|
-
`);
|
|
79
|
-
process.exitCode = args.command === undefined || args.command === '--help' ? 0 : 2;
|
|
63
|
+
if (args.command === 'install') {
|
|
64
|
+
process.stderr.write(installMoved());
|
|
65
|
+
process.exitCode = 1;
|
|
80
66
|
return;
|
|
81
67
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
Without the extension, once your agent has started the helper, run this in
|
|
89
|
-
the page's console:
|
|
90
|
-
import('http://127.0.0.1:7823/__chippy/inject.mjs')
|
|
91
|
-
|
|
92
|
-
2. Enable Chippy for your page's origin in the extension popup, then ask your
|
|
93
|
-
agent to use Chippy on the page. The agent runs
|
|
94
|
-
${join(skillDir, 'scripts', 'chippy.sh')}
|
|
95
|
-
|
|
96
|
-
Optional: connect over MCP if your agent supports it. Ask your agent:
|
|
97
|
-
Connect Chippy over MCP by following ${join(skillDir, 'references', 'mcp.md')}
|
|
98
|
-
Or add this server to your agent's MCP configuration yourself:
|
|
99
|
-
${JSON.stringify(mcpConfig(skillDir), null, 2).replace(/^/gm, ' ')}
|
|
100
|
-
|
|
101
|
-
Helper: ${await helperStatus()}
|
|
102
|
-
`);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// The skill lives in a dot-folder, which folder pickers hide by default.
|
|
106
|
-
const FOLDER_PICKER_HINT = {
|
|
107
|
-
darwin: 'The folder is hidden in Finder: press Cmd+Shift+G in the picker and paste the path.',
|
|
108
|
-
win32: 'Paste the path into the picker\'s address bar.',
|
|
109
|
-
linux: 'The folder is hidden: press Ctrl+L in the picker and paste the path.',
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
function folderPickerHint(skillDir) {
|
|
113
|
-
if (!skillDir.split(/[\\/]/).some((part) => part.startsWith('.'))) return '';
|
|
114
|
-
return `\n ${FOLDER_PICKER_HINT[process.platform] ?? FOLDER_PICKER_HINT.linux}`;
|
|
68
|
+
if (args.help || !COMMAND_OPTIONS[args.command]) {
|
|
69
|
+
process.stdout.write(usage(name));
|
|
70
|
+
process.exitCode = args.help || args.command === undefined ? 0 : 2;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
await pack(args.config);
|
|
115
74
|
}
|
|
116
75
|
|
|
117
76
|
async function pack(configPath) {
|
|
118
|
-
// Loaded on demand: install must not depend on the packer.
|
|
119
77
|
const { loadPackConfig, packChippy } = await import('./pack.mjs');
|
|
120
78
|
const result = await packChippy(await loadPackConfig(resolve(configPath)));
|
|
121
79
|
process.stdout.write(`Packed Chippy:
|
package/bin/pack.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from 'node:fs';
|
|
9
9
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
10
10
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
11
|
+
import { ownsDirectory } from './skill-dir.mjs';
|
|
11
12
|
|
|
12
13
|
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
13
14
|
const BUILTIN_PLUGINS = [
|
|
@@ -18,14 +19,32 @@ const RUNTIME_BUNDLES = ['extension/avalanche.iife.js', 'extension/avalanche.es.
|
|
|
18
19
|
const BUILD_CONFIG_CONSUMERS = ['extension/background.js', 'extension/panel-settings.js'];
|
|
19
20
|
const PLUGIN_ID = /^[a-z][a-z0-9-]*$/;
|
|
20
21
|
const CHROME_VERSION = /^\d+(\.\d+){0,3}$/;
|
|
22
|
+
const CONFIG_KEYS = {
|
|
23
|
+
'': ['plugins', 'defaultPlugins', 'extension', 'skill', 'outDir'],
|
|
24
|
+
'plugins[]': ['id', 'entry', 'label'],
|
|
25
|
+
extension: ['name', 'version', 'description', 'key'],
|
|
26
|
+
skill: ['overlay'],
|
|
27
|
+
};
|
|
21
28
|
|
|
22
29
|
function fail(message) {
|
|
23
30
|
throw new Error(`chippy pack: ${message}`);
|
|
24
31
|
}
|
|
25
32
|
|
|
33
|
+
// A misspelled key would otherwise build silently without the setting.
|
|
34
|
+
function rejectUnknownKeys(value, keys, path) {
|
|
35
|
+
if (!value || typeof value !== 'object') return;
|
|
36
|
+
for (const key of Object.keys(value)) {
|
|
37
|
+
if (!keys.includes(key)) fail(`unknown config key ${path ? `${path}.` : ''}${key}; expected one of ${keys.join(', ')}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
26
41
|
/** Validates a pack config and resolves its paths against `baseDir`. */
|
|
27
42
|
export function normalizePackConfig(config, baseDir) {
|
|
28
43
|
if (!config || typeof config !== 'object') fail('the config must export an object');
|
|
44
|
+
rejectUnknownKeys(config, CONFIG_KEYS[''], '');
|
|
45
|
+
rejectUnknownKeys(config.extension, CONFIG_KEYS.extension, 'extension');
|
|
46
|
+
rejectUnknownKeys(config.skill, CONFIG_KEYS.skill, 'skill');
|
|
47
|
+
(config.plugins ?? []).forEach((plugin, index) => rejectUnknownKeys(plugin, CONFIG_KEYS['plugins[]'], `plugins[${index}]`));
|
|
29
48
|
const plugins = (config.plugins ?? []).map((plugin, index) => {
|
|
30
49
|
if (!plugin || typeof plugin !== 'object') fail(`plugins[${index}] must be { id, entry, label? }`);
|
|
31
50
|
if (!PLUGIN_ID.test(plugin.id ?? '')) fail(`plugins[${index}].id must match ${PLUGIN_ID}`);
|
|
@@ -150,6 +169,7 @@ export async function packChippy(config, { source = join(PACKAGE_ROOT, 'lib', 'm
|
|
|
150
169
|
const { plugins, defaultPlugins, extension, overlay, outDir } = config;
|
|
151
170
|
const code = await bundlePlugins(plugins, defaultPlugins);
|
|
152
171
|
const skillDir = join(outDir, 'chippy');
|
|
172
|
+
if (!ownsDirectory(skillDir)) fail(`${skillDir} exists and is not a Chippy skill; choose another outDir`);
|
|
153
173
|
rmSync(skillDir, { recursive: true, force: true });
|
|
154
174
|
mkdirSync(outDir, { recursive: true });
|
|
155
175
|
cpSync(source, skillDir, {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Ownership checks for the directories Chippy's CLI replaces. A directory is
|
|
2
|
+
// Chippy's to replace when it is empty or holds a Chippy skill; anything else
|
|
3
|
+
// belongs to someone else and is never deleted.
|
|
4
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
export function isChippySkill(directory) {
|
|
8
|
+
const skill = join(directory, 'SKILL.md');
|
|
9
|
+
return existsSync(skill) && /^name:\s*chippy\s*$/m.test(readFileSync(skill, 'utf8'));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function ownsDirectory(directory) {
|
|
13
|
+
return !existsSync(directory) || readdirSync(directory).length === 0 || isChippySkill(directory);
|
|
14
|
+
}
|
|
15
|
+
|