@bridgeline-digital/hawk-app-cli 0.1.0-dev.20261020202308 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -7
- package/README.md +770 -770
- package/agents/AGENTS.md +192 -192
- package/agents/project/custom.md +21 -21
- package/agents/project/next-mui.md +21 -21
- package/agents/project/next.md +24 -24
- package/agents/project/vanilla.md +22 -22
- package/agents/project/vite.md +19 -19
- package/bin/hawk-app.mjs +0 -0
- package/docs/bridge/README.md +1315 -1315
- package/docs/showcase/README.md +47 -47
- package/docs/showcase/functions/echo.ts +12 -12
- package/docs/showcase/functions/long-task.ts +31 -31
- package/docs/showcase/functions/showcase.ts +186 -186
- package/docs/showcase/hawk-app.json +73 -73
- package/docs/showcase/queries/assistant-prompts-by-day.json +6 -6
- package/docs/showcase/queries/assistant-prompts-by-day.sql +8 -8
- package/docs/showcase/queries/searches-by-day.json +6 -6
- package/docs/showcase/queries/searches-by-day.sql +7 -7
- package/docs/showcase/src/App.tsx +1436 -1436
- package/docs/showcase/src/main.tsx +10 -10
- package/docs/showcase/src/styles.css +71 -71
- package/docs/showcase/src/useHawkApp.ts +44 -44
- package/docs/showcase/src/vite-env.d.ts +1 -1
- package/docs/ui/README.md +166 -166
- package/package.json +1 -1
- package/schema/hawk-app.schema.json +611 -611
- package/src/accessibility.mjs +105 -105
- package/src/agents.mjs +145 -145
- package/src/aliases.mjs +30 -30
- package/src/app-icon.mjs +82 -82
- package/src/channel.mjs +116 -116
- package/src/cli.mjs +825 -825
- package/src/clients.mjs +221 -221
- package/src/config.mjs +148 -148
- package/src/create.mjs +186 -186
- package/src/credentials.mjs +56 -56
- package/src/data.mjs +169 -169
- package/src/deploy.mjs +349 -349
- package/src/dev.mjs +459 -459
- package/src/doctor.mjs +176 -176
- package/src/env.mjs +140 -140
- package/src/functions.mjs +313 -313
- package/src/login.mjs +166 -166
- package/src/logs.mjs +64 -64
- package/src/manifest.mjs +425 -425
- package/src/mcp-docs.mjs +168 -168
- package/src/mcp-hawksearch.mjs +342 -342
- package/src/mcp-reports.mjs +130 -130
- package/src/mcp-rules.mjs +109 -109
- package/src/mcp.mjs +539 -539
- package/src/platform.mjs +116 -116
- package/src/project-checks.mjs +153 -153
- package/src/queries.mjs +289 -289
- package/src/scope-check.mjs +91 -91
- package/src/secrets.mjs +86 -86
- package/src/slug-guard.mjs +80 -80
- package/src/sso.mjs +182 -182
- package/src/telemetry.mjs +50 -50
- package/src/upgrade.mjs +406 -406
- package/src/versions.mjs +102 -102
- package/templates/next/next.config.ts +13 -13
- package/templates/next/package.json +26 -26
- package/templates/next/src/lib/useHawkApp.ts +46 -46
- package/templates/next-mui/next.config.ts +13 -13
- package/templates/next-mui/package.json +30 -30
- package/templates/next-mui/src/app/layout.tsx +19 -19
- package/templates/next-mui/src/app/page.tsx +78 -78
- package/templates/next-mui/src/app/providers.tsx +10 -10
- package/templates/preact/package.json +26 -26
- package/templates/preact/src/useHawkApp.ts +44 -44
- package/templates/preact/src/vite-env.d.ts +1 -1
- package/templates/preact/tsconfig.json +17 -17
- package/templates/preact/vite.config.ts +11 -11
- package/templates/react/package.json +29 -29
- package/templates/react/src/useHawkApp.ts +44 -44
- package/templates/react/src/vite-env.d.ts +1 -1
- package/templates/react/tsconfig.json +16 -16
- package/templates/react/vite.config.ts +11 -11
- package/templates/vanilla/package.json +15 -15
package/README.md
CHANGED
|
@@ -1,770 +1,770 @@
|
|
|
1
|
-
# @bridgeline-digital/hawk-app-cli
|
|
2
|
-
|
|
3
|
-
Build, run and release **Hawk Apps**, that run inside HawkSearch. Sign in with your Bridgeline account (or a
|
|
4
|
-
**publisher key** in CI): no AWS account, no bucket names, no platform repo.
|
|
5
|
-
|
|
6
|
-
The full developer docs, with guides, the bridge API and every command, are at
|
|
7
|
-
**[developers.hawksearchapps.com](https://developers.hawksearchapps.com)** (sign in with your Bridgeline account;
|
|
8
|
-
`npx hawk-app docs` opens them).
|
|
9
|
-
|
|
10
|
-
## Quickstart
|
|
11
|
-
|
|
12
|
-
```bash
|
|
13
|
-
npx @bridgeline-digital/hawk-app-cli@latest sso # 1. once per machine: sign in with your Bridgeline account
|
|
14
|
-
npx @bridgeline-digital/hawk-app-cli@latest create my-app && cd my-app && npm install # 2. a new app
|
|
15
|
-
npm run app:dev # 3. run it locally inside a stand-in for HawkSearch
|
|
16
|
-
npm run app:deploy # 4. build and upload a new version (a draft)
|
|
17
|
-
npx hawk-app open # 5. review it in HawkSearch and submit it for approval
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
Then `npx hawk-app status` shows where the app stands, and `npx hawk-app doctor` checks everything (Node, login,
|
|
21
|
-
`hawk-app.json`, the project, the scopes the code uses) and says what to fix. `npx hawk-app help` lists the commands by
|
|
22
|
-
task; `npx hawk-app help <command>` gives one command's details.
|
|
23
|
-
|
|
24
|
-
The templates add the CLI as a dev dependency; elsewhere: `npm i -D @bridgeline-digital/hawk-app-cli`.
|
|
25
|
-
|
|
26
|
-
## Start a new app
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
npx @bridgeline-digital/hawk-app-cli@latest create keyword-explorer # React + Vite + TypeScript
|
|
30
|
-
npx @bridgeline-digital/hawk-app-cli@latest create keyword-explorer --template preact # Preact + Vite + TypeScript (small bundle)
|
|
31
|
-
npx @bridgeline-digital/hawk-app-cli@latest create keyword-explorer --template next # Next.js (static export)
|
|
32
|
-
npx @bridgeline-digital/hawk-app-cli@latest create keyword-explorer --template next-mui # Next.js + Material UI (the UI kit's components)
|
|
33
|
-
npx @bridgeline-digital/hawk-app-cli@latest create keyword-explorer --template vanilla # plain HTML/JS, no build
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
The folder name becomes the app's slug. Every template comes ready to run:
|
|
37
|
-
- the bridge set up, with the base path read from `HAWK_APP_BASE_PATH` (set by `hawk-app deploy`; `/<slug>/` in local dev)
|
|
38
|
-
- Spark's look in the host's colors and mode, from the UI kit
|
|
39
|
-
([`@bridgeline-digital/hawk-app-ui`](../ui/README.md)): `next-mui` its MUI theme, provider and components;
|
|
40
|
-
every other template its stylesheet and elements (`<hawk-kpi>`, `<hawk-data-table>`, `<hawk-column-chart>`, …;
|
|
41
|
-
`vanilla` loads them from the CDN)
|
|
42
|
-
- a working analytics example (total searches for the selected engine and date range, per day)
|
|
43
|
-
- a `hawk-app.json` (new apps start as drafts)
|
|
44
|
-
- `app:dev` / `app:deploy` scripts and a README
|
|
45
|
-
- `AGENTS.md`, `CLAUDE.md` and the MCP server's settings for AI coding assistants (see [AI coding assistants](#ai-coding-assistants-agentsmd))
|
|
46
|
-
|
|
47
|
-
Add `--name "Keyword Explorer"` to set the display name, and `--install` to run `npm install` straight away.
|
|
48
|
-
|
|
49
|
-
## Add to an existing app
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
npx hawk-app init # creates hawk-app.json and AGENTS.md
|
|
53
|
-
npx hawk-app validate # checks hawk-app.json and the project (bridge, build script)
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
`init` also writes [AGENTS.md](#ai-coding-assistants-agentsmd) with what a template would have done for you: the bridge,
|
|
57
|
-
the base path, the build folder and the dev server. `npx hawk-app validate --build` builds the app as deploy does and
|
|
58
|
-
checks that its HTML only loads URLs under the version's base path.
|
|
59
|
-
|
|
60
|
-
`hawk-app.json` decides the app's **visibility** and what data it can read (which clients have it is managed
|
|
61
|
-
separately, see [Clients](#clients)):
|
|
62
|
-
|
|
63
|
-
```jsonc
|
|
64
|
-
{
|
|
65
|
-
"slug": "keyword-explorer", // unique; each deploy is served at <apps CDN>/keyword-explorer/v<n>/
|
|
66
|
-
"name": "Keyword Explorer",
|
|
67
|
-
"visibility": "allowList", // allowList: only the app's clients; all: every client except excluded ones;
|
|
68
|
-
// internal: Bridgeline users only; store: the app's clients, and every other
|
|
69
|
-
// client can request it from the store (needs "store", below) (clients: see "Clients")
|
|
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, …
|
|
73
|
-
// through the dashboard API (see the bridge's "Writing to HawkSearch");
|
|
74
|
-
// data:read = your own SQL queries in queries/ (see "Custom data");
|
|
75
|
-
// functions:invoke = server-side functions (see "Functions");
|
|
76
|
-
// email:send = send email (see "Email");
|
|
77
|
-
// storage = the app's own data and files per client (see the bridge's "Storage");
|
|
78
|
-
// hawksearch:sysparams:write = set HawkSearch system parameters
|
|
79
|
-
// (with "systemParameters", see "HawkSearch system parameters");
|
|
80
|
-
// hawksearch:search = HawkSearch search and autocomplete, live or preview;
|
|
81
|
-
// hawksearch:recommendations = the engine's recommendation widgets;
|
|
82
|
-
// hawksearch:index = rebuild the engine's index (where HawkSearch allows it)
|
|
83
|
-
// and read its indexes; hawksearch:index:items = add, update, delete items;
|
|
84
|
-
// hawksearch:index:manage = create, switch and delete indexes, hierarchy, mappings;
|
|
85
|
-
// hawksearch:features:write = HawkSearch features on and off;
|
|
86
|
-
// lambdas:invoke = Lambdas outside Hawk Apps (with "lambdas");
|
|
87
|
-
// ai:invoke = Claude through the platform (see "AI")
|
|
88
|
-
// "config": { "signupNotifyEmails": { "type": "emails", "default": ["a@example.com"] } }
|
|
89
|
-
// = non-secret settings the app reads with app.config, values set without a deploy (see "App config")
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
Optional `"listed": false` keeps the app off the host's Apps landing page (the host also hides its own title and back
|
|
94
|
-
button for it), while it still opens by its direct URL. It only changes the listing, not who can open the app
|
|
95
|
-
(`visibility` and the app's clients still apply); the default is `true`, and `hawk-app status` shows `listed no` when it's off.
|
|
96
|
-
|
|
97
|
-
**Store apps.** `"visibility": "store"` offers the app to every client that doesn't have it, on the Store tab of the
|
|
98
|
-
Apps page in HawkSearch: a client user requests it, and a Hawk-App-Manager approves (the client is added to its
|
|
99
|
-
clients) or declines. It needs the listing:
|
|
100
|
-
|
|
101
|
-
```jsonc
|
|
102
|
-
"store": {
|
|
103
|
-
"tagline": "Find and fix searches that return nothing", // one line, at most 120 characters
|
|
104
|
-
"highlights": ["Zero-result searches by volume"], // optional, up to 5
|
|
105
|
-
"screenshots": ["store/overview.png", "store/fix.png"] // 1-5 .png, .jpg or .webp next to hawk-app.json, at most 1 MB each
|
|
106
|
-
}
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
A view with `"trial": true` (one at most, no params) is the live trial: the card runs it on the client's own data
|
|
110
|
-
before they have the app, with a read-only token (only the app's `reports:read`, `hawksearch:read`,
|
|
111
|
-
`hawksearch:search`, `hawksearch:recommendations` and `data:read`). There `app.context.trial` is true: hide what needs
|
|
112
|
-
other scopes (`validate` lists them); `navigate({ intent: 'app' })` opens the request dialog. Without a trial view, or
|
|
113
|
-
for a client without analytics data, the card shows the screenshots.
|
|
114
|
-
|
|
115
|
-
`validate` and `deploy` check the screenshots, and `deploy` uploads them with each version (`_hawk/store/1.png`…). The
|
|
116
|
-
listing is reviewed like the other settings; the price label is set by Bridgeline, not here. `hawk-app status` shows
|
|
117
|
-
whether the app is in the store, its price label and how many requests are open, approved and declined.
|
|
118
|
-
|
|
119
|
-
There is no `status` field any more: approvers release versions from the Apps page in HawkSearch (see [Review](#review-in-hawksearch)). An old `status` is ignored with a warning.
|
|
120
|
-
|
|
121
|
-
Every deploy is a new version served at `/<slug>/v<n>/`, so the build needs that base path. `hawk-app deploy` runs `npm run build` with `HAWK_APP_BASE_PATH=/<slug>/v<n>`; read it in your build config with `/<slug>` as the local fallback:
|
|
122
|
-
- Vite: ``base: `${process.env.HAWK_APP_BASE_PATH ?? '/keyword-explorer'}/` ``
|
|
123
|
-
- Next.js (`output: 'export'`): `basePath: process.env.HAWK_APP_BASE_PATH ?? '/keyword-explorer'`
|
|
124
|
-
- Other build tools: set the base href / public path from `HAWK_APP_BASE_PATH` the same way (plus a trailing `/` where the tool expects one)
|
|
125
|
-
|
|
126
|
-
Apps that use only relative paths (plain HTML) can skip the build with `hawk-app deploy <folder> --no-build`.
|
|
127
|
-
|
|
128
|
-
## Log in (once per machine)
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
npx hawk-app sso # sign in with your Bridgeline account in the browser: the platform issues your own key
|
|
132
|
-
# (--publisher <id>: another team's apps, e.g. qa-team; one saved key per platform)
|
|
133
|
-
npx hawk-app login # or: paste a publisher key from Engineering (input hidden), checked and saved for every app
|
|
134
|
-
npx hawk-app whoami # are you logged in? which platform and key apply here?
|
|
135
|
-
npx hawk-app logout # remove the saved key from this machine
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
**`sso`** opens the browser on the HawkSearch sign-in, which goes straight to Bridgeline's AD sign-in (Microsoft). Only
|
|
139
|
-
Bridgeline AD accounts get a key; HawkSearch client logins can't. The platform then issues a key for you on this
|
|
140
|
-
machine (`sso-<name>-<computer>`, in the Services team's publisher, so you work on the team's apps; `--publisher qa-team`
|
|
141
|
-
for another team the platform allows, e.g. QA's own test apps) and the CLI saves it
|
|
142
|
-
like `login` does. You're also added to Hawk-App-Developers in HawkSearch, so you can submit apps for approval. It
|
|
143
|
-
works for 90 days; then commands say it was refused, and you run `sso` again. Signing in again on
|
|
144
|
-
the same computer replaces that computer's key. Works on dev and prod (`npx hawk-app sso --env prod`). Pipelines keep using keys from the portal with `HAWK_APPS_PUBLISHER_KEY`.
|
|
145
|
-
|
|
146
|
-
**`login`** takes a publisher key made in the Hawk Analytics portal. Those keys don't expire.
|
|
147
|
-
|
|
148
|
-
`login` saves the key in your user profile, so every app on the machine uses it and no project files hold it:
|
|
149
|
-
- **Windows:** `%APPDATA%\hawk-app\credentials.json`
|
|
150
|
-
- **macOS / Linux:** `~/.config/hawk-app/credentials.json`, readable only by you
|
|
151
|
-
|
|
152
|
-
It works the same in PowerShell, cmd and bash. The key is never shown, never stored in shell history, and never passed as a command-line argument.
|
|
153
|
-
|
|
154
|
-
Every command works on **dev** unless you say otherwise. `--env prod` (on any command) picks prod:
|
|
155
|
-
`npx hawk-app sso --env prod` signs you in to prod, and `npx hawk-app deploy --env prod` deploys there. Logging in
|
|
156
|
-
never changes which environment later commands use, so prod is always chosen on purpose; `deploy` says
|
|
157
|
-
when they go to prod. Keys are stored per environment, so a dev key is never sent to prod. (`--platform <url>` targets
|
|
158
|
-
any other platform, e.g. one running locally.)
|
|
159
|
-
|
|
160
|
-
**Which environment**, first match wins: `--env` / `--platform`, then `HAWK_APPS_ENV` (`dev` or `prod`) or
|
|
161
|
-
`HAWK_APPS_PLATFORM_URL` in the environment (CI) or the project's `.env.local` / `.env`, else dev.
|
|
162
|
-
|
|
163
|
-
**Which key**, first match wins:
|
|
164
|
-
1. The environment variable `HAWK_APPS_PUBLISHER_KEY`. CI pipelines set it as a secured variable.
|
|
165
|
-
2. The project's `.env.local` (git-ignored), for a per-project override. `npx hawk-app login --local` writes it for you.
|
|
166
|
-
3. The machine login from `hawk-app login` for that environment.
|
|
167
|
-
|
|
168
|
-
## Run locally without HawkSearch (`hawk-app dev`)
|
|
169
|
-
|
|
170
|
-
```bash
|
|
171
|
-
npm run app:dev # = npx hawk-app dev; asks which client the first time
|
|
172
|
-
npx hawk-app dev --client e3141 --engine 3141
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
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
|
-
`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 always run from the **deployed** version, even here:
|
|
178
|
-
deploy after changing them.
|
|
179
|
-
|
|
180
|
-
Starts your app (`npm run dev`) and a local stand-in for HawkSearch at `http://localhost:3200`, which embeds the app
|
|
181
|
-
the way HawkSearch does (the bridge's own `<hawk-app-frame>`). The app gets **real tokens and real data**: the CLI asks
|
|
182
|
-
the platform for an app token with your publisher key, for any client you name, as a Bridgeline user previewing your
|
|
183
|
-
newest version (with the scopes waiting for review). The app's platform calls go through the local page, so nothing needs
|
|
184
|
-
CORS setup, on dev or prod; so does the AI stream (`app.ai.stream`), which the page pipes through as it arrives.
|
|
185
|
-
|
|
186
|
-
- The toolbar changes the client, site (engine), user (Bridgeline or client user), date range and theme. Toasts,
|
|
187
|
-
titles and navigation requests from the app show in the page; a panel shows the token's claims and expiry.
|
|
188
|
-
- `--url <dev server>`: use an app dev server that is already running (nothing is started). The default is where the
|
|
189
|
-
template serves the app: `http://localhost:3100/<slug>/` for Next.js, `http://localhost:3100/` otherwise. `--no-start`: don't run `npm run dev`. `--port`: the local page's port (default 3200). `--client-user`: start
|
|
190
|
-
as a client user. `HAWK_APPS_DEV_CLIENT` / `HAWK_APPS_DEV_ENGINE` in `.env.local` replace `--client` / `--engine`.
|
|
191
|
-
- The HawkSearch dashboard API (`app.dashboard`, writes, and authenticated search) needs a HawkSearch key, which only a
|
|
192
|
-
host has. Put yours in `.env.local` as `HAWKSEARCH_API_KEY` (and `HAWKSEARCH_DOMAIN`, default
|
|
193
|
-
`https://dev.hawksearch.net`; `HAWKSEARCH_GROUP_KEY=true` for a group key): the platform seals it into the token. It is
|
|
194
|
-
never written anywhere else.
|
|
195
|
-
- 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.
|
|
197
|
-
|
|
198
|
-
## Deploy
|
|
199
|
-
|
|
200
|
-
**One app, one codebase.** Every developer of a publisher (team) can deploy any of its apps, so the CLI
|
|
201
|
-
guards against mixing two apps under one slug: `create` and `init` refuse a slug that's already an app (saying who last
|
|
202
|
-
changed it; `--yes` to set up a folder for that existing app on purpose), and `deploy` asks before going on
|
|
203
|
-
when someone else's key made the app's last change ("`showcase` was last deployed by Jane Doe on … Deploy a new version
|
|
204
|
-
of it? (y/N)"). `--yes` skips the question; in a pipeline (no terminal) it's a warning and the command goes on. Another
|
|
205
|
-
publisher's slug is always refused (`409 slug_owned`).
|
|
206
|
-
|
|
207
|
-
```bash
|
|
208
|
-
npx hawk-app deploy # build and upload a new version, with hawk-app.json
|
|
209
|
-
npx hawk-app status # released or not, the newest version, the approval request, the aliases
|
|
210
|
-
npx hawk-app open # the app in HawkSearch: review it and submit it for approval
|
|
211
|
-
npx hawk-app clients add e3141 # give a client the app (see "Clients")
|
|
212
|
-
npx hawk-app deploy --dry-run # what hawk-app.json would change, without a version (use this on pull requests)
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
| Command | What it does |
|
|
216
|
-
|---|---|
|
|
217
|
-
| `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. |
|
|
219
|
-
| `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
|
-
| `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
|
-
| `versions` | Every version with its status (draft, waiting, approved, rejected) and the aliases on it. |
|
|
222
|
-
| `alias set <name> <version>` / `alias delete <name>` | Point an alias at a version that isn't approved (only Bridgeline staff in its clients see it), or delete one of yours with no clients on it. Approvers do the rest. |
|
|
223
|
-
| `open [--alias <name>]` | Opens the app's page in HawkSearch (dev or prod, from `--env`) to review it and submit it for approval; `--alias` shows it as that alias's clients see it. Prints the link when there's no browser. |
|
|
224
|
-
| `docs [<topic>]` | Opens the developer docs at developers.hawksearchapps.com, or a topic's page (`start`, `versions`, `bridge`, `api`, `cli`, `commands`, `functions`, …; `hawk-app help docs` lists them). Prints the link when there's no browser. |
|
|
225
|
-
| `doctor` | Checks everything a developer needs: Node, the login and platform, `hawk-app.json`, the project (bridge, build script), the template, the scopes the code uses, the app on the platform and the MCP config. Each line says what to fix; exits 1 when something must be. |
|
|
226
|
-
| `help [<command> \| all]` | The commands by task; one command's details; or every detail. |
|
|
227
|
-
| `why <client id>` | What a client's users see of the app, and why (the client list, its alias, that version's status). |
|
|
228
|
-
| `status` | Shows the automated review checks approvers will see (bundle, dependencies, origins, queries, …), the released version, the newest version and its status, settings waiting for review, the approval request (waiting for approval, or rejected and why), auto-approve, review emails (when an approver turned them off), the view cache (when Engineering shortened or stopped it), visibility, scopes, system parameters, config keys and clients. |
|
|
229
|
-
| `clients` / `clients add <id> [<id> …] [--name "<name>"] [--alias <name>]` / `clients exclude <id> [--name "<name>"]` / `clients remove <id>` | Which clients have the app, and which version (alias) each gets (see [Clients](#clients)). |
|
|
230
|
-
| `data tables` / `data describe <table>` / `data run <file.sql> --client <id> [--param name=value …]` | The tables app queries can use, and running a query on dev (see [Custom data](#custom-data-queries)). |
|
|
231
|
-
| `secrets list` / `secrets set <NAME> [--value-from-env VAR]` / `secrets delete <NAME>` | The app's function secrets (see [Functions](#functions)). |
|
|
232
|
-
| `logs <function> [--since 1h] [--follow]` | A function's logs (see [Functions](#functions)). |
|
|
233
|
-
| `config list [--client <id>]` / `config set <key> <value> [--client <id>]` / `config unset <key> [--client <id>]` | The app's config values, global or for one client, without a deploy (see [App config](#app-config)). |
|
|
234
|
-
| `sso [--publisher <id>]` | Sign in with your Bridgeline account; the platform issues your own key (see above). |
|
|
235
|
-
| `login [--local]` / `whoami` / `logout` | Save a publisher key (CI, or without SSO), check which key and platform apply, or remove the saved key. |
|
|
236
|
-
| `errors` / `usage` | The app's page errors, and its opens, users and clients per day (see [Errors and usage](#errors-and-usage-hawk-app-errors-hawk-app-usage)). |
|
|
237
|
-
| `upgrade` / `templates` / `mcp` | Keep up with the template (see below); list the templates; the MCP server for AI assistants (see [MCP server](#mcp-server-hawk-app-mcp)). |
|
|
238
|
-
|
|
239
|
-
Options: `--manifest <file>` to use a manifest other than `hawk-app.json`, `--env dev|prod` (default dev), and `--platform <url>` to target another platform. `deploy` never uploads hidden files such as `.env.local`.
|
|
240
|
-
|
|
241
|
-
## Keep up with the template (`hawk-app upgrade`)
|
|
242
|
-
|
|
243
|
-
Templates move on (a new Next.js, a security fix, a config change). `hawk-app upgrade` brings an app's template-owned
|
|
244
|
-
parts up to the template this CLI ships:
|
|
245
|
-
|
|
246
|
-
```bash
|
|
247
|
-
npx hawk-app upgrade # shows what would change; changes nothing
|
|
248
|
-
npx hawk-app upgrade --yes # applies it and runs npm install
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
- **Dependencies:** the packages the template pins (e.g. `next`, `react`, `@mui/material`, the bridge) are raised to the
|
|
252
|
-
template's version. Never lowered, and your own packages are never touched. A package the template gained since the
|
|
253
|
-
app was made is added: a `react`, `preact`, `next` or `next-mui` app gets the UI kit
|
|
254
|
-
(`@bridgeline-digital/hawk-app-ui`), and `upgrade` lists the code to change to start using it (next-mui: the
|
|
255
|
-
provider; the others: the stylesheet and elements imports). A major version (Material UI 7 → 9) is called out, with
|
|
256
|
-
what to check.
|
|
257
|
-
- **Config files** (`next.config.ts`, `vite.config.ts`, `tsconfig.json`): replaced only if you never edited them (the
|
|
258
|
-
CLI knows every version the templates have shipped). An edited one is left alone, with a diff of what the template
|
|
259
|
-
changed so you can apply it yourself.
|
|
260
|
-
- The vanilla template's CDN imports (the bridge and the UI kit, in `public/app.js` and `public/index.html`) are raised
|
|
261
|
-
together to the template's minor version. A vanilla app made before the kit adds its two lines itself: see a new
|
|
262
|
-
app's `public/index.html` and `public/app.js`.
|
|
263
|
-
- **AGENTS.md:** the CLI's part is refreshed, and `CLAUDE.md` gets its `@AGENTS.md` import (see below).
|
|
264
|
-
- **Never** your code (`src/`), `hawk-app.json` settings or any other file.
|
|
265
|
-
|
|
266
|
-
It uses `"template"` in `hawk-app.json`, which `hawk-app create` writes; for older apps it guesses from the dependencies
|
|
267
|
-
(and records it on `--yes`), or pass `--template <name>`. An app set up with `init` that follows no template gets only
|
|
268
|
-
AGENTS.md refreshed. `hawk-app deploy` tells you when an app is behind its template. After upgrading: build, test
|
|
269
|
-
(`npm run dev`) and deploy as usual. `--no-install` skips `npm install`.
|
|
270
|
-
|
|
271
|
-
## Versions and aliases
|
|
272
|
-
|
|
273
|
-
Every deploy is a version with a status from its review: draft, waiting (submitted), approved or rejected. An **alias**
|
|
274
|
-
points at a version and takes its status: on an approved version its clients' users see the app; on any other only
|
|
275
|
-
Bridgeline staff in them do. **`default`** is the version released to everyone, for every client without another alias.
|
|
276
|
-
|
|
277
|
-
```bash
|
|
278
|
-
npx hawk-app deploy --alias staging # staging → the new version (staff only until it's approved)
|
|
279
|
-
npx hawk-app clients add e4 e5 --alias staging # new clients on staging: staff in them test it
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
When an approver approves that version, they choose: release to everyone (`default` moves), or only to the aliases on it
|
|
283
|
-
(staging's clients see it, everyone else stays on `default`). You can point aliases only at versions that aren't approved,
|
|
284
|
-
and put on them only clients whose users don't see the app yet; anything users see is for approvers (the portal or
|
|
285
|
-
HawkSearch). The full picture, with diagrams: docs/versions-and-aliases.md in the hawk-apps repository. `hawk-app status` shows the aliases, `hawk-app versions` every version with its status:
|
|
286
|
-
|
|
287
|
-
```
|
|
288
|
-
released v7 (default: every client without another alias)
|
|
289
|
-
aliases default v7 (all other clients) · pilot v8 (3 clients) · staging v9 (draft: Bridgeline staff only, 2 clients)
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
## Errors and usage (`hawk-app errors`, `hawk-app usage`)
|
|
293
|
-
|
|
294
|
-
The bridge reports the app's page errors and one open per page load (platform/docs/telemetry.md):
|
|
295
|
-
|
|
296
|
-
```bash
|
|
297
|
-
npx hawk-app errors # the last 7 days (--days 1-30): grouped, the most frequent first, with versions and clients
|
|
298
|
-
npx hawk-app usage # the last 30 days (--days 1-400): opens, view opens, users and clients; staff apart
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
## Accessibility check
|
|
302
|
-
|
|
303
|
-
`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
|
|
304
|
-
the version: approvers see it as the `accessibility` review check (critical issues fail it; a force approve still goes
|
|
305
|
-
through). `hawk-app validate --build` prints critical and serious issues before you deploy. It sees each page's HTML as
|
|
306
|
-
built, not what the app draws later in the browser, and no layout (so no color contrast).
|
|
307
|
-
|
|
308
|
-
## Release channels: latest and dev
|
|
309
|
-
|
|
310
|
-
The bridge, the UI kit and the CLI are published together, with the same version, on two npm tags:
|
|
311
|
-
- **`latest`**, from `main`: stable releases, matched to the prod platform. What `npm install` and `npx hawk-app` get.
|
|
312
|
-
- **`dev`**, from `dev`: pre-releases (`x.y.z-dev.<time>`) with features still in development, which may need the dev
|
|
313
|
-
platform.
|
|
314
|
-
|
|
315
|
-
To start an app on dev features, create it with the dev CLI. It puts the bridge, the UI kit and the CLI on the `dev` tag (the vanilla
|
|
316
|
-
template's CDN imports too), so the app gets the dev bridge and not the release its template pins:
|
|
317
|
-
|
|
318
|
-
```bash
|
|
319
|
-
npx @bridgeline-digital/hawk-app-cli@dev create my-app # a dev app (or --channel dev with any CLI)
|
|
320
|
-
npx hawk-app upgrade --channel latest # once the features are released: ^ the latest versions (--yes applies)
|
|
321
|
-
npx hawk-app upgrade --channel dev # move an existing app onto the dev builds (again: onto the newest ones)
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
- `--channel` installs the packages it moves by name (`npm install <package>@dev`, or `@^<latest>`), so
|
|
325
|
-
`package-lock.json` moves with them; npm then writes the version it installed (`^0.1.0-dev.<time>`), which is
|
|
326
|
-
still the dev channel. Running `--channel dev` again moves to the newest dev builds.
|
|
327
|
-
- A dev app's `npm update` follows the newest dev builds; its `package-lock.json` records the exact one, so commit it.
|
|
328
|
-
- `hawk-app upgrade` keeps a dev app on `dev`; `--channel` is the only thing that moves it.
|
|
329
|
-
- `hawk-app validate` warns when a dev app is set to use the prod platform (`--env prod`, `HAWK_APPS_ENV=prod`).
|
|
330
|
-
|
|
331
|
-
## AI coding assistants (AGENTS.md)
|
|
332
|
-
|
|
333
|
-
`create` and `init` write these files for AI coding assistants, so they build Hawk Apps the way the platform expects
|
|
334
|
-
instead of guessing:
|
|
335
|
-
|
|
336
|
-
- **`AGENTS.md`**, read by GitHub Copilot, Cursor, Codex and others: the rules (no secrets, data only through the
|
|
337
|
-
bridge, static files, the base path), the bridge's calls and their scopes, queries and functions, the commands, and
|
|
338
|
-
what fails review. Its project section matches the app's template, or, for an app set up with `init`, says what the
|
|
339
|
-
project has to do itself.
|
|
340
|
-
- **`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`.
|
|
344
|
-
|
|
345
|
-
The CLI owns the part of AGENTS.md between its `<!-- hawk-app:start … -->` and `<!-- hawk-app:end -->` markers, and
|
|
346
|
-
`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 MCP
|
|
348
|
-
settings file gets the `hawk-apps` server added (one with comments is left alone: add it yourself).
|
|
349
|
-
|
|
350
|
-
### MCP server (`hawk-app mcp`)
|
|
351
|
-
|
|
352
|
-
`hawk-app mcp` serves the CLI's commands to AI coding assistants over stdio (the [Model Context
|
|
353
|
-
Protocol](https://modelcontextprotocol.io)), so they can look things up and check their own work:
|
|
354
|
-
|
|
355
|
-
| Tool | What it does |
|
|
356
|
-
| --- | --- |
|
|
357
|
-
| `docs` | Searches or reads the bridge and UI kit READMEs (the app's installed versions), this README, the `hawk-app.json` schema and the showcase app, whose code shows every feature working. |
|
|
358
|
-
| `validate` | `hawk-app validate`, optionally with `--build`. |
|
|
359
|
-
| `status` | `hawk-app status`, including the automated review checks approvers will see. |
|
|
360
|
-
| `doctor` | `hawk-app doctor`: what's wrong with the setup, and how to fix it. |
|
|
361
|
-
| `versions`, `why` | Every version with its status and aliases; what a client sees of the app, and why. |
|
|
362
|
-
| `errors`, `usage` | The app's page errors; its opens, users and clients per day. |
|
|
363
|
-
| `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
|
-
| `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
|
-
| `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). |
|
|
366
|
-
| `data_tables`, `data_describe` | Table names and columns for queries. Never data: `data run` isn't a tool, because its rows would go to the assistant. |
|
|
367
|
-
| `logs` | A function's logs. |
|
|
368
|
-
| `init`, `upgrade` | `hawk-app init`; `hawk-app upgrade` (`apply` for `--yes`). |
|
|
369
|
-
| `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
|
-
| `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
|
-
|
|
372
|
-
It also has **prompts**, ready-made tasks that use the tools. Claude Code lists them as slash commands (type `/` and
|
|
373
|
-
`hawk`):
|
|
374
|
-
|
|
375
|
-
| Prompt | In Claude Code | What it does |
|
|
376
|
-
| --- | --- | --- |
|
|
377
|
-
| `check-before-submit` | `/mcp__hawk-apps__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. |
|
|
378
|
-
| `add-feature` | `/mcp__hawk-apps__add-feature "<what to build>"` | Finds the bridge calls, scopes and showcase examples it needs, says what the review will see, makes the change and validates it. |
|
|
379
|
-
| `explain-app` | `/mcp__hawk-apps__explain-app` | What the app does, its scopes (unused or missing ones too) and where it stands in review. |
|
|
380
|
-
| `deploy-to-client` | `/mcp__hawk-apps__deploy-to-client "<client name>"` | Deploys to dev and gives a client the app; you pick the client from the matches for its name. |
|
|
381
|
-
|
|
382
|
-
Each takes an optional `dir` for a workspace with several apps. In VS Code with Copilot they're under `/` too.
|
|
383
|
-
|
|
384
|
-
- **Dev only.** Every tool runs with `--env dev`; nothing reaches prod. Approvals, secrets, removing clients and logins
|
|
385
|
-
aren't tools.
|
|
386
|
-
- **Your key stays put.** The tools use your saved login (or `.env.local`) like the CLI does; the key never passes
|
|
387
|
-
through the assistant.
|
|
388
|
-
- **No client names.** Tool output shows client ids only, and client names go to you, never to the assistant: the
|
|
389
|
-
client picker is a form your assistant shows you (MCP elicitation). An assistant without forms tells you to run
|
|
390
|
-
`npx hawk-app clients add --pick "<name>"` in a terminal instead.
|
|
391
|
-
- **No silent go-aheads.** Where the CLI would ask (a teammate made the app's last change, or `npm audit` found high or
|
|
392
|
-
critical vulnerabilities) the tool stops and tells the assistant, which has to ask you and call again with `force`.
|
|
393
|
-
- **Several apps in one workspace?** Each tool takes `dir`, the app's folder relative to the workspace.
|
|
394
|
-
|
|
395
|
-
`create` and `init` add it to `.mcp.json` and `.vscode/mcp.json`, started as
|
|
396
|
-
`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, copy the `mcpServers` entry of `.mcp.json` to `.cursor/mcp.json`.
|
|
398
|
-
|
|
399
|
-
## Clients
|
|
400
|
-
|
|
401
|
-
Which clients have the app is not in `hawk-app.json`: the platform's registry holds it, and Engineering and approvers
|
|
402
|
-
change it in the Hawk Analytics portal. Developers can change it with the CLI:
|
|
403
|
-
|
|
404
|
-
```bash
|
|
405
|
-
npx hawk-app clients # the app's visibility and its clients
|
|
406
|
-
npx hawk-app clients search outdoor # find a client's id by its name
|
|
407
|
-
npx hawk-app clients add e3141 --name "Outdoor Demo" # give a client the app (visibility allowList)
|
|
408
|
-
npx hawk-app clients add --pick "outdoor" # the same, picking the client from the matches
|
|
409
|
-
npx hawk-app clients add e4 e5 --alias staging # on an alias: the version they get
|
|
410
|
-
npx hawk-app clients add e4 --alias default # back to the default version
|
|
411
|
-
npx hawk-app clients exclude e2002 # opt a client out (visibility all)
|
|
412
|
-
npx hawk-app clients remove e3141 # take the client off the list
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
`add` and `exclude` take only ids from the environment's HawkSearch client list (the Hawk Analytics portal's client
|
|
416
|
-
picker): an id the dashboard doesn't use, such as `3141` for `e3141`, is refused with the id you probably meant. The
|
|
417
|
-
client's name comes from that list unless you pass `--name`. `remove` takes any id on the app's list.
|
|
418
|
-
|
|
419
|
-
`hawk-app clients` lists `+ e3141 (Outdoor Demo)` for a client that has the app and `- e2002 (Acme Retail)` for one
|
|
420
|
-
that is opted out; `[on pilot]` after a client on an alias (`[on staging · draft: Bridgeline staff only]` when its version
|
|
421
|
-
isn't approved). What the list means depends on `visibility` in `hawk-app.json`:
|
|
422
|
-
- `allowList`: only the clients marked `+` have the app.
|
|
423
|
-
- `all`: every client has it, except the ones marked `-`.
|
|
424
|
-
- `internal`: Bridgeline users only; the list is ignored.
|
|
425
|
-
|
|
426
|
-
Changes apply at once, released or not, without a deploy or an approval, and are recorded in the app's history.
|
|
427
|
-
`--name` is a label for people (up to 100 characters); the client id is what grants access. Register the app before
|
|
428
|
-
adding clients.
|
|
429
|
-
|
|
430
|
-
`--alias` sets the version a client gets (see [Versions and aliases](#versions-and-aliases)). You can change it only for
|
|
431
|
-
clients whose users don't see the app yet; after that, approvers do.
|
|
432
|
-
|
|
433
|
-
## Custom data (queries)
|
|
434
|
-
|
|
435
|
-
Apps with the `data:read` scope can ship their own SQL, run by the platform against the current client's analytics
|
|
436
|
-
tables. Put each query in `queries/` next to `hawk-app.json` (not in the build folder) as a pair of files:
|
|
437
|
-
|
|
438
|
-
```text
|
|
439
|
-
my-app/
|
|
440
|
-
hawk-app.json "scopes": ["data:read"]
|
|
441
|
-
queries/
|
|
442
|
-
top-searches.sql
|
|
443
|
-
top-searches.json
|
|
444
|
-
```
|
|
445
|
-
|
|
446
|
-
```sql
|
|
447
|
-
-- queries/top-searches.sql
|
|
448
|
-
SELECT search_date, sum(search_count) AS searches
|
|
449
|
-
FROM dailysearches
|
|
450
|
-
WHERE search_date BETWEEN ? AND ? AND {{partitions(from, to)}}
|
|
451
|
-
GROUP BY 1 ORDER BY 1
|
|
452
|
-
```
|
|
453
|
-
|
|
454
|
-
```json
|
|
455
|
-
{ "params": [{ "name": "from", "type": "date", "required": true }, { "name": "to", "type": "date", "required": true }] }
|
|
456
|
-
```
|
|
457
|
-
|
|
458
|
-
The app calls it with `app.data.query('top-searches', { from, to })` (see the bridge README for types, the
|
|
459
|
-
`{{partitions(from, to)}}` macro, the tables and the errors).
|
|
460
|
-
|
|
461
|
-
**Step by step**
|
|
462
|
-
1. Add `"data:read"` to `scopes` in `hawk-app.json`.
|
|
463
|
-
2. Find your table and columns: `npx hawk-app data tables`, then `npx hawk-app data describe <table>`.
|
|
464
|
-
3. Write `queries/<name>.sql` and `queries/<name>.json`. Filter by a date range and add
|
|
465
|
-
`{{partitions(from, to)}}`.
|
|
466
|
-
4. Try it on dev: `npx hawk-app data run queries/<name>.sql --client <id> --param from=… --param to=…`.
|
|
467
|
-
5. Call it from the app with `app.data.query('<name>', { … })`. Queries always run from a **deployed** version, even
|
|
468
|
-
in dev mode (`?devUrl=`): the platform never reads your local `queries/` folder. So after adding or editing a
|
|
469
|
-
query, deploy (step 6) before calling it from the app; use `data run` to iterate on the SQL itself.
|
|
470
|
-
6. `npm run app:deploy` (checks the queries and uploads them with the new version, with `hawk-app.json`).
|
|
471
|
-
7. Bridgeline staff review the version in HawkSearch: they see each added or changed query and its SQL diff.
|
|
472
|
-
Once approved (or right away on a draft or auto-approve app), the app's users get the new queries.
|
|
473
|
-
|
|
474
|
-
`hawk-app validate` and `hawk-app deploy` check the queries before anything is uploaded, with the same rules as the
|
|
475
|
-
platform:
|
|
476
|
-
- names are lowercase letters, digits and single dashes (up to 64), and every `.sql` has a `.json` (and vice versa);
|
|
477
|
-
only `.sql` and `.json` files, no subfolders, at most 50 queries
|
|
478
|
-
- each `.sql` is one `SELECT` or `WITH … SELECT` statement (no `;`), up to 64 KB
|
|
479
|
-
- the `?` count equals the number of params (a `string[]` param is one `?`, written `IN ?`)
|
|
480
|
-
- params have a `name` and a `type` (`string`, `int`, `decimal`, `date`, `timestamp`, `boolean`, `string[]`), and
|
|
481
|
-
optionally `required`, `default`, `min`/`max` (numbers) and `maxItems` (`string[]`, up to 100)
|
|
482
|
-
- `{{partitions(from, to)}}` (optionally `{{partitions(from, to, alias)}}`) uses params declared as `date`
|
|
483
|
-
- `queries/` needs `data:read` in `scopes`.
|
|
484
|
-
|
|
485
|
-
`deploy` uploads them with the version (`… 3 files, 12 KB from dist, 2 queries`). They're part of the version: staff
|
|
486
|
-
review them with it, and each version runs its own queries.
|
|
487
|
-
|
|
488
|
-
Try a query on dev before deploying it:
|
|
489
|
-
|
|
490
|
-
```bash
|
|
491
|
-
npx hawk-app data tables # the tables queries can use (and which are partitioned)
|
|
492
|
-
npx hawk-app data tables --app my-app # also: may my-app use the sensitive tables?
|
|
493
|
-
npx hawk-app data describe dailysearches # a table's columns and types
|
|
494
|
-
npx hawk-app data run queries/top-searches.sql --client e3141 --param from=2026-09-01 --param to=2026-09-30
|
|
495
|
-
```
|
|
496
|
-
|
|
497
|
-
`data run` sends the SQL and its `.json` from your disk (nothing needs to be deployed), waits for the result, and prints
|
|
498
|
-
up to 50 rows plus the bytes scanned. `--client` is the HawkSearch client id (the one in the app's context,
|
|
499
|
-
`context.tenant.clientId`); on dev, use a demo or test client. `string[]` values are comma-separated (`--param engines=a,b`). It only works
|
|
500
|
-
against the dev platform. When too many queries are running (429 `too_many_queries`) it waits and tries again.
|
|
501
|
-
|
|
502
|
-
The sensitive tables (visitor-level data such as `summary`) are listed apart: only apps Engineering has opted in can
|
|
503
|
-
query them. Ask Engineering if your app needs one.
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
## Functions
|
|
507
|
-
|
|
508
|
-
Server-side code that ships with each version (AWS Lambda, Node 22), called from the app with
|
|
509
|
-
`app.functions.call(name, input)`. The bridge README's **Functions** section covers `ctx`, errors and limits; this is
|
|
510
|
-
the CLI side.
|
|
511
|
-
|
|
512
|
-
**Before you start: Engineering must enable functions for your app** (they can reach the internet and hold secrets),
|
|
513
|
-
and a version whose functions changed **always needs review**, even on an auto-approve app.
|
|
514
|
-
|
|
515
|
-
1. Add `"functions:invoke"` to `scopes`, and declare each function in `hawk-app.json`:
|
|
516
|
-
```jsonc
|
|
517
|
-
"functions": {
|
|
518
|
-
"sync-feed": { "timeoutSeconds": 20, "memoryMb": 512, "secrets": ["FEED_API_KEY"] },
|
|
519
|
-
"echo": {}, // defaults: 10 s, 256 MB, no secrets
|
|
520
|
-
"nightly-report": { "job": true, "timeoutSeconds": 600 } // a background job (see below)
|
|
521
|
-
}
|
|
522
|
-
```
|
|
523
|
-
2. Write `functions/<name>.ts` (or `.mts`, `.js`, `.mjs`) exporting `handler`, with
|
|
524
|
-
`@bridgeline-digital/hawk-app-bridge` (0.4.0 or later) installed in the app:
|
|
525
|
-
```ts
|
|
526
|
-
import type { HawkFunction } from '@bridgeline-digital/hawk-app-bridge/server';
|
|
527
|
-
export const handler: HawkFunction<{ name: string }, { greeting: string }> = async (input, ctx) => ({
|
|
528
|
-
greeting: `Hello ${input.name} from ${ctx.client.id}`,
|
|
529
|
-
});
|
|
530
|
-
```
|
|
531
|
-
Shared code goes in subfolders (`functions/lib/…`) or anywhere else you import from (`src/`, npm packages).
|
|
532
|
-
3. Set its secrets once: `npx hawk-app secrets set FEED_API_KEY`.
|
|
533
|
-
4. `npx hawk-app validate`, then deploy as usual: `npm run app:deploy`.
|
|
534
|
-
5. Call it from the app: `await app.functions.call('sync-feed', { since: '2026-09-01' })`. Like queries, functions
|
|
535
|
-
always run from a **deployed** version, also in dev mode (`?devUrl=`).
|
|
536
|
-
|
|
537
|
-
`hawk-app validate` and `hawk-app deploy` check, with the platform's rules:
|
|
538
|
-
- at most 10 functions; names are lowercase letters, digits and single dashes, up to 40 characters;
|
|
539
|
-
- `job` true or false; `timeoutSeconds` 1–25 (1–900 for a job), `memoryMb` 128–1024, `secrets` up to 20 unique
|
|
540
|
-
`UPPER_SNAKE_CASE` names, no other keys;
|
|
541
|
-
- every declared function has its `functions/<name>.*` file, and every entry file in `functions/` is declared;
|
|
542
|
-
- the `functions:invoke` scope, and the bridge installed with its `/server` entry.
|
|
543
|
-
|
|
544
|
-
**What deploy does with them.** Before reserving a version, each function is bundled with esbuild (Node 22, ESM,
|
|
545
|
-
every dependency included, your `handler` wrapped for Lambda) into `index.mjs`, zipped as `bundle.zip` (10 MB at
|
|
546
|
-
most). `source.zip` (every file under `functions/` except `node_modules`, 5 MB at most) and `deps.json` (the npm
|
|
547
|
-
packages in the bundle, with versions) go along so reviewers can see the code and its dependencies. After the upload
|
|
548
|
-
the platform deploys each function and the CLI prints one line per function:
|
|
549
|
-
|
|
550
|
-
```
|
|
551
|
-
Bundling 2 functions…
|
|
552
|
-
ƒ echo 4 KB zipped, 1 package
|
|
553
|
-
ƒ sync-feed 38 KB zipped, 2 packages
|
|
554
|
-
Uploading v9 of my-app: 12 files, 210 KB from dist, 2 functions
|
|
555
|
-
✔ function echo (10 s, 256 MB) Lambda version 3
|
|
556
|
-
✔ function sync-feed (20 s, 512 MB, secrets FEED_API_KEY) Lambda version 5
|
|
557
|
-
✔ Deployed v9 of my-app (12 files, 2 functions). A draft: clients stay on v8 until it's approved in HawkSearch …
|
|
558
|
-
```
|
|
559
|
-
|
|
560
|
-
Zips are built the same way every time, so a function whose code didn't change keeps the same code hash (and doesn't
|
|
561
|
-
show as changed in review).
|
|
562
|
-
|
|
563
|
-
**Secrets.** App-level, stored by the platform (encrypted, in AWS Systems Manager Parameter Store), never in the app or
|
|
564
|
-
a version: a change applies to every version at once. A function receives only the secrets it declares, as
|
|
565
|
-
`ctx.secrets.NAME`.
|
|
566
|
-
|
|
567
|
-
```bash
|
|
568
|
-
npx hawk-app secrets list # names and when they were set; never values
|
|
569
|
-
npx hawk-app secrets set FEED_API_KEY # type the value (hidden, never echoed)
|
|
570
|
-
npx hawk-app secrets set FEED_API_KEY --value-from-env FEED # CI: read it from an environment variable
|
|
571
|
-
npx hawk-app secrets delete FEED_API_KEY
|
|
572
|
-
```
|
|
573
|
-
|
|
574
|
-
**Client secrets** are for values each client sets for itself, such as its email platform's API key. Declare them on
|
|
575
|
-
the function (`"clientSecrets": ["MAILCHIMP_API_KEY"]`). The app sets them with `app.clientSecrets.set(...)` and the
|
|
576
|
-
function reads `ctx.clientSecrets.MAILCHIMP_API_KEY`. The CLI never sees them.
|
|
577
|
-
|
|
578
|
-
Values are never printed, logged or passed as command-line arguments, and are at most 4 KB each (UTF-8 bytes).
|
|
579
|
-
|
|
580
|
-
**Logs.** `console.log` and `console.error` go to the function's log (kept 30 days):
|
|
581
|
-
|
|
582
|
-
```bash
|
|
583
|
-
npx hawk-app logs sync-feed # recent events
|
|
584
|
-
npx hawk-app logs sync-feed --since 1d # 15m, 1h, 1d or an ISO time
|
|
585
|
-
npx hawk-app logs sync-feed --follow # keep polling every 2 s (Ctrl+C to stop)
|
|
586
|
-
```
|
|
587
|
-
|
|
588
|
-
**When a call fails** the app gets a `HawkApiError`: `function_failed` (the handler threw: see the logs),
|
|
589
|
-
`function_timeout`, `secret_missing` (run `secrets set`), `functions_not_enabled` (ask Engineering) or
|
|
590
|
-
`function_not_found` (deploy the version with the function). The bridge README lists them all.
|
|
591
|
-
|
|
592
|
-
### Background jobs and schedules
|
|
593
|
-
|
|
594
|
-
A function with `"job": true` is a **background job**: the app starts it with `app.functions.start(name, input)`
|
|
595
|
-
(it can't be called with `call`, and a normal function can't be started), it runs up to 900 s (default 300), and the
|
|
596
|
-
app reads its result later with `app.functions.wait(jobId)`. The bridge README's **Background jobs** section has the
|
|
597
|
-
code and limits.
|
|
598
|
-
|
|
599
|
-
`schedules` run job functions on a timer, for the clients where a user turns them on from the app
|
|
600
|
-
(`app.schedules.enable(name)`); nothing runs until someone does:
|
|
601
|
-
|
|
602
|
-
```jsonc
|
|
603
|
-
"functions": { "nightly-report": { "job": true, "timeoutSeconds": 600 } },
|
|
604
|
-
"schedules": {
|
|
605
|
-
"nightly": { "function": "nightly-report", "cron": "0 7 ? * MON-FRI *", "timezone": "America/Chicago" }
|
|
606
|
-
}
|
|
607
|
-
```
|
|
608
|
-
|
|
609
|
-
`hawk-app validate` checks: at most 5 schedules, named like functions; `function` is a job function of the same
|
|
610
|
-
`hawk-app.json`; `cron` is an [EventBridge cron](https://docs.aws.amazon.com/scheduler/latest/UserGuide/schedule-types.html#cron-based)
|
|
611
|
-
with 6 fields (`minutes hours day-of-month month day-of-week year`, no `cron( )` wrapper) that runs **at most hourly**
|
|
612
|
-
(the minutes field is a single number, 0–59) with `?` for day-of-month or day-of-week (EventBridge needs one);
|
|
613
|
-
`timezone` is an IANA name like `America/Chicago`, or `UTC` (the default); the `functions:invoke`
|
|
614
|
-
scope. `schedules` is a setting sent with each deploy: on a released app a change waits for review like scopes,
|
|
615
|
-
and `hawk-app status` lists the registered schedules.
|
|
616
|
-
|
|
617
|
-
## Email
|
|
618
|
-
|
|
619
|
-
Apps with the `email:send` scope can send email with `app.email.send(...)` or, from a function,
|
|
620
|
-
`ctx.platform.email.send(...)` (the bridge README's **Email** section has the details). There are no CLI commands for
|
|
621
|
-
it: add `"email:send"` to `scopes` and deploy (without the scope, sends fail with `scope_missing`). Mail goes out from `noreply@hawksearch.com` with the app's name, to up to 10 recipients per
|
|
622
|
-
send and 200 recipients per app and client per day.
|
|
623
|
-
|
|
624
|
-
## HawkSearch system parameters
|
|
625
|
-
|
|
626
|
-
Apps with the `hawksearch:sysparams:write` scope can read and set the HawkSearch system parameters they list in
|
|
627
|
-
`systemParameters`, for the user's current engine, with `app.hawksearch.systemParameters.list() / get(name) /
|
|
628
|
-
set(name, value)` (or `ctx.platform.hawksearch.systemParameters` in a function; the bridge README's **HawkSearch
|
|
629
|
-
system parameters** section has the details). There are no CLI commands for it:
|
|
630
|
-
|
|
631
|
-
```json
|
|
632
|
-
{
|
|
633
|
-
"scopes": ["hawksearch:sysparams:write"],
|
|
634
|
-
"systemParameters": ["Timezone", "SearchPreviewURL"]
|
|
635
|
-
}
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
`hawk-app validate` checks the list: up to 50 names of 1-50 letters, digits, `_`, `.` or `-`, each listed once
|
|
639
|
-
(names are not case-sensitive); the list needs the scope, and the scope needs a non-empty list. The list is a setting:
|
|
640
|
-
on a released app a change waits for review like scopes. Any user of the app can set the listed parameters, including
|
|
641
|
-
"AE only" ones; encrypted, global and read-only ones never can. Changes apply within about 3 minutes and only to the
|
|
642
|
-
user's current engine (no group sync).
|
|
643
|
-
|
|
644
|
-
## App config
|
|
645
|
-
|
|
646
|
-
Settings someone changes without a deploy (who gets a notification email, a threshold, a switch) go in `config`:
|
|
647
|
-
declare each key with its type and an optional default, and read it in the app with `app.config.get(key)` /
|
|
648
|
-
`app.config.all()` (`ctx.config` in a function; the bridge README's **App config** section has the details).
|
|
649
|
-
|
|
650
|
-
```json
|
|
651
|
-
{
|
|
652
|
-
"config": {
|
|
653
|
-
"signupNotifyEmails": { "type": "emails", "label": "Sign-up notification recipients", "default": ["support@example.com"] },
|
|
654
|
-
"connectorsRequestEmails": { "type": "emails", "label": "API Connections request recipients" }
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
```
|
|
658
|
-
|
|
659
|
-
The values are set outside hawk-app.json, from the Hawk Analytics portal or the CLI, for the whole app (global) or for
|
|
660
|
-
one client, and apply within about 30 seconds, with no deploy or approval. A client's value wins over the global one,
|
|
661
|
-
which wins over the default. **Values are not secret**: they reach the browser, so keep keys in `hawk-app secrets`.
|
|
662
|
-
|
|
663
|
-
```bash
|
|
664
|
-
npx hawk-app config list [--client e3141] # each key: type, default, global and client values
|
|
665
|
-
npx hawk-app config set signupNotifyEmails a@example.com,b@example.com # global
|
|
666
|
-
npx hawk-app config set signupNotifyEmails csd@example.com --client e3141 # one client
|
|
667
|
-
npx hawk-app config unset signupNotifyEmails --client e3141
|
|
668
|
-
```
|
|
669
|
-
|
|
670
|
-
`set` reads the value as the key's type in your hawk-app.json: `string` as is, `number`, `boolean` (`true`/`false`),
|
|
671
|
-
`emails` comma-separated, `json` as JSON (quote it for your shell). `hawk-app validate` checks the declarations: at
|
|
672
|
-
most 50 keys named `^[A-Za-z][A-Za-z0-9_]{0,63}$`, each with `type` (`string`, `number`, `boolean`, `emails`,
|
|
673
|
-
`json`), optional `label` (up to 100 characters), `description` (up to 500) and `default` (a valid value of the type;
|
|
674
|
-
`emails` up to 50 different plain addresses; every value at most 16 KB). The declarations are a setting: on a released
|
|
675
|
-
app a change waits for review like scopes (values can already be set for a key waiting for review). `hawk-app status`
|
|
676
|
-
lists the declared keys.
|
|
677
|
-
|
|
678
|
-
## AI
|
|
679
|
-
|
|
680
|
-
Apps with the `ai:invoke` scope can ask Claude with `app.ai.generate(...)` / `app.ai.stream(...)` or, from a function,
|
|
681
|
-
`ctx.platform.ai` (the bridge README's **AI** section has the details). There are no CLI commands for it: add
|
|
682
|
-
`"ai:invoke"` to `scopes` and deploy. Two tiers, `fast` and `smart`, each with a daily token budget
|
|
683
|
-
per app and client, plus a per-minute request limit; `app.ai.info()` shows today's use. The scope is elevated: an app
|
|
684
|
-
with it is never auto-approved. Prompts and answers are never logged or stored.
|
|
685
|
-
|
|
686
|
-
## Review in HawkSearch
|
|
687
|
-
|
|
688
|
-
- **Not released yet.** A new app's versions are drafts: only Bridgeline staff see it, only on the clients it's enabled
|
|
689
|
-
for, on the Apps page in HawkSearch, always at its newest version. Developers can't release it; an approver does, on
|
|
690
|
-
the Apps page (see [Versions and aliases](#versions-and-aliases)).
|
|
691
|
-
- **Released apps.** Every deploy is a new version. Clients stay on the approved version until the new one is approved in
|
|
692
|
-
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
|
|
694
|
-
the same way. For `config` only the declarations are reviewed: its values are set at any time without review.
|
|
695
|
-
- **Functions.** Engineering enables functions per app. A version whose functions changed (added, removed, new code,
|
|
696
|
-
dependencies or settings) always waits for review, even with auto-approve; reviewers see each function's settings,
|
|
697
|
-
dependency changes and a diff of its sources.
|
|
698
|
-
- **Clients any time.** Client changes (`hawk-app clients` or the Hawk Analytics portal) apply at once, released or
|
|
699
|
-
not, without review (see [Clients](#clients)).
|
|
700
|
-
- **Auto-approve.** Approvers can set an app to auto-approve: new versions and setting changes are then released
|
|
701
|
-
straight away.
|
|
702
|
-
- **Approval workflow.** Bridgeline users submit a version or settings change for approval on the Apps page in
|
|
703
|
-
HawkSearch (`npx hawk-app open`; with an optional note). Members of the **Approvers** group approve it (it's released), reject it with a
|
|
704
|
-
reason, or force approve without a request (for example to roll back). The approvers get an email about each request
|
|
705
|
-
(they can stop these with the link at the bottom), the submitter gets one when it's approved or rejected, and every
|
|
706
|
-
step is posted to the #hawk-app-approvers Slack channel. A new main-line deploy or settings change clears a waiting request (deploy says so: submit it again in
|
|
707
|
-
HawkSearch). `hawk-app status` shows `request waiting for approval (submitted by Jane Doe, 2026-10-08: <note>)`
|
|
708
|
-
or `request rejected by Jane Doe on 2026-10-09: <reason>`.
|
|
709
|
-
- **Elevated apps.** Apps using queries, functions, system parameters, dashboard writes, AI, external Lambdas, HawkSearch
|
|
710
|
-
features or the index (rebuilds, items, management) are never auto-approved;
|
|
711
|
-
approvers review their queries, functions and scopes like any other change.
|
|
712
|
-
- **Rollback and retention.** The released version, the newest version and the 5 newest others are kept, so staff can
|
|
713
|
-
roll back in HawkSearch; older versions are removed when you deploy.
|
|
714
|
-
|
|
715
|
-
## How deploy works
|
|
716
|
-
|
|
717
|
-
1. The CLI asks the platform for the next version number (`v<n>`) with your publisher key, and builds the app for
|
|
718
|
-
`/<slug>/v<n>` (unless `--no-build`).
|
|
719
|
-
2. The CLI sends the list of files (paths and sizes). The platform checks them:
|
|
720
|
-
- the file types are static web content
|
|
721
|
-
- the sizes are within the limits
|
|
722
|
-
- the paths can't leave your app's folder
|
|
723
|
-
- you own the slug.
|
|
724
|
-
|
|
725
|
-
It then returns one **short-lived upload link per file**. Each link allows exactly one upload, to exactly that file in the version's folder, and expires after about 15 minutes.
|
|
726
|
-
3. The CLI uploads each file straight to storage using its link.
|
|
727
|
-
4. The CLI tells the platform it's done, with the result of `npm audit` of the production dependencies (run before the version was reserved, see below). The platform checks that every file arrived and marks the version ready: an app not released yet shows it to Bridgeline staff at once, a released app holds it for review (or releases it, with auto-approve). Versions never change once uploaded, so there's no CDN refresh to wait for.
|
|
728
|
-
|
|
729
|
-
The platform also sets content types and caching: hashed bundles are cached for a year, HTML is never cached, and everything else is cached for 5 minutes.
|
|
730
|
-
|
|
731
|
-
### Dependency check
|
|
732
|
-
|
|
733
|
-
Before it reserves a version number, `deploy` runs `npm audit --json --omit=dev` in the app folder (next to
|
|
734
|
-
`hawk-app.json`, 60 s at most), and it sends the vulnerability counts with the version. Reviewers see them with the other
|
|
735
|
-
pre-approval checks in HawkSearch and the Hawk Analytics portal. High or critical vulnerabilities fail that check, so
|
|
736
|
-
approvers can only force approve the version: when the audit finds any, `deploy` names the packages and asks before
|
|
737
|
-
going on.
|
|
738
|
-
|
|
739
|
-
```
|
|
740
|
-
dependencies 1 high, 1 moderate (npm audit)
|
|
741
|
-
npm audit found 1 high, 1 moderate in the production dependencies (postcss). Approvers can only force approve this
|
|
742
|
-
version. Deploy anyway? (Fix with npm audit fix or by updating the packages; npm run app:audit shows the details.) (y/N)
|
|
743
|
-
```
|
|
744
|
-
|
|
745
|
-
No cancels without reserving anything. `--yes` goes on with a warning, and so does a pipeline (no terminal to ask in).
|
|
746
|
-
Moderate and low findings are only reported. Check before deploying with `npm run app:audit` (in apps created from the
|
|
747
|
-
templates: `npm audit --omit=dev --audit-level=high`, which exits non-zero on high or critical findings, so it also works
|
|
748
|
-
as a CI step). Other outcomes:
|
|
749
|
-
|
|
750
|
-
```
|
|
751
|
-
dependencies no known vulnerabilities
|
|
752
|
-
dependencies not checked: no package-lock.json
|
|
753
|
-
```
|
|
754
|
-
|
|
755
|
-
An app in an npm workspace is audited with the lockfile at the repository root. Without a `package-lock.json` (in the
|
|
756
|
-
app folder or above it), when npm audit fails (for example a registry without audit support) or times out, the
|
|
757
|
-
version is marked "not checked" with a short reason. `--no-audit` skips it (`not checked: skipped (--no-audit)`).
|
|
758
|
-
|
|
759
|
-
## In CI (example: Bitbucket Pipelines)
|
|
760
|
-
|
|
761
|
-
```yaml
|
|
762
|
-
- step:
|
|
763
|
-
name: Deploy Hawk App (dev)
|
|
764
|
-
deployment: development # HAWK_APPS_ENV (dev or prod) and HAWK_APPS_PUBLISHER_KEY as secured variables
|
|
765
|
-
script:
|
|
766
|
-
- npm ci
|
|
767
|
-
- npx hawk-app deploy # builds with the version's base path, then uploads (with hawk-app.json)
|
|
768
|
-
```
|
|
769
|
-
|
|
770
|
-
Requires Node 20 or later. Dependencies: `esbuild` (bundles functions) and `fflate` (zips them).
|
|
1
|
+
# @bridgeline-digital/hawk-app-cli
|
|
2
|
+
|
|
3
|
+
Build, run and release **Hawk Apps**, that run inside HawkSearch. Sign in with your Bridgeline account (or a
|
|
4
|
+
**publisher key** in CI): no AWS account, no bucket names, no platform repo.
|
|
5
|
+
|
|
6
|
+
The full developer docs, with guides, the bridge API and every command, are at
|
|
7
|
+
**[developers.hawksearchapps.com](https://developers.hawksearchapps.com)** (sign in with your Bridgeline account;
|
|
8
|
+
`npx hawk-app docs` opens them).
|
|
9
|
+
|
|
10
|
+
## Quickstart
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npx @bridgeline-digital/hawk-app-cli@latest sso # 1. once per machine: sign in with your Bridgeline account
|
|
14
|
+
npx @bridgeline-digital/hawk-app-cli@latest create my-app && cd my-app && npm install # 2. a new app
|
|
15
|
+
npm run app:dev # 3. run it locally inside a stand-in for HawkSearch
|
|
16
|
+
npm run app:deploy # 4. build and upload a new version (a draft)
|
|
17
|
+
npx hawk-app open # 5. review it in HawkSearch and submit it for approval
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Then `npx hawk-app status` shows where the app stands, and `npx hawk-app doctor` checks everything (Node, login,
|
|
21
|
+
`hawk-app.json`, the project, the scopes the code uses) and says what to fix. `npx hawk-app help` lists the commands by
|
|
22
|
+
task; `npx hawk-app help <command>` gives one command's details.
|
|
23
|
+
|
|
24
|
+
The templates add the CLI as a dev dependency; elsewhere: `npm i -D @bridgeline-digital/hawk-app-cli`.
|
|
25
|
+
|
|
26
|
+
## Start a new app
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx @bridgeline-digital/hawk-app-cli@latest create keyword-explorer # React + Vite + TypeScript
|
|
30
|
+
npx @bridgeline-digital/hawk-app-cli@latest create keyword-explorer --template preact # Preact + Vite + TypeScript (small bundle)
|
|
31
|
+
npx @bridgeline-digital/hawk-app-cli@latest create keyword-explorer --template next # Next.js (static export)
|
|
32
|
+
npx @bridgeline-digital/hawk-app-cli@latest create keyword-explorer --template next-mui # Next.js + Material UI (the UI kit's components)
|
|
33
|
+
npx @bridgeline-digital/hawk-app-cli@latest create keyword-explorer --template vanilla # plain HTML/JS, no build
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The folder name becomes the app's slug. Every template comes ready to run:
|
|
37
|
+
- the bridge set up, with the base path read from `HAWK_APP_BASE_PATH` (set by `hawk-app deploy`; `/<slug>/` in local dev)
|
|
38
|
+
- Spark's look in the host's colors and mode, from the UI kit
|
|
39
|
+
([`@bridgeline-digital/hawk-app-ui`](../ui/README.md)): `next-mui` its MUI theme, provider and components;
|
|
40
|
+
every other template its stylesheet and elements (`<hawk-kpi>`, `<hawk-data-table>`, `<hawk-column-chart>`, …;
|
|
41
|
+
`vanilla` loads them from the CDN)
|
|
42
|
+
- a working analytics example (total searches for the selected engine and date range, per day)
|
|
43
|
+
- a `hawk-app.json` (new apps start as drafts)
|
|
44
|
+
- `app:dev` / `app:deploy` scripts and a README
|
|
45
|
+
- `AGENTS.md`, `CLAUDE.md` and the MCP server's settings for AI coding assistants (see [AI coding assistants](#ai-coding-assistants-agentsmd))
|
|
46
|
+
|
|
47
|
+
Add `--name "Keyword Explorer"` to set the display name, and `--install` to run `npm install` straight away.
|
|
48
|
+
|
|
49
|
+
## Add to an existing app
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npx hawk-app init # creates hawk-app.json and AGENTS.md
|
|
53
|
+
npx hawk-app validate # checks hawk-app.json and the project (bridge, build script)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`init` also writes [AGENTS.md](#ai-coding-assistants-agentsmd) with what a template would have done for you: the bridge,
|
|
57
|
+
the base path, the build folder and the dev server. `npx hawk-app validate --build` builds the app as deploy does and
|
|
58
|
+
checks that its HTML only loads URLs under the version's base path.
|
|
59
|
+
|
|
60
|
+
`hawk-app.json` decides the app's **visibility** and what data it can read (which clients have it is managed
|
|
61
|
+
separately, see [Clients](#clients)):
|
|
62
|
+
|
|
63
|
+
```jsonc
|
|
64
|
+
{
|
|
65
|
+
"slug": "keyword-explorer", // unique; each deploy is served at <apps CDN>/keyword-explorer/v<n>/
|
|
66
|
+
"name": "Keyword Explorer",
|
|
67
|
+
"visibility": "allowList", // allowList: only the app's clients; all: every client except excluded ones;
|
|
68
|
+
// internal: Bridgeline users only; store: the app's clients, and every other
|
|
69
|
+
// client can request it from the store (needs "store", below) (clients: see "Clients")
|
|
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, …
|
|
73
|
+
// through the dashboard API (see the bridge's "Writing to HawkSearch");
|
|
74
|
+
// data:read = your own SQL queries in queries/ (see "Custom data");
|
|
75
|
+
// functions:invoke = server-side functions (see "Functions");
|
|
76
|
+
// email:send = send email (see "Email");
|
|
77
|
+
// storage = the app's own data and files per client (see the bridge's "Storage");
|
|
78
|
+
// hawksearch:sysparams:write = set HawkSearch system parameters
|
|
79
|
+
// (with "systemParameters", see "HawkSearch system parameters");
|
|
80
|
+
// hawksearch:search = HawkSearch search and autocomplete, live or preview;
|
|
81
|
+
// hawksearch:recommendations = the engine's recommendation widgets;
|
|
82
|
+
// hawksearch:index = rebuild the engine's index (where HawkSearch allows it)
|
|
83
|
+
// and read its indexes; hawksearch:index:items = add, update, delete items;
|
|
84
|
+
// hawksearch:index:manage = create, switch and delete indexes, hierarchy, mappings;
|
|
85
|
+
// hawksearch:features:write = HawkSearch features on and off;
|
|
86
|
+
// lambdas:invoke = Lambdas outside Hawk Apps (with "lambdas");
|
|
87
|
+
// ai:invoke = Claude through the platform (see "AI")
|
|
88
|
+
// "config": { "signupNotifyEmails": { "type": "emails", "default": ["a@example.com"] } }
|
|
89
|
+
// = non-secret settings the app reads with app.config, values set without a deploy (see "App config")
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Optional `"listed": false` keeps the app off the host's Apps landing page (the host also hides its own title and back
|
|
94
|
+
button for it), while it still opens by its direct URL. It only changes the listing, not who can open the app
|
|
95
|
+
(`visibility` and the app's clients still apply); the default is `true`, and `hawk-app status` shows `listed no` when it's off.
|
|
96
|
+
|
|
97
|
+
**Store apps.** `"visibility": "store"` offers the app to every client that doesn't have it, on the Store tab of the
|
|
98
|
+
Apps page in HawkSearch: a client user requests it, and a Hawk-App-Manager approves (the client is added to its
|
|
99
|
+
clients) or declines. It needs the listing:
|
|
100
|
+
|
|
101
|
+
```jsonc
|
|
102
|
+
"store": {
|
|
103
|
+
"tagline": "Find and fix searches that return nothing", // one line, at most 120 characters
|
|
104
|
+
"highlights": ["Zero-result searches by volume"], // optional, up to 5
|
|
105
|
+
"screenshots": ["store/overview.png", "store/fix.png"] // 1-5 .png, .jpg or .webp next to hawk-app.json, at most 1 MB each
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
A view with `"trial": true` (one at most, no params) is the live trial: the card runs it on the client's own data
|
|
110
|
+
before they have the app, with a read-only token (only the app's `reports:read`, `hawksearch:read`,
|
|
111
|
+
`hawksearch:search`, `hawksearch:recommendations` and `data:read`). There `app.context.trial` is true: hide what needs
|
|
112
|
+
other scopes (`validate` lists them); `navigate({ intent: 'app' })` opens the request dialog. Without a trial view, or
|
|
113
|
+
for a client without analytics data, the card shows the screenshots.
|
|
114
|
+
|
|
115
|
+
`validate` and `deploy` check the screenshots, and `deploy` uploads them with each version (`_hawk/store/1.png`…). The
|
|
116
|
+
listing is reviewed like the other settings; the price label is set by Bridgeline, not here. `hawk-app status` shows
|
|
117
|
+
whether the app is in the store, its price label and how many requests are open, approved and declined.
|
|
118
|
+
|
|
119
|
+
There is no `status` field any more: approvers release versions from the Apps page in HawkSearch (see [Review](#review-in-hawksearch)). An old `status` is ignored with a warning.
|
|
120
|
+
|
|
121
|
+
Every deploy is a new version served at `/<slug>/v<n>/`, so the build needs that base path. `hawk-app deploy` runs `npm run build` with `HAWK_APP_BASE_PATH=/<slug>/v<n>`; read it in your build config with `/<slug>` as the local fallback:
|
|
122
|
+
- Vite: ``base: `${process.env.HAWK_APP_BASE_PATH ?? '/keyword-explorer'}/` ``
|
|
123
|
+
- Next.js (`output: 'export'`): `basePath: process.env.HAWK_APP_BASE_PATH ?? '/keyword-explorer'`
|
|
124
|
+
- Other build tools: set the base href / public path from `HAWK_APP_BASE_PATH` the same way (plus a trailing `/` where the tool expects one)
|
|
125
|
+
|
|
126
|
+
Apps that use only relative paths (plain HTML) can skip the build with `hawk-app deploy <folder> --no-build`.
|
|
127
|
+
|
|
128
|
+
## Log in (once per machine)
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
npx hawk-app sso # sign in with your Bridgeline account in the browser: the platform issues your own key
|
|
132
|
+
# (--publisher <id>: another team's apps, e.g. qa-team; one saved key per platform)
|
|
133
|
+
npx hawk-app login # or: paste a publisher key from Engineering (input hidden), checked and saved for every app
|
|
134
|
+
npx hawk-app whoami # are you logged in? which platform and key apply here?
|
|
135
|
+
npx hawk-app logout # remove the saved key from this machine
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**`sso`** opens the browser on the HawkSearch sign-in, which goes straight to Bridgeline's AD sign-in (Microsoft). Only
|
|
139
|
+
Bridgeline AD accounts get a key; HawkSearch client logins can't. The platform then issues a key for you on this
|
|
140
|
+
machine (`sso-<name>-<computer>`, in the Services team's publisher, so you work on the team's apps; `--publisher qa-team`
|
|
141
|
+
for another team the platform allows, e.g. QA's own test apps) and the CLI saves it
|
|
142
|
+
like `login` does. You're also added to Hawk-App-Developers in HawkSearch, so you can submit apps for approval. It
|
|
143
|
+
works for 90 days; then commands say it was refused, and you run `sso` again. Signing in again on
|
|
144
|
+
the same computer replaces that computer's key. Works on dev and prod (`npx hawk-app sso --env prod`). Pipelines keep using keys from the portal with `HAWK_APPS_PUBLISHER_KEY`.
|
|
145
|
+
|
|
146
|
+
**`login`** takes a publisher key made in the Hawk Analytics portal. Those keys don't expire.
|
|
147
|
+
|
|
148
|
+
`login` saves the key in your user profile, so every app on the machine uses it and no project files hold it:
|
|
149
|
+
- **Windows:** `%APPDATA%\hawk-app\credentials.json`
|
|
150
|
+
- **macOS / Linux:** `~/.config/hawk-app/credentials.json`, readable only by you
|
|
151
|
+
|
|
152
|
+
It works the same in PowerShell, cmd and bash. The key is never shown, never stored in shell history, and never passed as a command-line argument.
|
|
153
|
+
|
|
154
|
+
Every command works on **dev** unless you say otherwise. `--env prod` (on any command) picks prod:
|
|
155
|
+
`npx hawk-app sso --env prod` signs you in to prod, and `npx hawk-app deploy --env prod` deploys there. Logging in
|
|
156
|
+
never changes which environment later commands use, so prod is always chosen on purpose; `deploy` says
|
|
157
|
+
when they go to prod. Keys are stored per environment, so a dev key is never sent to prod. (`--platform <url>` targets
|
|
158
|
+
any other platform, e.g. one running locally.)
|
|
159
|
+
|
|
160
|
+
**Which environment**, first match wins: `--env` / `--platform`, then `HAWK_APPS_ENV` (`dev` or `prod`) or
|
|
161
|
+
`HAWK_APPS_PLATFORM_URL` in the environment (CI) or the project's `.env.local` / `.env`, else dev.
|
|
162
|
+
|
|
163
|
+
**Which key**, first match wins:
|
|
164
|
+
1. The environment variable `HAWK_APPS_PUBLISHER_KEY`. CI pipelines set it as a secured variable.
|
|
165
|
+
2. The project's `.env.local` (git-ignored), for a per-project override. `npx hawk-app login --local` writes it for you.
|
|
166
|
+
3. The machine login from `hawk-app login` for that environment.
|
|
167
|
+
|
|
168
|
+
## Run locally without HawkSearch (`hawk-app dev`)
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
npm run app:dev # = npx hawk-app dev; asks which client the first time
|
|
172
|
+
npx hawk-app dev --client e3141 --engine 3141
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
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
|
+
`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 always run from the **deployed** version, even here:
|
|
178
|
+
deploy after changing them.
|
|
179
|
+
|
|
180
|
+
Starts your app (`npm run dev`) and a local stand-in for HawkSearch at `http://localhost:3200`, which embeds the app
|
|
181
|
+
the way HawkSearch does (the bridge's own `<hawk-app-frame>`). The app gets **real tokens and real data**: the CLI asks
|
|
182
|
+
the platform for an app token with your publisher key, for any client you name, as a Bridgeline user previewing your
|
|
183
|
+
newest version (with the scopes waiting for review). The app's platform calls go through the local page, so nothing needs
|
|
184
|
+
CORS setup, on dev or prod; so does the AI stream (`app.ai.stream`), which the page pipes through as it arrives.
|
|
185
|
+
|
|
186
|
+
- The toolbar changes the client, site (engine), user (Bridgeline or client user), date range and theme. Toasts,
|
|
187
|
+
titles and navigation requests from the app show in the page; a panel shows the token's claims and expiry.
|
|
188
|
+
- `--url <dev server>`: use an app dev server that is already running (nothing is started). The default is where the
|
|
189
|
+
template serves the app: `http://localhost:3100/<slug>/` for Next.js, `http://localhost:3100/` otherwise. `--no-start`: don't run `npm run dev`. `--port`: the local page's port (default 3200). `--client-user`: start
|
|
190
|
+
as a client user. `HAWK_APPS_DEV_CLIENT` / `HAWK_APPS_DEV_ENGINE` in `.env.local` replace `--client` / `--engine`.
|
|
191
|
+
- The HawkSearch dashboard API (`app.dashboard`, writes, and authenticated search) needs a HawkSearch key, which only a
|
|
192
|
+
host has. Put yours in `.env.local` as `HAWKSEARCH_API_KEY` (and `HAWKSEARCH_DOMAIN`, default
|
|
193
|
+
`https://dev.hawksearch.net`; `HAWKSEARCH_GROUP_KEY=true` for a group key): the platform seals it into the token. It is
|
|
194
|
+
never written anywhere else.
|
|
195
|
+
- 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.
|
|
197
|
+
|
|
198
|
+
## Deploy
|
|
199
|
+
|
|
200
|
+
**One app, one codebase.** Every developer of a publisher (team) can deploy any of its apps, so the CLI
|
|
201
|
+
guards against mixing two apps under one slug: `create` and `init` refuse a slug that's already an app (saying who last
|
|
202
|
+
changed it; `--yes` to set up a folder for that existing app on purpose), and `deploy` asks before going on
|
|
203
|
+
when someone else's key made the app's last change ("`showcase` was last deployed by Jane Doe on … Deploy a new version
|
|
204
|
+
of it? (y/N)"). `--yes` skips the question; in a pipeline (no terminal) it's a warning and the command goes on. Another
|
|
205
|
+
publisher's slug is always refused (`409 slug_owned`).
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
npx hawk-app deploy # build and upload a new version, with hawk-app.json
|
|
209
|
+
npx hawk-app status # released or not, the newest version, the approval request, the aliases
|
|
210
|
+
npx hawk-app open # the app in HawkSearch: review it and submit it for approval
|
|
211
|
+
npx hawk-app clients add e3141 # give a client the app (see "Clients")
|
|
212
|
+
npx hawk-app deploy --dry-run # what hawk-app.json would change, without a version (use this on pull requests)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
| Command | What it does |
|
|
216
|
+
|---|---|
|
|
217
|
+
| `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. |
|
|
219
|
+
| `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
|
+
| `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
|
+
| `versions` | Every version with its status (draft, waiting, approved, rejected) and the aliases on it. |
|
|
222
|
+
| `alias set <name> <version>` / `alias delete <name>` | Point an alias at a version that isn't approved (only Bridgeline staff in its clients see it), or delete one of yours with no clients on it. Approvers do the rest. |
|
|
223
|
+
| `open [--alias <name>]` | Opens the app's page in HawkSearch (dev or prod, from `--env`) to review it and submit it for approval; `--alias` shows it as that alias's clients see it. Prints the link when there's no browser. |
|
|
224
|
+
| `docs [<topic>]` | Opens the developer docs at developers.hawksearchapps.com, or a topic's page (`start`, `versions`, `bridge`, `api`, `cli`, `commands`, `functions`, …; `hawk-app help docs` lists them). Prints the link when there's no browser. |
|
|
225
|
+
| `doctor` | Checks everything a developer needs: Node, the login and platform, `hawk-app.json`, the project (bridge, build script), the template, the scopes the code uses, the app on the platform and the MCP config. Each line says what to fix; exits 1 when something must be. |
|
|
226
|
+
| `help [<command> \| all]` | The commands by task; one command's details; or every detail. |
|
|
227
|
+
| `why <client id>` | What a client's users see of the app, and why (the client list, its alias, that version's status). |
|
|
228
|
+
| `status` | Shows the automated review checks approvers will see (bundle, dependencies, origins, queries, …), the released version, the newest version and its status, settings waiting for review, the approval request (waiting for approval, or rejected and why), auto-approve, review emails (when an approver turned them off), the view cache (when Engineering shortened or stopped it), visibility, scopes, system parameters, config keys and clients. |
|
|
229
|
+
| `clients` / `clients add <id> [<id> …] [--name "<name>"] [--alias <name>]` / `clients exclude <id> [--name "<name>"]` / `clients remove <id>` | Which clients have the app, and which version (alias) each gets (see [Clients](#clients)). |
|
|
230
|
+
| `data tables` / `data describe <table>` / `data run <file.sql> --client <id> [--param name=value …]` | The tables app queries can use, and running a query on dev (see [Custom data](#custom-data-queries)). |
|
|
231
|
+
| `secrets list` / `secrets set <NAME> [--value-from-env VAR]` / `secrets delete <NAME>` | The app's function secrets (see [Functions](#functions)). |
|
|
232
|
+
| `logs <function> [--since 1h] [--follow]` | A function's logs (see [Functions](#functions)). |
|
|
233
|
+
| `config list [--client <id>]` / `config set <key> <value> [--client <id>]` / `config unset <key> [--client <id>]` | The app's config values, global or for one client, without a deploy (see [App config](#app-config)). |
|
|
234
|
+
| `sso [--publisher <id>]` | Sign in with your Bridgeline account; the platform issues your own key (see above). |
|
|
235
|
+
| `login [--local]` / `whoami` / `logout` | Save a publisher key (CI, or without SSO), check which key and platform apply, or remove the saved key. |
|
|
236
|
+
| `errors` / `usage` | The app's page errors, and its opens, users and clients per day (see [Errors and usage](#errors-and-usage-hawk-app-errors-hawk-app-usage)). |
|
|
237
|
+
| `upgrade` / `templates` / `mcp` | Keep up with the template (see below); list the templates; the MCP server for AI assistants (see [MCP server](#mcp-server-hawk-app-mcp)). |
|
|
238
|
+
|
|
239
|
+
Options: `--manifest <file>` to use a manifest other than `hawk-app.json`, `--env dev|prod` (default dev), and `--platform <url>` to target another platform. `deploy` never uploads hidden files such as `.env.local`.
|
|
240
|
+
|
|
241
|
+
## Keep up with the template (`hawk-app upgrade`)
|
|
242
|
+
|
|
243
|
+
Templates move on (a new Next.js, a security fix, a config change). `hawk-app upgrade` brings an app's template-owned
|
|
244
|
+
parts up to the template this CLI ships:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
npx hawk-app upgrade # shows what would change; changes nothing
|
|
248
|
+
npx hawk-app upgrade --yes # applies it and runs npm install
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
- **Dependencies:** the packages the template pins (e.g. `next`, `react`, `@mui/material`, the bridge) are raised to the
|
|
252
|
+
template's version. Never lowered, and your own packages are never touched. A package the template gained since the
|
|
253
|
+
app was made is added: a `react`, `preact`, `next` or `next-mui` app gets the UI kit
|
|
254
|
+
(`@bridgeline-digital/hawk-app-ui`), and `upgrade` lists the code to change to start using it (next-mui: the
|
|
255
|
+
provider; the others: the stylesheet and elements imports). A major version (Material UI 7 → 9) is called out, with
|
|
256
|
+
what to check.
|
|
257
|
+
- **Config files** (`next.config.ts`, `vite.config.ts`, `tsconfig.json`): replaced only if you never edited them (the
|
|
258
|
+
CLI knows every version the templates have shipped). An edited one is left alone, with a diff of what the template
|
|
259
|
+
changed so you can apply it yourself.
|
|
260
|
+
- The vanilla template's CDN imports (the bridge and the UI kit, in `public/app.js` and `public/index.html`) are raised
|
|
261
|
+
together to the template's minor version. A vanilla app made before the kit adds its two lines itself: see a new
|
|
262
|
+
app's `public/index.html` and `public/app.js`.
|
|
263
|
+
- **AGENTS.md:** the CLI's part is refreshed, and `CLAUDE.md` gets its `@AGENTS.md` import (see below).
|
|
264
|
+
- **Never** your code (`src/`), `hawk-app.json` settings or any other file.
|
|
265
|
+
|
|
266
|
+
It uses `"template"` in `hawk-app.json`, which `hawk-app create` writes; for older apps it guesses from the dependencies
|
|
267
|
+
(and records it on `--yes`), or pass `--template <name>`. An app set up with `init` that follows no template gets only
|
|
268
|
+
AGENTS.md refreshed. `hawk-app deploy` tells you when an app is behind its template. After upgrading: build, test
|
|
269
|
+
(`npm run dev`) and deploy as usual. `--no-install` skips `npm install`.
|
|
270
|
+
|
|
271
|
+
## Versions and aliases
|
|
272
|
+
|
|
273
|
+
Every deploy is a version with a status from its review: draft, waiting (submitted), approved or rejected. An **alias**
|
|
274
|
+
points at a version and takes its status: on an approved version its clients' users see the app; on any other only
|
|
275
|
+
Bridgeline staff in them do. **`default`** is the version released to everyone, for every client without another alias.
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
npx hawk-app deploy --alias staging # staging → the new version (staff only until it's approved)
|
|
279
|
+
npx hawk-app clients add e4 e5 --alias staging # new clients on staging: staff in them test it
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
When an approver approves that version, they choose: release to everyone (`default` moves), or only to the aliases on it
|
|
283
|
+
(staging's clients see it, everyone else stays on `default`). You can point aliases only at versions that aren't approved,
|
|
284
|
+
and put on them only clients whose users don't see the app yet; anything users see is for approvers (the portal or
|
|
285
|
+
HawkSearch). The full picture, with diagrams: docs/versions-and-aliases.md in the hawk-apps repository. `hawk-app status` shows the aliases, `hawk-app versions` every version with its status:
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
released v7 (default: every client without another alias)
|
|
289
|
+
aliases default v7 (all other clients) · pilot v8 (3 clients) · staging v9 (draft: Bridgeline staff only, 2 clients)
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## Errors and usage (`hawk-app errors`, `hawk-app usage`)
|
|
293
|
+
|
|
294
|
+
The bridge reports the app's page errors and one open per page load (platform/docs/telemetry.md):
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
npx hawk-app errors # the last 7 days (--days 1-30): grouped, the most frequent first, with versions and clients
|
|
298
|
+
npx hawk-app usage # the last 30 days (--days 1-400): opens, view opens, users and clients; staff apart
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Accessibility check
|
|
302
|
+
|
|
303
|
+
`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
|
|
304
|
+
the version: approvers see it as the `accessibility` review check (critical issues fail it; a force approve still goes
|
|
305
|
+
through). `hawk-app validate --build` prints critical and serious issues before you deploy. It sees each page's HTML as
|
|
306
|
+
built, not what the app draws later in the browser, and no layout (so no color contrast).
|
|
307
|
+
|
|
308
|
+
## Release channels: latest and dev
|
|
309
|
+
|
|
310
|
+
The bridge, the UI kit and the CLI are published together, with the same version, on two npm tags:
|
|
311
|
+
- **`latest`**, from `main`: stable releases, matched to the prod platform. What `npm install` and `npx hawk-app` get.
|
|
312
|
+
- **`dev`**, from `dev`: pre-releases (`x.y.z-dev.<time>`) with features still in development, which may need the dev
|
|
313
|
+
platform.
|
|
314
|
+
|
|
315
|
+
To start an app on dev features, create it with the dev CLI. It puts the bridge, the UI kit and the CLI on the `dev` tag (the vanilla
|
|
316
|
+
template's CDN imports too), so the app gets the dev bridge and not the release its template pins:
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
npx @bridgeline-digital/hawk-app-cli@dev create my-app # a dev app (or --channel dev with any CLI)
|
|
320
|
+
npx hawk-app upgrade --channel latest # once the features are released: ^ the latest versions (--yes applies)
|
|
321
|
+
npx hawk-app upgrade --channel dev # move an existing app onto the dev builds (again: onto the newest ones)
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
- `--channel` installs the packages it moves by name (`npm install <package>@dev`, or `@^<latest>`), so
|
|
325
|
+
`package-lock.json` moves with them; npm then writes the version it installed (`^0.1.0-dev.<time>`), which is
|
|
326
|
+
still the dev channel. Running `--channel dev` again moves to the newest dev builds.
|
|
327
|
+
- A dev app's `npm update` follows the newest dev builds; its `package-lock.json` records the exact one, so commit it.
|
|
328
|
+
- `hawk-app upgrade` keeps a dev app on `dev`; `--channel` is the only thing that moves it.
|
|
329
|
+
- `hawk-app validate` warns when a dev app is set to use the prod platform (`--env prod`, `HAWK_APPS_ENV=prod`).
|
|
330
|
+
|
|
331
|
+
## AI coding assistants (AGENTS.md)
|
|
332
|
+
|
|
333
|
+
`create` and `init` write these files for AI coding assistants, so they build Hawk Apps the way the platform expects
|
|
334
|
+
instead of guessing:
|
|
335
|
+
|
|
336
|
+
- **`AGENTS.md`**, read by GitHub Copilot, Cursor, Codex and others: the rules (no secrets, data only through the
|
|
337
|
+
bridge, static files, the base path), the bridge's calls and their scopes, queries and functions, the commands, and
|
|
338
|
+
what fails review. Its project section matches the app's template, or, for an app set up with `init`, says what the
|
|
339
|
+
project has to do itself.
|
|
340
|
+
- **`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`.
|
|
344
|
+
|
|
345
|
+
The CLI owns the part of AGENTS.md between its `<!-- hawk-app:start … -->` and `<!-- hawk-app:end -->` markers, and
|
|
346
|
+
`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 MCP
|
|
348
|
+
settings file gets the `hawk-apps` server added (one with comments is left alone: add it yourself).
|
|
349
|
+
|
|
350
|
+
### MCP server (`hawk-app mcp`)
|
|
351
|
+
|
|
352
|
+
`hawk-app mcp` serves the CLI's commands to AI coding assistants over stdio (the [Model Context
|
|
353
|
+
Protocol](https://modelcontextprotocol.io)), so they can look things up and check their own work:
|
|
354
|
+
|
|
355
|
+
| Tool | What it does |
|
|
356
|
+
| --- | --- |
|
|
357
|
+
| `docs` | Searches or reads the bridge and UI kit READMEs (the app's installed versions), this README, the `hawk-app.json` schema and the showcase app, whose code shows every feature working. |
|
|
358
|
+
| `validate` | `hawk-app validate`, optionally with `--build`. |
|
|
359
|
+
| `status` | `hawk-app status`, including the automated review checks approvers will see. |
|
|
360
|
+
| `doctor` | `hawk-app doctor`: what's wrong with the setup, and how to fix it. |
|
|
361
|
+
| `versions`, `why` | Every version with its status and aliases; what a client sees of the app, and why. |
|
|
362
|
+
| `errors`, `usage` | The app's page errors; its opens, users and clients per day. |
|
|
363
|
+
| `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
|
+
| `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
|
+
| `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). |
|
|
366
|
+
| `data_tables`, `data_describe` | Table names and columns for queries. Never data: `data run` isn't a tool, because its rows would go to the assistant. |
|
|
367
|
+
| `logs` | A function's logs. |
|
|
368
|
+
| `init`, `upgrade` | `hawk-app init`; `hawk-app upgrade` (`apply` for `--yes`). |
|
|
369
|
+
| `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
|
+
| `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
|
+
|
|
372
|
+
It also has **prompts**, ready-made tasks that use the tools. Claude Code lists them as slash commands (type `/` and
|
|
373
|
+
`hawk`):
|
|
374
|
+
|
|
375
|
+
| Prompt | In Claude Code | What it does |
|
|
376
|
+
| --- | --- | --- |
|
|
377
|
+
| `check-before-submit` | `/mcp__hawk-apps__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. |
|
|
378
|
+
| `add-feature` | `/mcp__hawk-apps__add-feature "<what to build>"` | Finds the bridge calls, scopes and showcase examples it needs, says what the review will see, makes the change and validates it. |
|
|
379
|
+
| `explain-app` | `/mcp__hawk-apps__explain-app` | What the app does, its scopes (unused or missing ones too) and where it stands in review. |
|
|
380
|
+
| `deploy-to-client` | `/mcp__hawk-apps__deploy-to-client "<client name>"` | Deploys to dev and gives a client the app; you pick the client from the matches for its name. |
|
|
381
|
+
|
|
382
|
+
Each takes an optional `dir` for a workspace with several apps. In VS Code with Copilot they're under `/` too.
|
|
383
|
+
|
|
384
|
+
- **Dev only.** Every tool runs with `--env dev`; nothing reaches prod. Approvals, secrets, removing clients and logins
|
|
385
|
+
aren't tools.
|
|
386
|
+
- **Your key stays put.** The tools use your saved login (or `.env.local`) like the CLI does; the key never passes
|
|
387
|
+
through the assistant.
|
|
388
|
+
- **No client names.** Tool output shows client ids only, and client names go to you, never to the assistant: the
|
|
389
|
+
client picker is a form your assistant shows you (MCP elicitation). An assistant without forms tells you to run
|
|
390
|
+
`npx hawk-app clients add --pick "<name>"` in a terminal instead.
|
|
391
|
+
- **No silent go-aheads.** Where the CLI would ask (a teammate made the app's last change, or `npm audit` found high or
|
|
392
|
+
critical vulnerabilities) the tool stops and tells the assistant, which has to ask you and call again with `force`.
|
|
393
|
+
- **Several apps in one workspace?** Each tool takes `dir`, the app's folder relative to the workspace.
|
|
394
|
+
|
|
395
|
+
`create` and `init` add it to `.mcp.json` and `.vscode/mcp.json`, started as
|
|
396
|
+
`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, copy the `mcpServers` entry of `.mcp.json` to `.cursor/mcp.json`.
|
|
398
|
+
|
|
399
|
+
## Clients
|
|
400
|
+
|
|
401
|
+
Which clients have the app is not in `hawk-app.json`: the platform's registry holds it, and Engineering and approvers
|
|
402
|
+
change it in the Hawk Analytics portal. Developers can change it with the CLI:
|
|
403
|
+
|
|
404
|
+
```bash
|
|
405
|
+
npx hawk-app clients # the app's visibility and its clients
|
|
406
|
+
npx hawk-app clients search outdoor # find a client's id by its name
|
|
407
|
+
npx hawk-app clients add e3141 --name "Outdoor Demo" # give a client the app (visibility allowList)
|
|
408
|
+
npx hawk-app clients add --pick "outdoor" # the same, picking the client from the matches
|
|
409
|
+
npx hawk-app clients add e4 e5 --alias staging # on an alias: the version they get
|
|
410
|
+
npx hawk-app clients add e4 --alias default # back to the default version
|
|
411
|
+
npx hawk-app clients exclude e2002 # opt a client out (visibility all)
|
|
412
|
+
npx hawk-app clients remove e3141 # take the client off the list
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
`add` and `exclude` take only ids from the environment's HawkSearch client list (the Hawk Analytics portal's client
|
|
416
|
+
picker): an id the dashboard doesn't use, such as `3141` for `e3141`, is refused with the id you probably meant. The
|
|
417
|
+
client's name comes from that list unless you pass `--name`. `remove` takes any id on the app's list.
|
|
418
|
+
|
|
419
|
+
`hawk-app clients` lists `+ e3141 (Outdoor Demo)` for a client that has the app and `- e2002 (Acme Retail)` for one
|
|
420
|
+
that is opted out; `[on pilot]` after a client on an alias (`[on staging · draft: Bridgeline staff only]` when its version
|
|
421
|
+
isn't approved). What the list means depends on `visibility` in `hawk-app.json`:
|
|
422
|
+
- `allowList`: only the clients marked `+` have the app.
|
|
423
|
+
- `all`: every client has it, except the ones marked `-`.
|
|
424
|
+
- `internal`: Bridgeline users only; the list is ignored.
|
|
425
|
+
|
|
426
|
+
Changes apply at once, released or not, without a deploy or an approval, and are recorded in the app's history.
|
|
427
|
+
`--name` is a label for people (up to 100 characters); the client id is what grants access. Register the app before
|
|
428
|
+
adding clients.
|
|
429
|
+
|
|
430
|
+
`--alias` sets the version a client gets (see [Versions and aliases](#versions-and-aliases)). You can change it only for
|
|
431
|
+
clients whose users don't see the app yet; after that, approvers do.
|
|
432
|
+
|
|
433
|
+
## Custom data (queries)
|
|
434
|
+
|
|
435
|
+
Apps with the `data:read` scope can ship their own SQL, run by the platform against the current client's analytics
|
|
436
|
+
tables. Put each query in `queries/` next to `hawk-app.json` (not in the build folder) as a pair of files:
|
|
437
|
+
|
|
438
|
+
```text
|
|
439
|
+
my-app/
|
|
440
|
+
hawk-app.json "scopes": ["data:read"]
|
|
441
|
+
queries/
|
|
442
|
+
top-searches.sql
|
|
443
|
+
top-searches.json
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
```sql
|
|
447
|
+
-- queries/top-searches.sql
|
|
448
|
+
SELECT search_date, sum(search_count) AS searches
|
|
449
|
+
FROM dailysearches
|
|
450
|
+
WHERE search_date BETWEEN ? AND ? AND {{partitions(from, to)}}
|
|
451
|
+
GROUP BY 1 ORDER BY 1
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
```json
|
|
455
|
+
{ "params": [{ "name": "from", "type": "date", "required": true }, { "name": "to", "type": "date", "required": true }] }
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
The app calls it with `app.data.query('top-searches', { from, to })` (see the bridge README for types, the
|
|
459
|
+
`{{partitions(from, to)}}` macro, the tables and the errors).
|
|
460
|
+
|
|
461
|
+
**Step by step**
|
|
462
|
+
1. Add `"data:read"` to `scopes` in `hawk-app.json`.
|
|
463
|
+
2. Find your table and columns: `npx hawk-app data tables`, then `npx hawk-app data describe <table>`.
|
|
464
|
+
3. Write `queries/<name>.sql` and `queries/<name>.json`. Filter by a date range and add
|
|
465
|
+
`{{partitions(from, to)}}`.
|
|
466
|
+
4. Try it on dev: `npx hawk-app data run queries/<name>.sql --client <id> --param from=… --param to=…`.
|
|
467
|
+
5. Call it from the app with `app.data.query('<name>', { … })`. Queries always run from a **deployed** version, even
|
|
468
|
+
in dev mode (`?devUrl=`): the platform never reads your local `queries/` folder. So after adding or editing a
|
|
469
|
+
query, deploy (step 6) before calling it from the app; use `data run` to iterate on the SQL itself.
|
|
470
|
+
6. `npm run app:deploy` (checks the queries and uploads them with the new version, with `hawk-app.json`).
|
|
471
|
+
7. Bridgeline staff review the version in HawkSearch: they see each added or changed query and its SQL diff.
|
|
472
|
+
Once approved (or right away on a draft or auto-approve app), the app's users get the new queries.
|
|
473
|
+
|
|
474
|
+
`hawk-app validate` and `hawk-app deploy` check the queries before anything is uploaded, with the same rules as the
|
|
475
|
+
platform:
|
|
476
|
+
- names are lowercase letters, digits and single dashes (up to 64), and every `.sql` has a `.json` (and vice versa);
|
|
477
|
+
only `.sql` and `.json` files, no subfolders, at most 50 queries
|
|
478
|
+
- each `.sql` is one `SELECT` or `WITH … SELECT` statement (no `;`), up to 64 KB
|
|
479
|
+
- the `?` count equals the number of params (a `string[]` param is one `?`, written `IN ?`)
|
|
480
|
+
- params have a `name` and a `type` (`string`, `int`, `decimal`, `date`, `timestamp`, `boolean`, `string[]`), and
|
|
481
|
+
optionally `required`, `default`, `min`/`max` (numbers) and `maxItems` (`string[]`, up to 100)
|
|
482
|
+
- `{{partitions(from, to)}}` (optionally `{{partitions(from, to, alias)}}`) uses params declared as `date`
|
|
483
|
+
- `queries/` needs `data:read` in `scopes`.
|
|
484
|
+
|
|
485
|
+
`deploy` uploads them with the version (`… 3 files, 12 KB from dist, 2 queries`). They're part of the version: staff
|
|
486
|
+
review them with it, and each version runs its own queries.
|
|
487
|
+
|
|
488
|
+
Try a query on dev before deploying it:
|
|
489
|
+
|
|
490
|
+
```bash
|
|
491
|
+
npx hawk-app data tables # the tables queries can use (and which are partitioned)
|
|
492
|
+
npx hawk-app data tables --app my-app # also: may my-app use the sensitive tables?
|
|
493
|
+
npx hawk-app data describe dailysearches # a table's columns and types
|
|
494
|
+
npx hawk-app data run queries/top-searches.sql --client e3141 --param from=2026-09-01 --param to=2026-09-30
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
`data run` sends the SQL and its `.json` from your disk (nothing needs to be deployed), waits for the result, and prints
|
|
498
|
+
up to 50 rows plus the bytes scanned. `--client` is the HawkSearch client id (the one in the app's context,
|
|
499
|
+
`context.tenant.clientId`); on dev, use a demo or test client. `string[]` values are comma-separated (`--param engines=a,b`). It only works
|
|
500
|
+
against the dev platform. When too many queries are running (429 `too_many_queries`) it waits and tries again.
|
|
501
|
+
|
|
502
|
+
The sensitive tables (visitor-level data such as `summary`) are listed apart: only apps Engineering has opted in can
|
|
503
|
+
query them. Ask Engineering if your app needs one.
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
## Functions
|
|
507
|
+
|
|
508
|
+
Server-side code that ships with each version (AWS Lambda, Node 22), called from the app with
|
|
509
|
+
`app.functions.call(name, input)`. The bridge README's **Functions** section covers `ctx`, errors and limits; this is
|
|
510
|
+
the CLI side.
|
|
511
|
+
|
|
512
|
+
**Before you start: Engineering must enable functions for your app** (they can reach the internet and hold secrets),
|
|
513
|
+
and a version whose functions changed **always needs review**, even on an auto-approve app.
|
|
514
|
+
|
|
515
|
+
1. Add `"functions:invoke"` to `scopes`, and declare each function in `hawk-app.json`:
|
|
516
|
+
```jsonc
|
|
517
|
+
"functions": {
|
|
518
|
+
"sync-feed": { "timeoutSeconds": 20, "memoryMb": 512, "secrets": ["FEED_API_KEY"] },
|
|
519
|
+
"echo": {}, // defaults: 10 s, 256 MB, no secrets
|
|
520
|
+
"nightly-report": { "job": true, "timeoutSeconds": 600 } // a background job (see below)
|
|
521
|
+
}
|
|
522
|
+
```
|
|
523
|
+
2. Write `functions/<name>.ts` (or `.mts`, `.js`, `.mjs`) exporting `handler`, with
|
|
524
|
+
`@bridgeline-digital/hawk-app-bridge` (0.4.0 or later) installed in the app:
|
|
525
|
+
```ts
|
|
526
|
+
import type { HawkFunction } from '@bridgeline-digital/hawk-app-bridge/server';
|
|
527
|
+
export const handler: HawkFunction<{ name: string }, { greeting: string }> = async (input, ctx) => ({
|
|
528
|
+
greeting: `Hello ${input.name} from ${ctx.client.id}`,
|
|
529
|
+
});
|
|
530
|
+
```
|
|
531
|
+
Shared code goes in subfolders (`functions/lib/…`) or anywhere else you import from (`src/`, npm packages).
|
|
532
|
+
3. Set its secrets once: `npx hawk-app secrets set FEED_API_KEY`.
|
|
533
|
+
4. `npx hawk-app validate`, then deploy as usual: `npm run app:deploy`.
|
|
534
|
+
5. Call it from the app: `await app.functions.call('sync-feed', { since: '2026-09-01' })`. Like queries, functions
|
|
535
|
+
always run from a **deployed** version, also in dev mode (`?devUrl=`).
|
|
536
|
+
|
|
537
|
+
`hawk-app validate` and `hawk-app deploy` check, with the platform's rules:
|
|
538
|
+
- at most 10 functions; names are lowercase letters, digits and single dashes, up to 40 characters;
|
|
539
|
+
- `job` true or false; `timeoutSeconds` 1–25 (1–900 for a job), `memoryMb` 128–1024, `secrets` up to 20 unique
|
|
540
|
+
`UPPER_SNAKE_CASE` names, no other keys;
|
|
541
|
+
- every declared function has its `functions/<name>.*` file, and every entry file in `functions/` is declared;
|
|
542
|
+
- the `functions:invoke` scope, and the bridge installed with its `/server` entry.
|
|
543
|
+
|
|
544
|
+
**What deploy does with them.** Before reserving a version, each function is bundled with esbuild (Node 22, ESM,
|
|
545
|
+
every dependency included, your `handler` wrapped for Lambda) into `index.mjs`, zipped as `bundle.zip` (10 MB at
|
|
546
|
+
most). `source.zip` (every file under `functions/` except `node_modules`, 5 MB at most) and `deps.json` (the npm
|
|
547
|
+
packages in the bundle, with versions) go along so reviewers can see the code and its dependencies. After the upload
|
|
548
|
+
the platform deploys each function and the CLI prints one line per function:
|
|
549
|
+
|
|
550
|
+
```
|
|
551
|
+
Bundling 2 functions…
|
|
552
|
+
ƒ echo 4 KB zipped, 1 package
|
|
553
|
+
ƒ sync-feed 38 KB zipped, 2 packages
|
|
554
|
+
Uploading v9 of my-app: 12 files, 210 KB from dist, 2 functions
|
|
555
|
+
✔ function echo (10 s, 256 MB) Lambda version 3
|
|
556
|
+
✔ function sync-feed (20 s, 512 MB, secrets FEED_API_KEY) Lambda version 5
|
|
557
|
+
✔ Deployed v9 of my-app (12 files, 2 functions). A draft: clients stay on v8 until it's approved in HawkSearch …
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
Zips are built the same way every time, so a function whose code didn't change keeps the same code hash (and doesn't
|
|
561
|
+
show as changed in review).
|
|
562
|
+
|
|
563
|
+
**Secrets.** App-level, stored by the platform (encrypted, in AWS Systems Manager Parameter Store), never in the app or
|
|
564
|
+
a version: a change applies to every version at once. A function receives only the secrets it declares, as
|
|
565
|
+
`ctx.secrets.NAME`.
|
|
566
|
+
|
|
567
|
+
```bash
|
|
568
|
+
npx hawk-app secrets list # names and when they were set; never values
|
|
569
|
+
npx hawk-app secrets set FEED_API_KEY # type the value (hidden, never echoed)
|
|
570
|
+
npx hawk-app secrets set FEED_API_KEY --value-from-env FEED # CI: read it from an environment variable
|
|
571
|
+
npx hawk-app secrets delete FEED_API_KEY
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
**Client secrets** are for values each client sets for itself, such as its email platform's API key. Declare them on
|
|
575
|
+
the function (`"clientSecrets": ["MAILCHIMP_API_KEY"]`). The app sets them with `app.clientSecrets.set(...)` and the
|
|
576
|
+
function reads `ctx.clientSecrets.MAILCHIMP_API_KEY`. The CLI never sees them.
|
|
577
|
+
|
|
578
|
+
Values are never printed, logged or passed as command-line arguments, and are at most 4 KB each (UTF-8 bytes).
|
|
579
|
+
|
|
580
|
+
**Logs.** `console.log` and `console.error` go to the function's log (kept 30 days):
|
|
581
|
+
|
|
582
|
+
```bash
|
|
583
|
+
npx hawk-app logs sync-feed # recent events
|
|
584
|
+
npx hawk-app logs sync-feed --since 1d # 15m, 1h, 1d or an ISO time
|
|
585
|
+
npx hawk-app logs sync-feed --follow # keep polling every 2 s (Ctrl+C to stop)
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
**When a call fails** the app gets a `HawkApiError`: `function_failed` (the handler threw: see the logs),
|
|
589
|
+
`function_timeout`, `secret_missing` (run `secrets set`), `functions_not_enabled` (ask Engineering) or
|
|
590
|
+
`function_not_found` (deploy the version with the function). The bridge README lists them all.
|
|
591
|
+
|
|
592
|
+
### Background jobs and schedules
|
|
593
|
+
|
|
594
|
+
A function with `"job": true` is a **background job**: the app starts it with `app.functions.start(name, input)`
|
|
595
|
+
(it can't be called with `call`, and a normal function can't be started), it runs up to 900 s (default 300), and the
|
|
596
|
+
app reads its result later with `app.functions.wait(jobId)`. The bridge README's **Background jobs** section has the
|
|
597
|
+
code and limits.
|
|
598
|
+
|
|
599
|
+
`schedules` run job functions on a timer, for the clients where a user turns them on from the app
|
|
600
|
+
(`app.schedules.enable(name)`); nothing runs until someone does:
|
|
601
|
+
|
|
602
|
+
```jsonc
|
|
603
|
+
"functions": { "nightly-report": { "job": true, "timeoutSeconds": 600 } },
|
|
604
|
+
"schedules": {
|
|
605
|
+
"nightly": { "function": "nightly-report", "cron": "0 7 ? * MON-FRI *", "timezone": "America/Chicago" }
|
|
606
|
+
}
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
`hawk-app validate` checks: at most 5 schedules, named like functions; `function` is a job function of the same
|
|
610
|
+
`hawk-app.json`; `cron` is an [EventBridge cron](https://docs.aws.amazon.com/scheduler/latest/UserGuide/schedule-types.html#cron-based)
|
|
611
|
+
with 6 fields (`minutes hours day-of-month month day-of-week year`, no `cron( )` wrapper) that runs **at most hourly**
|
|
612
|
+
(the minutes field is a single number, 0–59) with `?` for day-of-month or day-of-week (EventBridge needs one);
|
|
613
|
+
`timezone` is an IANA name like `America/Chicago`, or `UTC` (the default); the `functions:invoke`
|
|
614
|
+
scope. `schedules` is a setting sent with each deploy: on a released app a change waits for review like scopes,
|
|
615
|
+
and `hawk-app status` lists the registered schedules.
|
|
616
|
+
|
|
617
|
+
## Email
|
|
618
|
+
|
|
619
|
+
Apps with the `email:send` scope can send email with `app.email.send(...)` or, from a function,
|
|
620
|
+
`ctx.platform.email.send(...)` (the bridge README's **Email** section has the details). There are no CLI commands for
|
|
621
|
+
it: add `"email:send"` to `scopes` and deploy (without the scope, sends fail with `scope_missing`). Mail goes out from `noreply@hawksearch.com` with the app's name, to up to 10 recipients per
|
|
622
|
+
send and 200 recipients per app and client per day.
|
|
623
|
+
|
|
624
|
+
## HawkSearch system parameters
|
|
625
|
+
|
|
626
|
+
Apps with the `hawksearch:sysparams:write` scope can read and set the HawkSearch system parameters they list in
|
|
627
|
+
`systemParameters`, for the user's current engine, with `app.hawksearch.systemParameters.list() / get(name) /
|
|
628
|
+
set(name, value)` (or `ctx.platform.hawksearch.systemParameters` in a function; the bridge README's **HawkSearch
|
|
629
|
+
system parameters** section has the details). There are no CLI commands for it:
|
|
630
|
+
|
|
631
|
+
```json
|
|
632
|
+
{
|
|
633
|
+
"scopes": ["hawksearch:sysparams:write"],
|
|
634
|
+
"systemParameters": ["Timezone", "SearchPreviewURL"]
|
|
635
|
+
}
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
`hawk-app validate` checks the list: up to 50 names of 1-50 letters, digits, `_`, `.` or `-`, each listed once
|
|
639
|
+
(names are not case-sensitive); the list needs the scope, and the scope needs a non-empty list. The list is a setting:
|
|
640
|
+
on a released app a change waits for review like scopes. Any user of the app can set the listed parameters, including
|
|
641
|
+
"AE only" ones; encrypted, global and read-only ones never can. Changes apply within about 3 minutes and only to the
|
|
642
|
+
user's current engine (no group sync).
|
|
643
|
+
|
|
644
|
+
## App config
|
|
645
|
+
|
|
646
|
+
Settings someone changes without a deploy (who gets a notification email, a threshold, a switch) go in `config`:
|
|
647
|
+
declare each key with its type and an optional default, and read it in the app with `app.config.get(key)` /
|
|
648
|
+
`app.config.all()` (`ctx.config` in a function; the bridge README's **App config** section has the details).
|
|
649
|
+
|
|
650
|
+
```json
|
|
651
|
+
{
|
|
652
|
+
"config": {
|
|
653
|
+
"signupNotifyEmails": { "type": "emails", "label": "Sign-up notification recipients", "default": ["support@example.com"] },
|
|
654
|
+
"connectorsRequestEmails": { "type": "emails", "label": "API Connections request recipients" }
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
The values are set outside hawk-app.json, from the Hawk Analytics portal or the CLI, for the whole app (global) or for
|
|
660
|
+
one client, and apply within about 30 seconds, with no deploy or approval. A client's value wins over the global one,
|
|
661
|
+
which wins over the default. **Values are not secret**: they reach the browser, so keep keys in `hawk-app secrets`.
|
|
662
|
+
|
|
663
|
+
```bash
|
|
664
|
+
npx hawk-app config list [--client e3141] # each key: type, default, global and client values
|
|
665
|
+
npx hawk-app config set signupNotifyEmails a@example.com,b@example.com # global
|
|
666
|
+
npx hawk-app config set signupNotifyEmails csd@example.com --client e3141 # one client
|
|
667
|
+
npx hawk-app config unset signupNotifyEmails --client e3141
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
`set` reads the value as the key's type in your hawk-app.json: `string` as is, `number`, `boolean` (`true`/`false`),
|
|
671
|
+
`emails` comma-separated, `json` as JSON (quote it for your shell). `hawk-app validate` checks the declarations: at
|
|
672
|
+
most 50 keys named `^[A-Za-z][A-Za-z0-9_]{0,63}$`, each with `type` (`string`, `number`, `boolean`, `emails`,
|
|
673
|
+
`json`), optional `label` (up to 100 characters), `description` (up to 500) and `default` (a valid value of the type;
|
|
674
|
+
`emails` up to 50 different plain addresses; every value at most 16 KB). The declarations are a setting: on a released
|
|
675
|
+
app a change waits for review like scopes (values can already be set for a key waiting for review). `hawk-app status`
|
|
676
|
+
lists the declared keys.
|
|
677
|
+
|
|
678
|
+
## AI
|
|
679
|
+
|
|
680
|
+
Apps with the `ai:invoke` scope can ask Claude with `app.ai.generate(...)` / `app.ai.stream(...)` or, from a function,
|
|
681
|
+
`ctx.platform.ai` (the bridge README's **AI** section has the details). There are no CLI commands for it: add
|
|
682
|
+
`"ai:invoke"` to `scopes` and deploy. Two tiers, `fast` and `smart`, each with a daily token budget
|
|
683
|
+
per app and client, plus a per-minute request limit; `app.ai.info()` shows today's use. The scope is elevated: an app
|
|
684
|
+
with it is never auto-approved. Prompts and answers are never logged or stored.
|
|
685
|
+
|
|
686
|
+
## Review in HawkSearch
|
|
687
|
+
|
|
688
|
+
- **Not released yet.** A new app's versions are drafts: only Bridgeline staff see it, only on the clients it's enabled
|
|
689
|
+
for, on the Apps page in HawkSearch, always at its newest version. Developers can't release it; an approver does, on
|
|
690
|
+
the Apps page (see [Versions and aliases](#versions-and-aliases)).
|
|
691
|
+
- **Released apps.** Every deploy is a new version. Clients stay on the approved version until the new one is approved in
|
|
692
|
+
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
|
|
694
|
+
the same way. For `config` only the declarations are reviewed: its values are set at any time without review.
|
|
695
|
+
- **Functions.** Engineering enables functions per app. A version whose functions changed (added, removed, new code,
|
|
696
|
+
dependencies or settings) always waits for review, even with auto-approve; reviewers see each function's settings,
|
|
697
|
+
dependency changes and a diff of its sources.
|
|
698
|
+
- **Clients any time.** Client changes (`hawk-app clients` or the Hawk Analytics portal) apply at once, released or
|
|
699
|
+
not, without review (see [Clients](#clients)).
|
|
700
|
+
- **Auto-approve.** Approvers can set an app to auto-approve: new versions and setting changes are then released
|
|
701
|
+
straight away.
|
|
702
|
+
- **Approval workflow.** Bridgeline users submit a version or settings change for approval on the Apps page in
|
|
703
|
+
HawkSearch (`npx hawk-app open`; with an optional note). Members of the **Approvers** group approve it (it's released), reject it with a
|
|
704
|
+
reason, or force approve without a request (for example to roll back). The approvers get an email about each request
|
|
705
|
+
(they can stop these with the link at the bottom), the submitter gets one when it's approved or rejected, and every
|
|
706
|
+
step is posted to the #hawk-app-approvers Slack channel. A new main-line deploy or settings change clears a waiting request (deploy says so: submit it again in
|
|
707
|
+
HawkSearch). `hawk-app status` shows `request waiting for approval (submitted by Jane Doe, 2026-10-08: <note>)`
|
|
708
|
+
or `request rejected by Jane Doe on 2026-10-09: <reason>`.
|
|
709
|
+
- **Elevated apps.** Apps using queries, functions, system parameters, dashboard writes, AI, external Lambdas, HawkSearch
|
|
710
|
+
features or the index (rebuilds, items, management) are never auto-approved;
|
|
711
|
+
approvers review their queries, functions and scopes like any other change.
|
|
712
|
+
- **Rollback and retention.** The released version, the newest version and the 5 newest others are kept, so staff can
|
|
713
|
+
roll back in HawkSearch; older versions are removed when you deploy.
|
|
714
|
+
|
|
715
|
+
## How deploy works
|
|
716
|
+
|
|
717
|
+
1. The CLI asks the platform for the next version number (`v<n>`) with your publisher key, and builds the app for
|
|
718
|
+
`/<slug>/v<n>` (unless `--no-build`).
|
|
719
|
+
2. The CLI sends the list of files (paths and sizes). The platform checks them:
|
|
720
|
+
- the file types are static web content
|
|
721
|
+
- the sizes are within the limits
|
|
722
|
+
- the paths can't leave your app's folder
|
|
723
|
+
- you own the slug.
|
|
724
|
+
|
|
725
|
+
It then returns one **short-lived upload link per file**. Each link allows exactly one upload, to exactly that file in the version's folder, and expires after about 15 minutes.
|
|
726
|
+
3. The CLI uploads each file straight to storage using its link.
|
|
727
|
+
4. The CLI tells the platform it's done, with the result of `npm audit` of the production dependencies (run before the version was reserved, see below). The platform checks that every file arrived and marks the version ready: an app not released yet shows it to Bridgeline staff at once, a released app holds it for review (or releases it, with auto-approve). Versions never change once uploaded, so there's no CDN refresh to wait for.
|
|
728
|
+
|
|
729
|
+
The platform also sets content types and caching: hashed bundles are cached for a year, HTML is never cached, and everything else is cached for 5 minutes.
|
|
730
|
+
|
|
731
|
+
### Dependency check
|
|
732
|
+
|
|
733
|
+
Before it reserves a version number, `deploy` runs `npm audit --json --omit=dev` in the app folder (next to
|
|
734
|
+
`hawk-app.json`, 60 s at most), and it sends the vulnerability counts with the version. Reviewers see them with the other
|
|
735
|
+
pre-approval checks in HawkSearch and the Hawk Analytics portal. High or critical vulnerabilities fail that check, so
|
|
736
|
+
approvers can only force approve the version: when the audit finds any, `deploy` names the packages and asks before
|
|
737
|
+
going on.
|
|
738
|
+
|
|
739
|
+
```
|
|
740
|
+
dependencies 1 high, 1 moderate (npm audit)
|
|
741
|
+
npm audit found 1 high, 1 moderate in the production dependencies (postcss). Approvers can only force approve this
|
|
742
|
+
version. Deploy anyway? (Fix with npm audit fix or by updating the packages; npm run app:audit shows the details.) (y/N)
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
No cancels without reserving anything. `--yes` goes on with a warning, and so does a pipeline (no terminal to ask in).
|
|
746
|
+
Moderate and low findings are only reported. Check before deploying with `npm run app:audit` (in apps created from the
|
|
747
|
+
templates: `npm audit --omit=dev --audit-level=high`, which exits non-zero on high or critical findings, so it also works
|
|
748
|
+
as a CI step). Other outcomes:
|
|
749
|
+
|
|
750
|
+
```
|
|
751
|
+
dependencies no known vulnerabilities
|
|
752
|
+
dependencies not checked: no package-lock.json
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
An app in an npm workspace is audited with the lockfile at the repository root. Without a `package-lock.json` (in the
|
|
756
|
+
app folder or above it), when npm audit fails (for example a registry without audit support) or times out, the
|
|
757
|
+
version is marked "not checked" with a short reason. `--no-audit` skips it (`not checked: skipped (--no-audit)`).
|
|
758
|
+
|
|
759
|
+
## In CI (example: Bitbucket Pipelines)
|
|
760
|
+
|
|
761
|
+
```yaml
|
|
762
|
+
- step:
|
|
763
|
+
name: Deploy Hawk App (dev)
|
|
764
|
+
deployment: development # HAWK_APPS_ENV (dev or prod) and HAWK_APPS_PUBLISHER_KEY as secured variables
|
|
765
|
+
script:
|
|
766
|
+
- npm ci
|
|
767
|
+
- npx hawk-app deploy # builds with the version's base path, then uploads (with hawk-app.json)
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
Requires Node 20 or later. Dependencies: `esbuild` (bundles functions) and `fflate` (zips them).
|