@bridgeline-digital/hawk-app-cli 0.1.1-dev.20261021024847 → 0.1.1-dev.20261026155507
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 +188 -24
- package/agents/AGENTS.md +32 -8
- package/claude-plugin/.claude-plugin/marketplace.json +14 -0
- package/claude-plugin/hawk-apps/.claude-plugin/plugin.json +8 -0
- package/claude-plugin/hawk-apps/.mcp.json +8 -0
- package/claude-plugin/hawk-apps/scripts/mcp.mjs +35 -0
- package/claude-plugin/hawk-apps/skills/add-function/SKILL.md +46 -0
- package/claude-plugin/hawk-apps/skills/add-query/SKILL.md +31 -0
- package/claude-plugin/hawk-apps/skills/new-screen/SKILL.md +36 -0
- package/claude-plugin/hawk-apps/skills/prepare-review/SKILL.md +36 -0
- package/docs/bridge/README.md +380 -24
- package/docs/showcase/hawk-app.json +8 -3
- package/docs/showcase/src/App.tsx +255 -1
- package/docs/ui/README.md +7 -0
- package/package.json +12 -1
- package/schema/hawk-app.schema.json +226 -2
- package/src/agents.mjs +38 -7
- package/src/claude-plugin.mjs +67 -0
- package/src/cli.mjs +56 -7
- package/src/deploy.mjs +16 -8
- package/src/dev.mjs +177 -4
- package/src/doctor.mjs +17 -3
- package/src/error-brief.mjs +128 -0
- package/src/function-test-runner.mjs +16 -0
- package/src/function-test.d.ts +51 -0
- package/src/function-test.mjs +303 -0
- package/src/functions.mjs +35 -21
- package/src/manifest.mjs +240 -1
- package/src/mcp-hawksearch.mjs +14 -4
- package/src/mcp.mjs +16 -0
- package/src/platform.mjs +64 -0
- package/src/project-checks.mjs +19 -5
- package/src/scope-check.mjs +9 -2
- package/src/sourcemaps.mjs +168 -0
- package/src/telemetry.mjs +38 -4
- package/src/template-history.json +8 -4
- package/src/test-command.mjs +88 -0
- package/templates/next/next.config.ts +3 -0
- package/templates/next/src/app/page.tsx +6 -0
- package/templates/next-mui/next.config.ts +3 -0
- package/templates/next-mui/src/app/page.tsx +3 -1
- package/templates/preact/src/App.tsx +6 -0
- package/templates/preact/vite.config.ts +3 -0
- package/templates/react/src/App.tsx +6 -0
- package/templates/react/vite.config.ts +3 -0
- package/templates/vanilla/public/app.js +2 -0
package/README.md
CHANGED
|
@@ -68,8 +68,9 @@ separately, see [Clients](#clients)):
|
|
|
68
68
|
// internal: Bridgeline users only; store: the app's clients, and every other
|
|
69
69
|
// client can request it from the store (needs "store", below) (clients: see "Clients")
|
|
70
70
|
"icon": "icon.svg", // the app's icon: an .svg (drawn in the host's color) or a square .png, next to this file
|
|
71
|
-
"scopes": ["reports:read"] // reports:read = analytics; hawksearch:read = dashboard API (read);
|
|
72
|
-
// hawksearch:write = create/update/delete synonyms, redirects, banners,
|
|
71
|
+
"scopes": ["reports:read"] // reports:read = analytics; hawksearch:read = dashboard API (read) and preview tools;
|
|
72
|
+
// hawksearch:write = create/update/delete synonyms, redirects, banners, …,
|
|
73
|
+
// and pin items (hawksearch.pinning)
|
|
73
74
|
// through the dashboard API (see the bridge's "Writing to HawkSearch");
|
|
74
75
|
// data:read = your own SQL queries in queries/ (see "Custom data");
|
|
75
76
|
// functions:invoke = server-side functions (see "Functions");
|
|
@@ -79,6 +80,8 @@ separately, see [Clients](#clients)):
|
|
|
79
80
|
// (with "systemParameters", see "HawkSearch system parameters");
|
|
80
81
|
// hawksearch:search = HawkSearch search and autocomplete, live or preview;
|
|
81
82
|
// hawksearch:recommendations = the engine's recommendation widgets;
|
|
83
|
+
// hawkai:read = HawkAI agents and smart responses: list, run a smart
|
|
84
|
+
// response, open an agent in HawkAI's assistant widget;
|
|
82
85
|
// hawksearch:index = rebuild the engine's index (where HawkSearch allows it)
|
|
83
86
|
// and read its indexes; hawksearch:index:items = add, update, delete items;
|
|
84
87
|
// hawksearch:index:manage = create, switch and delete indexes, hierarchy, mappings;
|
|
@@ -174,8 +177,8 @@ npx hawk-app dev --client e3141 --engine 3141
|
|
|
174
177
|
|
|
175
178
|
The first time, it asks for part of a client's name and lets you pick the client, then saves it in `.env.local` as
|
|
176
179
|
`HAWK_APPS_DEV_CLIENT` (`--client <id>` uses another). A new app is registered on the platform the first time (not
|
|
177
|
-
released: only Bridgeline staff see it). Queries and functions
|
|
178
|
-
deploy after changing them.
|
|
180
|
+
released: only Bridgeline staff see it). Queries and functions called from the app run from the **deployed** version,
|
|
181
|
+
even here: deploy after changing them (or test them locally with `hawk-app test`).
|
|
179
182
|
|
|
180
183
|
Starts your app (`npm run dev`) and a local stand-in for HawkSearch at `http://localhost:3200`, which embeds the app
|
|
181
184
|
the way HawkSearch does (the bridge's own `<hawk-app-frame>`). The app gets **real tokens and real data**: the CLI asks
|
|
@@ -193,7 +196,19 @@ CORS setup, on dev or prod; so does the AI stream (`app.ai.stream`), which the p
|
|
|
193
196
|
`https://dev.hawksearch.net`; `HAWKSEARCH_GROUP_KEY=true` for a group key): the platform seals it into the token. It is
|
|
194
197
|
never written anywhere else.
|
|
195
198
|
- Queries and functions run against your **current deployed version** (deploy first after changing them); the UI is your
|
|
196
|
-
local code. Every dev token is audit-logged with your publisher key.
|
|
199
|
+
local code. Every dev token is audit-logged with your publisher key. To run functions from your local code, use
|
|
200
|
+
`hawk-app test` ([Testing functions](#testing-functions-hawk-app-test)).
|
|
201
|
+
- **Actions:** when `hawk-app.json` has `actions`, the toolbar's **Action** menu opens an action's view in a dialog as
|
|
202
|
+
HawkSearch will (type the value its targets would read from the page); `app.action.done()` closes it.
|
|
203
|
+
- **Export as PDF:** the toolbar has HawkSearch's PDF icon. It shows while the page allows it (`app.setPdfExport` /
|
|
204
|
+
`usePdfExport`; the events list logs it), and a click downloads the page as a PDF.
|
|
205
|
+
- **Explain this page:** the toolbar has HawkSearch's **Explain this page** button. It shows once the page says what
|
|
206
|
+
it shows (`app.assistant.setContext` / `usePageContext`; the events list logs each one), and explains it with the
|
|
207
|
+
platform's AI for the client.
|
|
208
|
+
- **Client API:** `http://localhost:3200/v1/clients/<id>/apps/<slug>/api/<name>` (with `X-HawkSearch-ApiKey`) runs the
|
|
209
|
+
endpoint's function from your **local** `functions/` (bundled on each call), with a real token for the client whose
|
|
210
|
+
HawkSearch connection is the key you send. The platform's own checks (the key's engine, API access, the limit) are
|
|
211
|
+
skipped; secrets and config are empty.
|
|
197
212
|
|
|
198
213
|
## Deploy
|
|
199
214
|
|
|
@@ -215,7 +230,7 @@ npx hawk-app deploy --dry-run # what hawk-app.json would change, without a v
|
|
|
215
230
|
| Command | What it does |
|
|
216
231
|
|---|---|
|
|
217
232
|
| `init` | Creates `hawk-app.json` (never overwrites an existing one) and adds [AGENTS.md](#ai-coding-assistants-agentsmd). |
|
|
218
|
-
| `validate [--build [<folder>]]` | Checks `hawk-app.json` locally, `queries/` and `functions/` if the app has them, and the project: the bridge is used and current, and there's a build script (or `--no-build` deploys). `--build` also builds the app with a stand-in base path (`/<slug>/v0`) and fails on absolute URLs outside it; the folder comes from the `app:deploy` script unless given. |
|
|
233
|
+
| `validate [--build [<folder>]]` | Checks `hawk-app.json` locally, `queries/` and `functions/` if the app has them, and the project: the bridge is used and current, the app says what its page shows ("Explain this page"), and there's a build script (or `--no-build` deploys). `--build` also builds the app with a stand-in base path (`/<slug>/v0`) and fails on absolute URLs outside it; the folder comes from the `app:deploy` script unless given. |
|
|
219
234
|
| `deploy [<folder>] [--no-build] [--no-audit] [--alias <name>] [--dry-run]` | Sends hawk-app.json first (its scopes and settings go with every deploy), then reserves the next version number, runs `npm run build` with `HAWK_APP_BASE_PATH=/<slug>/v<n>` (when package.json has a `build` script, unless `--no-build`), then uploads every file in the build folder (the template's, `dist`, `out` or `public`, unless given) to `/<slug>/v<n>/` on the apps CDN, plus `queries/` and the bundled `functions` (if any) to the platform's private storage, and deploys the functions. Runs `npm audit` of the production dependencies first, asks before deploying with high or critical findings, and reports the counts to reviewers (see [Dependency check](#dependency-check); `--no-audit` skips it). Warns when the HTML loads absolute URLs outside `/<slug>/v<n>/`, which wouldn't load, and when the code calls a bridge feature whose scope `hawk-app.json` lacks (e.g. `app.ai.generate` without `ai:invoke`; `validate` and `doctor` check it too). Says whether the version is a draft (staff only until it's approved) or auto-approved, and when it cleared an approval request. `--alias staging` points the alias at the new version (see [Versions and aliases](#versions-and-aliases)). `--dry-run` only shows what hawk-app.json would change. |
|
|
220
235
|
| `register` | Sends `hawk-app.json` without a new version (for scripts; `deploy` and `dev` already send it). It never releases anything and never changes the app's clients. On a released app, changes wait for review. |
|
|
221
236
|
| `versions` | Every version with its status (draft, waiting, approved, rejected) and the aliases on it. |
|
|
@@ -294,10 +309,30 @@ HawkSearch). The full picture, with diagrams: docs/versions-and-aliases.md in th
|
|
|
294
309
|
The bridge reports the app's page errors and one open per page load (platform/docs/telemetry.md):
|
|
295
310
|
|
|
296
311
|
```bash
|
|
297
|
-
npx hawk-app errors # the last 7 days (--days 1-30): grouped, the most frequent first, with versions and
|
|
312
|
+
npx hawk-app errors # the last 7 days (--days 1-30): grouped, the most frequent first, with versions, clients and an [id]
|
|
313
|
+
npx hawk-app errors --explain 3fa1c2d9 # one error's brief, then Claude's likely cause and patch (--no-ai: the brief only)
|
|
298
314
|
npx hawk-app usage # the last 30 days (--days 1-400): opens, view opens, users and clients; staff apart
|
|
299
315
|
```
|
|
300
316
|
|
|
317
|
+
**Errors that come with a fix.** `--explain <id>` writes the error's **brief** and asks Claude for the cause:
|
|
318
|
+
|
|
319
|
+
- **The stack, mapped to your source:** each of the app's frames as `src/app/page.tsx:42:17`, with the code around it
|
|
320
|
+
and the line that threw marked `>`. The maps are the version's own, so the code is what ran, not what's in your
|
|
321
|
+
checkout now. Library frames are listed on one line each.
|
|
322
|
+
- **The platform calls that failed** in the same versions and days (route, status, code, detail), often the reason
|
|
323
|
+
(e.g. a report that answered 502 left `rows` undefined).
|
|
324
|
+
- **What changed since the last version without it:** a diff of the files in the stack between that version and the
|
|
325
|
+
one that threw.
|
|
326
|
+
- **Then Claude** (the platform's, on its account, no scope or key needed) answers with the likely cause, a patch as a
|
|
327
|
+
unified diff, and how to check it. Check the patch before you apply it; nothing is changed for you.
|
|
328
|
+
- **In Claude Code,** the MCP server's `error_brief` tool gives it the same brief, so it can fix the code in your repo
|
|
329
|
+
directly (ask: "fix error 3fa1c2d9").
|
|
330
|
+
|
|
331
|
+
It needs the version's **source maps**: the templates build them (`productionBrowserSourceMaps` in next.config.ts,
|
|
332
|
+
`build.sourcemap: 'hidden'` in vite.config.ts), and `hawk-app deploy` sends every `.map` file of the build to the
|
|
333
|
+
platform's private bucket instead of the CDN, so your source is never public. Without maps the stack stays minified and
|
|
334
|
+
the brief says so. Errors of versions deployed before keep their minified stacks; the next deploy maps the new ones.
|
|
335
|
+
|
|
301
336
|
## Accessibility check
|
|
302
337
|
|
|
303
338
|
`hawk-app deploy` runs axe-core (WCAG 2.1 A/AA and best practices) over up to 10 built pages and sends the result with
|
|
@@ -338,14 +373,40 @@ instead of guessing:
|
|
|
338
373
|
what fails review. Its project section matches the app's template, or, for an app set up with `init`, says what the
|
|
339
374
|
project has to do itself.
|
|
340
375
|
- **`CLAUDE.md`** with `@AGENTS.md`, so Claude Code reads the same file.
|
|
341
|
-
|
|
342
|
-
- **`.mcp.json`** (Claude Code) and **`.vscode/mcp.json`** (VS Code / Copilot), which start the CLI's MCP server (see
|
|
343
|
-
below) from `node_modules`.
|
|
376
|
+
- **`.vscode/mcp.json`** (VS Code / Copilot), which starts the CLI's MCP server (see below) from `node_modules`.
|
|
344
377
|
|
|
345
378
|
The CLI owns the part of AGENTS.md between its `<!-- hawk-app:start … -->` and `<!-- hawk-app:end -->` markers, and
|
|
346
379
|
`npx hawk-app upgrade` refreshes it when a new CLI changes it. Write your own notes outside the markers; they're kept.
|
|
347
|
-
An existing AGENTS.md or CLAUDE.md is never replaced: the CLI's part (or the import) is added to it. An existing
|
|
348
|
-
|
|
380
|
+
An existing AGENTS.md or CLAUDE.md is never replaced: the CLI's part (or the import) is added to it. An existing
|
|
381
|
+
`.vscode/mcp.json` gets the `hawk-apps` server added (one with comments is left alone: add it yourself).
|
|
382
|
+
|
|
383
|
+
### Claude Code plugin (`hawk-app claude`)
|
|
384
|
+
|
|
385
|
+
Claude Code gets the MCP server, and skills for the common jobs, from the **Hawk Apps plugin**, installed once per
|
|
386
|
+
machine (not per app):
|
|
387
|
+
|
|
388
|
+
```bash
|
|
389
|
+
npx @bridgeline-digital/hawk-app-cli@latest claude
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
It copies the plugin, which ships in this package, next to your login (`%APPDATA%\hawk-app\claude-plugin` on Windows,
|
|
393
|
+
`~/.config/hawk-app/claude-plugin` elsewhere) and adds it to Claude Code as a local marketplace with `claude plugin`
|
|
394
|
+
when that command is on the PATH. Otherwise it prints the two commands to type in the Claude Code panel:
|
|
395
|
+
`/plugin marketplace add <folder>` and `/plugin install hawk-apps@hawk-apps`. No repository access is needed. Run it
|
|
396
|
+
again to update: the plugin carries the CLI's version, so Claude Code takes the new one (`/reload-plugins` in an open
|
|
397
|
+
session). Engineers can also add the repository itself as the marketplace (`.claude-plugin/marketplace.json` at its root).
|
|
398
|
+
|
|
399
|
+
| Skill | What it does |
|
|
400
|
+
| --- | --- |
|
|
401
|
+
| `/hawk-apps:new-screen` | A new page, view or panel from the UI kit: its components, the data's bridge calls and scopes, the states every screen needs, then `validate`. |
|
|
402
|
+
| `/hawk-apps:add-query` | A query in `queries/`: report first if one fits, tables and columns from `data_tables`/`data_describe`, the partitions rule, the params file, `validate`; client rows never come into the chat. |
|
|
403
|
+
| `/hawk-apps:add-function` | A function, job or schedule: the declaration, the handler, secrets set by you in a terminal, errors, `validate`. |
|
|
404
|
+
| `/hawk-apps:prepare-review` | The approvers' checks (bundle, dependencies, accessibility, outside origins, scopes, queries, functions): fixes them or says what's left. |
|
|
405
|
+
|
|
406
|
+
Claude also picks a skill on its own when your request matches it. The plugin's MCP server starts in the open folder,
|
|
407
|
+
from the app's own CLI (`node_modules`) when it has one, else the released CLI through `npx`. The CLI no longer writes
|
|
408
|
+
`.mcp.json` for Claude Code; `upgrade` takes the old `hawk-apps` entry out of it (other servers are kept), so the tools
|
|
409
|
+
aren't listed twice.
|
|
349
410
|
|
|
350
411
|
### MCP server (`hawk-app mcp`)
|
|
351
412
|
|
|
@@ -360,6 +421,7 @@ Protocol](https://modelcontextprotocol.io)), so they can look things up and chec
|
|
|
360
421
|
| `doctor` | `hawk-app doctor`: what's wrong with the setup, and how to fix it. |
|
|
361
422
|
| `versions`, `why` | Every version with its status and aliases; what a client sees of the app, and why. |
|
|
362
423
|
| `errors`, `usage` | The app's page errors; its opens, users and clients per day. |
|
|
424
|
+
| `error_brief` | One error's brief (`errors --explain <id> --no-ai`): the stack mapped to the source with the code, the failed platform calls, what changed since the last version without it. For fixing it in the repo. |
|
|
363
425
|
| `ui_components` | The UI kit's components for the app's template (next-mui: the provider, components and theme; the others: the stylesheet classes and elements), or what's said about one, from the kit's README, with a link to the live [gallery](https://developers.hawksearchapps.com/reference/ui-kit-gallery/). |
|
|
364
426
|
| `analytics_reports` | The analytics API's reports for `app.analytics.report()`, with one report's columns, filters and an example call, read from the developer portal's dev spec (public; no client data). |
|
|
365
427
|
| `hawksearch_api` | HawkSearch's API reference (developerdocs.hawksearch.com): an operation's parameters, request and response fields, the bridge call (`app.dashboard.*`, `app.hawksearch.*`) and its scope, or why apps can't call it (public; no client data). |
|
|
@@ -369,17 +431,17 @@ Protocol](https://modelcontextprotocol.io)), so they can look things up and chec
|
|
|
369
431
|
| `deploy` | `hawk-app deploy` (the folder is optional; `alias`, and `dryRun` to only show what hawk-app.json would change). Marked destructive, so the assistant asks you first. |
|
|
370
432
|
| `clients`, `clients_add` | The app's clients, by id. Say "deploy to OTC": `clients_add` finds the matching clients and shows *you* a form to pick one; the assistant only hears whether a client was added. Marked destructive too. Removing clients isn't a tool. |
|
|
371
433
|
|
|
372
|
-
It also has **prompts**, ready-made tasks that use the tools
|
|
373
|
-
|
|
434
|
+
It also has **prompts**, ready-made tasks that use the tools (in Claude Code, the plugin's skills cover the same
|
|
435
|
+
jobs; its slash commands list the prompts too, under the plugin's server):
|
|
374
436
|
|
|
375
|
-
| Prompt |
|
|
376
|
-
| --- | --- |
|
|
377
|
-
| `check-before-submit` |
|
|
378
|
-
| `add-feature` |
|
|
379
|
-
| `explain-app` |
|
|
380
|
-
| `deploy-to-client` |
|
|
437
|
+
| Prompt | What it does |
|
|
438
|
+
| --- | --- |
|
|
439
|
+
| `check-before-submit` | Validates (with the build check), reads the status and review checks, and explains how to fix what an approver would have to force approve. |
|
|
440
|
+
| `add-feature` | Finds the bridge calls, scopes and showcase examples it needs, says what the review will see, makes the change and validates it. |
|
|
441
|
+
| `explain-app` | What the app does, its scopes (unused or missing ones too) and where it stands in review. |
|
|
442
|
+
| `deploy-to-client` | Deploys to dev and gives a client the app; you pick the client from the matches for its name. |
|
|
381
443
|
|
|
382
|
-
Each takes an optional `dir` for a workspace with several apps. In VS Code with Copilot they're under
|
|
444
|
+
Each takes an optional `dir` for a workspace with several apps. In VS Code with Copilot they're under `/`.
|
|
383
445
|
|
|
384
446
|
- **Dev only.** Every tool runs with `--env dev`; nothing reaches prod. Approvals, secrets, removing clients and logins
|
|
385
447
|
aren't tools.
|
|
@@ -392,9 +454,10 @@ Each takes an optional `dir` for a workspace with several apps. In VS Code with
|
|
|
392
454
|
critical vulnerabilities) the tool stops and tells the assistant, which has to ask you and call again with `force`.
|
|
393
455
|
- **Several apps in one workspace?** Each tool takes `dir`, the app's folder relative to the workspace.
|
|
394
456
|
|
|
395
|
-
`create` and `init` add it to `.
|
|
457
|
+
`create` and `init` add it to `.vscode/mcp.json`, started as
|
|
396
458
|
`node node_modules/@bridgeline-digital/hawk-app-cli/bin/hawk-app.mjs mcp` (the CLI must be a dev dependency;
|
|
397
|
-
`validate` warns when it isn't). For Cursor,
|
|
459
|
+
`validate` warns when it isn't); Claude Code gets it from the plugin (above). For Cursor, add the same command to
|
|
460
|
+
`.cursor/mcp.json` under `mcpServers`.
|
|
398
461
|
|
|
399
462
|
## Clients
|
|
400
463
|
|
|
@@ -614,6 +677,107 @@ with 6 fields (`minutes hours day-of-month month day-of-week year`, no `cron( )`
|
|
|
614
677
|
scope. `schedules` is a setting sent with each deploy: on a released app a change waits for review like scopes,
|
|
615
678
|
and `hawk-app status` lists the registered schedules.
|
|
616
679
|
|
|
680
|
+
`events` run a job function when something happens for a client, so the app doesn't need a schedule to notice it.
|
|
681
|
+
`client.added` runs when a client's users get the app (a store request approved, the client added to the app's list,
|
|
682
|
+
or the app released to the clients on its list), `config.changed` when a config value changes for a client (its own,
|
|
683
|
+
or a global one), `version.released` when another version reaches a client that had the app, and, read from HawkSearch
|
|
684
|
+
once a minute, `hawksearch.changed` (a dashboard change; `"sections": ["synonym", …]` to follow only some) and
|
|
685
|
+
`index.rebuilt` (a rebuild finished):
|
|
686
|
+
|
|
687
|
+
```jsonc
|
|
688
|
+
"functions": { "welcome": { "job": true }, "migrate": { "job": true } },
|
|
689
|
+
"events": { "client.added": { "function": "welcome" }, "version.released": { "function": "migrate" } }
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
The run is a job for that client, without a site or HawkSearch connection, except the HawkSearch events, which get the
|
|
693
|
+
engine and the client's connection (the bridge README's **Events** section has the details). `hawk-app validate` checks the event names, that each `function` is a job function of the same
|
|
694
|
+
`hawk-app.json`, and the `functions:invoke` scope; `events` is reviewed like scopes, and `hawk-app status` lists them.
|
|
695
|
+
|
|
696
|
+
`api` turns functions into endpoints the client's own systems call (a developer's script, a job on their side) with
|
|
697
|
+
their engine's HawkSearch site API key, once Bridgeline turns API access on for the client in HawkSearch (the app's
|
|
698
|
+
kebab menu › API access, which shows each endpoint's URL and a curl):
|
|
699
|
+
|
|
700
|
+
```jsonc
|
|
701
|
+
"functions": { "design-html": {} },
|
|
702
|
+
"api": { "design-html": { "function": "design-html", "method": "GET", "description": "A saved design's email HTML" } }
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
The function isn't a job (the caller waits); its input is the query string (GET) or the JSON body (POST), and
|
|
706
|
+
`apiResponse()` from the bridge's `/server` entry answers HTML or any other type (the bridge README's **Client API**
|
|
707
|
+
section). `hawk-app validate` checks the names (at most 10), that each `function` is a function of the same
|
|
708
|
+
`hawk-app.json` without `"job": true`, the method (GET or POST) and the `functions:invoke` scope; `api` is reviewed like
|
|
709
|
+
scopes, and `hawk-app status` lists the endpoints.
|
|
710
|
+
|
|
711
|
+
### Actions (start the app from HawkSearch's screens)
|
|
712
|
+
|
|
713
|
+
`actions` put an app where the work is: a button in HawkSearch's own pages that opens one of the app's views in a
|
|
714
|
+
dialog, with values read from the page as the view's params. The app says where with **targets**: the page (a regular
|
|
715
|
+
expression on its path), a CSS selector for the things (one button per match), where the button goes, and how each
|
|
716
|
+
param is read:
|
|
717
|
+
|
|
718
|
+
```jsonc
|
|
719
|
+
"views": { "fix": { "path": "views/fix", "title": "Fix a search", "params": ["keyword"] } },
|
|
720
|
+
"actions": {
|
|
721
|
+
"fix-keyword": {
|
|
722
|
+
"label": "Fix with Zero Results Fixer",
|
|
723
|
+
"view": "fix",
|
|
724
|
+
"targets": { "hawksearch": [{
|
|
725
|
+
"path": "/admin/advanced-analytics/top-queries/?$",
|
|
726
|
+
"selector": "bl-table tbody.table__body > tr.table__row",
|
|
727
|
+
"anchor": "td.table__cell:first-child",
|
|
728
|
+
"params": { "keyword": { "selector": "td.table__cell:first-child" } }
|
|
729
|
+
}] }
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
Deploy; once the version is approved, the buttons appear in HawkSearch with no change there. `actions` always needs an
|
|
735
|
+
approver (never auto-approve): reviewers see each target and what it reads. Pick stable selectors (ids, `data-*`
|
|
736
|
+
attributes, element names): if HawkSearch changes its markup, deploy new targets. A param can also name a `data-*`
|
|
737
|
+
attribute (`"data": "hawk-keyword"`), used as is when the page has it, with its selector and pattern as the fallback. The view reads
|
|
738
|
+
`app.context.view.params` and `app.context.view.action`, and calls `app.action.done({ changed })` to close the dialog
|
|
739
|
+
(the bridge README's **Actions**, with every target setting). `hawk-app validate` checks the targets (paths and
|
|
740
|
+
patterns are regular expressions, params the view declares); `hawk-app status` lists the actions. `hawk-app dev` has an
|
|
741
|
+
**Action** menu: pick an action, type a value, and the view opens in a dialog as in HawkSearch. For complete recipes
|
|
742
|
+
(actions, schedules, events, a client API endpoint and their tests), see [docs/examples.md](../../docs/examples.md).
|
|
743
|
+
|
|
744
|
+
### Testing functions (`hawk-app test`)
|
|
745
|
+
|
|
746
|
+
Functions can be tested without deploying. Put tests next to them as `functions/<name>.test.ts` (Node's own
|
|
747
|
+
`node:test` and `node:assert`: nothing to install) and run `npx hawk-app test` (or `npm run app:test`):
|
|
748
|
+
|
|
749
|
+
```ts
|
|
750
|
+
// functions/design-html.test.ts
|
|
751
|
+
import assert from 'node:assert/strict';
|
|
752
|
+
import { test } from 'node:test';
|
|
753
|
+
import { runFunction } from '@bridgeline-digital/hawk-app-cli/testing';
|
|
754
|
+
|
|
755
|
+
test('answers the design as JSON', async () => {
|
|
756
|
+
const run = await runFunction('design-html', {
|
|
757
|
+
trigger: 'api', // call (default), job, schedule, event or api
|
|
758
|
+
input: { designId: 'spring' },
|
|
759
|
+
storage: { 'design#spring': design }, // storage in memory
|
|
760
|
+
answers: [{ method: 'POST', path: 'hawksearch/recommendations', body: recommendations }],
|
|
761
|
+
});
|
|
762
|
+
assert.equal(run.error, null, run.logs);
|
|
763
|
+
assert.equal(run.result.subject, 'New for spring');
|
|
764
|
+
});
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
- `runFunction(name, options)` bundles the function exactly as `hawk-app deploy` does and runs it in its own Node
|
|
768
|
+
process with the event the platform sends (`input`, `trigger`, `client`, `engine`, `user`, `secrets`,
|
|
769
|
+
`clientSecrets`, `config`), with its `timeoutSeconds`. It answers `{ result, error, calls, progress, storage, logs }`:
|
|
770
|
+
what it returned or threw, every platform call it made (with its body and what answered it), `ctx.job.progress`
|
|
771
|
+
reports, the storage afterwards, and its console output.
|
|
772
|
+
- Its platform calls go to a fake platform: `answers` first, then storage and config in memory (when the test gives
|
|
773
|
+
`storage` or `config`), then a **recording** (`recording: 'spring'` reads `functions/__recordings__/spring.json`).
|
|
774
|
+
A call nothing answers fails with 501 `not_recorded`.
|
|
775
|
+
- `hawk-app test --record` makes the recordings: calls nothing else answers go to the platform once, as the test's
|
|
776
|
+
`client` (and `engine`; `HAWKSEARCH_API_KEY` for HawkSearch), and are saved. Recordings hold real answers: review them
|
|
777
|
+
before committing. They are never deployed with the function's source.
|
|
778
|
+
- `hawk-app test <filter>` runs only the test files whose path contains the filter. Tests aren't functions: they are
|
|
779
|
+
never deployed as one.
|
|
780
|
+
|
|
617
781
|
## Email
|
|
618
782
|
|
|
619
783
|
Apps with the `email:send` scope can send email with `app.email.send(...)` or, from a function,
|
|
@@ -690,7 +854,7 @@ with it is never auto-approved. Prompts and answers are never logged or stored.
|
|
|
690
854
|
the Apps page (see [Versions and aliases](#versions-and-aliases)).
|
|
691
855
|
- **Released apps.** Every deploy is a new version. Clients stay on the approved version until the new one is approved in
|
|
692
856
|
HawkSearch (staff can open it there first). Changes to settings in `hawk-app.json` (name, description,
|
|
693
|
-
icon, category, version, visibility, hosting, scopes, `systemParameters`, `schedules`, `config`, `externalOrigins`, `listed`, `minProtocolVersion`) wait for approval
|
|
857
|
+
icon, category, version, visibility, hosting, scopes, `systemParameters`, `schedules`, `events`, `api`, `actions`, `config`, `externalOrigins`, `listed`, `minProtocolVersion`) wait for approval
|
|
694
858
|
the same way. For `config` only the declarations are reviewed: its values are set at any time without review.
|
|
695
859
|
- **Functions.** Engineering enables functions per app. A version whose functions changed (added, removed, new code,
|
|
696
860
|
dependencies or settings) always waits for review, even with auto-approve; reviewers see each function's settings,
|
package/agents/AGENTS.md
CHANGED
|
@@ -50,11 +50,14 @@ app.onContextChange(render); // the user switched client, engine or dates
|
|
|
50
50
|
| Call | Scope |
|
|
51
51
|
| --- | --- |
|
|
52
52
|
| `app.analytics.report('queries', { limit, startDate, endDate, filters })` | `reports:read` |
|
|
53
|
-
| `app.dashboard.get('Synonym')`: HawkSearch dashboard API, no API version in the path | `hawksearch:read` |
|
|
53
|
+
| `app.dashboard.get('Synonym')`: HawkSearch dashboard API, no API version in the path. Typed routes: `app.dashboard.resource('Synonym').list()/get(id)` (reads) and `.create/update/delete` (`hawksearch:write`), models as `HawkSearchModel<'Synonym'>` | `hawksearch:read` |
|
|
54
54
|
| `app.dashboard.post/put/delete(path, body)`: merchandising and relevancy resources only, never retried | `hawksearch:write` |
|
|
55
|
-
| `app.hawksearch.
|
|
55
|
+
| `app.hawksearch.pinning.set(itemPinId, docId, isPinned)`, `.order(itemPinId, docIds)`: the Item Pin rule a search applied (its `ItemPinId`), saved as the user | `hawksearch:write` |
|
|
56
|
+
| `app.hawksearch.search(...)`, `app.hawksearch.autocomplete(...)` on the user's engine; preview: `IsInPreview`, `SmartBar`, `ClientData.PreviewBuckets`, `IndexName`, `ExplainDocId` | `hawksearch:search` |
|
|
57
|
+
| `app.hawksearch.preview.settings/indexes/item/explain`: the preview page's visitor targets, indexes, item details, keyword explain | `hawksearch:read` |
|
|
58
|
+
| `app.hawkai.agents/agent/smartResponses/smartResponse`: the client's HawkAI agents and smart responses (names and settings); `runSmartResponse(id, { query, search })`: its answer for a (preview) search; `assistant(agentId)`: the values for HawkAI's assistant widget | `hawkai:read` |
|
|
56
59
|
| `app.data.query('name', params)`: your SQL in `queries/<name>.sql` + `.json` | `data:read` |
|
|
57
|
-
| `app.functions.call('name', input)`, `.start/.wait/.job` for jobs: `functions/<name>.ts` exporting `handler` | `functions:invoke` |
|
|
60
|
+
| `app.functions.call('name', input)`, `.start/.wait/.job` for jobs: `functions/<name>.ts` exporting `handler`. `schedules` and `events` in hawk-app.json run job functions on a cron, or when something happens for a client (`client.added`, `config.changed`, `version.released`, `hawksearch.changed` with optional `sections`, `index.rebuilt`; input types `HawkAppEvent` from the bridge's `/server`). `api` in hawk-app.json makes a function (not a job) an endpoint the client's own systems call with their HawkSearch site API key (`{ "<name>": { "function", "method": "GET" \| "POST" } }`; `apiResponse(body, { contentType })` for HTML). Test functions without deploying: `functions/<name>.test.ts` with `node:test` and `runFunction` from `@bridgeline-digital/hawk-app-cli/testing` (storage, answers, recordings), run by `npx hawk-app test` | `functions:invoke` |
|
|
58
61
|
| `app.email.send(...)` | `email:send` |
|
|
59
62
|
| `app.storage.get/put/delete/list/batch/usage`: the app's own JSON data per client (350 KB a value, `ttlDays`, `ifVersion`); `app.storage.files.upload/url/download/list/delete`: its files (100 MB each) | `storage` |
|
|
60
63
|
| `app.hawksearch.systemParameters.list/get/set`: names listed in `systemParameters` | `hawksearch:sysparams:write` |
|
|
@@ -92,21 +95,23 @@ https://www.npmjs.com/package/@bridgeline-digital/hawk-app-bridge. Its TypeScrip
|
|
|
92
95
|
|
|
93
96
|
## MCP server
|
|
94
97
|
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
The CLI's MCP server, `hawk-apps`: Claude Code gets it from the Hawk Apps plugin (`npx @bridgeline-digital/hawk-app-cli@latest
|
|
99
|
+
claude`, once per machine; it also adds the skills `/hawk-apps:new-screen`, `/hawk-apps:add-query`,
|
|
100
|
+
`/hawk-apps:add-function` and `/hawk-apps:prepare-review`), and VS Code from `.vscode/mcp.json`; both start this app's
|
|
101
|
+
copy in `node_modules` (`npm install` first). Its tools: `docs` (the bridge, UI kit, CLI, `hawk-app.json` schema and the
|
|
97
102
|
showcase app, which has working code for every feature), `ui_components` (the UI kit's components for this app's
|
|
98
103
|
template: use them before writing your own), `validate`, `status` (with the review checks approvers will see),
|
|
99
104
|
`analytics_reports` (every report `app.analytics.report()` can call, with its columns and filters),
|
|
100
105
|
`hawksearch_api` (HawkSearch's dashboard, search, recommendation and indexing operations: fields, the bridge call, its scope),
|
|
101
106
|
`data_tables` and `data_describe` (names and columns, never data), `logs`, `init`, `upgrade`,
|
|
102
|
-
`deploy` (with `alias`, and `dryRun` to only show what hawk-app.json would change), `versions`, `why` (what a client sees, and why), `doctor` (what's wrong with the setup), `errors`, `usage`, and `clients` and `clients_add`, which only ever use the dev platform. Prefer them to guessing. When the
|
|
107
|
+
`deploy` (with `alias`, and `dryRun` to only show what hawk-app.json would change), `versions`, `why` (what a client sees, and why), `doctor` (what's wrong with the setup), `errors`, `error_brief` (one error mapped to the source, with the failed calls and what changed since the last version without it: use it to fix production errors here), `usage`, and `clients` and `clients_add`, which only ever use the dev platform. Prefer them to guessing. When the
|
|
103
108
|
user names a client ("deploy to OTC"), call `clients_add` with their words: it shows them a form to pick the exact
|
|
104
109
|
client. With `alias` (`hawk-app clients add <id> --alias review`) the client gets that alias's version: on a version that
|
|
105
110
|
isn't approved, only Bridgeline staff in it see the app. Only approvers change what a client whose users see the app
|
|
106
111
|
gets. `hawk-app deploy out --alias staging` deploys off the main line: the version carries this hawk-app.json, `staging`
|
|
107
112
|
points at it, and the version under review isn't touched. A plain `hawk-app deploy` sends hawk-app.json first, so scope
|
|
108
113
|
changes go with the deploy. Client names aren't shared with you; don't ask for them in the chat. Its prompts (`check-before-submit`, `add-feature`,
|
|
109
|
-
`explain-app`, `deploy-to-client
|
|
114
|
+
`explain-app`, `deploy-to-client`) are ready-made tasks that use them; in Claude Code prefer the plugin's skills.
|
|
110
115
|
|
|
111
116
|
## Icon
|
|
112
117
|
|
|
@@ -136,6 +141,24 @@ Each view is a route, e.g. `src/app/views/preview/page.tsx` for `"path": "views/
|
|
|
136
141
|
`{ cache: 'no-store' }` on a call that writes (e.g. an analytics report that records something), or
|
|
137
142
|
`{ cache: 'reload' }` to refresh. The full app page is never cached.
|
|
138
143
|
|
|
144
|
+
**Say what each page shows**: HawkSearch's "Explain this page" button explains the app's page from it (no scope).
|
|
145
|
+
With the UI kit: `usePageContext(rows ? { page: '<the page>', dateRange, totals, top10 } : null)`; otherwise
|
|
146
|
+
`app.assistant.setContext({ ... })` whenever the data or filters change. Plain names and numbers of what's on screen,
|
|
147
|
+
at most 50 KB; summarize long tables. `hawk-app validate` warns when an app never sets one.
|
|
148
|
+
|
|
149
|
+
**Export as PDF** (only on pages worth keeping: a report, a summary): `usePdfExport({ fileName })` from the UI kit
|
|
150
|
+
(`false` while loading), or `app.setPdfExport({ fileName })` and `app.setPdfExport(null)` when the page leaves. The
|
|
151
|
+
host shows a PDF icon and downloads the whole page; mark buttons and filters `data-hawk-pdf-ignore`.
|
|
152
|
+
|
|
153
|
+
**Actions** (`actions` in `hawk-app.json`) add a button to HawkSearch's own pages that opens a view in a dialog:
|
|
154
|
+
`{ "<name>": { "label": "Fix with …", "view": "fix", "targets": { "hawksearch": [{ "path": "<regex on the page path>",
|
|
155
|
+
"selector": "<CSS: each thing>", "anchor"?: "<CSS inside it>", "position"?: "after", "params": { "keyword": { "data"?,
|
|
156
|
+
"selector"?, "attribute"?, "pattern"? } } }] } } }`. `"data": "keyword"` reads `data-keyword` as is when the page has it,
|
|
157
|
+
else the rest of the reader applies. Prefer stable selectors (ids, `data-*`, element names); the view must list the
|
|
158
|
+
params in its `params` and handle them missing. The view reads `app.context.view.params` (and `context.view.action`:
|
|
159
|
+
`{ name }`), its toasts show, and `app.action.done({ changed: true })` closes the dialog once the work is saved. Actions
|
|
160
|
+
always need an approver. Try it with `hawk-app dev`'s Action menu.
|
|
161
|
+
|
|
139
162
|
A slot can also hold the **entire app** (its own page, with no `view` in the context): it then runs inside another
|
|
140
163
|
page, at the slot's height, so don't rely on the full window either.
|
|
141
164
|
|
|
@@ -169,7 +192,8 @@ npx hawk-app versions # every version with its status and aliases
|
|
|
169
192
|
npx hawk-app open # the app's page in HawkSearch (review, submit for approval)
|
|
170
193
|
npx hawk-app logs <function> # a function's logs
|
|
171
194
|
npx hawk-app config list # config values (config set <key> <value> [--client <id>], config unset <key>)
|
|
172
|
-
npx hawk-app errors # errors in the app's pages (deployed versions), the most frequent first
|
|
195
|
+
npx hawk-app errors # errors in the app's pages (deployed versions), the most frequent first, with [id]s
|
|
196
|
+
npx hawk-app errors --explain <id> # one error mapped to the source, the failed calls, what changed; then a likely fix
|
|
173
197
|
npx hawk-app usage # opens, users and clients per day
|
|
174
198
|
npx hawk-app upgrade # bring the app (and this file) up to the CLI's current template
|
|
175
199
|
npx hawk-app upgrade --channel latest # a dev app (bridge/CLI on the `dev` tag) to the released versions
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hawk-apps",
|
|
3
|
+
"owner": { "name": "Bridgeline Digital" },
|
|
4
|
+
"metadata": {
|
|
5
|
+
"description": "Hawk Apps for Claude Code: the hawk-apps MCP server and skills for building, checking and shipping Hawk Apps."
|
|
6
|
+
},
|
|
7
|
+
"plugins": [
|
|
8
|
+
{
|
|
9
|
+
"name": "hawk-apps",
|
|
10
|
+
"source": "./hawk-apps",
|
|
11
|
+
"description": "Build Hawk Apps with Claude Code: the hawk-apps MCP server (docs, UI kit, validate, deploy to dev) and skills for new screens, queries, functions and review."
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hawk-apps",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Build Hawk Apps with Claude Code: the hawk-apps MCP server (docs, UI kit, validate, deploy to dev) and skills for new screens, queries, functions and review.",
|
|
5
|
+
"author": { "name": "Bridgeline Digital" },
|
|
6
|
+
"homepage": "https://developers.hawksearchapps.com/start/with-claude/",
|
|
7
|
+
"keywords": ["hawk-apps", "hawksearch"]
|
|
8
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Starts the Hawk Apps MCP server (`hawk-app mcp`) for Claude Code, in the open project: the project's own CLI when it
|
|
3
|
+
// has one (a dev dependency, so the tools match the app's CLI version and channel), else the released CLI through npx.
|
|
4
|
+
// stdio is passed straight through: it is the MCP protocol.
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
6
|
+
import { existsSync } from 'node:fs';
|
|
7
|
+
import { dirname, join, resolve } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
|
|
10
|
+
const CLI_BIN = join('node_modules', '@bridgeline-digital', 'hawk-app-cli', 'bin', 'hawk-app.mjs');
|
|
11
|
+
|
|
12
|
+
/** The CLI installed in the project or a folder above it (a workspace of apps), or null. */
|
|
13
|
+
export function findProjectCli(start) {
|
|
14
|
+
for (let dir = resolve(start); ; dir = dirname(dir)) {
|
|
15
|
+
const bin = join(dir, CLI_BIN);
|
|
16
|
+
if (existsSync(bin)) return bin;
|
|
17
|
+
if (dirname(dir) === dir) return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function run() {
|
|
22
|
+
const project = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
23
|
+
const local = findProjectCli(project);
|
|
24
|
+
const child = local
|
|
25
|
+
? spawn(process.execPath, [local, 'mcp'], { cwd: project, stdio: 'inherit' })
|
|
26
|
+
: spawn('npx', ['--yes', '@bridgeline-digital/hawk-app-cli@latest', 'mcp'], { cwd: project, stdio: 'inherit', shell: process.platform === 'win32' });
|
|
27
|
+
child.on('error', (error) => {
|
|
28
|
+
process.stderr.write(`hawk-apps: could not start the MCP server (${error.message}). Is Node.js installed?\n`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
});
|
|
31
|
+
child.on('exit', (code, signal) => process.exit(signal ? 1 : (code ?? 0)));
|
|
32
|
+
for (const signal of ['SIGINT', 'SIGTERM']) process.on(signal, () => child.kill(signal));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) run();
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: add-function
|
|
3
|
+
description: Add or change a server-side function (functions/<name>.ts, AWS Lambda), a background job, a schedule, an event handler or a client API endpoint in a Hawk App, with tests. Use when the app needs server code, a third-party API with a secret key, work longer than a page should wait, something on a timer, setup when a client gets the app, or data the client's own systems fetch.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Add a function
|
|
7
|
+
|
|
8
|
+
Functions are the app's own server code (Node 22 Lambda), shipped with each version. **Engineering must enable
|
|
9
|
+
functions for the app**, and a version whose functions changed always waits for review. Before writing one, check
|
|
10
|
+
whether the bridge already does it from the page (`docs`): no function is simpler.
|
|
11
|
+
|
|
12
|
+
1. **Scope and declaration.** Tell the user, then add `functions:invoke` to `scopes` and declare the function in
|
|
13
|
+
`hawk-app.json`:
|
|
14
|
+
```jsonc
|
|
15
|
+
"functions": { "sync-feed": { "timeoutSeconds": 20, "memoryMb": 512, "secrets": ["FEED_API_KEY"] } }
|
|
16
|
+
```
|
|
17
|
+
Names: lowercase letters, digits, single dashes, up to 40. `timeoutSeconds` 1–25 (1–900 with `"job": true`),
|
|
18
|
+
`memoryMb` 128–1024, at most 10 functions.
|
|
19
|
+
2. **Write `functions/<name>.ts`** exporting `handler`:
|
|
20
|
+
```ts
|
|
21
|
+
import type { HawkFunction } from '@bridgeline-digital/hawk-app-bridge/server';
|
|
22
|
+
export const handler: HawkFunction<In, Out> = async (input, ctx) => { … };
|
|
23
|
+
```
|
|
24
|
+
Use `ctx.platform` (the same APIs as `app`), `ctx.client.id`, `ctx.user`, `ctx.config`, `ctx.secrets.NAME`. Stateless:
|
|
25
|
+
keep state in `ctx.platform.storage`. Look up `ctx` and the errors with `docs` ("Functions").
|
|
26
|
+
3. **Secrets** are never in code, `hawk-app.json` or the chat. Tell the user to run `npx hawk-app secrets set NAME`
|
|
27
|
+
themselves (hidden input). A value each client sets for itself is a `clientSecrets` entry instead.
|
|
28
|
+
4. **Long work** is a job (`"job": true`, up to 900 s): the app calls `app.functions.start(name, input)` and
|
|
29
|
+
`app.functions.wait(jobId)`. **On a timer** it's a `schedules` entry (a job function, a 6-field EventBridge cron at
|
|
30
|
+
most hourly, an IANA `timezone`) that users turn on per client with `app.schedules.enable(name)`. **When something
|
|
31
|
+
happens for a client** it's an `events` entry (`"<event>": { "function": "<job>" }`): `client.added` (it got the
|
|
32
|
+
app), `config.changed` (its config changed), `version.released` (a new version reached it): these run without a site
|
|
33
|
+
or HawkSearch connection, so they can't call HawkSearch. `hawksearch.changed` (a dashboard change, `sections` to
|
|
34
|
+
narrow it) and `index.rebuilt` come from HawkSearch within 1–2 minutes and run with the engine and connection; keep
|
|
35
|
+
them idempotent.
|
|
36
|
+
5. **Call it** from the app with `app.functions.call('<name>', input)` and handle `function_failed`,
|
|
37
|
+
`function_timeout`, `secret_missing` and `functions_not_enabled`. **For the client's own systems** (a script, a job
|
|
38
|
+
on their side) make it an endpoint instead: `"api": { "<name>": { "function": "<function>", "method": "GET" } }`
|
|
39
|
+
(not a job; input from the query or JSON body; `apiResponse(body, { contentType })` from `/server` for HTML).
|
|
40
|
+
Bridgeline turns API access on per client; the client calls it with its HawkSearch site API key.
|
|
41
|
+
6. **Test it** without deploying: `functions/<name>.test.ts` with `node:test` and `runFunction` from
|
|
42
|
+
`@bridgeline-digital/hawk-app-cli/testing` (input, trigger, `storage` in memory, `answers` for platform calls,
|
|
43
|
+
recordings), then `npx hawk-app test`. A client API endpoint also runs locally under `hawk-app dev` at
|
|
44
|
+
`http://localhost:3200/v1/clients/<id>/apps/<slug>/api/<name>`.
|
|
45
|
+
7. **Check it.** Run `validate`. Called from the app, functions run from the **deployed** version; deploy only when the
|
|
46
|
+
user asks. After a deploy, `logs` shows the function's output.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: add-query
|
|
3
|
+
description: Add or change a SQL query (queries/<name>.sql + .json) that a Hawk App runs on the client's analytics data with app.data.query. Use when the user wants data no analytics report gives, a custom aggregate, or to fix a query in a Hawk App.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Add a query
|
|
7
|
+
|
|
8
|
+
Queries are the app's own SQL, run by the platform (Athena) on the current client's analytics tables. They ship with
|
|
9
|
+
each version and approvers review them. Check first whether an analytics report already gives the data (the hawk-apps
|
|
10
|
+
`analytics_reports` tool): a report needs no SQL and no review.
|
|
11
|
+
|
|
12
|
+
1. **Scope.** `data:read` in `hawk-app.json` `scopes` (elevated: the app is never auto-approved). Tell the user before
|
|
13
|
+
adding it.
|
|
14
|
+
2. **Tables and columns.** `data_tables` (which tables exist, which are partitioned, which are sensitive) and
|
|
15
|
+
`data_describe <table>`. Never guess a column. Sensitive tables (visitor-level data) need Engineering's opt-in:
|
|
16
|
+
say so instead of using one.
|
|
17
|
+
3. **Write `queries/<name>.sql`** next to `hawk-app.json` (not in the build folder). Name: lowercase letters, digits,
|
|
18
|
+
single dashes.
|
|
19
|
+
- one `SELECT` or `WITH … SELECT`, no `;`;
|
|
20
|
+
- a `?` for each parameter, in order (`IN ?` for a `string[]`);
|
|
21
|
+
- every query over dated data filters by a date range **and** has `{{partitions(from, to)}}` (params of type `date`);
|
|
22
|
+
- name every column (no `SELECT *`), and `LIMIT` every list.
|
|
23
|
+
4. **Write `queries/<name>.json`**: `{ "params": [{ "name": "from", "type": "date", "required": true }, …] }`. Types:
|
|
24
|
+
`string`, `int`, `decimal`, `date`, `timestamp`, `boolean`, `string[]`; optional `required`, `default`,
|
|
25
|
+
`min`/`max`, `maxItems`.
|
|
26
|
+
5. **Call it** with `app.data.query('<name>', { from, to })` (or `ctx.platform.data.query` in a function). Look up the
|
|
27
|
+
result shape and errors with `docs` ("data.query"). Handle a client without analytics.
|
|
28
|
+
6. **Check it.** Run `validate`. To try the SQL, tell the user to run
|
|
29
|
+
`npx hawk-app data run queries/<name>.sql --client <test client id> --param from=… --param to=…` in a terminal: its rows
|
|
30
|
+
are client data and must not come into the chat. Queries run only from a **deployed** version, so the app sees a
|
|
31
|
+
new or changed query after a deploy (only when the user asks).
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: new-screen
|
|
3
|
+
description: Build a new screen, page, view or panel in a Hawk App with the Hawk Apps UI kit (Spark's look in the host's colors). Use when the user asks for a new page, tab, dashboard, table, chart, form or preview card in a Hawk App (a folder with hawk-app.json).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# New screen from the UI kit
|
|
7
|
+
|
|
8
|
+
A Hawk App's screens use the Hawk Apps UI kit (`@bridgeline-digital/hawk-app-ui`), so they look like Spark in the host's
|
|
9
|
+
colors and light/dark mode. Never restyle the kit or add another font.
|
|
10
|
+
|
|
11
|
+
1. **Read the app.** `hawk-app.json` (its `template`: `next-mui` uses the kit's React + MUI provider and components;
|
|
12
|
+
`next`, `react`, `preact` and `vanilla` use its stylesheet and custom elements) and the CLI's part of `AGENTS.md`.
|
|
13
|
+
2. **Find the components.** Call the hawk-apps `ui_components` tool for this app's template, then for each component
|
|
14
|
+
you plan to use. Use what the kit has (tables, cards, filters, empty states, dialogs) before writing your own. For
|
|
15
|
+
your own styles use the kit's `--hawk-ui-*` variables.
|
|
16
|
+
3. **Find the data.** For each number or list on the screen, pick the bridge call and look it up with `docs` (options,
|
|
17
|
+
limits, error codes; the showcase sections have working code):
|
|
18
|
+
- analytics reports: `analytics_reports` gives the report's columns and filters;
|
|
19
|
+
- the client's HawkSearch setup or searches: `hawksearch_api`;
|
|
20
|
+
- your own SQL: the **add-query** skill;
|
|
21
|
+
- server work or secrets: the **add-function** skill.
|
|
22
|
+
Tell the user which scopes the screen needs before adding them to `hawk-app.json`.
|
|
23
|
+
4. **Build it** in the app's style:
|
|
24
|
+
- read the client, engine and dates from `app.context` and re-load on `app.onContextChange`;
|
|
25
|
+
- when `app.context.tenant.analyticsProvisioned === false`, show a message instead of calling analytics or queries;
|
|
26
|
+
- loading, empty and error states (`HawkApiError.code`), no page heading (the host shows the app's name);
|
|
27
|
+
- dialogs in the page (never `window.confirm`/`alert`), placed in the visible part of the frame;
|
|
28
|
+
- accessible: labelled fields, `alt` on images, one `<main>`, headings in order.
|
|
29
|
+
5. **A view** (a preview card or an embeddable slot) is a route declared in `views` in `hawk-app.json`: keep it small,
|
|
30
|
+
read stored results, transparent background, height from the content. See the Views section of `AGENTS.md`.
|
|
31
|
+
**Opened from HawkSearch's own pages** (a button by a search term in a report, on an AI Advisor insight…), it's an
|
|
32
|
+
`actions` entry: the view, and `targets` per host (a regex on the page path, CSS selectors, how each param is read;
|
|
33
|
+
the bridge README's **Actions**). The view reads `app.context.view.params`, handles them missing, and calls
|
|
34
|
+
`app.action.done({ changed: true })` when the work is saved. Try it with `npm run app:dev`'s Action menu.
|
|
35
|
+
6. **Check it.** Run the hawk-apps `validate` tool (with `build: true` when the build setup changed) and fix what it
|
|
36
|
+
says. Offer `npm run app:dev` to see it locally. Deploy only when the user asks.
|