@assertrx/dino 1.0.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.
Files changed (89) hide show
  1. package/README.md +293 -0
  2. package/extensions/dino.ts +70 -0
  3. package/extensions/mabl-mcp.ts +306 -0
  4. package/extensions/playwright-mcp.ts +142 -0
  5. package/package.json +47 -0
  6. package/prompts/create-pr.md +110 -0
  7. package/prompts/generate-bdd-from-url.md +65 -0
  8. package/prompts/generate-pom-from-url-cli.md +48 -0
  9. package/prompts/generate-pom-from-url.md +45 -0
  10. package/prompts/generate-spec-from-feature.md +64 -0
  11. package/prompts/heal-playwright-failures.md +106 -0
  12. package/prompts/setup-playwright.md +81 -0
  13. package/scripts/start-dino.cmd +8 -0
  14. package/scripts/start-dino.mjs +226 -0
  15. package/scripts/start-dino.ps1 +9 -0
  16. package/scripts/start-dino.sh +10 -0
  17. package/skills/acli/SKILL.md +292 -0
  18. package/skills/bdd-gherkin/SKILL.md +552 -0
  19. package/skills/build-review-interface/SKILL.md +96 -0
  20. package/skills/error-analysis/SKILL.md +164 -0
  21. package/skills/eval-audit/SKILL.md +183 -0
  22. package/skills/evaluate-rag/SKILL.md +177 -0
  23. package/skills/generate-synthetic-data/SKILL.md +131 -0
  24. package/skills/mabl-mcp/SKILL.md +79 -0
  25. package/skills/playwright-cli/SKILL.md +246 -0
  26. package/skills/playwright-cli/advanced-workflows.md +679 -0
  27. package/skills/playwright-cli/core-commands.md +375 -0
  28. package/skills/playwright-cli/device-emulation.md +475 -0
  29. package/skills/playwright-cli/request-mocking.md +373 -0
  30. package/skills/playwright-cli/running-custom-code.md +553 -0
  31. package/skills/playwright-cli/screenshots-and-media.md +457 -0
  32. package/skills/playwright-cli/session-management.md +425 -0
  33. package/skills/playwright-cli/storage-and-auth.md +487 -0
  34. package/skills/playwright-cli/test-generation.md +336 -0
  35. package/skills/playwright-cli/tracing-and-debugging.md +501 -0
  36. package/skills/playwright-core/SKILL.md +101 -0
  37. package/skills/playwright-core/accessibility.md +1517 -0
  38. package/skills/playwright-core/angular.md +1086 -0
  39. package/skills/playwright-core/api-testing.md +1623 -0
  40. package/skills/playwright-core/assertions-and-waiting.md +703 -0
  41. package/skills/playwright-core/auth-flows.md +1081 -0
  42. package/skills/playwright-core/authentication.md +1409 -0
  43. package/skills/playwright-core/browser-apis.md +673 -0
  44. package/skills/playwright-core/browser-extensions.md +319 -0
  45. package/skills/playwright-core/canvas-and-webgl.md +494 -0
  46. package/skills/playwright-core/clock-and-time-mocking.md +427 -0
  47. package/skills/playwright-core/common-pitfalls.md +1318 -0
  48. package/skills/playwright-core/component-testing.md +1179 -0
  49. package/skills/playwright-core/configuration.md +731 -0
  50. package/skills/playwright-core/crud-testing.md +945 -0
  51. package/skills/playwright-core/debugging.md +827 -0
  52. package/skills/playwright-core/drag-and-drop.md +992 -0
  53. package/skills/playwright-core/electron-testing.md +622 -0
  54. package/skills/playwright-core/error-and-edge-cases.md +1181 -0
  55. package/skills/playwright-core/error-index.md +1903 -0
  56. package/skills/playwright-core/file-operations.md +749 -0
  57. package/skills/playwright-core/file-upload-download.md +1012 -0
  58. package/skills/playwright-core/fixtures-and-hooks.md +1072 -0
  59. package/skills/playwright-core/flaky-tests.md +883 -0
  60. package/skills/playwright-core/forms-and-validation.md +1056 -0
  61. package/skills/playwright-core/i18n-and-localization.md +622 -0
  62. package/skills/playwright-core/iframes-and-shadow-dom.md +488 -0
  63. package/skills/playwright-core/locator-strategy.md +588 -0
  64. package/skills/playwright-core/locators.md +743 -0
  65. package/skills/playwright-core/mobile-and-responsive.md +1669 -0
  66. package/skills/playwright-core/multi-context-and-popups.md +581 -0
  67. package/skills/playwright-core/multi-user-and-collaboration.md +477 -0
  68. package/skills/playwright-core/network-mocking.md +1395 -0
  69. package/skills/playwright-core/nextjs.md +1013 -0
  70. package/skills/playwright-core/performance-testing.md +608 -0
  71. package/skills/playwright-core/react.md +1082 -0
  72. package/skills/playwright-core/search-and-filter.md +1366 -0
  73. package/skills/playwright-core/security-testing.md +624 -0
  74. package/skills/playwright-core/service-workers-and-pwa.md +524 -0
  75. package/skills/playwright-core/test-architecture.md +569 -0
  76. package/skills/playwright-core/test-data-management.md +1209 -0
  77. package/skills/playwright-core/test-organization.md +947 -0
  78. package/skills/playwright-core/third-party-integrations.md +754 -0
  79. package/skills/playwright-core/trace-analysis.md +161 -0
  80. package/skills/playwright-core/visual-regression.md +1027 -0
  81. package/skills/playwright-core/vue.md +1162 -0
  82. package/skills/playwright-core/websockets-and-realtime.md +623 -0
  83. package/skills/playwright-core/when-to-mock.md +827 -0
  84. package/skills/playwright-pom/SKILL.md +94 -0
  85. package/skills/playwright-pom/page-object-model.md +921 -0
  86. package/skills/playwright-pom/pom-vs-fixtures-vs-helpers.md +937 -0
  87. package/skills/validate-evaluator/SKILL.md +215 -0
  88. package/skills/write-judge-prompt/SKILL.md +144 -0
  89. package/themes/dino.json +66 -0
package/README.md ADDED
@@ -0,0 +1,293 @@
1
+ # Dino
2
+
3
+ An Automated Verification agent based on Pi Harness
4
+
5
+ ## What's Inside
6
+
7
+ | Type | Name | What it does |
8
+ |------|------|-------------|
9
+ | Extension | `dino` | Replaces the pi TUI header with a Dino-themed ASCII header and provides a `/dino-header` command to restore the built-in header |
10
+ | Extension | `playwright-mcp` | Bridges Microsoft Playwright MCP tools into pi.dev so the agent can drive a browser for live page inspection, POM generation, and web verification |
11
+ | Extension | `mabl-mcp` | Bridges the mabl MCP server into pi.dev so the agent can query tests, runs, plans, and workspaces via mabl's official CLI using OAuth authentication |
12
+ | Skill | `acli` | Interact with Atlassian Cloud (Jira and Confluence) via the `acli` CLI — search work items, manage sprints/boards, create Confluence pages, and more |
13
+ | Skill | `bdd-gherkin` | Generate domain-driven Gherkin/Cucumber BDD scenarios focused on business requirements with minimal technical details |
14
+ | Skill | `playwright-cli` | Automates browser interactions via `playwright-cli` for terminal-first browser control, navigation, screenshots, tracing, and test generation |
15
+ | Skill | `playwright-core` | Battle-tested Playwright patterns for writing and debugging reliable E2E, API, component, visual, accessibility, and security tests |
16
+ | Skill | `playwright-pom` | Page Object Model patterns for Playwright — when to use POM, how to structure page objects, and when fixtures or helpers are a better fit |
17
+ | Skill | `mabl-mcp` | Guidance for using the mabl MCP bridge — workspace context, running tests, retrieving results, and handling OAuth auth |
18
+ | Prompt | `create-pr` | Create a GitHub pull request for changes in the current workspace, installing `gh` and setting up git if needed |
19
+ | Prompt | `generate-bdd-from-url` | Generate exhaustive Gherkin BDD scenarios for a single page by inspecting it with `playwright-cli` or Playwright MCP |
20
+ | Prompt | `generate-pom-from-url` | Generate Playwright Page Object Models from a live URL using the Playwright MCP bridge (`browser_*` tools) |
21
+ | Prompt | `generate-pom-from-url-cli` | Generate Playwright Page Object Models from a live URL using `playwright-cli` commands |
22
+ | Prompt | `generate-spec-from-feature` | Generate a Playwright test spec from a Gherkin feature file, reusing existing POMs |
23
+ | Prompt | `heal-playwright-failures` | Detect, diagnose, and auto-heal failed Playwright tests by updating selectors, waits, and assertions, then re-run to verify |
24
+ | Prompt | `setup-playwright` | Set up a vanilla Playwright project in the current workspace with TypeScript, config, and a starter test |
25
+
26
+ ## AI Evals Skills
27
+
28
+ These skills help design, run, and validate evaluations for LLM-powered systems.
29
+
30
+ | Skill | Description |
31
+ |-------|-------------|
32
+ | `build-review-interface` | Build a custom browser-based annotation interface for reviewing LLM traces and collecting structured human labels. |
33
+ | `error-analysis` | Systematically identify and categorize failure modes in an LLM pipeline by reading traces. |
34
+ | `eval-audit` | Audit an LLM eval pipeline for problems such as unvalidated judges, vanity metrics, and missing error analysis. |
35
+ | `evaluate-rag` | Evaluate retrieval-augmented generation pipelines, including retrieval quality, generation faithfulness, and relevance. |
36
+ | `generate-synthetic-data` | Create diverse synthetic test inputs for LLM evaluation using dimension-based tuple generation. |
37
+ | `validate-evaluator` | Calibrate an LLM-as-judge against human labels using metrics like TPR/TNR and bias correction. |
38
+ | `write-judge-prompt` | Design LLM-as-judge evaluators for subjective criteria that code-based checks cannot handle. |
39
+
40
+ ## Prompts
41
+
42
+ Dino includes reusable slash-command prompts for common verification workflows.
43
+
44
+ ### Choosing a browser automation prompt
45
+
46
+ For browser-based tasks, **prefer the `playwright-cli` prompts**. They are more token-efficient because the agent reads compact terminal output instead of full MCP tool-result payloads.
47
+
48
+ **Use the Playwright MCP prompts only as a fallback** when the `playwright-cli` prompt fails, the page cannot be reached via the CLI, or you need richer live-browser introspection.
49
+
50
+ | Prompt | What it does | Example usage |
51
+ |--------|--------------|---------------|
52
+ | `/create-pr` | Create a GitHub pull request for changes in the current workspace, installing `gh` if needed. | `/create-pr` |
53
+ | `/generate-bdd-from-url` | Inspect a page and generate exhaustive Gherkin BDD scenarios for that page only. | `/generate-bdd-from-url https://example.com tests/features` |
54
+ | `/generate-pom-from-url-cli` | Generate Playwright Page Object Models from a live URL using `playwright-cli` (**preferred**). | `/generate-pom-from-url-cli https://example.com tests/pom` |
55
+ | `/generate-pom-from-url` | Generate Page Object Models from a live URL using the Playwright MCP bridge (**fallback only**). | `/generate-pom-from-url https://example.com tests/pom` |
56
+ | `/generate-spec-from-feature` | Generate a Playwright test spec from a Gherkin feature file, reusing existing POMs. | `/generate-spec-from-feature https://example.com tests/features/login.feature tests/pom tests/specs` |
57
+ | `/heal-playwright-failures` | Run the Playwright suite, detect failures, diagnose root causes, apply safe fixes, and re-run to verify. | `/heal-playwright-failures` or `/heal-playwright-failures tests/login.spec.ts 3` |
58
+ | `/setup-playwright` | Set up a vanilla Playwright project in the current workspace with TypeScript, config, and a starter test. | `/setup-playwright` |
59
+
60
+ ## Prerequisite
61
+
62
+ Dino requires the `pi` CLI to be installed. If `pi` is not present, install it globally via npm:
63
+
64
+ ```bash
65
+ npm install -g @earendil-works/pi-coding-agent
66
+ ```
67
+
68
+ Verify the installation:
69
+
70
+ ```bash
71
+ pi --version
72
+ ```
73
+
74
+ ## Install
75
+
76
+ Dino is installed **per-project** using the `-l` flag. This records the package entry in the project's `.pi/settings.json` instead of the global `~/.pi/agent/settings.json`, so Dino is only loaded when you start `pi` inside that project.
77
+
78
+ ### Why install locally instead of globally?
79
+
80
+ We recommend installing Dino as a **local** pi package rather than a global one for two important reasons:
81
+
82
+ 1. **Avoid overwriting other pi agents.** Installing Dino globally would replace the active global `pi` agent, which can overwrite or conflict with existing pi packages. Installing locally keeps Dino scoped to the current project without affecting your global `pi` setup or other agents.
83
+
84
+ 2. **Allow postinstall scripts to run.** Dino relies on npm `postinstall` scripts to set up required runtime dependencies — for example, installing the Playwright browser binaries and writing extension/configuration files needed by the test verification agent. When Dino is installed locally in a project, its own `npm install` runs fully and these postinstall steps execute as intended. Global installation paths or shared package managers may suppress or skip these scripts, leaving browser tooling incomplete.
85
+
86
+ In short, a local install keeps Dino isolated to the project that needs it, protects your other `pi` agents, and ensures the Playwright-based verification tooling is fully configured.
87
+
88
+ ### Example: install Dino into a `YourTesting` project
89
+
90
+ Assume you are starting fresh with an empty project folder. The goal is to have this layout:
91
+
92
+ ```
93
+ ~/work/
94
+ ├── YourTesting/ # your project
95
+ └── dino/ # Dino package clone
96
+ ```
97
+
98
+ 1. Create your project folder and navigate into it:
99
+
100
+ ```bash
101
+ mkdir -p ~/work/YourTesting
102
+ cd ~/work/YourTesting
103
+ ```
104
+
105
+ 2. Clone the Dino repository next to your project folder:
106
+
107
+ ```bash
108
+ git clone https://github.com/testdino-hq/dino.git ../dino
109
+ ```
110
+
111
+ Your folder structure should now look like:
112
+
113
+ ```
114
+ ~/work/
115
+ ├── YourTesting/
116
+ └── dino/
117
+ ```
118
+
119
+ 3. Install Dino's npm dependencies. Pi does **not** run `npm install` automatically for local-path packages, so you must do this once in the Dino directory:
120
+
121
+ ```bash
122
+ cd ../dino
123
+ npm install
124
+ cd - # returns you to ~/work/YourTesting
125
+ ```
126
+
127
+ This installs Dino's dependencies (including `@playwright/mcp`, `@playwright/cli`, and the MCP SDK) and runs the package `postinstall` script.
128
+
129
+ 4. Install Dino into the current project only:
130
+
131
+ ```bash
132
+ pi install ../dino --approve -l
133
+ ```
134
+
135
+ The `-l` flag tells pi to write the package reference to `.pi/settings.json` inside `YourTesting`.
136
+
137
+ 5. Verify the install. You should now have a `.pi/settings.json` file in your project, and Dino will load automatically the next time you start pi from `~/work/YourTesting`:
138
+
139
+ ```bash
140
+ pi list
141
+ ```
142
+
143
+ > **Note:** If you want to try Dino once without persisting it anywhere, use `--extension` (or `-e`) for a temporary install:
144
+ > ```bash
145
+ > pi -e ../dino
146
+ > ```
147
+
148
+ ## Remove
149
+
150
+ If Dino was installed with the `-l` flag, remove it from the current project's `.pi/settings.json`:
151
+
152
+ ```bash
153
+ pi uninstall dino -l
154
+ ```
155
+
156
+ Or remove the local path entry manually from `.pi/settings.json`.
157
+
158
+ ## Playwright MCP bridge
159
+
160
+ Dino can expose the official Microsoft Playwright MCP server (`@playwright/mcp`) as pi.dev tools. This lets the agent navigate, snapshot, click, fill, and otherwise control a browser — useful for live page inspection when generating Page Object Models or verifying web behavior.
161
+
162
+ ### Setup
163
+
164
+ 1. In the Dino directory, install the package dependencies (this pulls in `@playwright/mcp` and the MCP SDK and also installs the Chromium browser via the `postinstall` script):
165
+
166
+ ```bash
167
+ cd /path/to/dino
168
+ npm install
169
+ ```
170
+
171
+ 2. If the Chromium binary was not installed by the `postinstall` script, install it manually:
172
+
173
+ ```bash
174
+ npx playwright install chromium
175
+ ```
176
+
177
+ 3. Make sure Dino is installed as a project-local pi package so the extension is loaded (run this from your project directory, e.g. `YourTesting`):
178
+
179
+ ```bash
180
+ pi install ../dino --approve -l
181
+ ```
182
+
183
+ If already installed, reload the package:
184
+
185
+ ```bash
186
+ pi update --extension ../dino
187
+ # or just reinstall
188
+ pi install ../dino --approve -l
189
+ ```
190
+
191
+ 4. Start pi in a project where you want to use the browser tools. On startup you should see:
192
+
193
+ ```
194
+ [playwright-mcp] Registered 24 Playwright MCP tool(s).
195
+ ```
196
+
197
+ ### Available tools
198
+
199
+ Once loaded, tools such as these are available to the model:
200
+
201
+ - `browser_navigate` — navigate to a URL
202
+ - `browser_snapshot` — capture accessibility tree of the current page
203
+ - `browser_click`, `browser_type`, `browser_fill_form` — interact with elements
204
+ - `browser_take_screenshot` — capture a screenshot
205
+ - `browser_console_messages`, `browser_network_requests` — inspect page activity
206
+
207
+ ### Using with the `playwright-pom` skill
208
+
209
+ The `playwright-pom` skill now includes an MCP-driven workflow:
210
+
211
+ 1. User asks for a POM for a page/URL.
212
+ 2. The agent calls `browser_navigate` to open the page.
213
+ 3. The agent calls `browser_snapshot` to inspect the accessibility tree.
214
+ 4. The agent generates a page object using resilient locators (`getByRole`, `getByLabel`, `getByTestId`).
215
+
216
+ See `skills/playwright-pom/SKILL.md` for the full workflow and example.
217
+
218
+ ## mabl MCP bridge
219
+
220
+ Dino can expose the official [mabl MCP server](https://help.mabl.com/hc/en-us/articles/47299375773844-mabl-MCP-overview) as pi.dev tools using OAuth authentication. This lets the agent query mabl workspaces, tests, plans, runs, results, and other mabl resources directly from the conversation.
221
+
222
+ ### Setup
223
+
224
+ 1. In the Dino directory, install the package dependencies (this pulls in `@mablhq/mabl-cli` and the MCP SDK):
225
+
226
+ ```bash
227
+ cd /path/to/dino
228
+ npm install
229
+ ```
230
+
231
+ 2. Make sure Dino is installed as a project-local pi package so the extension is loaded (run this from your project directory, e.g. `YourTesting`):
232
+
233
+ ```bash
234
+ pi install ../dino --approve -l
235
+ ```
236
+
237
+ If already installed, reload the package:
238
+
239
+ ```bash
240
+ pi update --extension ../dino
241
+ # or just reinstall
242
+ pi install ../dino --approve -l
243
+ ```
244
+
245
+ 3. Start pi in a project where you want to use the mabl tools. The extension no longer runs the OAuth check automatically on startup. To check authentication, run the OAuth flow, and load the mabl MCP tools, use the extension command:
246
+
247
+ ```
248
+ /mabl-auth
249
+ ```
250
+
251
+ If you are not already authenticated with mabl, the command runs `mabl auth login --auto` to perform the OAuth flow. Follow the on-screen prompts (open the authorization URL in a browser and approve access). After authentication succeeds you should see:
252
+
253
+ ```
254
+ [mabl-mcp] Registered N mabl MCP tool(s).
255
+ ```
256
+
257
+ > **Note:** `mabl auth login --auto` is designed for headless/agent environments. It captures the OAuth authorization code automatically, but you still need to approve the login in a browser the first time.
258
+
259
+ ### Manual OAuth (if the automatic flow is blocked)
260
+
261
+ If the automatic OAuth flow cannot complete inside pi (for example, because no browser is available), authenticate outside of pi and then run the command inside pi:
262
+
263
+ ```bash
264
+ cd /path/to/dino
265
+ npx @mablhq/mabl-cli auth login --auto
266
+ # approve the login in your browser, then:
267
+ pi start
268
+ ```
269
+
270
+ Once pi is running, invoke `/mabl-auth` to load the mabl MCP tools.
271
+
272
+ ### Available tools
273
+
274
+ Once loaded, the mabl MCP tools exposed by the server become available to the model. Tool names and capabilities depend on the current mabl MCP server version; examples include:
275
+
276
+ - Querying workspaces, applications, environments, and plans
277
+ - Listing tests and test runs
278
+ - Retrieving run results and diagnostics
279
+
280
+ The extension discovers the tool list dynamically from the server, so new tools are automatically available after updating `@mablhq/mabl-cli`.
281
+
282
+ ### Authentication note
283
+
284
+ The extension stores no credentials itself. It relies on the mabl CLI's own OAuth token storage (managed by `@mablhq/mabl-cli`). To sign out, run:
285
+
286
+ ```bash
287
+ npx @mablhq/mabl-cli auth clear
288
+ ```
289
+
290
+ ### Command reference
291
+
292
+ - `/mabl-auth` — Check mabl OAuth status, run `mabl auth login --auto` if needed, and start the mabl MCP server so the model can use the mabl tools.
293
+
@@ -0,0 +1,70 @@
1
+ import type { ExtensionAPI, Theme } from "@earendil-works/pi-coding-agent";
2
+ import { VERSION } from "@earendil-works/pi-coding-agent";
3
+
4
+ function getDinoHeader(theme: Theme): string[] {
5
+ const purple = (text: string) => theme.fg("accent", text);
6
+ const muted = (text: string) => theme.fg("muted", text);
7
+ const dim = (text: string) => theme.fg("dim", text);
8
+
9
+ // Block-letter ASCII for "DINO"
10
+ const d = ["█████", "█ █", "█ █", "█ █", "█████"];
11
+ const i = [" ███ ", " █ ", " █ ", " █ ", " ███ "];
12
+ const n = ["█ █", "██ █", "█ █ █", "█ ██", "█ █"];
13
+ const o = [" ███ ", "█ █", "█ █", "█ █", " ███ "];
14
+
15
+ const letters = [d, i, n, o];
16
+ const titleLines: string[] = [];
17
+ for (let i = 0; i < 5; i++) {
18
+ titleLines.push(purple(letters.map((l) => l[i]).join(" ")));
19
+ }
20
+
21
+ const dino = [
22
+ " ████████",
23
+ " ██░░░░░░░░██",
24
+ " ██░░░░░░░░░░░░██",
25
+ " █░░░░░░░░░░░░░░░█",
26
+ " █░░░░░░░░░░░░░░░░░█",
27
+ " █░░░░░░░░░░░░░░░░░░░█",
28
+ " █░░░░░░░░░░░░░░░░░░░░█",
29
+ " █░░░░░░░░░░░░░░░░░░░░░░█",
30
+ " █░░░░░░░░░░░░░░░░░░░░░░░█",
31
+ " █░░░░░░░░░░░░░░░░░░░░░░░░░█",
32
+ " █░░░░░░░░░░░░░░░░░░░░░░░░░░░█",
33
+ " █░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█",
34
+ " █░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█",
35
+ " █░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█",
36
+ "███████████████████████████████████████",
37
+ "█░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█",
38
+ "█░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█",
39
+ " ████████████████████████████████████",
40
+ " █ █ █ █",
41
+ " █ █ █ █",
42
+ ];
43
+
44
+ const subtitle = `${muted(" friendly purple dinosaur")}${dim(` v${VERSION}`)}`;
45
+
46
+ return ["", ...titleLines, subtitle, "", ...dino.map(purple), ""];
47
+ }
48
+
49
+ export default function (pi: ExtensionAPI) {
50
+ pi.on("session_start", async (_event, ctx) => {
51
+ if (ctx.mode === "tui") {
52
+ ctx.ui.setHeader((_tui, theme) => {
53
+ return {
54
+ render(_width: number): string[] {
55
+ return getDinoHeader(theme);
56
+ },
57
+ invalidate() {},
58
+ };
59
+ });
60
+ }
61
+ });
62
+
63
+ pi.registerCommand("dino-header", {
64
+ description: "Restore built-in header",
65
+ handler: async (_args, ctx) => {
66
+ ctx.ui.setHeader(undefined);
67
+ ctx.ui.notify("Built-in header restored", "info");
68
+ },
69
+ });
70
+ }
@@ -0,0 +1,306 @@
1
+ /**
2
+ * Bridge the mabl MCP server into pi.dev as custom tools.
3
+ *
4
+ * This extension does NOT run the mabl OAuth check at startup. Instead it
5
+ * registers a `/mabl-auth` command that the user can run explicitly. The
6
+ * command checks authentication, runs `mabl auth login --auto` if needed, and
7
+ * then starts `mabl mcp start` and registers the exposed tools.
8
+ */
9
+ import { createRequire } from "node:module";
10
+ import path from "node:path";
11
+ import { spawn } from "node:child_process";
12
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
13
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
14
+ import type {
15
+ ExtensionAPI,
16
+ ExtensionCommandContext,
17
+ } from "@earendil-works/pi-coding-agent";
18
+ import { Type, type TSchema } from "typebox";
19
+
20
+ const require = createRequire(import.meta.url);
21
+
22
+ function jsonSchemaToTypeBox(schema: unknown): TSchema {
23
+ if (!schema || typeof schema !== "object") {
24
+ return Type.Any();
25
+ }
26
+ const s = schema as Record<string, any>;
27
+
28
+ switch (s.type) {
29
+ case "object": {
30
+ const properties: Record<string, TSchema> = {};
31
+ const required = new Set<string>(s.required ?? []);
32
+ for (const [key, value] of Object.entries(s.properties ?? {})) {
33
+ const child = jsonSchemaToTypeBox(value);
34
+ properties[key] = required.has(key) ? child : Type.Optional(child);
35
+ }
36
+ return Type.Object(properties);
37
+ }
38
+ case "array":
39
+ return Type.Array(jsonSchemaToTypeBox(s.items));
40
+ case "string":
41
+ return Type.String({ description: s.description });
42
+ case "number":
43
+ case "integer":
44
+ return Type.Number({ description: s.description });
45
+ case "boolean":
46
+ return Type.Boolean({ description: s.description });
47
+ case "null":
48
+ return Type.Null();
49
+ default:
50
+ break;
51
+ }
52
+
53
+ if (Array.isArray(s.enum)) {
54
+ return Type.Union(s.enum.map((value: unknown) => Type.Literal(value as any)));
55
+ }
56
+
57
+ return Type.Any({ description: s.description });
58
+ }
59
+
60
+ function runCommand(
61
+ command: string,
62
+ args: string[],
63
+ options?: { stdio?: "inherit" | "pipe"; timeoutMs?: number }
64
+ ): Promise<{ exitCode: number; stdout: string; stderr: string }> {
65
+ return new Promise((resolve) => {
66
+ const stdio = options?.stdio === "inherit" ? "inherit" : "pipe";
67
+ const child = spawn(command, args, {
68
+ stdio: ["inherit", stdio, stdio],
69
+ shell: false,
70
+ windowsHide: true,
71
+ });
72
+
73
+ const stdoutChunks: Buffer[] = [];
74
+ const stderrChunks: Buffer[] = [];
75
+ let killedByTimeout = false;
76
+
77
+ if (child.stdout && stdio === "pipe") {
78
+ child.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
79
+ }
80
+ if (child.stderr && stdio === "pipe") {
81
+ child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
82
+ }
83
+
84
+ const timeout = options?.timeoutMs
85
+ ? setTimeout(() => {
86
+ killedByTimeout = true;
87
+ child.kill("SIGTERM");
88
+ }, options.timeoutMs)
89
+ : undefined;
90
+
91
+ let settled = false;
92
+
93
+ child.on("error", (err) => {
94
+ if (settled) return;
95
+ settled = true;
96
+ if (timeout) clearTimeout(timeout);
97
+ resolve({
98
+ exitCode: -1,
99
+ stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
100
+ stderr: Buffer.concat(stderrChunks).toString("utf-8") || String(err),
101
+ });
102
+ });
103
+
104
+ child.on("exit", (code) => {
105
+ if (settled) return;
106
+ settled = true;
107
+ if (timeout) clearTimeout(timeout);
108
+ resolve({
109
+ exitCode: killedByTimeout ? -1 : code ?? 0,
110
+ stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
111
+ stderr: Buffer.concat(stderrChunks).toString("utf-8"),
112
+ });
113
+ });
114
+ });
115
+ }
116
+
117
+ async function resolveMablBin(): Promise<string | undefined> {
118
+ try {
119
+ const pkgPath = require.resolve("@mablhq/mabl-cli/package.json");
120
+ return path.join(path.dirname(pkgPath), "cli.js");
121
+ } catch {
122
+ return undefined;
123
+ }
124
+ }
125
+
126
+ async function checkAuthentication(
127
+ nodePath: string,
128
+ mablBin: string
129
+ ): Promise<boolean> {
130
+ const infoResult = await runCommand(nodePath, [mablBin, "auth", "info"], {
131
+ timeoutMs: 30000,
132
+ });
133
+ return (
134
+ infoResult.exitCode === 0 &&
135
+ !infoResult.stdout.toLowerCase().includes("not logged in") &&
136
+ !infoResult.stderr.toLowerCase().includes("not logged in")
137
+ );
138
+ }
139
+
140
+ export default async function (pi: ExtensionAPI) {
141
+ let mcpState:
142
+ | {
143
+ transport: StdioClientTransport;
144
+ client: Client;
145
+ toolsRegistered: number;
146
+ }
147
+ | undefined;
148
+ let cleanupRegistered = false;
149
+
150
+ const cleanup = async () => {
151
+ if (!mcpState) return;
152
+ try {
153
+ await mcpState.transport.close();
154
+ await mcpState.client.close();
155
+ } catch {
156
+ // ignore cleanup errors
157
+ }
158
+ mcpState = undefined;
159
+ };
160
+
161
+ pi.registerCommand("mabl-auth", {
162
+ description:
163
+ "Check mabl OAuth status, log in if needed, and load mabl MCP tools.",
164
+ handler: async (_args: string, ctx: ExtensionCommandContext) => {
165
+ const mablBin = await resolveMablBin();
166
+ if (!mablBin) {
167
+ ctx.ui.notify(
168
+ "[mabl-mcp] @mablhq/mabl-cli is not installed. Run `npm install` in the Dino package directory.",
169
+ "error"
170
+ );
171
+ return;
172
+ }
173
+
174
+ const nodePath = process.execPath;
175
+
176
+ if (mcpState) {
177
+ const stillAuthenticated = await checkAuthentication(nodePath, mablBin);
178
+ if (stillAuthenticated) {
179
+ ctx.ui.notify(
180
+ `[mabl-mcp] Already authenticated. ${mcpState.toolsRegistered} mabl MCP tool(s) loaded.`,
181
+ "info"
182
+ );
183
+ } else {
184
+ ctx.ui.notify(
185
+ "[mabl-mcp] MCP server is running but authentication appears expired. Run `/mabl-auth` again after logging in.",
186
+ "warning"
187
+ );
188
+ }
189
+ return;
190
+ }
191
+
192
+ ctx.ui.notify("[mabl-mcp] Checking mabl authentication...", "info");
193
+ let authenticated = await checkAuthentication(nodePath, mablBin);
194
+
195
+ if (!authenticated) {
196
+ ctx.ui.notify(
197
+ "[mabl-mcp] Not authenticated. Starting OAuth login (auto-capture mode)...",
198
+ "info"
199
+ );
200
+ const loginResult = await runCommand(
201
+ nodePath,
202
+ [mablBin, "auth", "login", "--auto"],
203
+ { stdio: "inherit" }
204
+ );
205
+ if (loginResult.exitCode !== 0) {
206
+ ctx.ui.notify(
207
+ `[mabl-mcp] mabl OAuth login failed or was cancelled (exit ${loginResult.exitCode}). Run manually with: npx @mablhq/mabl-cli auth login --auto`,
208
+ "error"
209
+ );
210
+ return;
211
+ }
212
+ authenticated = await checkAuthentication(nodePath, mablBin);
213
+ if (!authenticated) {
214
+ ctx.ui.notify(
215
+ "[mabl-mcp] mabl OAuth login appeared to succeed but is still not authenticated.",
216
+ "error"
217
+ );
218
+ return;
219
+ }
220
+ ctx.ui.notify("[mabl-mcp] mabl OAuth login succeeded.", "info");
221
+ } else {
222
+ ctx.ui.notify("[mabl-mcp] Already authenticated with mabl.", "info");
223
+ }
224
+
225
+ // Start the mabl MCP server and register tools.
226
+ ctx.ui.notify("[mabl-mcp] Starting mabl MCP server...", "info");
227
+ const transport = new StdioClientTransport({
228
+ command: nodePath,
229
+ args: [mablBin, "mcp", "start"],
230
+ stderr: "inherit",
231
+ });
232
+
233
+ const client = new Client({
234
+ name: "dino-mabl-mcp",
235
+ version: "0.1.0",
236
+ });
237
+
238
+ try {
239
+ await client.connect(transport);
240
+ } catch (err) {
241
+ ctx.ui.notify(
242
+ `[mabl-mcp] Could not connect to mabl MCP server: ${err}`,
243
+ "error"
244
+ );
245
+ return;
246
+ }
247
+
248
+ let tools;
249
+ try {
250
+ const result = await client.listTools();
251
+ tools = result.tools;
252
+ } catch (err) {
253
+ ctx.ui.notify(
254
+ `[mabl-mcp] Could not list mabl MCP tools: ${err}`,
255
+ "error"
256
+ );
257
+ return;
258
+ }
259
+
260
+ for (const tool of tools) {
261
+ pi.registerTool({
262
+ name: tool.name,
263
+ label: tool.name,
264
+ description: tool.description ?? ``,
265
+ promptSnippet: `mabl MCP tool: ${tool.description ?? tool.name}`,
266
+ parameters: jsonSchemaToTypeBox(tool.inputSchema),
267
+ async execute(_toolCallId, params) {
268
+ const result = await client.callTool({
269
+ name: tool.name,
270
+ arguments: (params ?? {}) as Record<string, unknown>,
271
+ });
272
+
273
+ const content = (result.content as any[]).map((item: any) => {
274
+ if (item.type === "text") {
275
+ return { type: "text" as const, text: item.text };
276
+ }
277
+ if (item.type === "image") {
278
+ return {
279
+ type: "image" as const,
280
+ data: item.data,
281
+ mimeType: item.mimeType,
282
+ };
283
+ }
284
+ return { type: "text" as const, text: JSON.stringify(item) };
285
+ });
286
+
287
+ return { content, details: result };
288
+ },
289
+ });
290
+ }
291
+
292
+ mcpState = { transport, client, toolsRegistered: tools.length };
293
+ ctx.ui.notify(
294
+ `[mabl-mcp] Registered ${tools.length} mabl MCP tool(s).`,
295
+ "info"
296
+ );
297
+
298
+ if (!cleanupRegistered) {
299
+ cleanupRegistered = true;
300
+ process.on("SIGINT", cleanup);
301
+ process.on("SIGTERM", cleanup);
302
+ pi.on("session_shutdown", cleanup);
303
+ }
304
+ },
305
+ });
306
+ }